Fix conflicts
8
.github/CODEOWNERS
vendored
@ -1 +1,9 @@
|
|||||||
* @micahflee
|
* @micahflee
|
||||||
|
|
||||||
|
# localization
|
||||||
|
/share/locale/ @emmapeel2
|
||||||
|
|
||||||
|
# tests
|
||||||
|
/tests/ @mig5
|
||||||
|
/tests_gui_local/ @mig5
|
||||||
|
/tests_gui_tor/ @mig5
|
||||||
|
16
.travis.yml
@ -1,6 +1,6 @@
|
|||||||
language: python
|
language: python
|
||||||
# sudo: required
|
dist: trusty
|
||||||
dist: bionic
|
sudo: required
|
||||||
python:
|
python:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.6-dev"
|
- "3.6-dev"
|
||||||
@ -8,14 +8,16 @@ python:
|
|||||||
- "nightly"
|
- "nightly"
|
||||||
# command to install dependencies
|
# command to install dependencies
|
||||||
install:
|
install:
|
||||||
|
- sudo apt-get update && sudo apt-get install python3-pyqt5
|
||||||
- pip install -r install/requirements.txt
|
- pip install -r install/requirements.txt
|
||||||
- pip install pytest-cov coveralls flake8
|
- pip install -r install/requirements-tests.txt
|
||||||
|
- pip install pytest-cov flake8
|
||||||
before_script:
|
before_script:
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
- 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
|
# 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
|
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||||
# command to run tests
|
# run CLI tests and local GUI tests
|
||||||
script: pytest --cov=onionshare test/
|
script:
|
||||||
after_success:
|
- pytest --cov=onionshare tests/
|
||||||
- coveralls
|
- cd tests_gui_local/ && xvfb-run ./run_unit_tests.sh
|
||||||
|
30
BUILD.md
@ -11,11 +11,11 @@ cd onionshare
|
|||||||
|
|
||||||
Install the needed dependencies:
|
Install the needed dependencies:
|
||||||
|
|
||||||
For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy python3-cryptography python3-crypto python3-nacl python3-pip python3-socks python3-sha3`
|
For Debian-like distros: `apt install -y python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python`
|
||||||
|
|
||||||
On some older versions of Debian you may need to install pysha3 with `pip3 install pysha3` if python3-sha3 is not available.
|
On some older versions of Debian you may need to install pysha3 with `pip3 install pysha3` if python3-sha3 is not available.
|
||||||
|
|
||||||
For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 python3-pynacl python3-cryptography python3-crypto python3-pip python3-pysocks`
|
For Fedora-like distros: `dnf install -y python3-flask python3-stem python3-qt5 python3-pynacl python3-cryptography python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build`
|
||||||
|
|
||||||
After that you can try both the CLI and the GUI version of OnionShare:
|
After that you can try both the CLI and the GUI version of OnionShare:
|
||||||
|
|
||||||
@ -137,8 +137,30 @@ This will prompt you to codesign three binaries and execute one unsigned binary.
|
|||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
OnionShare includes PyTest unit tests. To run the tests:
|
OnionShare includes PyTest unit tests. To run the tests, first install some dependencies:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pytest test/
|
pip3 install -r install/requirements-tests.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you'd like to run the CLI-based tests that Travis runs:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like to run the GUI unit tests in 'local only mode':
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd tests_gui_local/
|
||||||
|
./run_unit_tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like to run the GUI unit tests in 'tor' (bundled) mode:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd tests_gui_tor/
|
||||||
|
./run_unit_tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive.
|
||||||
|
@ -10,4 +10,4 @@ include install/onionshare.desktop
|
|||||||
include install/onionshare.appdata.xml
|
include install/onionshare.appdata.xml
|
||||||
include install/onionshare80.xpm
|
include install/onionshare80.xpm
|
||||||
include install/scripts/onionshare-nautilus.py
|
include install/scripts/onionshare-nautilus.py
|
||||||
include test/*.py
|
include tests/*.py
|
||||||
|
14
dev_scripts/run_all_tests.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
ROOT="$( dirname $(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd ))"
|
||||||
|
|
||||||
|
# CLI tests
|
||||||
|
cd $ROOT
|
||||||
|
pytest tests/
|
||||||
|
|
||||||
|
# Local GUI tests
|
||||||
|
cd $ROOT/tests_gui_local
|
||||||
|
./run_unit_tests.sh
|
||||||
|
|
||||||
|
# Tor GUI tests
|
||||||
|
cd $ROOT/tests_gui_tor
|
||||||
|
./run_unit_tests.sh
|
@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
|
|||||||
rm -r build dist >/dev/null 2>&1
|
rm -r build dist >/dev/null 2>&1
|
||||||
|
|
||||||
# build binary package
|
# build binary package
|
||||||
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, nautilus-python, tor, obfs4"
|
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-pynacl, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
|
||||||
|
|
||||||
# install it
|
# install it
|
||||||
echo ""
|
echo ""
|
||||||
|
@ -59,7 +59,7 @@ def main():
|
|||||||
files_in(dir, 'onionshare_gui/share_mode') + \
|
files_in(dir, 'onionshare_gui/share_mode') + \
|
||||||
files_in(dir, 'onionshare_gui/receive_mode') + \
|
files_in(dir, 'onionshare_gui/receive_mode') + \
|
||||||
files_in(dir, 'install/scripts') + \
|
files_in(dir, 'install/scripts') + \
|
||||||
files_in(dir, 'test')
|
files_in(dir, 'tests')
|
||||||
pysrc = [p for p in src if p.endswith('.py')]
|
pysrc = [p for p in src if p.endswith('.py')]
|
||||||
|
|
||||||
lang_code = args.lang_code
|
lang_code = args.lang_code
|
||||||
|
@ -35,9 +35,9 @@ import subprocess
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0/TorBrowser-8.0-osx64_en-US.dmg'
|
dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.1/TorBrowser-8.0.1-osx64_en-US.dmg'
|
||||||
dmg_filename = 'TorBrowser-8.0-osx64_en-US.dmg'
|
dmg_filename = 'TorBrowser-8.0.1-osx64_en-US.dmg'
|
||||||
expected_dmg_sha256 = '15603ae7b3a1942863c98acc92f509e4409db48fe22c9acae6b15c9cb9bf3088'
|
expected_dmg_sha256 = 'fb1be2a0f850a65bae38747c3abbf9061742c5d7799e1693405078aaf38d2b08'
|
||||||
|
|
||||||
# Build paths
|
# Build paths
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
||||||
|
@ -33,9 +33,9 @@ import subprocess
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0/torbrowser-install-8.0_en-US.exe'
|
exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.1/torbrowser-install-8.0.1_en-US.exe'
|
||||||
exe_filename = 'torbrowser-install-8.0_en-US.exe'
|
exe_filename = 'torbrowser-install-8.0.1_en-US.exe'
|
||||||
expected_exe_sha256 = '0682b44eff5877dfc2fe2fdd5b46e678d47adad86d564e7cb6654c5f60eb1ed2'
|
expected_exe_sha256 = 'bdf81d4282b991a6425c213c7b03b3f5c1f17bb02986b7fe9a1891e577e51639'
|
||||||
# Build paths
|
# Build paths
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
||||||
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
|
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
|
||||||
|
11
install/requirements-tests.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
atomicwrites==1.2.1
|
||||||
|
attrs==18.2.0
|
||||||
|
more-itertools==4.3.0
|
||||||
|
pluggy==0.6.0
|
||||||
|
py==1.6.0
|
||||||
|
pytest==3.4.2
|
||||||
|
pytest-faulthandler==1.5.0
|
||||||
|
pytest-ordering==0.5
|
||||||
|
pytest-qt==3.1.0
|
||||||
|
six==1.11.0
|
||||||
|
urllib3==1.23
|
@ -65,13 +65,18 @@ def main(cwd=None):
|
|||||||
receive = bool(args.receive)
|
receive = bool(args.receive)
|
||||||
config = args.config
|
config = args.config
|
||||||
|
|
||||||
|
if receive:
|
||||||
|
mode = 'receive'
|
||||||
|
else:
|
||||||
|
mode = 'share'
|
||||||
|
|
||||||
# Make sure filenames given if not using receiver mode
|
# Make sure filenames given if not using receiver mode
|
||||||
if not receive and len(filenames) == 0:
|
if mode == 'share' and len(filenames) == 0:
|
||||||
print(strings._('no_filenames'))
|
parser.print_help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# Validate filenames
|
# Validate filenames
|
||||||
if not receive:
|
if mode == 'share':
|
||||||
valid = True
|
valid = True
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
||||||
@ -90,7 +95,7 @@ def main(cwd=None):
|
|||||||
common.debug = debug
|
common.debug = debug
|
||||||
|
|
||||||
# Create the Web object
|
# Create the Web object
|
||||||
web = Web(common, False, receive)
|
web = Web(common, False, mode)
|
||||||
|
|
||||||
# Start the Onion object
|
# Start the Onion object
|
||||||
onion = Onion(common)
|
onion = Onion(common)
|
||||||
@ -116,17 +121,18 @@ def main(cwd=None):
|
|||||||
print(e.args[0])
|
print(e.args[0])
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
if mode == 'share':
|
||||||
# Prepare files to share
|
# Prepare files to share
|
||||||
print(strings._("preparing_files"))
|
print(strings._("preparing_files"))
|
||||||
try:
|
try:
|
||||||
web.set_file_info(filenames)
|
web.share_mode.set_file_info(filenames)
|
||||||
app.cleanup_filenames.append(web.zip_filename)
|
app.cleanup_filenames += web.share_mode.cleanup_filenames
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print(e.strerror)
|
print(e.strerror)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Warn about sending large files over Tor
|
# Warn about sending large files over Tor
|
||||||
if web.zip_filesize >= 157286400: # 150mb
|
if web.share_mode.download_filesize >= 157286400: # 150mb
|
||||||
print('')
|
print('')
|
||||||
print(strings._("large_filesize"))
|
print(strings._("large_filesize"))
|
||||||
print('')
|
print('')
|
||||||
@ -157,7 +163,7 @@ def main(cwd=None):
|
|||||||
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
|
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
|
||||||
|
|
||||||
print('')
|
print('')
|
||||||
if receive:
|
if mode == 'receive':
|
||||||
print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir')))
|
print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir')))
|
||||||
print('')
|
print('')
|
||||||
print(strings._('receive_mode_warning'))
|
print(strings._('receive_mode_warning'))
|
||||||
@ -186,8 +192,9 @@ def main(cwd=None):
|
|||||||
if app.shutdown_timeout > 0:
|
if app.shutdown_timeout > 0:
|
||||||
# if the shutdown timer was set and has run out, stop the server
|
# if the shutdown timer was set and has run out, stop the server
|
||||||
if not app.shutdown_timer.is_alive():
|
if not app.shutdown_timer.is_alive():
|
||||||
|
if mode == 'share':
|
||||||
# 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.share_mode.download_count == 0 or web.done:
|
||||||
print(strings._("close_on_timeout"))
|
print(strings._("close_on_timeout"))
|
||||||
web.stop(app.port)
|
web.stop(app.port)
|
||||||
break
|
break
|
||||||
|
@ -211,6 +211,7 @@ class Common(object):
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid #666666;
|
border: 1px solid #666666;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
|
||||||
@ -248,11 +249,46 @@ class Common(object):
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}""",
|
}""",
|
||||||
|
|
||||||
|
'downloads_uploads_empty': """
|
||||||
|
QWidget {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
QWidget QLabel {
|
||||||
|
background-color: none;
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
|
||||||
|
'downloads_uploads_empty_text': """
|
||||||
|
QLabel {
|
||||||
|
color: #999999;
|
||||||
|
}""",
|
||||||
|
|
||||||
'downloads_uploads_label': """
|
'downloads_uploads_label': """
|
||||||
QLabel {
|
QLabel {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size 14px;
|
font-size 14px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
background-color: none;
|
||||||
|
border: none;
|
||||||
|
}""",
|
||||||
|
|
||||||
|
'downloads_uploads_clear': """
|
||||||
|
QPushButton {
|
||||||
|
color: #3f7fcf;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
|
||||||
|
'download_uploads_indicator': """
|
||||||
|
QLabel {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #f44449;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 7px;
|
||||||
|
text-align: center;
|
||||||
}""",
|
}""",
|
||||||
|
|
||||||
'downloads_uploads_progress_bar': """
|
'downloads_uploads_progress_bar': """
|
||||||
@ -261,7 +297,7 @@ class Common(object):
|
|||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #9b9b9b;
|
color: #9b9b9b;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
QProgressBar::chunk {
|
QProgressBar::chunk {
|
||||||
background-color: #4e064f;
|
background-color: #4e064f;
|
||||||
@ -433,7 +469,7 @@ class Common(object):
|
|||||||
tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
|
tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
|
||||||
break
|
break
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise OSError(e)
|
pass
|
||||||
_, port = tmpsock.getsockname()
|
_, port = tmpsock.getsockname()
|
||||||
return port
|
return port
|
||||||
|
|
||||||
|
@ -402,6 +402,8 @@ class Onion(object):
|
|||||||
# ephemeral stealth onion services are not supported
|
# ephemeral stealth onion services are not supported
|
||||||
self.supports_stealth = False
|
self.supports_stealth = False
|
||||||
|
|
||||||
|
# Does this version of Tor support next-gen ('v3') onions?
|
||||||
|
self.supports_next_gen_onions = self.tor_version > Version('0.3.3.1')
|
||||||
|
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
"""
|
"""
|
||||||
@ -427,7 +429,6 @@ class Onion(object):
|
|||||||
raise TorTooOld(strings._('error_stealth_not_supported'))
|
raise TorTooOld(strings._('error_stealth_not_supported'))
|
||||||
|
|
||||||
print(strings._("config_onion_service").format(int(port)))
|
print(strings._("config_onion_service").format(int(port)))
|
||||||
print(strings._('using_ephemeral'))
|
|
||||||
|
|
||||||
if self.stealth:
|
if self.stealth:
|
||||||
if self.settings.get('hidservauth_string'):
|
if self.settings.get('hidservauth_string'):
|
||||||
|
@ -1,846 +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 hmac
|
|
||||||
import logging
|
|
||||||
import mimetypes
|
|
||||||
import os
|
|
||||||
import queue
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import zipfile
|
|
||||||
import re
|
|
||||||
import io
|
|
||||||
from distutils.version import LooseVersion as Version
|
|
||||||
from urllib.request import urlopen
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import flask
|
|
||||||
from flask import (
|
|
||||||
Flask, Response, Request, request, render_template, abort, make_response,
|
|
||||||
flash, redirect, __version__ as flask_version
|
|
||||||
)
|
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
|
|
||||||
from . import strings
|
|
||||||
from .common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable
|
|
||||||
|
|
||||||
|
|
||||||
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
|
||||||
# are not applicable to OnionShare
|
|
||||||
def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
|
|
||||||
pass
|
|
||||||
|
|
||||||
flask.cli.show_server_banner = stubbed_show_server_banner
|
|
||||||
|
|
||||||
|
|
||||||
class Web(object):
|
|
||||||
"""
|
|
||||||
The Web object is the OnionShare web server, powered by flask
|
|
||||||
"""
|
|
||||||
REQUEST_LOAD = 0
|
|
||||||
REQUEST_STARTED = 1
|
|
||||||
REQUEST_PROGRESS = 2
|
|
||||||
REQUEST_OTHER = 3
|
|
||||||
REQUEST_CANCELED = 4
|
|
||||||
REQUEST_RATE_LIMIT = 5
|
|
||||||
REQUEST_CLOSE_SERVER = 6
|
|
||||||
REQUEST_UPLOAD_FILE_RENAMED = 7
|
|
||||||
REQUEST_UPLOAD_FINISHED = 8
|
|
||||||
REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9
|
|
||||||
REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10
|
|
||||||
|
|
||||||
def __init__(self, common, gui_mode, receive_mode=False):
|
|
||||||
self.common = common
|
|
||||||
|
|
||||||
# The flask app
|
|
||||||
self.app = Flask(__name__,
|
|
||||||
static_folder=self.common.get_resource_path('static'),
|
|
||||||
template_folder=self.common.get_resource_path('templates'))
|
|
||||||
self.app.secret_key = self.common.random_string(8)
|
|
||||||
|
|
||||||
# Debug mode?
|
|
||||||
if self.common.debug:
|
|
||||||
self.debug_mode()
|
|
||||||
|
|
||||||
# Are we running in GUI mode?
|
|
||||||
self.gui_mode = gui_mode
|
|
||||||
|
|
||||||
# Are we using receive mode?
|
|
||||||
self.receive_mode = receive_mode
|
|
||||||
if self.receive_mode:
|
|
||||||
# Use custom WSGI middleware, to modify environ
|
|
||||||
self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self)
|
|
||||||
# Use a custom Request class to track upload progess
|
|
||||||
self.app.request_class = ReceiveModeRequest
|
|
||||||
|
|
||||||
# Starting in Flask 0.11, render_template_string autoescapes template variables
|
|
||||||
# by default. To prevent content injection through template variables in
|
|
||||||
# earlier versions of Flask, we force autoescaping in the Jinja2 template
|
|
||||||
# engine if we detect a Flask version with insecure default behavior.
|
|
||||||
if Version(flask_version) < Version('0.11'):
|
|
||||||
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
|
||||||
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
|
|
||||||
|
|
||||||
# Information about the file
|
|
||||||
self.file_info = []
|
|
||||||
self.zip_filename = None
|
|
||||||
self.zip_filesize = None
|
|
||||||
|
|
||||||
self.security_headers = [
|
|
||||||
('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
|
|
||||||
('X-Frame-Options', 'DENY'),
|
|
||||||
('X-Xss-Protection', '1; mode=block'),
|
|
||||||
('X-Content-Type-Options', 'nosniff'),
|
|
||||||
('Referrer-Policy', 'no-referrer'),
|
|
||||||
('Server', 'OnionShare')
|
|
||||||
]
|
|
||||||
|
|
||||||
self.q = queue.Queue()
|
|
||||||
|
|
||||||
self.slug = None
|
|
||||||
|
|
||||||
self.download_count = 0
|
|
||||||
self.upload_count = 0
|
|
||||||
|
|
||||||
self.error404_count = 0
|
|
||||||
|
|
||||||
# If "Stop After First Download" is checked (stay_open == False), only allow
|
|
||||||
# one download at a time.
|
|
||||||
self.download_in_progress = False
|
|
||||||
|
|
||||||
self.done = False
|
|
||||||
|
|
||||||
# If the client closes the OnionShare window while a download is in progress,
|
|
||||||
# it should immediately stop serving the file. The client_cancel global is
|
|
||||||
# used to tell the download function that the client is canceling the download.
|
|
||||||
self.client_cancel = False
|
|
||||||
|
|
||||||
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
|
|
||||||
self.shutdown_slug = self.common.random_string(16)
|
|
||||||
|
|
||||||
# Keep track if the server is running
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
# Define the ewb app routes
|
|
||||||
self.common_routes()
|
|
||||||
if self.receive_mode:
|
|
||||||
self.receive_routes()
|
|
||||||
else:
|
|
||||||
self.send_routes()
|
|
||||||
|
|
||||||
def send_routes(self):
|
|
||||||
"""
|
|
||||||
The web app routes for sharing files
|
|
||||||
"""
|
|
||||||
@self.app.route("/<slug_candidate>")
|
|
||||||
def index(slug_candidate):
|
|
||||||
self.check_slug_candidate(slug_candidate)
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
@self.app.route("/")
|
|
||||||
def index_public():
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.error404()
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
def index_logic(slug_candidate=''):
|
|
||||||
"""
|
|
||||||
Render the template for the onionshare landing page.
|
|
||||||
"""
|
|
||||||
self.add_request(Web.REQUEST_LOAD, request.path)
|
|
||||||
|
|
||||||
# Deny new downloads if "Stop After First Download" is checked and there is
|
|
||||||
# currently a download
|
|
||||||
deny_download = not self.stay_open and self.download_in_progress
|
|
||||||
if deny_download:
|
|
||||||
r = make_response(render_template('denied.html'))
|
|
||||||
return self.add_security_headers(r)
|
|
||||||
|
|
||||||
# If download is allowed to continue, serve download page
|
|
||||||
if self.slug:
|
|
||||||
r = make_response(render_template(
|
|
||||||
'send.html',
|
|
||||||
slug=self.slug,
|
|
||||||
file_info=self.file_info,
|
|
||||||
filename=os.path.basename(self.zip_filename),
|
|
||||||
filesize=self.zip_filesize,
|
|
||||||
filesize_human=self.common.human_readable_filesize(self.zip_filesize)))
|
|
||||||
else:
|
|
||||||
# If download is allowed to continue, serve download page
|
|
||||||
r = make_response(render_template(
|
|
||||||
'send.html',
|
|
||||||
file_info=self.file_info,
|
|
||||||
filename=os.path.basename(self.zip_filename),
|
|
||||||
filesize=self.zip_filesize,
|
|
||||||
filesize_human=self.common.human_readable_filesize(self.zip_filesize)))
|
|
||||||
return self.add_security_headers(r)
|
|
||||||
|
|
||||||
@self.app.route("/<slug_candidate>/download")
|
|
||||||
def download(slug_candidate):
|
|
||||||
self.check_slug_candidate(slug_candidate)
|
|
||||||
return download_logic()
|
|
||||||
|
|
||||||
@self.app.route("/download")
|
|
||||||
def download_public():
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.error404()
|
|
||||||
return download_logic()
|
|
||||||
|
|
||||||
def download_logic(slug_candidate=''):
|
|
||||||
"""
|
|
||||||
Download the zip file.
|
|
||||||
"""
|
|
||||||
# Deny new downloads if "Stop After First Download" is checked and there is
|
|
||||||
# currently a download
|
|
||||||
deny_download = not self.stay_open and self.download_in_progress
|
|
||||||
if deny_download:
|
|
||||||
r = make_response(render_template('denied.html'))
|
|
||||||
return self.add_security_headers(r)
|
|
||||||
|
|
||||||
# Each download has a unique id
|
|
||||||
download_id = self.download_count
|
|
||||||
self.download_count += 1
|
|
||||||
|
|
||||||
# Prepare some variables to use inside generate() function below
|
|
||||||
# which is outside of the request context
|
|
||||||
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
|
||||||
path = request.path
|
|
||||||
|
|
||||||
# Tell GUI the download started
|
|
||||||
self.add_request(Web.REQUEST_STARTED, path, {
|
|
||||||
'id': download_id}
|
|
||||||
)
|
|
||||||
|
|
||||||
dirname = os.path.dirname(self.zip_filename)
|
|
||||||
basename = os.path.basename(self.zip_filename)
|
|
||||||
|
|
||||||
def generate():
|
|
||||||
# The user hasn't canceled the download
|
|
||||||
self.client_cancel = False
|
|
||||||
|
|
||||||
# Starting a new download
|
|
||||||
if not self.stay_open:
|
|
||||||
self.download_in_progress = True
|
|
||||||
|
|
||||||
chunk_size = 102400 # 100kb
|
|
||||||
|
|
||||||
fp = open(self.zip_filename, 'rb')
|
|
||||||
self.done = False
|
|
||||||
canceled = False
|
|
||||||
while not self.done:
|
|
||||||
# The user has canceled the download, so stop serving the file
|
|
||||||
if self.client_cancel:
|
|
||||||
self.add_request(Web.REQUEST_CANCELED, path, {
|
|
||||||
'id': download_id
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
chunk = fp.read(chunk_size)
|
|
||||||
if chunk == b'':
|
|
||||||
self.done = True
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
# tell GUI the progress
|
|
||||||
downloaded_bytes = fp.tell()
|
|
||||||
percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100
|
|
||||||
|
|
||||||
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
|
|
||||||
if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
|
||||||
sys.stdout.write(
|
|
||||||
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
self.add_request(Web.REQUEST_PROGRESS, path, {
|
|
||||||
'id': download_id,
|
|
||||||
'bytes': downloaded_bytes
|
|
||||||
})
|
|
||||||
self.done = False
|
|
||||||
except:
|
|
||||||
# looks like the download was canceled
|
|
||||||
self.done = True
|
|
||||||
canceled = True
|
|
||||||
|
|
||||||
# tell the GUI the download has canceled
|
|
||||||
self.add_request(Web.REQUEST_CANCELED, path, {
|
|
||||||
'id': download_id
|
|
||||||
})
|
|
||||||
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
if self.common.platform != 'Darwin':
|
|
||||||
sys.stdout.write("\n")
|
|
||||||
|
|
||||||
# Download is finished
|
|
||||||
if not self.stay_open:
|
|
||||||
self.download_in_progress = False
|
|
||||||
|
|
||||||
# Close the server, if necessary
|
|
||||||
if not self.stay_open and not canceled:
|
|
||||||
print(strings._("closing_automatically"))
|
|
||||||
self.running = False
|
|
||||||
try:
|
|
||||||
if shutdown_func is None:
|
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
|
||||||
shutdown_func()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
r = Response(generate())
|
|
||||||
r.headers.set('Content-Length', self.zip_filesize)
|
|
||||||
r.headers.set('Content-Disposition', 'attachment', filename=basename)
|
|
||||||
r = self.add_security_headers(r)
|
|
||||||
# guess content type
|
|
||||||
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
|
||||||
if content_type is not None:
|
|
||||||
r.headers.set('Content-Type', content_type)
|
|
||||||
return r
|
|
||||||
|
|
||||||
def receive_routes(self):
|
|
||||||
"""
|
|
||||||
The web app routes for receiving files
|
|
||||||
"""
|
|
||||||
def index_logic():
|
|
||||||
self.add_request(Web.REQUEST_LOAD, request.path)
|
|
||||||
|
|
||||||
if self.common.settings.get('public_mode'):
|
|
||||||
upload_action = '/upload'
|
|
||||||
close_action = '/close'
|
|
||||||
else:
|
|
||||||
upload_action = '/{}/upload'.format(self.slug)
|
|
||||||
close_action = '/{}/close'.format(self.slug)
|
|
||||||
|
|
||||||
r = make_response(render_template(
|
|
||||||
'receive.html',
|
|
||||||
upload_action=upload_action,
|
|
||||||
close_action=close_action,
|
|
||||||
receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown')))
|
|
||||||
return self.add_security_headers(r)
|
|
||||||
|
|
||||||
@self.app.route("/<slug_candidate>")
|
|
||||||
def index(slug_candidate):
|
|
||||||
self.check_slug_candidate(slug_candidate)
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
@self.app.route("/")
|
|
||||||
def index_public():
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.error404()
|
|
||||||
return index_logic()
|
|
||||||
|
|
||||||
|
|
||||||
def upload_logic(slug_candidate=''):
|
|
||||||
"""
|
|
||||||
Upload files.
|
|
||||||
"""
|
|
||||||
# Make sure downloads_dir exists
|
|
||||||
valid = True
|
|
||||||
try:
|
|
||||||
self.common.validate_downloads_dir()
|
|
||||||
except DownloadsDirErrorCannotCreate:
|
|
||||||
self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path)
|
|
||||||
print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir')))
|
|
||||||
valid = False
|
|
||||||
except DownloadsDirErrorNotWritable:
|
|
||||||
self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path)
|
|
||||||
print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir')))
|
|
||||||
valid = False
|
|
||||||
if not valid:
|
|
||||||
flash('Error uploading, please inform the OnionShare user', 'error')
|
|
||||||
if self.common.settings.get('public_mode'):
|
|
||||||
return redirect('/')
|
|
||||||
else:
|
|
||||||
return redirect('/{}'.format(slug_candidate))
|
|
||||||
|
|
||||||
files = request.files.getlist('file[]')
|
|
||||||
filenames = []
|
|
||||||
print('')
|
|
||||||
for f in files:
|
|
||||||
if f.filename != '':
|
|
||||||
# Automatically rename the file, if a file of the same name already exists
|
|
||||||
filename = secure_filename(f.filename)
|
|
||||||
filenames.append(filename)
|
|
||||||
local_path = os.path.join(self.common.settings.get('downloads_dir'), filename)
|
|
||||||
if os.path.exists(local_path):
|
|
||||||
if '.' in filename:
|
|
||||||
# Add "-i", e.g. change "foo.txt" to "foo-2.txt"
|
|
||||||
parts = filename.split('.')
|
|
||||||
name = parts[:-1]
|
|
||||||
ext = parts[-1]
|
|
||||||
|
|
||||||
i = 2
|
|
||||||
valid = False
|
|
||||||
while not valid:
|
|
||||||
new_filename = '{}-{}.{}'.format('.'.join(name), i, ext)
|
|
||||||
local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename)
|
|
||||||
if os.path.exists(local_path):
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
valid = True
|
|
||||||
else:
|
|
||||||
# If no extension, just add "-i", e.g. change "foo" to "foo-2"
|
|
||||||
i = 2
|
|
||||||
valid = False
|
|
||||||
while not valid:
|
|
||||||
new_filename = '{}-{}'.format(filename, i)
|
|
||||||
local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename)
|
|
||||||
if os.path.exists(local_path):
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
valid = True
|
|
||||||
|
|
||||||
basename = os.path.basename(local_path)
|
|
||||||
if f.filename != basename:
|
|
||||||
# Tell the GUI that the file has changed names
|
|
||||||
self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, {
|
|
||||||
'id': request.upload_id,
|
|
||||||
'old_filename': f.filename,
|
|
||||||
'new_filename': basename
|
|
||||||
})
|
|
||||||
|
|
||||||
self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path))
|
|
||||||
print(strings._('receive_mode_received_file').format(local_path))
|
|
||||||
f.save(local_path)
|
|
||||||
|
|
||||||
# Note that flash strings are on English, and not translated, on purpose,
|
|
||||||
# to avoid leaking the locale of the OnionShare user
|
|
||||||
if len(filenames) == 0:
|
|
||||||
flash('No files uploaded', 'info')
|
|
||||||
else:
|
|
||||||
for filename in filenames:
|
|
||||||
flash('Sent {}'.format(filename), 'info')
|
|
||||||
|
|
||||||
if self.common.settings.get('public_mode'):
|
|
||||||
return redirect('/')
|
|
||||||
else:
|
|
||||||
return redirect('/{}'.format(slug_candidate))
|
|
||||||
|
|
||||||
@self.app.route("/<slug_candidate>/upload", methods=['POST'])
|
|
||||||
def upload(slug_candidate):
|
|
||||||
self.check_slug_candidate(slug_candidate)
|
|
||||||
return upload_logic(slug_candidate)
|
|
||||||
|
|
||||||
@self.app.route("/upload", methods=['POST'])
|
|
||||||
def upload_public():
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.error404()
|
|
||||||
return upload_logic()
|
|
||||||
|
|
||||||
|
|
||||||
def close_logic(slug_candidate=''):
|
|
||||||
if self.common.settings.get('receive_allow_receiver_shutdown'):
|
|
||||||
self.force_shutdown()
|
|
||||||
r = make_response(render_template('closed.html'))
|
|
||||||
self.add_request(Web.REQUEST_CLOSE_SERVER, request.path)
|
|
||||||
return self.add_security_headers(r)
|
|
||||||
else:
|
|
||||||
return redirect('/{}'.format(slug_candidate))
|
|
||||||
|
|
||||||
@self.app.route("/<slug_candidate>/close", methods=['POST'])
|
|
||||||
def close(slug_candidate):
|
|
||||||
self.check_slug_candidate(slug_candidate)
|
|
||||||
return close_logic(slug_candidate)
|
|
||||||
|
|
||||||
@self.app.route("/close", methods=['POST'])
|
|
||||||
def close_public():
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
return self.error404()
|
|
||||||
return close_logic()
|
|
||||||
|
|
||||||
def common_routes(self):
|
|
||||||
"""
|
|
||||||
Common web app routes between sending and receiving
|
|
||||||
"""
|
|
||||||
@self.app.errorhandler(404)
|
|
||||||
def page_not_found(e):
|
|
||||||
"""
|
|
||||||
404 error page.
|
|
||||||
"""
|
|
||||||
return self.error404()
|
|
||||||
|
|
||||||
@self.app.route("/<slug_candidate>/shutdown")
|
|
||||||
def shutdown(slug_candidate):
|
|
||||||
"""
|
|
||||||
Stop the flask web server, from the context of an http request.
|
|
||||||
"""
|
|
||||||
self.check_shutdown_slug_candidate(slug_candidate)
|
|
||||||
self.force_shutdown()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def error404(self):
|
|
||||||
self.add_request(Web.REQUEST_OTHER, request.path)
|
|
||||||
if request.path != '/favicon.ico':
|
|
||||||
self.error404_count += 1
|
|
||||||
|
|
||||||
# In receive mode, with public mode enabled, skip rate limiting 404s
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
if self.error404_count == 20:
|
|
||||||
self.add_request(Web.REQUEST_RATE_LIMIT, request.path)
|
|
||||||
self.force_shutdown()
|
|
||||||
print(strings._('error_rate_limit'))
|
|
||||||
|
|
||||||
r = make_response(render_template('404.html'), 404)
|
|
||||||
return self.add_security_headers(r)
|
|
||||||
|
|
||||||
def add_security_headers(self, r):
|
|
||||||
"""
|
|
||||||
Add security headers to a request
|
|
||||||
"""
|
|
||||||
for header, value in self.security_headers:
|
|
||||||
r.headers.set(header, value)
|
|
||||||
return r
|
|
||||||
|
|
||||||
def set_file_info(self, filenames, processed_size_callback=None):
|
|
||||||
"""
|
|
||||||
Using the list of filenames being shared, fill in details that the web
|
|
||||||
page will need to display. This includes zipping up the file in order to
|
|
||||||
get the zip file's name and size.
|
|
||||||
"""
|
|
||||||
# build file info list
|
|
||||||
self.file_info = {'files': [], 'dirs': []}
|
|
||||||
for filename in filenames:
|
|
||||||
info = {
|
|
||||||
'filename': filename,
|
|
||||||
'basename': os.path.basename(filename.rstrip('/'))
|
|
||||||
}
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
info['size'] = os.path.getsize(filename)
|
|
||||||
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
|
||||||
self.file_info['files'].append(info)
|
|
||||||
if os.path.isdir(filename):
|
|
||||||
info['size'] = self.common.dir_size(filename)
|
|
||||||
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
|
||||||
self.file_info['dirs'].append(info)
|
|
||||||
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
|
|
||||||
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
|
|
||||||
|
|
||||||
# zip up the files and folders
|
|
||||||
z = ZipWriter(self.common, processed_size_callback=processed_size_callback)
|
|
||||||
for info in self.file_info['files']:
|
|
||||||
z.add_file(info['filename'])
|
|
||||||
for info in self.file_info['dirs']:
|
|
||||||
z.add_dir(info['filename'])
|
|
||||||
z.close()
|
|
||||||
self.zip_filename = z.zip_filename
|
|
||||||
self.zip_filesize = os.path.getsize(self.zip_filename)
|
|
||||||
|
|
||||||
def _safe_select_jinja_autoescape(self, filename):
|
|
||||||
if filename is None:
|
|
||||||
return True
|
|
||||||
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
|
||||||
|
|
||||||
def add_request(self, request_type, path, data=None):
|
|
||||||
"""
|
|
||||||
Add a request to the queue, to communicate with the GUI.
|
|
||||||
"""
|
|
||||||
self.q.put({
|
|
||||||
'type': request_type,
|
|
||||||
'path': path,
|
|
||||||
'data': data
|
|
||||||
})
|
|
||||||
|
|
||||||
def generate_slug(self, persistent_slug=None):
|
|
||||||
self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug))
|
|
||||||
if persistent_slug != None and persistent_slug != '':
|
|
||||||
self.slug = persistent_slug
|
|
||||||
self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug))
|
|
||||||
else:
|
|
||||||
self.slug = self.common.build_slug()
|
|
||||||
self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug))
|
|
||||||
|
|
||||||
def debug_mode(self):
|
|
||||||
"""
|
|
||||||
Turn on debugging mode, which will log flask errors to a debug file.
|
|
||||||
"""
|
|
||||||
temp_dir = tempfile.gettempdir()
|
|
||||||
log_handler = logging.FileHandler(
|
|
||||||
os.path.join(temp_dir, 'onionshare_server.log'))
|
|
||||||
log_handler.setLevel(logging.WARNING)
|
|
||||||
self.app.logger.addHandler(log_handler)
|
|
||||||
|
|
||||||
def check_slug_candidate(self, slug_candidate):
|
|
||||||
self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate))
|
|
||||||
if self.common.settings.get('public_mode'):
|
|
||||||
abort(404)
|
|
||||||
if not hmac.compare_digest(self.slug, slug_candidate):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
def check_shutdown_slug_candidate(self, slug_candidate):
|
|
||||||
self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate))
|
|
||||||
if not hmac.compare_digest(self.shutdown_slug, slug_candidate):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
def force_shutdown(self):
|
|
||||||
"""
|
|
||||||
Stop the flask web server, from the context of the flask app.
|
|
||||||
"""
|
|
||||||
# Shutdown the flask service
|
|
||||||
try:
|
|
||||||
func = request.environ.get('werkzeug.server.shutdown')
|
|
||||||
if func is None:
|
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
|
||||||
func()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
def start(self, port, stay_open=False, public_mode=False, persistent_slug=None):
|
|
||||||
"""
|
|
||||||
Start the flask web server.
|
|
||||||
"""
|
|
||||||
self.common.log('Web', 'start', 'port={}, stay_open={}, persistent_slug={}'.format(port, stay_open, persistent_slug))
|
|
||||||
if not public_mode:
|
|
||||||
self.generate_slug(persistent_slug)
|
|
||||||
|
|
||||||
self.stay_open = stay_open
|
|
||||||
|
|
||||||
# In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
|
|
||||||
if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
|
|
||||||
host = '0.0.0.0'
|
|
||||||
else:
|
|
||||||
host = '127.0.0.1'
|
|
||||||
|
|
||||||
self.running = True
|
|
||||||
self.app.run(host=host, port=port, threaded=True)
|
|
||||||
|
|
||||||
def stop(self, port):
|
|
||||||
"""
|
|
||||||
Stop the flask web server by loading /shutdown.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# If the user cancels the download, let the download function know to stop
|
|
||||||
# serving the file
|
|
||||||
self.client_cancel = True
|
|
||||||
|
|
||||||
# To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
|
|
||||||
if self.running:
|
|
||||||
try:
|
|
||||||
s = socket.socket()
|
|
||||||
s.connect(('127.0.0.1', port))
|
|
||||||
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug))
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ZipWriter(object):
|
|
||||||
"""
|
|
||||||
ZipWriter accepts files and directories and compresses them into a zip file
|
|
||||||
with. If a zip_filename is not passed in, it will use the default onionshare
|
|
||||||
filename.
|
|
||||||
"""
|
|
||||||
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
|
||||||
self.common = common
|
|
||||||
|
|
||||||
if zip_filename:
|
|
||||||
self.zip_filename = zip_filename
|
|
||||||
else:
|
|
||||||
self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6))
|
|
||||||
|
|
||||||
self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
|
|
||||||
self.processed_size_callback = processed_size_callback
|
|
||||||
if self.processed_size_callback is None:
|
|
||||||
self.processed_size_callback = lambda _: None
|
|
||||||
self._size = 0
|
|
||||||
self.processed_size_callback(self._size)
|
|
||||||
|
|
||||||
def add_file(self, filename):
|
|
||||||
"""
|
|
||||||
Add a file to the zip archive.
|
|
||||||
"""
|
|
||||||
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
|
|
||||||
self._size += os.path.getsize(filename)
|
|
||||||
self.processed_size_callback(self._size)
|
|
||||||
|
|
||||||
def add_dir(self, filename):
|
|
||||||
"""
|
|
||||||
Add a directory, and all of its children, to the zip archive.
|
|
||||||
"""
|
|
||||||
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
|
||||||
for dirpath, dirnames, filenames in os.walk(filename):
|
|
||||||
for f in filenames:
|
|
||||||
full_filename = os.path.join(dirpath, f)
|
|
||||||
if not os.path.islink(full_filename):
|
|
||||||
arc_filename = full_filename[len(dir_to_strip):]
|
|
||||||
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
|
|
||||||
self._size += os.path.getsize(full_filename)
|
|
||||||
self.processed_size_callback(self._size)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Close the zip archive.
|
|
||||||
"""
|
|
||||||
self.z.close()
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveModeWSGIMiddleware(object):
|
|
||||||
"""
|
|
||||||
Custom WSGI middleware in order to attach the Web object to environ, so
|
|
||||||
ReceiveModeRequest can access it.
|
|
||||||
"""
|
|
||||||
def __init__(self, app, web):
|
|
||||||
self.app = app
|
|
||||||
self.web = web
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
environ['web'] = self.web
|
|
||||||
return self.app(environ, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveModeTemporaryFile(object):
|
|
||||||
"""
|
|
||||||
A custom TemporaryFile that tells ReceiveModeRequest every time data gets
|
|
||||||
written to it, in order to track the progress of uploads.
|
|
||||||
"""
|
|
||||||
def __init__(self, filename, write_func, close_func):
|
|
||||||
self.onionshare_filename = filename
|
|
||||||
self.onionshare_write_func = write_func
|
|
||||||
self.onionshare_close_func = close_func
|
|
||||||
|
|
||||||
# Create a temporary file
|
|
||||||
self.f = tempfile.TemporaryFile('wb+')
|
|
||||||
|
|
||||||
# Make all the file-like methods and attributes actually access the
|
|
||||||
# TemporaryFile, except for write
|
|
||||||
attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode',
|
|
||||||
'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto',
|
|
||||||
'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell',
|
|
||||||
'truncate', 'writable', 'writelines']
|
|
||||||
for attr in attrs:
|
|
||||||
setattr(self, attr, getattr(self.f, attr))
|
|
||||||
|
|
||||||
def write(self, b):
|
|
||||||
"""
|
|
||||||
Custom write method that calls out to onionshare_write_func
|
|
||||||
"""
|
|
||||||
bytes_written = self.f.write(b)
|
|
||||||
self.onionshare_write_func(self.onionshare_filename, bytes_written)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Custom close method that calls out to onionshare_close_func
|
|
||||||
"""
|
|
||||||
self.f.close()
|
|
||||||
self.onionshare_close_func(self.onionshare_filename)
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveModeRequest(Request):
|
|
||||||
"""
|
|
||||||
A custom flask Request object that keeps track of how much data has been
|
|
||||||
uploaded for each file, for receive mode.
|
|
||||||
"""
|
|
||||||
def __init__(self, environ, populate_request=True, shallow=False):
|
|
||||||
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
|
|
||||||
self.web = environ['web']
|
|
||||||
|
|
||||||
# Is this a valid upload request?
|
|
||||||
self.upload_request = False
|
|
||||||
if self.method == 'POST':
|
|
||||||
if self.path == '/{}/upload'.format(self.web.slug):
|
|
||||||
self.upload_request = True
|
|
||||||
else:
|
|
||||||
if self.web.common.settings.get('public_mode'):
|
|
||||||
if self.path == '/upload':
|
|
||||||
self.upload_request = True
|
|
||||||
|
|
||||||
if self.upload_request:
|
|
||||||
# A dictionary that maps filenames to the bytes uploaded so far
|
|
||||||
self.progress = {}
|
|
||||||
|
|
||||||
# Create an upload_id, attach it to the request
|
|
||||||
self.upload_id = self.web.upload_count
|
|
||||||
self.web.upload_count += 1
|
|
||||||
|
|
||||||
# Figure out the content length
|
|
||||||
try:
|
|
||||||
self.content_length = int(self.headers['Content-Length'])
|
|
||||||
except:
|
|
||||||
self.content_length = 0
|
|
||||||
|
|
||||||
print("{}: {}".format(
|
|
||||||
datetime.now().strftime("%b %d, %I:%M%p"),
|
|
||||||
strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length))
|
|
||||||
))
|
|
||||||
|
|
||||||
# Tell the GUI
|
|
||||||
self.web.add_request(Web.REQUEST_STARTED, self.path, {
|
|
||||||
'id': self.upload_id,
|
|
||||||
'content_length': self.content_length
|
|
||||||
})
|
|
||||||
|
|
||||||
self.previous_file = None
|
|
||||||
|
|
||||||
def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None):
|
|
||||||
"""
|
|
||||||
This gets called for each file that gets uploaded, and returns an file-like
|
|
||||||
writable stream.
|
|
||||||
"""
|
|
||||||
if self.upload_request:
|
|
||||||
self.progress[filename] = {
|
|
||||||
'uploaded_bytes': 0,
|
|
||||||
'complete': False
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Closing the request.
|
|
||||||
"""
|
|
||||||
super(ReceiveModeRequest, self).close()
|
|
||||||
if self.upload_request:
|
|
||||||
# Inform the GUI that the upload has finished
|
|
||||||
self.web.add_request(Web.REQUEST_UPLOAD_FINISHED, self.path, {
|
|
||||||
'id': self.upload_id
|
|
||||||
})
|
|
||||||
|
|
||||||
def file_write_func(self, filename, length):
|
|
||||||
"""
|
|
||||||
This function gets called when a specific file is written to.
|
|
||||||
"""
|
|
||||||
if self.upload_request:
|
|
||||||
self.progress[filename]['uploaded_bytes'] += length
|
|
||||||
|
|
||||||
if self.previous_file != filename:
|
|
||||||
if self.previous_file is not None:
|
|
||||||
print('')
|
|
||||||
self.previous_file = filename
|
|
||||||
|
|
||||||
print('\r=> {:15s} {}'.format(
|
|
||||||
self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']),
|
|
||||||
filename
|
|
||||||
), end='')
|
|
||||||
|
|
||||||
# Update the GUI on the upload progress
|
|
||||||
self.web.add_request(Web.REQUEST_PROGRESS, self.path, {
|
|
||||||
'id': self.upload_id,
|
|
||||||
'progress': self.progress
|
|
||||||
})
|
|
||||||
|
|
||||||
def file_close_func(self, filename):
|
|
||||||
"""
|
|
||||||
This function gets called when a specific file is closed.
|
|
||||||
"""
|
|
||||||
self.progress[filename]['complete'] = True
|
|
21
onionshare/web/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .web import Web
|
325
onionshare/web/receive_mode.py
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Request, request, render_template, make_response, flash, redirect
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable
|
||||||
|
from .. import strings
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveModeWeb(object):
|
||||||
|
"""
|
||||||
|
All of the web logic for receive mode
|
||||||
|
"""
|
||||||
|
def __init__(self, common, web):
|
||||||
|
self.common = common
|
||||||
|
self.common.log('ReceiveModeWeb', '__init__')
|
||||||
|
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
self.upload_count = 0
|
||||||
|
|
||||||
|
self.define_routes()
|
||||||
|
|
||||||
|
def define_routes(self):
|
||||||
|
"""
|
||||||
|
The web app routes for receiving files
|
||||||
|
"""
|
||||||
|
def index_logic():
|
||||||
|
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
||||||
|
|
||||||
|
if self.common.settings.get('public_mode'):
|
||||||
|
upload_action = '/upload'
|
||||||
|
close_action = '/close'
|
||||||
|
else:
|
||||||
|
upload_action = '/{}/upload'.format(self.web.slug)
|
||||||
|
close_action = '/{}/close'.format(self.web.slug)
|
||||||
|
|
||||||
|
r = make_response(render_template(
|
||||||
|
'receive.html',
|
||||||
|
upload_action=upload_action,
|
||||||
|
close_action=close_action,
|
||||||
|
receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown')))
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
|
@self.web.app.route("/<slug_candidate>")
|
||||||
|
def index(slug_candidate):
|
||||||
|
self.web.check_slug_candidate(slug_candidate)
|
||||||
|
return index_logic()
|
||||||
|
|
||||||
|
@self.web.app.route("/")
|
||||||
|
def index_public():
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
return self.web.error404()
|
||||||
|
return index_logic()
|
||||||
|
|
||||||
|
|
||||||
|
def upload_logic(slug_candidate=''):
|
||||||
|
"""
|
||||||
|
Upload files.
|
||||||
|
"""
|
||||||
|
# Make sure downloads_dir exists
|
||||||
|
valid = True
|
||||||
|
try:
|
||||||
|
self.common.validate_downloads_dir()
|
||||||
|
except DownloadsDirErrorCannotCreate:
|
||||||
|
self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path)
|
||||||
|
print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir')))
|
||||||
|
valid = False
|
||||||
|
except DownloadsDirErrorNotWritable:
|
||||||
|
self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path)
|
||||||
|
print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir')))
|
||||||
|
valid = False
|
||||||
|
if not valid:
|
||||||
|
flash('Error uploading, please inform the OnionShare user', 'error')
|
||||||
|
if self.common.settings.get('public_mode'):
|
||||||
|
return redirect('/')
|
||||||
|
else:
|
||||||
|
return redirect('/{}'.format(slug_candidate))
|
||||||
|
|
||||||
|
files = request.files.getlist('file[]')
|
||||||
|
filenames = []
|
||||||
|
print('')
|
||||||
|
for f in files:
|
||||||
|
if f.filename != '':
|
||||||
|
# Automatically rename the file, if a file of the same name already exists
|
||||||
|
filename = secure_filename(f.filename)
|
||||||
|
filenames.append(filename)
|
||||||
|
local_path = os.path.join(self.common.settings.get('downloads_dir'), filename)
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
if '.' in filename:
|
||||||
|
# Add "-i", e.g. change "foo.txt" to "foo-2.txt"
|
||||||
|
parts = filename.split('.')
|
||||||
|
name = parts[:-1]
|
||||||
|
ext = parts[-1]
|
||||||
|
|
||||||
|
i = 2
|
||||||
|
valid = False
|
||||||
|
while not valid:
|
||||||
|
new_filename = '{}-{}.{}'.format('.'.join(name), i, ext)
|
||||||
|
local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename)
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
valid = True
|
||||||
|
else:
|
||||||
|
# If no extension, just add "-i", e.g. change "foo" to "foo-2"
|
||||||
|
i = 2
|
||||||
|
valid = False
|
||||||
|
while not valid:
|
||||||
|
new_filename = '{}-{}'.format(filename, i)
|
||||||
|
local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename)
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
valid = True
|
||||||
|
|
||||||
|
basename = os.path.basename(local_path)
|
||||||
|
if f.filename != basename:
|
||||||
|
# Tell the GUI that the file has changed names
|
||||||
|
self.web.add_request(self.web.REQUEST_UPLOAD_FILE_RENAMED, request.path, {
|
||||||
|
'id': request.upload_id,
|
||||||
|
'old_filename': f.filename,
|
||||||
|
'new_filename': basename
|
||||||
|
})
|
||||||
|
|
||||||
|
self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path))
|
||||||
|
print(strings._('receive_mode_received_file').format(local_path))
|
||||||
|
f.save(local_path)
|
||||||
|
|
||||||
|
# Note that flash strings are on English, and not translated, on purpose,
|
||||||
|
# to avoid leaking the locale of the OnionShare user
|
||||||
|
if len(filenames) == 0:
|
||||||
|
flash('No files uploaded', 'info')
|
||||||
|
else:
|
||||||
|
for filename in filenames:
|
||||||
|
flash('Sent {}'.format(filename), 'info')
|
||||||
|
|
||||||
|
if self.common.settings.get('public_mode'):
|
||||||
|
return redirect('/')
|
||||||
|
else:
|
||||||
|
return redirect('/{}'.format(slug_candidate))
|
||||||
|
|
||||||
|
@self.web.app.route("/<slug_candidate>/upload", methods=['POST'])
|
||||||
|
def upload(slug_candidate):
|
||||||
|
self.web.check_slug_candidate(slug_candidate)
|
||||||
|
return upload_logic(slug_candidate)
|
||||||
|
|
||||||
|
@self.web.app.route("/upload", methods=['POST'])
|
||||||
|
def upload_public():
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
return self.web.error404()
|
||||||
|
return upload_logic()
|
||||||
|
|
||||||
|
|
||||||
|
def close_logic(slug_candidate=''):
|
||||||
|
if self.common.settings.get('receive_allow_receiver_shutdown'):
|
||||||
|
self.web.force_shutdown()
|
||||||
|
r = make_response(render_template('closed.html'))
|
||||||
|
self.web.add_request(self.web.REQUEST_CLOSE_SERVER, request.path)
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
else:
|
||||||
|
return redirect('/{}'.format(slug_candidate))
|
||||||
|
|
||||||
|
@self.web.app.route("/<slug_candidate>/close", methods=['POST'])
|
||||||
|
def close(slug_candidate):
|
||||||
|
self.web.check_slug_candidate(slug_candidate)
|
||||||
|
return close_logic(slug_candidate)
|
||||||
|
|
||||||
|
@self.web.app.route("/close", methods=['POST'])
|
||||||
|
def close_public():
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
return self.web.error404()
|
||||||
|
return close_logic()
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveModeWSGIMiddleware(object):
|
||||||
|
"""
|
||||||
|
Custom WSGI middleware in order to attach the Web object to environ, so
|
||||||
|
ReceiveModeRequest can access it.
|
||||||
|
"""
|
||||||
|
def __init__(self, app, web):
|
||||||
|
self.app = app
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['web'] = self.web
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveModeTemporaryFile(object):
|
||||||
|
"""
|
||||||
|
A custom TemporaryFile that tells ReceiveModeRequest every time data gets
|
||||||
|
written to it, in order to track the progress of uploads.
|
||||||
|
"""
|
||||||
|
def __init__(self, filename, write_func, close_func):
|
||||||
|
self.onionshare_filename = filename
|
||||||
|
self.onionshare_write_func = write_func
|
||||||
|
self.onionshare_close_func = close_func
|
||||||
|
|
||||||
|
# Create a temporary file
|
||||||
|
self.f = tempfile.TemporaryFile('wb+')
|
||||||
|
|
||||||
|
# Make all the file-like methods and attributes actually access the
|
||||||
|
# TemporaryFile, except for write
|
||||||
|
attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode',
|
||||||
|
'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto',
|
||||||
|
'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell',
|
||||||
|
'truncate', 'writable', 'writelines']
|
||||||
|
for attr in attrs:
|
||||||
|
setattr(self, attr, getattr(self.f, attr))
|
||||||
|
|
||||||
|
def write(self, b):
|
||||||
|
"""
|
||||||
|
Custom write method that calls out to onionshare_write_func
|
||||||
|
"""
|
||||||
|
bytes_written = self.f.write(b)
|
||||||
|
self.onionshare_write_func(self.onionshare_filename, bytes_written)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Custom close method that calls out to onionshare_close_func
|
||||||
|
"""
|
||||||
|
self.f.close()
|
||||||
|
self.onionshare_close_func(self.onionshare_filename)
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveModeRequest(Request):
|
||||||
|
"""
|
||||||
|
A custom flask Request object that keeps track of how much data has been
|
||||||
|
uploaded for each file, for receive mode.
|
||||||
|
"""
|
||||||
|
def __init__(self, environ, populate_request=True, shallow=False):
|
||||||
|
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
|
||||||
|
self.web = environ['web']
|
||||||
|
|
||||||
|
# Is this a valid upload request?
|
||||||
|
self.upload_request = False
|
||||||
|
if self.method == 'POST':
|
||||||
|
if self.path == '/{}/upload'.format(self.web.slug):
|
||||||
|
self.upload_request = True
|
||||||
|
else:
|
||||||
|
if self.web.common.settings.get('public_mode'):
|
||||||
|
if self.path == '/upload':
|
||||||
|
self.upload_request = True
|
||||||
|
|
||||||
|
if self.upload_request:
|
||||||
|
# A dictionary that maps filenames to the bytes uploaded so far
|
||||||
|
self.progress = {}
|
||||||
|
|
||||||
|
# Create an upload_id, attach it to the request
|
||||||
|
self.upload_id = self.web.receive_mode.upload_count
|
||||||
|
self.web.receive_mode.upload_count += 1
|
||||||
|
|
||||||
|
# Figure out the content length
|
||||||
|
try:
|
||||||
|
self.content_length = int(self.headers['Content-Length'])
|
||||||
|
except:
|
||||||
|
self.content_length = 0
|
||||||
|
|
||||||
|
print("{}: {}".format(
|
||||||
|
datetime.now().strftime("%b %d, %I:%M%p"),
|
||||||
|
strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length))
|
||||||
|
))
|
||||||
|
|
||||||
|
# Tell the GUI
|
||||||
|
self.web.add_request(self.web.REQUEST_STARTED, self.path, {
|
||||||
|
'id': self.upload_id,
|
||||||
|
'content_length': self.content_length
|
||||||
|
})
|
||||||
|
|
||||||
|
self.previous_file = None
|
||||||
|
|
||||||
|
def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None):
|
||||||
|
"""
|
||||||
|
This gets called for each file that gets uploaded, and returns an file-like
|
||||||
|
writable stream.
|
||||||
|
"""
|
||||||
|
if self.upload_request:
|
||||||
|
self.progress[filename] = {
|
||||||
|
'uploaded_bytes': 0,
|
||||||
|
'complete': False
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Closing the request.
|
||||||
|
"""
|
||||||
|
super(ReceiveModeRequest, self).close()
|
||||||
|
if self.upload_request:
|
||||||
|
# Inform the GUI that the upload has finished
|
||||||
|
self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, {
|
||||||
|
'id': self.upload_id
|
||||||
|
})
|
||||||
|
|
||||||
|
def file_write_func(self, filename, length):
|
||||||
|
"""
|
||||||
|
This function gets called when a specific file is written to.
|
||||||
|
"""
|
||||||
|
if self.upload_request:
|
||||||
|
self.progress[filename]['uploaded_bytes'] += length
|
||||||
|
|
||||||
|
if self.previous_file != filename:
|
||||||
|
if self.previous_file is not None:
|
||||||
|
print('')
|
||||||
|
self.previous_file = filename
|
||||||
|
|
||||||
|
print('\r=> {:15s} {}'.format(
|
||||||
|
self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']),
|
||||||
|
filename
|
||||||
|
), end='')
|
||||||
|
|
||||||
|
# Update the GUI on the upload progress
|
||||||
|
self.web.add_request(self.web.REQUEST_PROGRESS, self.path, {
|
||||||
|
'id': self.upload_id,
|
||||||
|
'progress': self.progress
|
||||||
|
})
|
||||||
|
|
||||||
|
def file_close_func(self, filename):
|
||||||
|
"""
|
||||||
|
This function gets called when a specific file is closed.
|
||||||
|
"""
|
||||||
|
self.progress[filename]['complete'] = True
|
384
onionshare/web/share_mode.py
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
import mimetypes
|
||||||
|
import gzip
|
||||||
|
from flask import Response, request, render_template, make_response
|
||||||
|
|
||||||
|
from .. import strings
|
||||||
|
|
||||||
|
|
||||||
|
class ShareModeWeb(object):
|
||||||
|
"""
|
||||||
|
All of the web logic for share mode
|
||||||
|
"""
|
||||||
|
def __init__(self, common, web):
|
||||||
|
self.common = common
|
||||||
|
self.common.log('ShareModeWeb', '__init__')
|
||||||
|
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
# Information about the file to be shared
|
||||||
|
self.file_info = []
|
||||||
|
self.is_zipped = False
|
||||||
|
self.download_filename = None
|
||||||
|
self.download_filesize = None
|
||||||
|
self.gzip_filename = None
|
||||||
|
self.gzip_filesize = None
|
||||||
|
self.zip_writer = None
|
||||||
|
|
||||||
|
self.download_count = 0
|
||||||
|
|
||||||
|
# If "Stop After First Download" is checked (stay_open == False), only allow
|
||||||
|
# one download at a time.
|
||||||
|
self.download_in_progress = False
|
||||||
|
|
||||||
|
# If the client closes the OnionShare window while a download is in progress,
|
||||||
|
# it should immediately stop serving the file. The client_cancel global is
|
||||||
|
# used to tell the download function that the client is canceling the download.
|
||||||
|
self.client_cancel = False
|
||||||
|
|
||||||
|
self.define_routes()
|
||||||
|
|
||||||
|
def define_routes(self):
|
||||||
|
"""
|
||||||
|
The web app routes for sharing files
|
||||||
|
"""
|
||||||
|
@self.web.app.route("/<slug_candidate>")
|
||||||
|
def index(slug_candidate):
|
||||||
|
self.web.check_slug_candidate(slug_candidate)
|
||||||
|
return index_logic()
|
||||||
|
|
||||||
|
@self.web.app.route("/")
|
||||||
|
def index_public():
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
return self.web.error404()
|
||||||
|
return index_logic()
|
||||||
|
|
||||||
|
def index_logic(slug_candidate=''):
|
||||||
|
"""
|
||||||
|
Render the template for the onionshare landing page.
|
||||||
|
"""
|
||||||
|
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
||||||
|
|
||||||
|
# Deny new downloads if "Stop After First Download" is checked and there is
|
||||||
|
# currently a download
|
||||||
|
deny_download = not self.web.stay_open and self.download_in_progress
|
||||||
|
if deny_download:
|
||||||
|
r = make_response(render_template('denied.html'))
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
|
# If download is allowed to continue, serve download page
|
||||||
|
if self.should_use_gzip():
|
||||||
|
self.filesize = self.gzip_filesize
|
||||||
|
else:
|
||||||
|
self.filesize = self.download_filesize
|
||||||
|
|
||||||
|
if self.web.slug:
|
||||||
|
r = make_response(render_template(
|
||||||
|
'send.html',
|
||||||
|
slug=self.web.slug,
|
||||||
|
file_info=self.file_info,
|
||||||
|
filename=os.path.basename(self.download_filename),
|
||||||
|
filesize=self.filesize,
|
||||||
|
filesize_human=self.common.human_readable_filesize(self.download_filesize),
|
||||||
|
is_zipped=self.is_zipped))
|
||||||
|
else:
|
||||||
|
# If download is allowed to continue, serve download page
|
||||||
|
r = make_response(render_template(
|
||||||
|
'send.html',
|
||||||
|
file_info=self.file_info,
|
||||||
|
filename=os.path.basename(self.download_filename),
|
||||||
|
filesize=self.filesize,
|
||||||
|
filesize_human=self.common.human_readable_filesize(self.download_filesize),
|
||||||
|
is_zipped=self.is_zipped))
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
|
@self.web.app.route("/<slug_candidate>/download")
|
||||||
|
def download(slug_candidate):
|
||||||
|
self.web.check_slug_candidate(slug_candidate)
|
||||||
|
return download_logic()
|
||||||
|
|
||||||
|
@self.web.app.route("/download")
|
||||||
|
def download_public():
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
return self.web.error404()
|
||||||
|
return download_logic()
|
||||||
|
|
||||||
|
def download_logic(slug_candidate=''):
|
||||||
|
"""
|
||||||
|
Download the zip file.
|
||||||
|
"""
|
||||||
|
# Deny new downloads if "Stop After First Download" is checked and there is
|
||||||
|
# currently a download
|
||||||
|
deny_download = not self.web.stay_open and self.download_in_progress
|
||||||
|
if deny_download:
|
||||||
|
r = make_response(render_template('denied.html'))
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
|
# Each download has a unique id
|
||||||
|
download_id = self.download_count
|
||||||
|
self.download_count += 1
|
||||||
|
|
||||||
|
# Prepare some variables to use inside generate() function below
|
||||||
|
# which is outside of the request context
|
||||||
|
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
||||||
|
path = request.path
|
||||||
|
|
||||||
|
# If this is a zipped file, then serve as-is. If it's not zipped, then,
|
||||||
|
# if the http client supports gzip compression, gzip the file first
|
||||||
|
# and serve that
|
||||||
|
use_gzip = self.should_use_gzip()
|
||||||
|
if use_gzip:
|
||||||
|
file_to_download = self.gzip_filename
|
||||||
|
self.filesize = self.gzip_filesize
|
||||||
|
else:
|
||||||
|
file_to_download = self.download_filename
|
||||||
|
self.filesize = self.download_filesize
|
||||||
|
|
||||||
|
# Tell GUI the download started
|
||||||
|
self.web.add_request(self.web.REQUEST_STARTED, path, {
|
||||||
|
'id': download_id,
|
||||||
|
'use_gzip': use_gzip
|
||||||
|
})
|
||||||
|
|
||||||
|
basename = os.path.basename(self.download_filename)
|
||||||
|
|
||||||
|
def generate():
|
||||||
|
# The user hasn't canceled the download
|
||||||
|
self.client_cancel = False
|
||||||
|
|
||||||
|
# Starting a new download
|
||||||
|
if not self.web.stay_open:
|
||||||
|
self.download_in_progress = True
|
||||||
|
|
||||||
|
chunk_size = 102400 # 100kb
|
||||||
|
|
||||||
|
fp = open(file_to_download, 'rb')
|
||||||
|
self.web.done = False
|
||||||
|
canceled = False
|
||||||
|
while not self.web.done:
|
||||||
|
# The user has canceled the download, so stop serving the file
|
||||||
|
if self.client_cancel:
|
||||||
|
self.web.add_request(self.web.REQUEST_CANCELED, path, {
|
||||||
|
'id': download_id
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
chunk = fp.read(chunk_size)
|
||||||
|
if chunk == b'':
|
||||||
|
self.web.done = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
# tell GUI the progress
|
||||||
|
downloaded_bytes = fp.tell()
|
||||||
|
percent = (1.0 * downloaded_bytes / self.filesize) * 100
|
||||||
|
|
||||||
|
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
|
||||||
|
if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
||||||
|
sys.stdout.write(
|
||||||
|
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
self.web.add_request(self.web.REQUEST_PROGRESS, path, {
|
||||||
|
'id': download_id,
|
||||||
|
'bytes': downloaded_bytes
|
||||||
|
})
|
||||||
|
self.web.done = False
|
||||||
|
except:
|
||||||
|
# looks like the download was canceled
|
||||||
|
self.web.done = True
|
||||||
|
canceled = True
|
||||||
|
|
||||||
|
# tell the GUI the download has canceled
|
||||||
|
self.web.add_request(self.web.REQUEST_CANCELED, path, {
|
||||||
|
'id': download_id
|
||||||
|
})
|
||||||
|
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
if self.common.platform != 'Darwin':
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
|
# Download is finished
|
||||||
|
if not self.web.stay_open:
|
||||||
|
self.download_in_progress = False
|
||||||
|
|
||||||
|
# Close the server, if necessary
|
||||||
|
if not self.web.stay_open and not canceled:
|
||||||
|
print(strings._("closing_automatically"))
|
||||||
|
self.web.running = False
|
||||||
|
try:
|
||||||
|
if shutdown_func is None:
|
||||||
|
raise RuntimeError('Not running with the Werkzeug Server')
|
||||||
|
shutdown_func()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
r = Response(generate())
|
||||||
|
if use_gzip:
|
||||||
|
r.headers.set('Content-Encoding', 'gzip')
|
||||||
|
r.headers.set('Content-Length', self.filesize)
|
||||||
|
r.headers.set('Content-Disposition', 'attachment', filename=basename)
|
||||||
|
r = self.web.add_security_headers(r)
|
||||||
|
# guess content type
|
||||||
|
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
||||||
|
if content_type is not None:
|
||||||
|
r.headers.set('Content-Type', content_type)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def set_file_info(self, filenames, processed_size_callback=None):
|
||||||
|
"""
|
||||||
|
Using the list of filenames being shared, fill in details that the web
|
||||||
|
page will need to display. This includes zipping up the file in order to
|
||||||
|
get the zip file's name and size.
|
||||||
|
"""
|
||||||
|
self.common.log("ShareModeWeb", "set_file_info")
|
||||||
|
self.web.cancel_compression = False
|
||||||
|
|
||||||
|
self.cleanup_filenames = []
|
||||||
|
|
||||||
|
# build file info list
|
||||||
|
self.file_info = {'files': [], 'dirs': []}
|
||||||
|
for filename in filenames:
|
||||||
|
info = {
|
||||||
|
'filename': filename,
|
||||||
|
'basename': os.path.basename(filename.rstrip('/'))
|
||||||
|
}
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
info['size'] = os.path.getsize(filename)
|
||||||
|
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
||||||
|
self.file_info['files'].append(info)
|
||||||
|
if os.path.isdir(filename):
|
||||||
|
info['size'] = self.common.dir_size(filename)
|
||||||
|
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
||||||
|
self.file_info['dirs'].append(info)
|
||||||
|
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
|
||||||
|
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
|
||||||
|
|
||||||
|
# Check if there's only 1 file and no folders
|
||||||
|
if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0:
|
||||||
|
self.download_filename = self.file_info['files'][0]['filename']
|
||||||
|
self.download_filesize = self.file_info['files'][0]['size']
|
||||||
|
|
||||||
|
# Compress the file with gzip now, so we don't have to do it on each request
|
||||||
|
self.gzip_filename = tempfile.mkstemp('wb+')[1]
|
||||||
|
self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback)
|
||||||
|
self.gzip_filesize = os.path.getsize(self.gzip_filename)
|
||||||
|
|
||||||
|
# Make sure the gzip file gets cleaned up when onionshare stops
|
||||||
|
self.cleanup_filenames.append(self.gzip_filename)
|
||||||
|
|
||||||
|
self.is_zipped = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Zip up the files and folders
|
||||||
|
self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback)
|
||||||
|
self.download_filename = self.zip_writer.zip_filename
|
||||||
|
for info in self.file_info['files']:
|
||||||
|
self.zip_writer.add_file(info['filename'])
|
||||||
|
# Canceling early?
|
||||||
|
if self.web.cancel_compression:
|
||||||
|
self.zip_writer.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
for info in self.file_info['dirs']:
|
||||||
|
if not self.zip_writer.add_dir(info['filename']):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.zip_writer.close()
|
||||||
|
self.download_filesize = os.path.getsize(self.download_filename)
|
||||||
|
|
||||||
|
# Make sure the zip file gets cleaned up when onionshare stops
|
||||||
|
self.cleanup_filenames.append(self.zip_writer.zip_filename)
|
||||||
|
|
||||||
|
self.is_zipped = True
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def should_use_gzip(self):
|
||||||
|
"""
|
||||||
|
Should we use gzip for this browser?
|
||||||
|
"""
|
||||||
|
return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower())
|
||||||
|
|
||||||
|
def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None):
|
||||||
|
"""
|
||||||
|
Compress a file with gzip, without loading the whole thing into memory
|
||||||
|
Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror
|
||||||
|
"""
|
||||||
|
bytes_processed = 0
|
||||||
|
blocksize = 1 << 16 # 64kB
|
||||||
|
with open(input_filename, 'rb') as input_file:
|
||||||
|
output_file = gzip.open(output_filename, 'wb', level)
|
||||||
|
while True:
|
||||||
|
if processed_size_callback is not None:
|
||||||
|
processed_size_callback(bytes_processed)
|
||||||
|
|
||||||
|
block = input_file.read(blocksize)
|
||||||
|
if len(block) == 0:
|
||||||
|
break
|
||||||
|
output_file.write(block)
|
||||||
|
bytes_processed += blocksize
|
||||||
|
|
||||||
|
output_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
class ZipWriter(object):
|
||||||
|
"""
|
||||||
|
ZipWriter accepts files and directories and compresses them into a zip file
|
||||||
|
with. If a zip_filename is not passed in, it will use the default onionshare
|
||||||
|
filename.
|
||||||
|
"""
|
||||||
|
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
||||||
|
self.common = common
|
||||||
|
self.cancel_compression = False
|
||||||
|
|
||||||
|
if zip_filename:
|
||||||
|
self.zip_filename = zip_filename
|
||||||
|
else:
|
||||||
|
self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6))
|
||||||
|
|
||||||
|
self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
|
||||||
|
self.processed_size_callback = processed_size_callback
|
||||||
|
if self.processed_size_callback is None:
|
||||||
|
self.processed_size_callback = lambda _: None
|
||||||
|
self._size = 0
|
||||||
|
self.processed_size_callback(self._size)
|
||||||
|
|
||||||
|
def add_file(self, filename):
|
||||||
|
"""
|
||||||
|
Add a file to the zip archive.
|
||||||
|
"""
|
||||||
|
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
|
||||||
|
self._size += os.path.getsize(filename)
|
||||||
|
self.processed_size_callback(self._size)
|
||||||
|
|
||||||
|
def add_dir(self, filename):
|
||||||
|
"""
|
||||||
|
Add a directory, and all of its children, to the zip archive.
|
||||||
|
"""
|
||||||
|
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
||||||
|
for dirpath, dirnames, filenames in os.walk(filename):
|
||||||
|
for f in filenames:
|
||||||
|
# Canceling early?
|
||||||
|
if self.cancel_compression:
|
||||||
|
return False
|
||||||
|
|
||||||
|
full_filename = os.path.join(dirpath, f)
|
||||||
|
if not os.path.islink(full_filename):
|
||||||
|
arc_filename = full_filename[len(dir_to_strip):]
|
||||||
|
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
|
||||||
|
self._size += os.path.getsize(full_filename)
|
||||||
|
self.processed_size_callback(self._size)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Close the zip archive.
|
||||||
|
"""
|
||||||
|
self.z.close()
|
252
onionshare/web/web.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import hmac
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import queue
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from distutils.version import LooseVersion as Version
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
import flask
|
||||||
|
from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version
|
||||||
|
|
||||||
|
from .. import strings
|
||||||
|
|
||||||
|
from .share_mode import ShareModeWeb
|
||||||
|
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest
|
||||||
|
|
||||||
|
|
||||||
|
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
||||||
|
# are not applicable to OnionShare
|
||||||
|
def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
|
||||||
|
pass
|
||||||
|
|
||||||
|
flask.cli.show_server_banner = stubbed_show_server_banner
|
||||||
|
|
||||||
|
|
||||||
|
class Web(object):
|
||||||
|
"""
|
||||||
|
The Web object is the OnionShare web server, powered by flask
|
||||||
|
"""
|
||||||
|
REQUEST_LOAD = 0
|
||||||
|
REQUEST_STARTED = 1
|
||||||
|
REQUEST_PROGRESS = 2
|
||||||
|
REQUEST_OTHER = 3
|
||||||
|
REQUEST_CANCELED = 4
|
||||||
|
REQUEST_RATE_LIMIT = 5
|
||||||
|
REQUEST_CLOSE_SERVER = 6
|
||||||
|
REQUEST_UPLOAD_FILE_RENAMED = 7
|
||||||
|
REQUEST_UPLOAD_FINISHED = 8
|
||||||
|
REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9
|
||||||
|
REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10
|
||||||
|
|
||||||
|
def __init__(self, common, is_gui, mode='share'):
|
||||||
|
self.common = common
|
||||||
|
self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode))
|
||||||
|
|
||||||
|
# The flask app
|
||||||
|
self.app = Flask(__name__,
|
||||||
|
static_folder=self.common.get_resource_path('static'),
|
||||||
|
template_folder=self.common.get_resource_path('templates'))
|
||||||
|
self.app.secret_key = self.common.random_string(8)
|
||||||
|
|
||||||
|
# Debug mode?
|
||||||
|
if self.common.debug:
|
||||||
|
self.debug_mode()
|
||||||
|
|
||||||
|
# Are we running in GUI mode?
|
||||||
|
self.is_gui = is_gui
|
||||||
|
|
||||||
|
# Are we using receive mode?
|
||||||
|
self.mode = mode
|
||||||
|
if self.mode == 'receive':
|
||||||
|
# Use custom WSGI middleware, to modify environ
|
||||||
|
self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self)
|
||||||
|
# Use a custom Request class to track upload progess
|
||||||
|
self.app.request_class = ReceiveModeRequest
|
||||||
|
|
||||||
|
# Starting in Flask 0.11, render_template_string autoescapes template variables
|
||||||
|
# by default. To prevent content injection through template variables in
|
||||||
|
# earlier versions of Flask, we force autoescaping in the Jinja2 template
|
||||||
|
# engine if we detect a Flask version with insecure default behavior.
|
||||||
|
if Version(flask_version) < Version('0.11'):
|
||||||
|
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
||||||
|
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
|
||||||
|
|
||||||
|
self.security_headers = [
|
||||||
|
('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
|
||||||
|
('X-Frame-Options', 'DENY'),
|
||||||
|
('X-Xss-Protection', '1; mode=block'),
|
||||||
|
('X-Content-Type-Options', 'nosniff'),
|
||||||
|
('Referrer-Policy', 'no-referrer'),
|
||||||
|
('Server', 'OnionShare')
|
||||||
|
]
|
||||||
|
|
||||||
|
self.q = queue.Queue()
|
||||||
|
self.slug = None
|
||||||
|
self.error404_count = 0
|
||||||
|
|
||||||
|
self.done = False
|
||||||
|
|
||||||
|
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
|
||||||
|
self.shutdown_slug = self.common.random_string(16)
|
||||||
|
|
||||||
|
# Keep track if the server is running
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
# Define the web app routes
|
||||||
|
self.define_common_routes()
|
||||||
|
|
||||||
|
# Create the mode web object, which defines its own routes
|
||||||
|
self.share_mode = None
|
||||||
|
self.receive_mode = None
|
||||||
|
if self.mode == 'receive':
|
||||||
|
self.receive_mode = ReceiveModeWeb(self.common, self)
|
||||||
|
elif self.mode == 'share':
|
||||||
|
self.share_mode = ShareModeWeb(self.common, self)
|
||||||
|
|
||||||
|
|
||||||
|
def define_common_routes(self):
|
||||||
|
"""
|
||||||
|
Common web app routes between sending and receiving
|
||||||
|
"""
|
||||||
|
@self.app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
"""
|
||||||
|
404 error page.
|
||||||
|
"""
|
||||||
|
return self.error404()
|
||||||
|
|
||||||
|
@self.app.route("/<slug_candidate>/shutdown")
|
||||||
|
def shutdown(slug_candidate):
|
||||||
|
"""
|
||||||
|
Stop the flask web server, from the context of an http request.
|
||||||
|
"""
|
||||||
|
self.check_shutdown_slug_candidate(slug_candidate)
|
||||||
|
self.force_shutdown()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def error404(self):
|
||||||
|
self.add_request(Web.REQUEST_OTHER, request.path)
|
||||||
|
if request.path != '/favicon.ico':
|
||||||
|
self.error404_count += 1
|
||||||
|
|
||||||
|
# In receive mode, with public mode enabled, skip rate limiting 404s
|
||||||
|
if not self.common.settings.get('public_mode'):
|
||||||
|
if self.error404_count == 20:
|
||||||
|
self.add_request(Web.REQUEST_RATE_LIMIT, request.path)
|
||||||
|
self.force_shutdown()
|
||||||
|
print(strings._('error_rate_limit'))
|
||||||
|
|
||||||
|
r = make_response(render_template('404.html'), 404)
|
||||||
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
|
def add_security_headers(self, r):
|
||||||
|
"""
|
||||||
|
Add security headers to a request
|
||||||
|
"""
|
||||||
|
for header, value in self.security_headers:
|
||||||
|
r.headers.set(header, value)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def _safe_select_jinja_autoescape(self, filename):
|
||||||
|
if filename is None:
|
||||||
|
return True
|
||||||
|
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
||||||
|
|
||||||
|
def add_request(self, request_type, path, data=None):
|
||||||
|
"""
|
||||||
|
Add a request to the queue, to communicate with the GUI.
|
||||||
|
"""
|
||||||
|
self.q.put({
|
||||||
|
'type': request_type,
|
||||||
|
'path': path,
|
||||||
|
'data': data
|
||||||
|
})
|
||||||
|
|
||||||
|
def generate_slug(self, persistent_slug=None):
|
||||||
|
self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug))
|
||||||
|
if persistent_slug != None and persistent_slug != '':
|
||||||
|
self.slug = persistent_slug
|
||||||
|
self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug))
|
||||||
|
else:
|
||||||
|
self.slug = self.common.build_slug()
|
||||||
|
self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug))
|
||||||
|
|
||||||
|
def debug_mode(self):
|
||||||
|
"""
|
||||||
|
Turn on debugging mode, which will log flask errors to a debug file.
|
||||||
|
"""
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
log_handler = logging.FileHandler(
|
||||||
|
os.path.join(temp_dir, 'onionshare_server.log'))
|
||||||
|
log_handler.setLevel(logging.WARNING)
|
||||||
|
self.app.logger.addHandler(log_handler)
|
||||||
|
|
||||||
|
def check_slug_candidate(self, slug_candidate):
|
||||||
|
self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate))
|
||||||
|
if self.common.settings.get('public_mode'):
|
||||||
|
abort(404)
|
||||||
|
if not hmac.compare_digest(self.slug, slug_candidate):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
def check_shutdown_slug_candidate(self, slug_candidate):
|
||||||
|
self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate))
|
||||||
|
if not hmac.compare_digest(self.shutdown_slug, slug_candidate):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
def force_shutdown(self):
|
||||||
|
"""
|
||||||
|
Stop the flask web server, from the context of the flask app.
|
||||||
|
"""
|
||||||
|
# Shutdown the flask service
|
||||||
|
try:
|
||||||
|
func = request.environ.get('werkzeug.server.shutdown')
|
||||||
|
if func is None:
|
||||||
|
raise RuntimeError('Not running with the Werkzeug Server')
|
||||||
|
func()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def start(self, port, stay_open=False, public_mode=False, persistent_slug=None):
|
||||||
|
"""
|
||||||
|
Start the flask web server.
|
||||||
|
"""
|
||||||
|
self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug))
|
||||||
|
if not public_mode:
|
||||||
|
self.generate_slug(persistent_slug)
|
||||||
|
|
||||||
|
self.stay_open = stay_open
|
||||||
|
|
||||||
|
# In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
|
||||||
|
if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
|
||||||
|
host = '0.0.0.0'
|
||||||
|
else:
|
||||||
|
host = '127.0.0.1'
|
||||||
|
|
||||||
|
self.running = True
|
||||||
|
self.app.run(host=host, port=port, threaded=True)
|
||||||
|
|
||||||
|
def stop(self, port):
|
||||||
|
"""
|
||||||
|
Stop the flask web server by loading /shutdown.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.mode == 'share':
|
||||||
|
# If the user cancels the download, let the download function know to stop
|
||||||
|
# serving the file
|
||||||
|
self.share_mode.client_cancel = True
|
||||||
|
|
||||||
|
# To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
|
||||||
|
if self.running:
|
||||||
|
try:
|
||||||
|
s = socket.socket()
|
||||||
|
s.connect(('127.0.0.1', port))
|
||||||
|
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug))
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read()
|
||||||
|
except:
|
||||||
|
pass
|
@ -18,7 +18,11 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import os, sys, platform, argparse
|
import os
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
import argparse
|
||||||
|
import signal
|
||||||
from .widgets import Alert
|
from .widgets import Alert
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
@ -58,6 +62,10 @@ def main():
|
|||||||
strings.load_strings(common)
|
strings.load_strings(common)
|
||||||
print(strings._('version_string').format(common.version))
|
print(strings._('version_string').format(common.version))
|
||||||
|
|
||||||
|
# Allow Ctrl-C to smoothly quit the program instead of throwing an exception
|
||||||
|
# https://stackoverflow.com/questions/42814093/how-to-handle-ctrlc-in-python-app-with-pyqt
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
# Start the Qt app
|
# Start the Qt app
|
||||||
global qtapp
|
global qtapp
|
||||||
qtapp = Application(common)
|
qtapp = Application(common)
|
||||||
|
@ -17,16 +17,14 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
from onionshare.common import ShutdownTimer
|
from onionshare.common import ShutdownTimer
|
||||||
|
|
||||||
from .server_status import ServerStatus
|
from ..server_status import ServerStatus
|
||||||
from .onion_thread import OnionThread
|
from ..threads import OnionThread
|
||||||
from .widgets import Alert
|
from ..widgets import Alert
|
||||||
|
|
||||||
class Mode(QtWidgets.QWidget):
|
class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
@ -39,7 +37,7 @@ class Mode(QtWidgets.QWidget):
|
|||||||
starting_server_error = QtCore.pyqtSignal(str)
|
starting_server_error = QtCore.pyqtSignal(str)
|
||||||
set_server_active = QtCore.pyqtSignal(bool)
|
set_server_active = QtCore.pyqtSignal(bool)
|
||||||
|
|
||||||
def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None):
|
def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False):
|
||||||
super(Mode, self).__init__()
|
super(Mode, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
@ -51,13 +49,18 @@ class Mode(QtWidgets.QWidget):
|
|||||||
|
|
||||||
self.filenames = filenames
|
self.filenames = filenames
|
||||||
|
|
||||||
self.setMinimumWidth(450)
|
|
||||||
|
|
||||||
# The web object gets created in init()
|
# The web object gets created in init()
|
||||||
self.web = None
|
self.web = None
|
||||||
|
|
||||||
|
# Local mode is passed from OnionShareGui
|
||||||
|
self.local_only = local_only
|
||||||
|
|
||||||
|
# Threads start out as None
|
||||||
|
self.onion_thread = None
|
||||||
|
self.web_thread = None
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status = ServerStatus(self.common, self.qtapp, self.app)
|
self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only)
|
||||||
self.server_status.server_started.connect(self.start_server)
|
self.server_status.server_started.connect(self.start_server)
|
||||||
self.server_status.server_stopped.connect(self.stop_server)
|
self.server_status.server_stopped.connect(self.stop_server)
|
||||||
self.server_status.server_canceled.connect(self.cancel_server)
|
self.server_status.server_canceled.connect(self.cancel_server)
|
||||||
@ -67,16 +70,17 @@ class Mode(QtWidgets.QWidget):
|
|||||||
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)
|
||||||
|
|
||||||
# Primary action layout
|
# Primary action
|
||||||
|
# Note: It's up to the downstream Mode to add this to its layout
|
||||||
self.primary_action_layout = QtWidgets.QVBoxLayout()
|
self.primary_action_layout = QtWidgets.QVBoxLayout()
|
||||||
self.primary_action_layout.addWidget(self.server_status)
|
self.primary_action_layout.addWidget(self.server_status)
|
||||||
self.primary_action = QtWidgets.QWidget()
|
self.primary_action = QtWidgets.QWidget()
|
||||||
self.primary_action.setLayout(self.primary_action_layout)
|
self.primary_action.setLayout(self.primary_action_layout)
|
||||||
|
|
||||||
# Layout
|
# Hack to allow a minimum width on the main layout
|
||||||
self.layout = QtWidgets.QVBoxLayout()
|
# Note: It's up to the downstream Mode to add this to its layout
|
||||||
self.layout.addWidget(self.primary_action)
|
self.min_width_widget = QtWidgets.QWidget()
|
||||||
self.setLayout(self.layout)
|
self.min_width_widget.setMinimumWidth(600)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
@ -138,34 +142,11 @@ class Mode(QtWidgets.QWidget):
|
|||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_status_label.setText('')
|
self.server_status_label.setText('')
|
||||||
|
|
||||||
# Start the onion service in a new thread
|
|
||||||
def start_onion_service(self):
|
|
||||||
# Choose a port for the web app
|
|
||||||
self.app.choose_port()
|
|
||||||
|
|
||||||
# Start http service in new thread
|
|
||||||
t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('public_mode'), self.common.settings.get('slug')))
|
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
# Wait for the web app slug to generate before continuing
|
|
||||||
if not self.common.settings.get('public_mode'):
|
|
||||||
while self.web.slug == None:
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
# Now start the onion service
|
|
||||||
try:
|
|
||||||
self.app.start_onion_service()
|
|
||||||
self.starting_server_step2.emit()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.starting_server_error.emit(e.args[0])
|
|
||||||
return
|
|
||||||
|
|
||||||
self.common.log('Mode', 'start_server', 'Starting an onion thread')
|
self.common.log('Mode', 'start_server', 'Starting an onion thread')
|
||||||
self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self})
|
self.onion_thread = OnionThread(self)
|
||||||
self.t.daemon = True
|
self.onion_thread.success.connect(self.starting_server_step2.emit)
|
||||||
self.t.start()
|
self.onion_thread.error.connect(self.starting_server_error.emit)
|
||||||
|
self.onion_thread.start()
|
||||||
|
|
||||||
def start_server_custom(self):
|
def start_server_custom(self):
|
||||||
"""
|
"""
|
||||||
@ -243,10 +224,22 @@ class Mode(QtWidgets.QWidget):
|
|||||||
"""
|
"""
|
||||||
Cancel the server while it is preparing to start
|
Cancel the server while it is preparing to start
|
||||||
"""
|
"""
|
||||||
if self.t:
|
self.cancel_server_custom()
|
||||||
self.t.quit()
|
|
||||||
|
if self.onion_thread:
|
||||||
|
self.common.log('Mode', 'cancel_server: quitting onion thread')
|
||||||
|
self.onion_thread.quit()
|
||||||
|
if self.web_thread:
|
||||||
|
self.common.log('Mode', 'cancel_server: quitting web thread')
|
||||||
|
self.web_thread.quit()
|
||||||
self.stop_server()
|
self.stop_server()
|
||||||
|
|
||||||
|
def cancel_server_custom(self):
|
||||||
|
"""
|
||||||
|
Add custom initialization here.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
"""
|
"""
|
||||||
Stop the onionshare server.
|
Stop the onionshare server.
|
548
onionshare_gui/mode/history.py
Normal file
@ -0,0 +1,548 @@
|
|||||||
|
# -*- 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
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
from onionshare import strings
|
||||||
|
from ..widgets import Alert
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryItem(QtWidgets.QWidget):
|
||||||
|
"""
|
||||||
|
The base history item
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super(HistoryItem, self).__init__()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadHistoryItem(HistoryItem):
|
||||||
|
"""
|
||||||
|
Download history item, for share mode
|
||||||
|
"""
|
||||||
|
def __init__(self, common, id, total_bytes):
|
||||||
|
super(DownloadHistoryItem, self).__init__()
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.id = id
|
||||||
|
self.total_bytes = total_bytes
|
||||||
|
self.downloaded_bytes = 0
|
||||||
|
self.started = time.time()
|
||||||
|
self.started_dt = datetime.fromtimestamp(self.started)
|
||||||
|
|
||||||
|
# Label
|
||||||
|
self.label = QtWidgets.QLabel(strings._('gui_download_in_progress').format(self.started_dt.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.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
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addWidget(self.label)
|
||||||
|
layout.addWidget(self.progress_bar)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# 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 UploadHistoryItemFile(QtWidgets.QWidget):
|
||||||
|
def __init__(self, common, filename):
|
||||||
|
super(UploadHistoryItemFile, self).__init__()
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('UploadHistoryItemFile', '__init__', 'filename: {}'.format(filename))
|
||||||
|
|
||||||
|
self.filename = filename
|
||||||
|
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 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('UploadHistoryItemFile', 'open_folder')
|
||||||
|
|
||||||
|
abs_filename = os.path.join(self.common.settings.get('downloads_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':
|
||||||
|
subprocess.call(['open', '-R', abs_filename])
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
elif self.common.platform == 'Windows':
|
||||||
|
subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)])
|
||||||
|
|
||||||
|
class UploadHistoryItem(HistoryItem):
|
||||||
|
def __init__(self, common, id, content_length):
|
||||||
|
super(UploadHistoryItem, self).__init__()
|
||||||
|
self.common = common
|
||||||
|
self.id = id
|
||||||
|
self.content_length = content_length
|
||||||
|
self.started = datetime.now()
|
||||||
|
|
||||||
|
# Label
|
||||||
|
self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress', True).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, data):
|
||||||
|
"""
|
||||||
|
Using the progress from Web, update the progress bar and file size labels
|
||||||
|
for each file
|
||||||
|
"""
|
||||||
|
if data['action'] == 'progress':
|
||||||
|
total_uploaded_bytes = 0
|
||||||
|
for filename in data['progress']:
|
||||||
|
total_uploaded_bytes += data['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(data['progress']):
|
||||||
|
# Add a new file if needed
|
||||||
|
if filename not in self.files:
|
||||||
|
self.files[filename] = UploadHistoryItemFile(self.common, filename)
|
||||||
|
self.files_layout.addWidget(self.files[filename])
|
||||||
|
|
||||||
|
# Update the file
|
||||||
|
self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete'])
|
||||||
|
|
||||||
|
elif data['action'] == 'rename':
|
||||||
|
self.files[data['old_filename']].rename(data['new_filename'])
|
||||||
|
self.files[data['new_filename']] = self.files.pop(data['old_filename'])
|
||||||
|
|
||||||
|
elif data['action'] == 'finished':
|
||||||
|
# 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', True).format(
|
||||||
|
self.started.strftime("%b %d, %I:%M%p")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
text = strings._('gui_upload_finished_range', True).format(
|
||||||
|
self.started.strftime("%b %d, %I:%M%p"),
|
||||||
|
self.ended.strftime("%I:%M%p")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
text = strings._('gui_upload_finished_range', True).format(
|
||||||
|
self.started.strftime("%b %d, %I:%M%p"),
|
||||||
|
self.ended.strftime("%b %d, %I:%M%p")
|
||||||
|
)
|
||||||
|
self.label.setText(text)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryItemList(QtWidgets.QScrollArea):
|
||||||
|
"""
|
||||||
|
List of items
|
||||||
|
"""
|
||||||
|
def __init__(self, common):
|
||||||
|
super(HistoryItemList, self).__init__()
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.items = {}
|
||||||
|
|
||||||
|
# The layout that holds all of the items
|
||||||
|
self.items_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.items_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.items_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
|
||||||
|
|
||||||
|
# Wrapper layout that also contains a stretch
|
||||||
|
wrapper_layout = QtWidgets.QVBoxLayout()
|
||||||
|
wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize)
|
||||||
|
wrapper_layout.addLayout(self.items_layout)
|
||||||
|
wrapper_layout.addStretch()
|
||||||
|
|
||||||
|
# The internal widget of the scroll area
|
||||||
|
widget = QtWidgets.QWidget()
|
||||||
|
widget.setLayout(wrapper_layout)
|
||||||
|
self.setWidget(widget)
|
||||||
|
self.setWidgetResizable(True)
|
||||||
|
|
||||||
|
# Other scroll area settings
|
||||||
|
self.setBackgroundRole(QtGui.QPalette.Light)
|
||||||
|
self.verticalScrollBar().rangeChanged.connect(self.resizeScroll)
|
||||||
|
|
||||||
|
def resizeScroll(self, minimum, maximum):
|
||||||
|
"""
|
||||||
|
Scroll to the bottom of the window when the range changes.
|
||||||
|
"""
|
||||||
|
self.verticalScrollBar().setValue(maximum)
|
||||||
|
|
||||||
|
def add(self, id, item):
|
||||||
|
"""
|
||||||
|
Add a new item. Override this method.
|
||||||
|
"""
|
||||||
|
self.items[id] = item
|
||||||
|
self.items_layout.addWidget(item)
|
||||||
|
|
||||||
|
def update(self, id, data):
|
||||||
|
"""
|
||||||
|
Update an item. Override this method.
|
||||||
|
"""
|
||||||
|
self.items[id].update(data)
|
||||||
|
|
||||||
|
def cancel(self, id):
|
||||||
|
"""
|
||||||
|
Cancel an item. Override this method.
|
||||||
|
"""
|
||||||
|
self.items[id].cancel()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""
|
||||||
|
Reset all items, emptying the list. Override this method.
|
||||||
|
"""
|
||||||
|
for item in self.items.values():
|
||||||
|
self.items_layout.removeWidget(item)
|
||||||
|
item.close()
|
||||||
|
self.items = {}
|
||||||
|
|
||||||
|
|
||||||
|
class History(QtWidgets.QWidget):
|
||||||
|
"""
|
||||||
|
A history of what's happened so far in this mode. This contains an internal
|
||||||
|
object full of a scrollable list of items.
|
||||||
|
"""
|
||||||
|
def __init__(self, common, empty_image, empty_text, header_text):
|
||||||
|
super(History, self).__init__()
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.setMinimumWidth(350)
|
||||||
|
|
||||||
|
# In progress and completed counters
|
||||||
|
self.in_progress_count = 0
|
||||||
|
self.completed_count = 0
|
||||||
|
|
||||||
|
# In progress and completed labels
|
||||||
|
self.in_progress_label = QtWidgets.QLabel()
|
||||||
|
self.in_progress_label.setStyleSheet(self.common.css['mode_info_label'])
|
||||||
|
self.completed_label = QtWidgets.QLabel()
|
||||||
|
self.completed_label.setStyleSheet(self.common.css['mode_info_label'])
|
||||||
|
|
||||||
|
# Header
|
||||||
|
self.header_label = QtWidgets.QLabel(header_text)
|
||||||
|
self.header_label.setStyleSheet(self.common.css['downloads_uploads_label'])
|
||||||
|
clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True))
|
||||||
|
clear_button.setStyleSheet(self.common.css['downloads_uploads_clear'])
|
||||||
|
clear_button.setFlat(True)
|
||||||
|
clear_button.clicked.connect(self.reset)
|
||||||
|
header_layout = QtWidgets.QHBoxLayout()
|
||||||
|
header_layout.addWidget(self.header_label)
|
||||||
|
header_layout.addStretch()
|
||||||
|
header_layout.addWidget(self.in_progress_label)
|
||||||
|
header_layout.addWidget(self.completed_label)
|
||||||
|
header_layout.addWidget(clear_button)
|
||||||
|
|
||||||
|
# When there are no items
|
||||||
|
self.empty_image = QtWidgets.QLabel()
|
||||||
|
self.empty_image.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.empty_image.setPixmap(empty_image)
|
||||||
|
self.empty_text = QtWidgets.QLabel(empty_text)
|
||||||
|
self.empty_text.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text'])
|
||||||
|
empty_layout = QtWidgets.QVBoxLayout()
|
||||||
|
empty_layout.addStretch()
|
||||||
|
empty_layout.addWidget(self.empty_image)
|
||||||
|
empty_layout.addWidget(self.empty_text)
|
||||||
|
empty_layout.addStretch()
|
||||||
|
self.empty = QtWidgets.QWidget()
|
||||||
|
self.empty.setStyleSheet(self.common.css['downloads_uploads_empty'])
|
||||||
|
self.empty.setLayout(empty_layout)
|
||||||
|
|
||||||
|
# When there are items
|
||||||
|
self.item_list = HistoryItemList(self.common)
|
||||||
|
self.not_empty_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.not_empty_layout.addLayout(header_layout)
|
||||||
|
self.not_empty_layout.addWidget(self.item_list)
|
||||||
|
self.not_empty = QtWidgets.QWidget()
|
||||||
|
self.not_empty.setLayout(self.not_empty_layout)
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.addWidget(self.empty)
|
||||||
|
layout.addWidget(self.not_empty)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Reset once at the beginning
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def add(self, id, item):
|
||||||
|
"""
|
||||||
|
Add a new item.
|
||||||
|
"""
|
||||||
|
self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item))
|
||||||
|
|
||||||
|
# Hide empty, show not empty
|
||||||
|
self.empty.hide()
|
||||||
|
self.not_empty.show()
|
||||||
|
|
||||||
|
# Add it to the list
|
||||||
|
self.item_list.add(id, item)
|
||||||
|
|
||||||
|
def update(self, id, data):
|
||||||
|
"""
|
||||||
|
Update an item.
|
||||||
|
"""
|
||||||
|
self.item_list.update(id, data)
|
||||||
|
|
||||||
|
def cancel(self, id):
|
||||||
|
"""
|
||||||
|
Cancel an item.
|
||||||
|
"""
|
||||||
|
self.item_list.cancel(id)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""
|
||||||
|
Reset all items.
|
||||||
|
"""
|
||||||
|
self.item_list.reset()
|
||||||
|
|
||||||
|
# Hide not empty, show empty
|
||||||
|
self.not_empty.hide()
|
||||||
|
self.empty.show()
|
||||||
|
|
||||||
|
# Reset counters
|
||||||
|
self.completed_count = 0
|
||||||
|
self.in_progress_count = 0
|
||||||
|
self.update_completed()
|
||||||
|
self.update_in_progress()
|
||||||
|
|
||||||
|
def update_completed(self):
|
||||||
|
"""
|
||||||
|
Update the 'completed' widget.
|
||||||
|
"""
|
||||||
|
if self.completed_count == 0:
|
||||||
|
image = self.common.get_resource_path('images/share_completed_none.png')
|
||||||
|
else:
|
||||||
|
image = self.common.get_resource_path('images/share_completed.png')
|
||||||
|
self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count))
|
||||||
|
self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count))
|
||||||
|
|
||||||
|
def update_in_progress(self):
|
||||||
|
"""
|
||||||
|
Update the 'in progress' widget.
|
||||||
|
"""
|
||||||
|
if self.in_progress_count == 0:
|
||||||
|
image = self.common.get_resource_path('images/share_in_progress_none.png')
|
||||||
|
else:
|
||||||
|
image = self.common.get_resource_path('images/share_in_progress.png')
|
||||||
|
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
|
||||||
|
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip', True).format(self.in_progress_count))
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleHistory(QtWidgets.QPushButton):
|
||||||
|
"""
|
||||||
|
Widget for toggling showing or hiding the history, as well as keeping track
|
||||||
|
of the indicator counter if it's hidden
|
||||||
|
"""
|
||||||
|
def __init__(self, common, current_mode, history_widget, icon, selected_icon):
|
||||||
|
super(ToggleHistory, self).__init__()
|
||||||
|
self.common = common
|
||||||
|
self.current_mode = current_mode
|
||||||
|
self.history_widget = history_widget
|
||||||
|
self.icon = icon
|
||||||
|
self.selected_icon = selected_icon
|
||||||
|
|
||||||
|
# Toggle button
|
||||||
|
self.setDefault(False)
|
||||||
|
self.setFixedWidth(35)
|
||||||
|
self.setFixedHeight(30)
|
||||||
|
self.setFlat(True)
|
||||||
|
self.setIcon(icon)
|
||||||
|
self.clicked.connect(self.toggle_clicked)
|
||||||
|
|
||||||
|
# Keep track of indicator
|
||||||
|
self.indicator_count = 0
|
||||||
|
self.indicator_label = QtWidgets.QLabel(parent=self)
|
||||||
|
self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator'])
|
||||||
|
self.update_indicator()
|
||||||
|
|
||||||
|
def update_indicator(self, increment=False):
|
||||||
|
"""
|
||||||
|
Update the display of the indicator count. If increment is True, then
|
||||||
|
only increment the counter if Downloads is hidden.
|
||||||
|
"""
|
||||||
|
if increment and not self.history_widget.isVisible():
|
||||||
|
self.indicator_count += 1
|
||||||
|
|
||||||
|
self.indicator_label.setText("{}".format(self.indicator_count))
|
||||||
|
|
||||||
|
if self.indicator_count == 0:
|
||||||
|
self.indicator_label.hide()
|
||||||
|
else:
|
||||||
|
size = self.indicator_label.sizeHint()
|
||||||
|
self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height())
|
||||||
|
self.indicator_label.show()
|
||||||
|
|
||||||
|
def toggle_clicked(self):
|
||||||
|
"""
|
||||||
|
Toggle showing and hiding the history widget
|
||||||
|
"""
|
||||||
|
self.common.log('ToggleHistory', 'toggle_clicked')
|
||||||
|
|
||||||
|
if self.history_widget.isVisible():
|
||||||
|
self.history_widget.hide()
|
||||||
|
self.setIcon(self.icon)
|
||||||
|
self.setFlat(True)
|
||||||
|
else:
|
||||||
|
self.history_widget.show()
|
||||||
|
self.setIcon(self.selected_icon)
|
||||||
|
self.setFlat(False)
|
||||||
|
|
||||||
|
# Reset the indicator count
|
||||||
|
self.indicator_count = 0
|
||||||
|
self.update_indicator()
|
196
onionshare_gui/mode/receive_mode/__init__.py
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
from onionshare import strings
|
||||||
|
from onionshare.web import Web
|
||||||
|
|
||||||
|
from ..history import History, ToggleHistory, UploadHistoryItem
|
||||||
|
from .. import Mode
|
||||||
|
|
||||||
|
class ReceiveMode(Mode):
|
||||||
|
"""
|
||||||
|
Parts of the main window UI for receiving files.
|
||||||
|
"""
|
||||||
|
def init(self):
|
||||||
|
"""
|
||||||
|
Custom initialization for ReceiveMode.
|
||||||
|
"""
|
||||||
|
# Create the Web object
|
||||||
|
self.web = Web(self.common, True, 'receive')
|
||||||
|
|
||||||
|
# Server status
|
||||||
|
self.server_status.set_mode('receive')
|
||||||
|
self.server_status.server_started_finished.connect(self.update_primary_action)
|
||||||
|
self.server_status.server_stopped.connect(self.update_primary_action)
|
||||||
|
self.server_status.server_canceled.connect(self.update_primary_action)
|
||||||
|
|
||||||
|
# Tell server_status about web, then update
|
||||||
|
self.server_status.web = self.web
|
||||||
|
self.server_status.update()
|
||||||
|
|
||||||
|
# Upload history
|
||||||
|
self.history = History(
|
||||||
|
self.common,
|
||||||
|
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png'))),
|
||||||
|
strings._('gui_no_uploads'),
|
||||||
|
strings._('gui_uploads')
|
||||||
|
)
|
||||||
|
self.history.hide()
|
||||||
|
|
||||||
|
# Toggle history
|
||||||
|
self.toggle_history = ToggleHistory(
|
||||||
|
self.common, self, self.history,
|
||||||
|
QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')),
|
||||||
|
QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Receive mode warning
|
||||||
|
receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True))
|
||||||
|
receive_warning.setMinimumHeight(80)
|
||||||
|
receive_warning.setWordWrap(True)
|
||||||
|
|
||||||
|
# Top bar
|
||||||
|
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||||
|
top_bar_layout.addStretch()
|
||||||
|
top_bar_layout.addWidget(self.toggle_history)
|
||||||
|
|
||||||
|
# Main layout
|
||||||
|
self.main_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.main_layout.addLayout(top_bar_layout)
|
||||||
|
self.main_layout.addWidget(receive_warning)
|
||||||
|
self.main_layout.addWidget(self.primary_action)
|
||||||
|
self.main_layout.addStretch()
|
||||||
|
self.main_layout.addWidget(self.min_width_widget)
|
||||||
|
|
||||||
|
# Wrapper layout
|
||||||
|
self.wrapper_layout = QtWidgets.QHBoxLayout()
|
||||||
|
self.wrapper_layout.addLayout(self.main_layout)
|
||||||
|
self.wrapper_layout.addWidget(self.history)
|
||||||
|
self.setLayout(self.wrapper_layout)
|
||||||
|
|
||||||
|
def get_stop_server_shutdown_timeout_text(self):
|
||||||
|
"""
|
||||||
|
Return the string to put on the stop server button, if there's a shutdown timeout
|
||||||
|
"""
|
||||||
|
return strings._('gui_receive_stop_server_shutdown_timeout', True)
|
||||||
|
|
||||||
|
def timeout_finished_should_stop_server(self):
|
||||||
|
"""
|
||||||
|
The shutdown timer expired, should we stop the server? Returns a bool
|
||||||
|
"""
|
||||||
|
# TODO: wait until the final upload is done before stoppign the server?
|
||||||
|
return True
|
||||||
|
|
||||||
|
def start_server_custom(self):
|
||||||
|
"""
|
||||||
|
Starting the server.
|
||||||
|
"""
|
||||||
|
# Reset web counters
|
||||||
|
self.web.receive_mode.upload_count = 0
|
||||||
|
self.web.error404_count = 0
|
||||||
|
|
||||||
|
# Hide and reset the uploads if we have previously shared
|
||||||
|
self.reset_info_counters()
|
||||||
|
|
||||||
|
def start_server_step2_custom(self):
|
||||||
|
"""
|
||||||
|
Step 2 in starting the server.
|
||||||
|
"""
|
||||||
|
# Continue
|
||||||
|
self.starting_server_step3.emit()
|
||||||
|
self.start_server_finished.emit()
|
||||||
|
|
||||||
|
def handle_tor_broke_custom(self):
|
||||||
|
"""
|
||||||
|
Connection to Tor broke.
|
||||||
|
"""
|
||||||
|
self.primary_action.hide()
|
||||||
|
|
||||||
|
def handle_request_load(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_LOAD event.
|
||||||
|
"""
|
||||||
|
self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True))
|
||||||
|
|
||||||
|
def handle_request_started(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_STARTED event.
|
||||||
|
"""
|
||||||
|
item = UploadHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"])
|
||||||
|
self.history.add(event["data"]["id"], item)
|
||||||
|
self.toggle_history.update_indicator(True)
|
||||||
|
self.history.in_progress_count += 1
|
||||||
|
self.history.update_in_progress()
|
||||||
|
|
||||||
|
self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True))
|
||||||
|
|
||||||
|
def handle_request_progress(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_PROGRESS event.
|
||||||
|
"""
|
||||||
|
self.history.update(event["data"]["id"], {
|
||||||
|
'action': 'progress',
|
||||||
|
'progress': event["data"]["progress"]
|
||||||
|
})
|
||||||
|
|
||||||
|
def handle_request_close_server(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_CLOSE_SERVER event.
|
||||||
|
"""
|
||||||
|
self.stop_server()
|
||||||
|
self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True))
|
||||||
|
|
||||||
|
def handle_request_upload_file_renamed(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_UPLOAD_FILE_RENAMED event.
|
||||||
|
"""
|
||||||
|
self.history.update(event["data"]["id"], {
|
||||||
|
'action': 'rename',
|
||||||
|
'old_filename': event["data"]["old_filename"],
|
||||||
|
'new_filename': event["data"]["new_filename"]
|
||||||
|
})
|
||||||
|
|
||||||
|
def handle_request_upload_finished(self, event):
|
||||||
|
"""
|
||||||
|
Handle REQUEST_UPLOAD_FINISHED event.
|
||||||
|
"""
|
||||||
|
self.history.update(event["data"]["id"], {
|
||||||
|
'action': 'finished'
|
||||||
|
})
|
||||||
|
self.history.completed_count += 1
|
||||||
|
self.history.in_progress_count -= 1
|
||||||
|
self.history.update_completed()
|
||||||
|
self.history.update_in_progress()
|
||||||
|
|
||||||
|
def on_reload_settings(self):
|
||||||
|
"""
|
||||||
|
We should be ok to re-enable the 'Start Receive Mode' button now.
|
||||||
|
"""
|
||||||
|
self.primary_action.show()
|
||||||
|
|
||||||
|
def reset_info_counters(self):
|
||||||
|
"""
|
||||||
|
Set the info counters back to zero.
|
||||||
|
"""
|
||||||
|
self.history.reset()
|
||||||
|
|
||||||
|
def update_primary_action(self):
|
||||||
|
self.common.log('ReceiveMode', 'update_primary_action')
|
@ -17,7 +17,6 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import threading
|
|
||||||
import os
|
import os
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
@ -27,9 +26,11 @@ from onionshare.common import Common
|
|||||||
from onionshare.web import Web
|
from onionshare.web import Web
|
||||||
|
|
||||||
from .file_selection import FileSelection
|
from .file_selection import FileSelection
|
||||||
from .downloads import Downloads
|
from .threads import CompressThread
|
||||||
from ..mode import Mode
|
from .. import Mode
|
||||||
from ..widgets import Alert
|
from ..history import History, ToggleHistory, DownloadHistoryItem
|
||||||
|
from ...widgets import Alert
|
||||||
|
|
||||||
|
|
||||||
class ShareMode(Mode):
|
class ShareMode(Mode):
|
||||||
"""
|
"""
|
||||||
@ -39,8 +40,11 @@ class ShareMode(Mode):
|
|||||||
"""
|
"""
|
||||||
Custom initialization for ReceiveMode.
|
Custom initialization for ReceiveMode.
|
||||||
"""
|
"""
|
||||||
|
# Threads start out as None
|
||||||
|
self.compress_thread = None
|
||||||
|
|
||||||
# Create the Web object
|
# Create the Web object
|
||||||
self.web = Web(self.common, True, False)
|
self.web = Web(self.common, True, 'share')
|
||||||
|
|
||||||
# File selection
|
# File selection
|
||||||
self.file_selection = FileSelection(self.common)
|
self.file_selection = FileSelection(self.common)
|
||||||
@ -67,40 +71,31 @@ class ShareMode(Mode):
|
|||||||
self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning'])
|
self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning'])
|
||||||
self.filesize_warning.hide()
|
self.filesize_warning.hide()
|
||||||
|
|
||||||
# Downloads
|
# Download history
|
||||||
self.downloads = Downloads(self.common)
|
self.history = History(
|
||||||
self.downloads_in_progress = 0
|
self.common,
|
||||||
self.downloads_completed = 0
|
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))),
|
||||||
|
strings._('gui_no_downloads'),
|
||||||
|
strings._('gui_downloads')
|
||||||
|
)
|
||||||
|
self.history.hide()
|
||||||
|
|
||||||
# Information about share, and show downloads button
|
# Info label
|
||||||
self.info_label = QtWidgets.QLabel()
|
self.info_label = QtWidgets.QLabel()
|
||||||
self.info_label.setStyleSheet(self.common.css['mode_info_label'])
|
self.info_label.hide()
|
||||||
|
|
||||||
self.info_show_downloads = QtWidgets.QToolButton()
|
# Toggle history
|
||||||
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
|
self.toggle_history = ToggleHistory(
|
||||||
self.info_show_downloads.setCheckable(True)
|
self.common, self, self.history,
|
||||||
self.info_show_downloads.toggled.connect(self.downloads_toggled)
|
QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')),
|
||||||
self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True))
|
QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png'))
|
||||||
|
)
|
||||||
|
|
||||||
self.info_in_progress_downloads_count = QtWidgets.QLabel()
|
# Top bar
|
||||||
self.info_in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label'])
|
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||||
|
top_bar_layout.addWidget(self.info_label)
|
||||||
self.info_completed_downloads_count = QtWidgets.QLabel()
|
top_bar_layout.addStretch()
|
||||||
self.info_completed_downloads_count.setStyleSheet(self.common.css['mode_info_label'])
|
top_bar_layout.addWidget(self.toggle_history)
|
||||||
|
|
||||||
self.update_downloads_completed()
|
|
||||||
self.update_downloads_in_progress()
|
|
||||||
|
|
||||||
self.info_layout = QtWidgets.QHBoxLayout()
|
|
||||||
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()
|
|
||||||
|
|
||||||
# Primary action layout
|
# Primary action layout
|
||||||
self.primary_action_layout.addWidget(self.filesize_warning)
|
self.primary_action_layout.addWidget(self.filesize_warning)
|
||||||
@ -110,9 +105,18 @@ class ShareMode(Mode):
|
|||||||
# Status bar, zip progress bar
|
# Status bar, zip progress bar
|
||||||
self._zip_progress_bar = None
|
self._zip_progress_bar = None
|
||||||
|
|
||||||
# Layout
|
# Main layout
|
||||||
self.layout.insertLayout(0, self.file_selection)
|
self.main_layout = QtWidgets.QVBoxLayout()
|
||||||
self.layout.insertWidget(0, self.info_widget)
|
self.main_layout.addLayout(top_bar_layout)
|
||||||
|
self.main_layout.addLayout(self.file_selection)
|
||||||
|
self.main_layout.addWidget(self.primary_action)
|
||||||
|
self.main_layout.addWidget(self.min_width_widget)
|
||||||
|
|
||||||
|
# Wrapper layout
|
||||||
|
self.wrapper_layout = QtWidgets.QHBoxLayout()
|
||||||
|
self.wrapper_layout.addLayout(self.main_layout)
|
||||||
|
self.wrapper_layout.addWidget(self.history)
|
||||||
|
self.setLayout(self.wrapper_layout)
|
||||||
|
|
||||||
# Always start with focus on file selection
|
# Always start with focus on file selection
|
||||||
self.file_selection.setFocus()
|
self.file_selection.setFocus()
|
||||||
@ -128,7 +132,7 @@ class ShareMode(Mode):
|
|||||||
The shutdown timer expired, should we stop the server? Returns a bool
|
The shutdown timer expired, should we stop the server? Returns a bool
|
||||||
"""
|
"""
|
||||||
# If there were no attempts to download the share, or all downloads are done, we can stop
|
# If there were no attempts to download the share, or all downloads are done, we can stop
|
||||||
if self.web.download_count == 0 or self.web.done:
|
if self.web.share_mode.download_count == 0 or self.web.done:
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.server_status_label.setText(strings._('close_on_timeout', True))
|
self.server_status_label.setText(strings._('close_on_timeout', True))
|
||||||
return True
|
return True
|
||||||
@ -142,7 +146,7 @@ class ShareMode(Mode):
|
|||||||
Starting the server.
|
Starting the server.
|
||||||
"""
|
"""
|
||||||
# Reset web counters
|
# Reset web counters
|
||||||
self.web.download_count = 0
|
self.web.share_mode.download_count = 0
|
||||||
self.web.error404_count = 0
|
self.web.error404_count = 0
|
||||||
|
|
||||||
# Hide and reset the downloads if we have previously shared
|
# Hide and reset the downloads if we have previously shared
|
||||||
@ -161,28 +165,13 @@ class ShareMode(Mode):
|
|||||||
self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames)
|
self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames)
|
||||||
self.status_bar.insertWidget(0, self._zip_progress_bar)
|
self.status_bar.insertWidget(0, self._zip_progress_bar)
|
||||||
|
|
||||||
# Prepare the files for sending in a new thread
|
# prepare the files for sending in a new thread
|
||||||
def finish_starting_server(self):
|
self.compress_thread = CompressThread(self)
|
||||||
# Prepare files to share
|
self.compress_thread.success.connect(self.starting_server_step3.emit)
|
||||||
def _set_processed_size(x):
|
self.compress_thread.success.connect(self.start_server_finished.emit)
|
||||||
if self._zip_progress_bar != None:
|
self.compress_thread.error.connect(self.starting_server_error.emit)
|
||||||
self._zip_progress_bar.update_processed_size_signal.emit(x)
|
self.server_status.server_canceled.connect(self.compress_thread.cancel)
|
||||||
|
self.compress_thread.start()
|
||||||
try:
|
|
||||||
self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
|
|
||||||
self.app.cleanup_filenames.append(self.web.zip_filename)
|
|
||||||
|
|
||||||
# Only continue if the server hasn't been canceled
|
|
||||||
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
|
||||||
self.starting_server_step3.emit()
|
|
||||||
self.start_server_finished.emit()
|
|
||||||
except OSError as e:
|
|
||||||
self.starting_server_error.emit(e.strerror)
|
|
||||||
return
|
|
||||||
|
|
||||||
t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
|
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
def start_server_step3_custom(self):
|
def start_server_step3_custom(self):
|
||||||
"""
|
"""
|
||||||
@ -195,7 +184,7 @@ class ShareMode(Mode):
|
|||||||
self._zip_progress_bar = None
|
self._zip_progress_bar = None
|
||||||
|
|
||||||
# Warn about sending large files over Tor
|
# Warn about sending large files over Tor
|
||||||
if self.web.zip_filesize >= 157286400: # 150mb
|
if self.web.share_mode.download_filesize >= 157286400: # 150mb
|
||||||
self.filesize_warning.setText(strings._("large_filesize", True))
|
self.filesize_warning.setText(strings._("large_filesize", True))
|
||||||
self.filesize_warning.show()
|
self.filesize_warning.show()
|
||||||
|
|
||||||
@ -217,17 +206,24 @@ class ShareMode(Mode):
|
|||||||
self._zip_progress_bar = None
|
self._zip_progress_bar = None
|
||||||
|
|
||||||
self.filesize_warning.hide()
|
self.filesize_warning.hide()
|
||||||
self.downloads_in_progress = 0
|
self.history.in_progress_count = 0
|
||||||
self.downloads_completed = 0
|
self.history.completed_count = 0
|
||||||
self.update_downloads_in_progress()
|
self.history.update_in_progress()
|
||||||
self.file_selection.file_list.adjustSize()
|
self.file_selection.file_list.adjustSize()
|
||||||
|
|
||||||
|
def cancel_server_custom(self):
|
||||||
|
"""
|
||||||
|
Stop the compression thread on cancel
|
||||||
|
"""
|
||||||
|
if self.compress_thread:
|
||||||
|
self.common.log('ShareMode', 'cancel_server: quitting compress thread')
|
||||||
|
self.compress_thread.quit()
|
||||||
|
|
||||||
def handle_tor_broke_custom(self):
|
def handle_tor_broke_custom(self):
|
||||||
"""
|
"""
|
||||||
Connection to Tor broke.
|
Connection to Tor broke.
|
||||||
"""
|
"""
|
||||||
self.primary_action.hide()
|
self.primary_action.hide()
|
||||||
self.info_widget.hide()
|
|
||||||
|
|
||||||
def handle_request_load(self, event):
|
def handle_request_load(self, event):
|
||||||
"""
|
"""
|
||||||
@ -239,9 +235,16 @@ class ShareMode(Mode):
|
|||||||
"""
|
"""
|
||||||
Handle REQUEST_STARTED event.
|
Handle REQUEST_STARTED event.
|
||||||
"""
|
"""
|
||||||
self.downloads.add(event["data"]["id"], self.web.zip_filesize)
|
if event["data"]["use_gzip"]:
|
||||||
self.downloads_in_progress += 1
|
filesize = self.web.share_mode.gzip_filesize
|
||||||
self.update_downloads_in_progress()
|
else:
|
||||||
|
filesize = self.web.share_mode.download_filesize
|
||||||
|
|
||||||
|
item = DownloadHistoryItem(self.common, event["data"]["id"], filesize)
|
||||||
|
self.history.add(event["data"]["id"], item)
|
||||||
|
self.toggle_history.update_indicator(True)
|
||||||
|
self.history.in_progress_count += 1
|
||||||
|
self.history.update_in_progress()
|
||||||
|
|
||||||
self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
|
self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
|
||||||
|
|
||||||
@ -249,18 +252,17 @@ class ShareMode(Mode):
|
|||||||
"""
|
"""
|
||||||
Handle REQUEST_PROGRESS event.
|
Handle REQUEST_PROGRESS event.
|
||||||
"""
|
"""
|
||||||
self.downloads.update(event["data"]["id"], event["data"]["bytes"])
|
self.history.update(event["data"]["id"], event["data"]["bytes"])
|
||||||
|
|
||||||
# Is the download complete?
|
# Is the download complete?
|
||||||
if event["data"]["bytes"] == self.web.zip_filesize:
|
if event["data"]["bytes"] == self.web.share_mode.filesize:
|
||||||
self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
|
self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
|
||||||
|
|
||||||
# Update the total 'completed downloads' info
|
# Update completed and in progress labels
|
||||||
self.downloads_completed += 1
|
self.history.completed_count += 1
|
||||||
self.update_downloads_completed()
|
self.history.in_progress_count -= 1
|
||||||
# Update the 'in progress downloads' info
|
self.history.update_completed()
|
||||||
self.downloads_in_progress -= 1
|
self.history.update_in_progress()
|
||||||
self.update_downloads_in_progress()
|
|
||||||
|
|
||||||
# Close on finish?
|
# Close on finish?
|
||||||
if self.common.settings.get('close_after_first_download'):
|
if self.common.settings.get('close_after_first_download'):
|
||||||
@ -269,19 +271,19 @@ class ShareMode(Mode):
|
|||||||
self.server_status_label.setText(strings._('closing_automatically', True))
|
self.server_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(event["data"]["id"])
|
self.history.cancel(event["data"]["id"])
|
||||||
self.downloads_in_progress = 0
|
self.history.in_progress_count = 0
|
||||||
self.update_downloads_in_progress()
|
self.history.update_in_progress()
|
||||||
|
|
||||||
def handle_request_canceled(self, event):
|
def handle_request_canceled(self, event):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_CANCELED event.
|
Handle REQUEST_CANCELED event.
|
||||||
"""
|
"""
|
||||||
self.downloads.cancel(event["data"]["id"])
|
self.history.cancel(event["data"]["id"])
|
||||||
|
|
||||||
# Update the 'in progress downloads' info
|
# Update in progress count
|
||||||
self.downloads_in_progress -= 1
|
self.history.in_progress_count -= 1
|
||||||
self.update_downloads_in_progress()
|
self.history.update_in_progress()
|
||||||
self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
|
self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
|
||||||
|
|
||||||
def on_reload_settings(self):
|
def on_reload_settings(self):
|
||||||
@ -291,14 +293,16 @@ class ShareMode(Mode):
|
|||||||
"""
|
"""
|
||||||
if self.server_status.file_selection.get_num_files() > 0:
|
if self.server_status.file_selection.get_num_files() > 0:
|
||||||
self.primary_action.show()
|
self.primary_action.show()
|
||||||
self.info_widget.show()
|
self.info_label.show()
|
||||||
|
|
||||||
def update_primary_action(self):
|
def update_primary_action(self):
|
||||||
|
self.common.log('ShareMode', 'update_primary_action')
|
||||||
|
|
||||||
# Show or hide primary action layout
|
# Show or hide primary action layout
|
||||||
file_count = self.file_selection.file_list.count()
|
file_count = self.file_selection.file_list.count()
|
||||||
if file_count > 0:
|
if file_count > 0:
|
||||||
self.primary_action.show()
|
self.primary_action.show()
|
||||||
self.info_widget.show()
|
self.info_label.show()
|
||||||
|
|
||||||
# Update the file count in the info label
|
# Update the file count in the info label
|
||||||
total_size_bytes = 0
|
total_size_bytes = 0
|
||||||
@ -314,54 +318,13 @@ class ShareMode(Mode):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
self.primary_action.hide()
|
self.primary_action.hide()
|
||||||
self.info_widget.hide()
|
self.info_label.hide()
|
||||||
|
|
||||||
# Resize window
|
|
||||||
self.adjustSize()
|
|
||||||
|
|
||||||
def downloads_toggled(self, checked):
|
|
||||||
"""
|
|
||||||
When the 'Show/hide downloads' button is toggled, show or hide the downloads window.
|
|
||||||
"""
|
|
||||||
self.common.log('ShareMode', 'toggle_downloads')
|
|
||||||
if checked:
|
|
||||||
self.downloads.show()
|
|
||||||
else:
|
|
||||||
self.downloads.hide()
|
|
||||||
|
|
||||||
def reset_info_counters(self):
|
def reset_info_counters(self):
|
||||||
"""
|
"""
|
||||||
Set the info counters back to zero.
|
Set the info counters back to zero.
|
||||||
"""
|
"""
|
||||||
self.downloads_completed = 0
|
self.history.reset()
|
||||||
self.downloads_in_progress = 0
|
|
||||||
self.update_downloads_completed()
|
|
||||||
self.update_downloads_in_progress()
|
|
||||||
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
|
|
||||||
self.downloads.reset()
|
|
||||||
|
|
||||||
def update_downloads_completed(self):
|
|
||||||
"""
|
|
||||||
Update the 'Downloads completed' info widget.
|
|
||||||
"""
|
|
||||||
if self.downloads_completed == 0:
|
|
||||||
image = self.common.get_resource_path('images/share_completed_none.png')
|
|
||||||
else:
|
|
||||||
image = self.common.get_resource_path('images/share_completed.png')
|
|
||||||
self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.downloads_completed))
|
|
||||||
self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.downloads_completed))
|
|
||||||
|
|
||||||
def update_downloads_in_progress(self):
|
|
||||||
"""
|
|
||||||
Update the 'Downloads in progress' info widget.
|
|
||||||
"""
|
|
||||||
if self.downloads_in_progress == 0:
|
|
||||||
image = self.common.get_resource_path('images/share_in_progress_none.png')
|
|
||||||
else:
|
|
||||||
image = self.common.get_resource_path('images/share_in_progress.png')
|
|
||||||
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png')))
|
|
||||||
self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.downloads_in_progress))
|
|
||||||
self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _compute_total_size(filenames):
|
def _compute_total_size(filenames):
|
||||||
@ -410,6 +373,7 @@ class ZipProgressBar(QtWidgets.QProgressBar):
|
|||||||
|
|
||||||
def update_processed_size(self, val):
|
def update_processed_size(self, val):
|
||||||
self._processed_size = val
|
self._processed_size = val
|
||||||
|
|
||||||
if self.processed_size < self.total_files_size:
|
if self.processed_size < self.total_files_size:
|
||||||
self.setValue(int((self.processed_size * 100) / self.total_files_size))
|
self.setValue(int((self.processed_size * 100) / self.total_files_size))
|
||||||
elif self.total_files_size != 0:
|
elif self.total_files_size != 0:
|
@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
|||||||
|
|
||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
|
|
||||||
from ..widgets import Alert, AddFileDialog
|
from ...widgets import Alert, AddFileDialog
|
||||||
|
|
||||||
class DropHereLabel(QtWidgets.QLabel):
|
class DropHereLabel(QtWidgets.QLabel):
|
||||||
"""
|
"""
|
||||||
@ -89,7 +89,7 @@ class FileList(QtWidgets.QListWidget):
|
|||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setIconSize(QtCore.QSize(32, 32))
|
self.setIconSize(QtCore.QSize(32, 32))
|
||||||
self.setSortingEnabled(True)
|
self.setSortingEnabled(True)
|
||||||
self.setMinimumHeight(205)
|
self.setMinimumHeight(160)
|
||||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
self.drop_here_image = DropHereLabel(self.common, self, True)
|
self.drop_here_image = DropHereLabel(self.common, self, True)
|
||||||
self.drop_here_text = DropHereLabel(self.common, self, False)
|
self.drop_here_text = DropHereLabel(self.common, self, False)
|
||||||
@ -261,6 +261,7 @@ class FileList(QtWidgets.QListWidget):
|
|||||||
|
|
||||||
# Item info widget, with a white background
|
# Item info widget, with a white background
|
||||||
item_info_layout = QtWidgets.QHBoxLayout()
|
item_info_layout = QtWidgets.QHBoxLayout()
|
||||||
|
item_info_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
item_info_layout.addWidget(item_size)
|
item_info_layout.addWidget(item_size)
|
||||||
item_info_layout.addWidget(item.item_button)
|
item_info_layout.addWidget(item.item_button)
|
||||||
item_info = QtWidgets.QWidget()
|
item_info = QtWidgets.QWidget()
|
63
onionshare_gui/mode/share_mode/threads.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
class CompressThread(QtCore.QThread):
|
||||||
|
"""
|
||||||
|
Compresses files to be shared
|
||||||
|
"""
|
||||||
|
success = QtCore.pyqtSignal()
|
||||||
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, mode):
|
||||||
|
super(CompressThread, self).__init__()
|
||||||
|
self.mode = mode
|
||||||
|
self.mode.common.log('CompressThread', '__init__')
|
||||||
|
|
||||||
|
# prepare files to share
|
||||||
|
def set_processed_size(self, x):
|
||||||
|
if self.mode._zip_progress_bar != None:
|
||||||
|
self.mode._zip_progress_bar.update_processed_size_signal.emit(x)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.mode.common.log('CompressThread', 'run')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size):
|
||||||
|
self.success.emit()
|
||||||
|
else:
|
||||||
|
# Cancelled
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames
|
||||||
|
except OSError as e:
|
||||||
|
self.error.emit(e.strerror)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.mode.common.log('CompressThread', 'cancel')
|
||||||
|
|
||||||
|
# Let the Web and ZipWriter objects know that we're canceling compression early
|
||||||
|
self.mode.web.cancel_compression = True
|
||||||
|
try:
|
||||||
|
self.mode.web.zip_writer.cancel_compression = True
|
||||||
|
except AttributeError:
|
||||||
|
# we never made it as far as creating a ZipWriter object
|
||||||
|
pass
|
@ -1,45 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
OnionShare | https://onionshare.org/
|
|
||||||
|
|
||||||
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
from PyQt5 import QtCore
|
|
||||||
|
|
||||||
class OnionThread(QtCore.QThread):
|
|
||||||
"""
|
|
||||||
A QThread for starting our Onion Service.
|
|
||||||
By using QThread rather than threading.Thread, we are able
|
|
||||||
to call quit() or terminate() on the startup if the user
|
|
||||||
decided to cancel (in which case do not proceed with obtaining
|
|
||||||
the Onion address and starting the web server).
|
|
||||||
"""
|
|
||||||
def __init__(self, common, function, kwargs=None):
|
|
||||||
super(OnionThread, self).__init__()
|
|
||||||
|
|
||||||
self.common = common
|
|
||||||
|
|
||||||
self.common.log('OnionThread', '__init__')
|
|
||||||
self.function = function
|
|
||||||
if not kwargs:
|
|
||||||
self.kwargs = {}
|
|
||||||
else:
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.common.log('OnionThread', 'run')
|
|
||||||
|
|
||||||
self.function(**self.kwargs)
|
|
@ -23,8 +23,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
|||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
from onionshare.web import Web
|
from onionshare.web import Web
|
||||||
|
|
||||||
from .share_mode import ShareMode
|
from .mode.share_mode import ShareMode
|
||||||
from .receive_mode import ReceiveMode
|
from .mode.receive_mode import ReceiveMode
|
||||||
|
|
||||||
from .tor_connection_dialog import TorConnectionDialog
|
from .tor_connection_dialog import TorConnectionDialog
|
||||||
from .settings_dialog import SettingsDialog
|
from .settings_dialog import SettingsDialog
|
||||||
@ -45,6 +45,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log('OnionShareGui', '__init__')
|
self.common.log('OnionShareGui', '__init__')
|
||||||
|
self.setMinimumWidth(820)
|
||||||
|
self.setMinimumHeight(660)
|
||||||
|
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
@ -55,7 +57,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
self.setWindowTitle('OnionShare')
|
self.setWindowTitle('OnionShare')
|
||||||
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
||||||
self.setMinimumWidth(450)
|
|
||||||
|
|
||||||
# Load settings
|
# Load settings
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -66,7 +67,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.settings_action = menu.addAction(strings._('gui_settings_window_title', True))
|
self.settings_action = menu.addAction(strings._('gui_settings_window_title', True))
|
||||||
self.settings_action.triggered.connect(self.open_settings)
|
self.settings_action.triggered.connect(self.open_settings)
|
||||||
help_action = menu.addAction(strings._('gui_settings_button_help', True))
|
help_action = menu.addAction(strings._('gui_settings_button_help', True))
|
||||||
help_action.triggered.connect(SettingsDialog.help_clicked)
|
help_action.triggered.connect(SettingsDialog.open_help)
|
||||||
exit_action = menu.addAction(strings._('systray_menu_exit', True))
|
exit_action = menu.addAction(strings._('systray_menu_exit', True))
|
||||||
exit_action.triggered.connect(self.close)
|
exit_action.triggered.connect(self.close)
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.setStatusBar(self.status_bar)
|
self.setStatusBar(self.status_bar)
|
||||||
|
|
||||||
# Share mode
|
# Share mode
|
||||||
self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames)
|
self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only)
|
||||||
self.share_mode.init()
|
self.share_mode.init()
|
||||||
self.share_mode.server_status.server_started.connect(self.update_server_status_indicator)
|
self.share_mode.server_status.server_started.connect(self.update_server_status_indicator)
|
||||||
self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
||||||
@ -135,7 +136,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.share_mode.set_server_active.connect(self.set_server_active)
|
self.share_mode.set_server_active.connect(self.set_server_active)
|
||||||
|
|
||||||
# Receive mode
|
# Receive mode
|
||||||
self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray)
|
self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only)
|
||||||
self.receive_mode.init()
|
self.receive_mode.init()
|
||||||
self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator)
|
self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator)
|
||||||
self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
||||||
@ -153,7 +154,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
# Layouts
|
# Layouts
|
||||||
contents_layout = QtWidgets.QVBoxLayout()
|
contents_layout = QtWidgets.QVBoxLayout()
|
||||||
contents_layout.setContentsMargins(10, 10, 10, 10)
|
contents_layout.setContentsMargins(10, 0, 10, 0)
|
||||||
contents_layout.addWidget(self.receive_mode)
|
contents_layout.addWidget(self.receive_mode)
|
||||||
contents_layout.addWidget(self.share_mode)
|
contents_layout.addWidget(self.share_mode)
|
||||||
|
|
||||||
@ -194,8 +195,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
|
|
||||||
self.share_mode.show()
|
|
||||||
self.receive_mode.hide()
|
self.receive_mode.hide()
|
||||||
|
self.share_mode.show()
|
||||||
else:
|
else:
|
||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
||||||
@ -205,9 +206,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
self.update_server_status_indicator()
|
self.update_server_status_indicator()
|
||||||
|
|
||||||
# Wait 1ms for the event loop to finish, then adjust size
|
|
||||||
QtCore.QTimer.singleShot(1, self.adjustSize)
|
|
||||||
|
|
||||||
def share_mode_clicked(self):
|
def share_mode_clicked(self):
|
||||||
if self.mode != self.MODE_SHARE:
|
if self.mode != self.MODE_SHARE:
|
||||||
self.common.log('OnionShareGui', 'share_mode_clicked')
|
self.common.log('OnionShareGui', 'share_mode_clicked')
|
||||||
|
@ -1,237 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
OnionShare | https://onionshare.org/
|
|
||||||
|
|
||||||
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
|
||||||
|
|
||||||
from onionshare import strings
|
|
||||||
from onionshare.web import Web
|
|
||||||
|
|
||||||
from .uploads import Uploads
|
|
||||||
from ..mode import Mode
|
|
||||||
|
|
||||||
class ReceiveMode(Mode):
|
|
||||||
"""
|
|
||||||
Parts of the main window UI for receiving files.
|
|
||||||
"""
|
|
||||||
def init(self):
|
|
||||||
"""
|
|
||||||
Custom initialization for ReceiveMode.
|
|
||||||
"""
|
|
||||||
# Create the Web object
|
|
||||||
self.web = Web(self.common, True, True)
|
|
||||||
|
|
||||||
# Server status
|
|
||||||
self.server_status.set_mode('receive')
|
|
||||||
self.server_status.server_started_finished.connect(self.update_primary_action)
|
|
||||||
self.server_status.server_stopped.connect(self.update_primary_action)
|
|
||||||
self.server_status.server_canceled.connect(self.update_primary_action)
|
|
||||||
|
|
||||||
# Tell server_status about web, then update
|
|
||||||
self.server_status.web = self.web
|
|
||||||
self.server_status.update()
|
|
||||||
|
|
||||||
# Downloads
|
|
||||||
self.uploads = Uploads(self.common)
|
|
||||||
self.uploads_in_progress = 0
|
|
||||||
self.uploads_completed = 0
|
|
||||||
self.new_upload = False # For scrolling to the bottom of the uploads list
|
|
||||||
|
|
||||||
# Information about share, and show uploads button
|
|
||||||
self.info_show_uploads = QtWidgets.QToolButton()
|
|
||||||
self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png')))
|
|
||||||
self.info_show_uploads.setCheckable(True)
|
|
||||||
self.info_show_uploads.toggled.connect(self.uploads_toggled)
|
|
||||||
self.info_show_uploads.setToolTip(strings._('gui_uploads_window_tooltip', True))
|
|
||||||
|
|
||||||
self.info_in_progress_uploads_count = QtWidgets.QLabel()
|
|
||||||
self.info_in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label'])
|
|
||||||
|
|
||||||
self.info_completed_uploads_count = QtWidgets.QLabel()
|
|
||||||
self.info_completed_uploads_count.setStyleSheet(self.common.css['mode_info_label'])
|
|
||||||
|
|
||||||
self.update_uploads_completed()
|
|
||||||
self.update_uploads_in_progress()
|
|
||||||
|
|
||||||
self.info_layout = QtWidgets.QHBoxLayout()
|
|
||||||
self.info_layout.addStretch()
|
|
||||||
self.info_layout.addWidget(self.info_in_progress_uploads_count)
|
|
||||||
self.info_layout.addWidget(self.info_completed_uploads_count)
|
|
||||||
self.info_layout.addWidget(self.info_show_uploads)
|
|
||||||
|
|
||||||
self.info_widget = QtWidgets.QWidget()
|
|
||||||
self.info_widget.setLayout(self.info_layout)
|
|
||||||
self.info_widget.hide()
|
|
||||||
|
|
||||||
# Receive mode info
|
|
||||||
self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True))
|
|
||||||
self.receive_info.setMinimumHeight(80)
|
|
||||||
self.receive_info.setWordWrap(True)
|
|
||||||
|
|
||||||
# Layout
|
|
||||||
self.layout.insertWidget(0, self.receive_info)
|
|
||||||
self.layout.insertWidget(0, self.info_widget)
|
|
||||||
|
|
||||||
def get_stop_server_shutdown_timeout_text(self):
|
|
||||||
"""
|
|
||||||
Return the string to put on the stop server button, if there's a shutdown timeout
|
|
||||||
"""
|
|
||||||
return strings._('gui_receive_stop_server_shutdown_timeout', True)
|
|
||||||
|
|
||||||
def timeout_finished_should_stop_server(self):
|
|
||||||
"""
|
|
||||||
The shutdown timer expired, should we stop the server? Returns a bool
|
|
||||||
"""
|
|
||||||
# TODO: wait until the final upload is done before stoppign the server?
|
|
||||||
return True
|
|
||||||
|
|
||||||
def start_server_custom(self):
|
|
||||||
"""
|
|
||||||
Starting the server.
|
|
||||||
"""
|
|
||||||
# Reset web counters
|
|
||||||
self.web.upload_count = 0
|
|
||||||
self.web.error404_count = 0
|
|
||||||
|
|
||||||
# Hide and reset the uploads if we have previously shared
|
|
||||||
self.reset_info_counters()
|
|
||||||
|
|
||||||
def start_server_step2_custom(self):
|
|
||||||
"""
|
|
||||||
Step 2 in starting the server.
|
|
||||||
"""
|
|
||||||
# Continue
|
|
||||||
self.starting_server_step3.emit()
|
|
||||||
self.start_server_finished.emit()
|
|
||||||
|
|
||||||
def handle_tor_broke_custom(self):
|
|
||||||
"""
|
|
||||||
Connection to Tor broke.
|
|
||||||
"""
|
|
||||||
self.primary_action.hide()
|
|
||||||
self.info_widget.hide()
|
|
||||||
|
|
||||||
def handle_request_load(self, event):
|
|
||||||
"""
|
|
||||||
Handle REQUEST_LOAD event.
|
|
||||||
"""
|
|
||||||
self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True))
|
|
||||||
|
|
||||||
def handle_request_started(self, event):
|
|
||||||
"""
|
|
||||||
Handle REQUEST_STARTED event.
|
|
||||||
"""
|
|
||||||
self.uploads.add(event["data"]["id"], event["data"]["content_length"])
|
|
||||||
self.uploads_in_progress += 1
|
|
||||||
self.update_uploads_in_progress()
|
|
||||||
|
|
||||||
self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True))
|
|
||||||
|
|
||||||
def handle_request_progress(self, event):
|
|
||||||
"""
|
|
||||||
Handle REQUEST_PROGRESS event.
|
|
||||||
"""
|
|
||||||
self.uploads.update(event["data"]["id"], event["data"]["progress"])
|
|
||||||
|
|
||||||
def handle_request_close_server(self, event):
|
|
||||||
"""
|
|
||||||
Handle REQUEST_CLOSE_SERVER event.
|
|
||||||
"""
|
|
||||||
self.stop_server()
|
|
||||||
self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True))
|
|
||||||
|
|
||||||
def handle_request_upload_file_renamed(self, event):
|
|
||||||
"""
|
|
||||||
Handle REQUEST_UPLOAD_FILE_RENAMED event.
|
|
||||||
"""
|
|
||||||
self.uploads.rename(event["data"]["id"], event["data"]["old_filename"], event["data"]["new_filename"])
|
|
||||||
|
|
||||||
def handle_request_upload_finished(self, event):
|
|
||||||
"""
|
|
||||||
Handle REQUEST_UPLOAD_FINISHED event.
|
|
||||||
"""
|
|
||||||
self.uploads.finished(event["data"]["id"])
|
|
||||||
# Update the total 'completed uploads' info
|
|
||||||
self.uploads_completed += 1
|
|
||||||
self.update_uploads_completed()
|
|
||||||
# Update the 'in progress uploads' info
|
|
||||||
self.uploads_in_progress -= 1
|
|
||||||
self.update_uploads_in_progress()
|
|
||||||
|
|
||||||
def on_reload_settings(self):
|
|
||||||
"""
|
|
||||||
We should be ok to re-enable the 'Start Receive Mode' button now.
|
|
||||||
"""
|
|
||||||
self.primary_action.show()
|
|
||||||
self.info_widget.show()
|
|
||||||
|
|
||||||
def reset_info_counters(self):
|
|
||||||
"""
|
|
||||||
Set the info counters back to zero.
|
|
||||||
"""
|
|
||||||
self.uploads_completed = 0
|
|
||||||
self.uploads_in_progress = 0
|
|
||||||
self.update_uploads_completed()
|
|
||||||
self.update_uploads_in_progress()
|
|
||||||
self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png')))
|
|
||||||
self.uploads.reset()
|
|
||||||
|
|
||||||
def update_uploads_completed(self):
|
|
||||||
"""
|
|
||||||
Update the 'Uploads completed' info widget.
|
|
||||||
"""
|
|
||||||
if self.uploads_completed == 0:
|
|
||||||
image = self.common.get_resource_path('images/share_completed_none.png')
|
|
||||||
else:
|
|
||||||
image = self.common.get_resource_path('images/share_completed.png')
|
|
||||||
self.info_completed_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_completed))
|
|
||||||
self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed))
|
|
||||||
|
|
||||||
def update_uploads_in_progress(self):
|
|
||||||
"""
|
|
||||||
Update the 'Uploads in progress' info widget.
|
|
||||||
"""
|
|
||||||
if self.uploads_in_progress == 0:
|
|
||||||
image = self.common.get_resource_path('images/share_in_progress_none.png')
|
|
||||||
else:
|
|
||||||
image = self.common.get_resource_path('images/share_in_progress.png')
|
|
||||||
self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png')))
|
|
||||||
self.info_in_progress_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_in_progress))
|
|
||||||
self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress))
|
|
||||||
|
|
||||||
def update_primary_action(self):
|
|
||||||
self.common.log('ReceiveMode', 'update_primary_action')
|
|
||||||
|
|
||||||
# Show the info widget when the server is active
|
|
||||||
if self.server_status.status == self.server_status.STATUS_STARTED:
|
|
||||||
self.info_widget.show()
|
|
||||||
else:
|
|
||||||
self.info_widget.hide()
|
|
||||||
|
|
||||||
# Resize window
|
|
||||||
self.adjustSize()
|
|
||||||
|
|
||||||
def uploads_toggled(self, checked):
|
|
||||||
"""
|
|
||||||
When the 'Show/hide uploads' button is toggled, show or hide the uploads window.
|
|
||||||
"""
|
|
||||||
self.common.log('ReceiveMode', 'toggle_uploads')
|
|
||||||
if checked:
|
|
||||||
self.uploads.show()
|
|
||||||
else:
|
|
||||||
self.uploads.hide()
|
|
@ -1,310 +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
|
|
||||||
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.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 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')
|
|
||||||
|
|
||||||
abs_filename = os.path.join(self.common.settings.get('downloads_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':
|
|
||||||
subprocess.call(['open', '-R', abs_filename])
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
elif self.common.platform == 'Windows':
|
|
||||||
subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)])
|
|
||||||
|
|
||||||
|
|
||||||
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', True).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 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', True).format(
|
|
||||||
self.started.strftime("%b %d, %I:%M%p")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
text = strings._('gui_upload_finished_range', True).format(
|
|
||||||
self.started.strftime("%b %d, %I:%M%p"),
|
|
||||||
self.ended.strftime("%I:%M%p")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
text = strings._('gui_upload_finished_range', True).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', True))
|
|
||||||
self.setWidgetResizable(True)
|
|
||||||
self.setMaximumHeight(600)
|
|
||||||
self.setMinimumHeight(150)
|
|
||||||
self.setMinimumWidth(350)
|
|
||||||
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
|
||||||
self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
|
|
||||||
self.vbar = self.verticalScrollBar()
|
|
||||||
self.vbar.rangeChanged.connect(self.resizeScroll)
|
|
||||||
|
|
||||||
uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True))
|
|
||||||
uploads_label.setStyleSheet(self.common.css['downloads_uploads_label'])
|
|
||||||
self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True))
|
|
||||||
|
|
||||||
self.uploads_layout = QtWidgets.QVBoxLayout()
|
|
||||||
|
|
||||||
widget = QtWidgets.QWidget()
|
|
||||||
layout = QtWidgets.QVBoxLayout()
|
|
||||||
layout.addWidget(uploads_label)
|
|
||||||
layout.addWidget(self.no_uploads_label)
|
|
||||||
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()
|
|
||||||
|
|
||||||
# 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():
|
|
||||||
self.uploads_layout.removeWidget(upload)
|
|
||||||
self.uploads = {}
|
|
||||||
|
|
||||||
self.no_uploads_label.show()
|
|
||||||
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():
|
|
||||||
if item.filename_label_width > width:
|
|
||||||
item.filename_label.setText(item.filename[:25] + '[...]')
|
|
||||||
item.adjustSize()
|
|
||||||
if width > item.filename_label_width:
|
|
||||||
item.filename_label.setText(item.filename)
|
|
||||||
except:
|
|
||||||
pass
|
|
@ -44,7 +44,7 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
STATUS_WORKING = 1
|
STATUS_WORKING = 1
|
||||||
STATUS_STARTED = 2
|
STATUS_STARTED = 2
|
||||||
|
|
||||||
def __init__(self, common, qtapp, app, file_selection=None):
|
def __init__(self, common, qtapp, app, file_selection=None, local_only=False):
|
||||||
super(ServerStatus, self).__init__()
|
super(ServerStatus, self).__init__()
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
@ -56,17 +56,23 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self.web = None
|
self.web = None
|
||||||
|
self.local_only = local_only
|
||||||
|
|
||||||
self.resizeEvent(None)
|
self.resizeEvent(None)
|
||||||
|
|
||||||
# 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
|
|
||||||
self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
|
self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
|
||||||
|
if self.local_only:
|
||||||
|
# For testing
|
||||||
|
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15))
|
||||||
|
self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime())
|
||||||
|
else:
|
||||||
|
# Set proposed timeout to be 5 minutes into the future
|
||||||
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
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 60s from now
|
||||||
self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
|
self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
|
||||||
self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
|
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)
|
||||||
@ -84,20 +90,20 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.server_button.clicked.connect(self.server_button_clicked)
|
self.server_button.clicked.connect(self.server_button_clicked)
|
||||||
|
|
||||||
# URL layout
|
# URL layout
|
||||||
url_font = QtGui.QFont()
|
url_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
|
||||||
self.url_description = QtWidgets.QLabel()
|
self.url_description = QtWidgets.QLabel()
|
||||||
self.url_description.setWordWrap(True)
|
self.url_description.setWordWrap(True)
|
||||||
self.url_description.setMinimumHeight(50)
|
self.url_description.setMinimumHeight(50)
|
||||||
self.url = QtWidgets.QLabel()
|
self.url = QtWidgets.QLabel()
|
||||||
self.url.setFont(url_font)
|
self.url.setFont(url_font)
|
||||||
self.url.setWordWrap(True)
|
self.url.setWordWrap(True)
|
||||||
self.url.setMinimumHeight(65)
|
|
||||||
self.url.setMinimumSize(self.url.sizeHint())
|
self.url.setMinimumSize(self.url.sizeHint())
|
||||||
self.url.setStyleSheet(self.common.css['server_status_url'])
|
self.url.setStyleSheet(self.common.css['server_status_url'])
|
||||||
|
|
||||||
self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True))
|
self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True))
|
||||||
self.copy_url_button.setFlat(True)
|
self.copy_url_button.setFlat(True)
|
||||||
self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons'])
|
self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons'])
|
||||||
|
self.copy_url_button.setMinimumHeight(65)
|
||||||
self.copy_url_button.clicked.connect(self.copy_url)
|
self.copy_url_button.clicked.connect(self.copy_url)
|
||||||
self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
|
self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
|
||||||
self.copy_hidservauth_button.setFlat(True)
|
self.copy_hidservauth_button.setFlat(True)
|
||||||
@ -136,12 +142,12 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
When the widget is resized, try and adjust the display of a v3 onion URL.
|
When the widget is resized, try and adjust the display of a v3 onion URL.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.get_url()
|
# Wrap the URL label
|
||||||
url_length=len(self.get_url())
|
url_length=len(self.get_url())
|
||||||
if url_length > 60:
|
if url_length > 60:
|
||||||
width = self.frameGeometry().width()
|
width = self.frameGeometry().width()
|
||||||
if width < 530:
|
if width < 530:
|
||||||
wrapped_onion_url = textwrap.fill(self.get_url(), 50)
|
wrapped_onion_url = textwrap.fill(self.get_url(), 46)
|
||||||
self.url.setText(wrapped_onion_url)
|
self.url.setText(wrapped_onion_url)
|
||||||
else:
|
else:
|
||||||
self.url.setText(self.get_url())
|
self.url.setText(self.get_url())
|
||||||
@ -154,7 +160,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
Reset the timeout in the UI after stopping a share
|
Reset the timeout in the UI after stopping a share
|
||||||
"""
|
"""
|
||||||
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
||||||
self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
|
if not self.local_only:
|
||||||
|
self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""
|
"""
|
||||||
@ -255,6 +262,9 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
"""
|
"""
|
||||||
if self.status == self.STATUS_STOPPED:
|
if self.status == self.STATUS_STOPPED:
|
||||||
if self.common.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
|
if self.local_only:
|
||||||
|
self.timeout = self.shutdown_timeout.dateTime().toPyDateTime()
|
||||||
|
else:
|
||||||
# Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
|
# 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.
|
||||||
|
@ -746,7 +746,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
|
onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
|
||||||
|
|
||||||
# If an exception hasn't been raised yet, the Tor settings work
|
# If an exception hasn't been raised yet, the Tor settings work
|
||||||
Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
|
Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions))
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
onion.cleanup()
|
onion.cleanup()
|
||||||
@ -883,8 +883,12 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||||||
Help button clicked.
|
Help button clicked.
|
||||||
"""
|
"""
|
||||||
self.common.log('SettingsDialog', 'help_clicked')
|
self.common.log('SettingsDialog', 'help_clicked')
|
||||||
help_site = 'https://github.com/micahflee/onionshare/wiki'
|
SettingsDialog.open_help()
|
||||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site))
|
|
||||||
|
@staticmethod
|
||||||
|
def open_help():
|
||||||
|
help_url = 'https://github.com/micahflee/onionshare/wiki'
|
||||||
|
QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url))
|
||||||
|
|
||||||
def settings_from_fields(self):
|
def settings_from_fields(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1,157 +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', True))
|
|
||||||
self.setWidgetResizable(True)
|
|
||||||
self.setMaximumHeight(600)
|
|
||||||
self.setMinimumHeight(150)
|
|
||||||
self.setMinimumWidth(350)
|
|
||||||
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
|
||||||
self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
|
|
||||||
self.vbar = self.verticalScrollBar()
|
|
||||||
self.vbar.rangeChanged.connect(self.resizeScroll)
|
|
||||||
|
|
||||||
downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True))
|
|
||||||
downloads_label.setStyleSheet(self.common.css['downloads_uploads_label'])
|
|
||||||
self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True))
|
|
||||||
|
|
||||||
self.downloads_layout = QtWidgets.QVBoxLayout()
|
|
||||||
|
|
||||||
widget = QtWidgets.QWidget()
|
|
||||||
layout = QtWidgets.QVBoxLayout()
|
|
||||||
layout.addWidget(downloads_label)
|
|
||||||
layout.addWidget(self.no_downloads_label)
|
|
||||||
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()
|
|
||||||
|
|
||||||
# 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.resize(self.sizeHint())
|
|
77
onionshare_gui/threads.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
|
from onionshare.onion import *
|
||||||
|
|
||||||
|
|
||||||
|
class OnionThread(QtCore.QThread):
|
||||||
|
"""
|
||||||
|
Starts the onion service, and waits for it to finish
|
||||||
|
"""
|
||||||
|
success = QtCore.pyqtSignal()
|
||||||
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, mode):
|
||||||
|
super(OnionThread, self).__init__()
|
||||||
|
self.mode = mode
|
||||||
|
self.mode.common.log('OnionThread', '__init__')
|
||||||
|
|
||||||
|
# allow this thread to be terminated
|
||||||
|
self.setTerminationEnabled()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.mode.common.log('OnionThread', 'run')
|
||||||
|
|
||||||
|
self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
|
||||||
|
|
||||||
|
# start onionshare http service in new thread
|
||||||
|
self.mode.web_thread = WebThread(self.mode)
|
||||||
|
self.mode.web_thread.start()
|
||||||
|
|
||||||
|
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.mode.app.start_onion_service()
|
||||||
|
self.success.emit()
|
||||||
|
|
||||||
|
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
|
||||||
|
self.error.emit(e.args[0])
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class WebThread(QtCore.QThread):
|
||||||
|
"""
|
||||||
|
Starts the web service
|
||||||
|
"""
|
||||||
|
success = QtCore.pyqtSignal()
|
||||||
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, mode):
|
||||||
|
super(WebThread, self).__init__()
|
||||||
|
self.mode = mode
|
||||||
|
self.mode.common.log('WebThread', '__init__')
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.mode.common.log('WebThread', 'run')
|
||||||
|
self.mode.app.choose_port()
|
||||||
|
self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.common.settings.get('slug'))
|
Before Width: | Height: | Size: 45 KiB |
9
setup.py
@ -65,7 +65,14 @@ setup(
|
|||||||
description=description, long_description=long_description,
|
description=description, long_description=long_description,
|
||||||
author=author, author_email=author_email,
|
author=author, author_email=author_email,
|
||||||
url=url, license=license, keywords=keywords,
|
url=url, license=license, keywords=keywords,
|
||||||
packages=['onionshare', 'onionshare_gui'],
|
packages=[
|
||||||
|
'onionshare',
|
||||||
|
'onionshare.web',
|
||||||
|
'onionshare_gui',
|
||||||
|
'onionshare_gui.mode',
|
||||||
|
'onionshare_gui.mode.share_mode',
|
||||||
|
'onionshare_gui.mode.receive_mode'
|
||||||
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],
|
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],
|
||||||
data_files=data_files
|
data_files=data_files
|
||||||
|
Before Width: | Height: | Size: 440 B |
Before Width: | Height: | Size: 761 B |
BIN
share/images/downloads.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
share/images/downloads_toggle.png
Normal file
After Width: | Height: | Size: 380 B |
BIN
share/images/downloads_toggle_selected.png
Normal file
After Width: | Height: | Size: 468 B |
BIN
share/images/downloads_transparent.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 483 B |
BIN
share/images/uploads.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
share/images/uploads_toggle.png
Normal file
After Width: | Height: | Size: 389 B |
BIN
share/images/uploads_toggle_selected.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
share/images/uploads_transparent.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"config_onion_service": "Nastavuji onion service na portu {0:d}.",
|
"config_onion_service": "Nastavuji onion service na portu {0:d}.",
|
||||||
"preparing_files": "Připravuji soubory ke sdílení.",
|
"preparing_files": "Připravuji soubory ke sdílení.",
|
||||||
"wait_for_hs": "Čekám na HS až bude připravena:",
|
|
||||||
"give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:",
|
"give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:",
|
||||||
"give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
|
"give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
|
||||||
"ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru",
|
"ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru",
|
||||||
@ -27,7 +26,6 @@
|
|||||||
"gui_copied_url": "URL zkopírováno do schránky",
|
"gui_copied_url": "URL zkopírováno do schránky",
|
||||||
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
|
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
|
||||||
"gui_please_wait": "Prosím čekejte...",
|
"gui_please_wait": "Prosím čekejte...",
|
||||||
"using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
|
|
||||||
"gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}",
|
"gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}",
|
||||||
"gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
|
"gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
|
||||||
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.",
|
"config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.",
|
||||||
"preparing_files": "Forbereder filer som skal deles.",
|
"preparing_files": "Forbereder filer som skal deles.",
|
||||||
"wait_for_hs": "Venter på at HS bliver klar:",
|
|
||||||
"give_this_url": "Giv denne URL til personen du sender filen til:",
|
"give_this_url": "Giv denne URL til personen du sender filen til:",
|
||||||
"give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:",
|
"give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:",
|
||||||
"ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren",
|
"ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren",
|
||||||
@ -40,7 +39,6 @@
|
|||||||
"gui_copied_url": "Kopierede URL til udklipsholder",
|
"gui_copied_url": "Kopierede URL til udklipsholder",
|
||||||
"gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder",
|
"gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder",
|
||||||
"gui_please_wait": "Vent venligst...",
|
"gui_please_wait": "Vent venligst...",
|
||||||
"using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse",
|
|
||||||
"gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}",
|
"gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}",
|
||||||
"gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)",
|
"gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)",
|
||||||
"gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%",
|
"gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%",
|
||||||
@ -53,9 +51,7 @@
|
|||||||
"error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.",
|
"error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.",
|
||||||
"error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.",
|
"error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.",
|
||||||
"gui_settings_window_title": "Indstillinger",
|
"gui_settings_window_title": "Indstillinger",
|
||||||
"gui_settings_stealth_label": "Usynlig (avanceret)",
|
|
||||||
"gui_settings_stealth_option": "Opret usynlige onion-tjenester",
|
"gui_settings_stealth_option": "Opret usynlige onion-tjenester",
|
||||||
"gui_settings_stealth_option_details": "Det gør OnionShare mere sikker, men også mere besværlig for modtageren at oprette forbindelse til den.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">Mere information</a>.",
|
|
||||||
"gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.",
|
"gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.",
|
||||||
"gui_settings_autoupdate_label": "Søg efter opdateringer",
|
"gui_settings_autoupdate_label": "Søg efter opdateringer",
|
||||||
"gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer",
|
"gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preparing_files": "Dateien werden vorbereitet.",
|
"preparing_files": "Dateien werden vorbereitet.",
|
||||||
"wait_for_hs": "Warte auf HS:",
|
|
||||||
"give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:",
|
"give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:",
|
||||||
"ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten",
|
"ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten",
|
||||||
"not_a_file": "{0:s} ist keine Datei.",
|
"not_a_file": "{0:s} ist keine Datei.",
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
{
|
{
|
||||||
"config_onion_service": "Configuring onion service on port {0:d}.",
|
"config_onion_service": "Setting up onion service on port {0:d}.",
|
||||||
"preparing_files": "Preparing files to share.",
|
"preparing_files": "Compressing files.",
|
||||||
"wait_for_hs": "Waiting for HS to be ready:",
|
"give_this_url": "Give this address to the recipient:",
|
||||||
"give_this_url": "Give this address to the person you're sending the file to:",
|
"give_this_url_stealth": "Give this address and HidServAuth line to the recipient:",
|
||||||
"give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:",
|
"give_this_url_receive": "Give this address to the sender:",
|
||||||
"give_this_url_receive": "Give this address to the people sending you files:",
|
"give_this_url_receive_stealth": "Give this address and HidServAuth to the sender:",
|
||||||
"give_this_url_receive_stealth": "Give this address and HidServAuth line to the people sending you files:",
|
|
||||||
"ctrlc_to_stop": "Press Ctrl+C to stop the server",
|
"ctrlc_to_stop": "Press Ctrl+C to stop the server",
|
||||||
"not_a_file": "{0:s} is not a valid file.",
|
"not_a_file": "{0:s} is not a valid file.",
|
||||||
"not_a_readable_file": "{0:s} is not a readable file.",
|
"not_a_readable_file": "{0:s} is not a readable file.",
|
||||||
"no_filenames": "You must specify a list of files to share.",
|
"no_available_port": "Could not find an available port to start the onion service",
|
||||||
"no_available_port": "Could not start the Onion service as there was no available port.",
|
|
||||||
"other_page_loaded": "Address loaded",
|
"other_page_loaded": "Address loaded",
|
||||||
"close_on_timeout": "Stopped because timer expired",
|
"close_on_timeout": "Stopped because auto-stop timer ran out",
|
||||||
"closing_automatically": "Stopped because download finished",
|
"closing_automatically": "Stopped because download finished",
|
||||||
"timeout_download_still_running": "Waiting for download to complete",
|
"timeout_download_still_running": "Waiting for download to complete",
|
||||||
"large_filesize": "Warning: Sending large files could take hours",
|
"large_filesize": "Warning: Sending a large share could take hours",
|
||||||
"systray_menu_exit": "Quit",
|
"systray_menu_exit": "Quit",
|
||||||
"systray_download_started_title": "OnionShare Download Started",
|
"systray_download_started_title": "OnionShare Download Started",
|
||||||
"systray_download_started_message": "A user started downloading your files",
|
"systray_download_started_message": "A user started downloading your files",
|
||||||
@ -25,69 +23,67 @@
|
|||||||
"systray_download_canceled_message": "The user canceled the download",
|
"systray_download_canceled_message": "The user canceled the download",
|
||||||
"systray_upload_started_title": "OnionShare Upload Started",
|
"systray_upload_started_title": "OnionShare Upload Started",
|
||||||
"systray_upload_started_message": "A user started uploading files to your computer",
|
"systray_upload_started_message": "A user started uploading files to your computer",
|
||||||
"help_local_only": "Do not attempt to use Tor: For development only",
|
"help_local_only": "Don't use Tor (only for development)",
|
||||||
"help_stay_open": "Keep onion service running after download has finished",
|
"help_stay_open": "Keep sharing after first download",
|
||||||
"help_shutdown_timeout": "Shut down the onion service after N seconds",
|
"help_shutdown_timeout": "Stop sharing after a given amount of seconds",
|
||||||
"help_stealth": "Create stealth onion service (advanced)",
|
"help_stealth": "Use client authorization (advanced)",
|
||||||
"help_receive": "Receive files instead of sending them",
|
"help_receive": "Receive shares instead of sending them",
|
||||||
"help_debug": "Log application errors to stdout, and log web errors to disk",
|
"help_debug": "Log OnionShare errors to stdout, and web errors to disk",
|
||||||
"help_filename": "List of files or folders to share",
|
"help_filename": "List of files or folders to share",
|
||||||
"help_config": "Path to a custom JSON config file (optional)",
|
"help_config": "Custom JSON config file location (optional)",
|
||||||
"gui_drag_and_drop": "Drag and drop files and folders\nto start sharing",
|
"gui_drag_and_drop": "Drag and drop files and folders\nto start sharing",
|
||||||
"gui_add": "Add",
|
"gui_add": "Add",
|
||||||
"gui_delete": "Delete",
|
"gui_delete": "Delete",
|
||||||
"gui_choose_items": "Choose",
|
"gui_choose_items": "Choose",
|
||||||
"gui_share_start_server": "Start Sharing",
|
"gui_share_start_server": "Start sharing",
|
||||||
"gui_share_stop_server": "Stop Sharing",
|
"gui_share_stop_server": "Stop sharing",
|
||||||
"gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)",
|
"gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)",
|
||||||
"gui_share_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}",
|
"gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}",
|
||||||
"gui_receive_start_server": "Start Receive Mode",
|
"gui_receive_start_server": "Start Receive Mode",
|
||||||
"gui_receive_stop_server": "Stop Receive Mode",
|
"gui_receive_stop_server": "Stop Receive Mode",
|
||||||
"gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)",
|
"gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)",
|
||||||
"gui_receive_stop_server_shutdown_timeout_tooltip": "Receive mode will expire automatically at {}",
|
"gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}",
|
||||||
"gui_copy_url": "Copy Address",
|
"gui_copy_url": "Copy Address",
|
||||||
"gui_copy_hidservauth": "Copy HidServAuth",
|
"gui_copy_hidservauth": "Copy HidServAuth",
|
||||||
"gui_downloads": "Download History",
|
"gui_downloads": "Download History",
|
||||||
"gui_downloads_window_tooltip": "Show/hide downloads",
|
"gui_no_downloads": "No Downloads Yet",
|
||||||
"gui_no_downloads": "No downloads yet.",
|
|
||||||
"gui_canceled": "Canceled",
|
"gui_canceled": "Canceled",
|
||||||
"gui_copied_url_title": "Copied OnionShare address",
|
"gui_copied_url_title": "Copied OnionShare Address",
|
||||||
"gui_copied_url": "The OnionShare address has been copied to clipboard",
|
"gui_copied_url": "OnionShare address copied to clipboard",
|
||||||
"gui_copied_hidservauth_title": "Copied HidServAuth",
|
"gui_copied_hidservauth_title": "Copied HidServAuth",
|
||||||
"gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard",
|
"gui_copied_hidservauth": "HidServAuth line copied to clipboard",
|
||||||
"gui_please_wait": "Starting… Click to cancel",
|
"gui_please_wait": "Starting… Click to cancel.",
|
||||||
"using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
|
"gui_download_upload_progress_complete": "%p%, {0:s} elapsed.",
|
||||||
"gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}",
|
"gui_download_upload_progress_starting": "{0:s}, %p% (calculating)",
|
||||||
"gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
|
|
||||||
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||||
"version_string": "OnionShare {0:s} | https://onionshare.org/",
|
"version_string": "OnionShare {0:s} | https://onionshare.org/",
|
||||||
"gui_quit_title": "Transfer in Progress",
|
"gui_quit_title": "Not so fast",
|
||||||
"gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
|
"gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
|
||||||
"gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?",
|
"gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?",
|
||||||
"gui_quit_warning_quit": "Quit",
|
"gui_quit_warning_quit": "Quit",
|
||||||
"gui_quit_warning_dont_quit": "Cancel",
|
"gui_quit_warning_dont_quit": "Cancel",
|
||||||
"error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.",
|
"error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the receipient a new address to share.",
|
||||||
"zip_progress_bar_format": "Compressing files: %p%",
|
"zip_progress_bar_format": "Compressing: %p%",
|
||||||
"error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.",
|
"error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.",
|
||||||
"error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.",
|
"error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.",
|
||||||
"gui_settings_window_title": "Settings",
|
"gui_settings_window_title": "Settings",
|
||||||
"gui_settings_whats_this": "<a href='{0:s}'>what's this?</a>",
|
"gui_settings_whats_this": "<a href='{0:s}'>What's this?</a>",
|
||||||
"gui_settings_stealth_option": "Create stealth onion services (legacy)",
|
"gui_settings_stealth_option": "Use client authorization (legacy)",
|
||||||
"gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.",
|
"gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now\nclick to copy your HidServAuth.",
|
||||||
"gui_settings_autoupdate_label": "Check for upgrades",
|
"gui_settings_autoupdate_label": "Check for new version",
|
||||||
"gui_settings_autoupdate_option": "Notify me when upgrades are available",
|
"gui_settings_autoupdate_option": "Notify me when a new version is available",
|
||||||
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
||||||
"gui_settings_autoupdate_timestamp_never": "Never",
|
"gui_settings_autoupdate_timestamp_never": "Never",
|
||||||
"gui_settings_autoupdate_check_button": "Check For Upgrades",
|
"gui_settings_autoupdate_check_button": "Check for New Version",
|
||||||
"gui_settings_general_label": "General settings",
|
"gui_settings_general_label": "General settings",
|
||||||
"gui_settings_sharing_label": "Sharing settings",
|
"gui_settings_sharing_label": "Sharing settings",
|
||||||
"gui_settings_close_after_first_download_option": "Stop sharing after first download",
|
"gui_settings_close_after_first_download_option": "Stop sharing after first download",
|
||||||
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
||||||
"gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare",
|
"gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare",
|
||||||
"gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser",
|
"gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser",
|
||||||
"gui_settings_connection_type_control_port_option": "Connect using control port",
|
"gui_settings_connection_type_control_port_option": "Connect using control port",
|
||||||
"gui_settings_connection_type_socket_file_option": "Connect using socket file",
|
"gui_settings_connection_type_socket_file_option": "Connect using socket file",
|
||||||
"gui_settings_connection_type_test_button": "Test Tor Settings",
|
"gui_settings_connection_type_test_button": "Test Connection to Tor",
|
||||||
"gui_settings_control_port_label": "Control port",
|
"gui_settings_control_port_label": "Control port",
|
||||||
"gui_settings_socket_file_label": "Socket file",
|
"gui_settings_socket_file_label": "Socket file",
|
||||||
"gui_settings_socks_label": "SOCKS port",
|
"gui_settings_socks_label": "SOCKS port",
|
||||||
@ -95,77 +91,77 @@
|
|||||||
"gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
|
"gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
|
||||||
"gui_settings_authenticate_password_option": "Password",
|
"gui_settings_authenticate_password_option": "Password",
|
||||||
"gui_settings_password_label": "Password",
|
"gui_settings_password_label": "Password",
|
||||||
"gui_settings_tor_bridges": "Tor Bridge support",
|
"gui_settings_tor_bridges": "Tor bridge support",
|
||||||
"gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges",
|
"gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges",
|
||||||
"gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports",
|
"gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports",
|
||||||
"gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)",
|
"gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)",
|
||||||
"gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports",
|
"gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports",
|
||||||
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)",
|
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)",
|
||||||
"gui_settings_meek_lite_expensive_warning": "Warning: the meek_lite bridges are very costly for the Tor Project to run!<br><br>You should only use meek_lite bridges if you are having trouble connecting to Tor directly, via obfs4 transports or other normal bridges.",
|
"gui_settings_meek_lite_expensive_warning": "Warning: The meek_lite bridges are very costly for the Tor Project to run.<br><br>Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.",
|
||||||
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges",
|
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges",
|
||||||
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
|
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
|
||||||
"gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to work.\nPlease try again by double-checking them or adding other ones.",
|
"gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.",
|
||||||
"gui_settings_button_save": "Save",
|
"gui_settings_button_save": "Save",
|
||||||
"gui_settings_button_cancel": "Cancel",
|
"gui_settings_button_cancel": "Cancel",
|
||||||
"gui_settings_button_help": "Help",
|
"gui_settings_button_help": "Help",
|
||||||
"gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer",
|
"gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer",
|
||||||
"gui_settings_shutdown_timeout": "Stop the share at:",
|
"gui_settings_shutdown_timeout": "Stop the share at:",
|
||||||
"settings_saved": "Settings saved to {}",
|
"settings_saved": "Settings saved in {}",
|
||||||
"settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.",
|
"settings_error_unknown": "Can't connect to Tor controller because your settings don't make sense.",
|
||||||
"settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.",
|
"settings_error_automatic": "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?",
|
||||||
"settings_error_socket_port": "Can't connect to Tor controller on {}:{}.",
|
"settings_error_socket_port": "Can't connect to the Tor controller at {}:{}.",
|
||||||
"settings_error_socket_file": "Can't connect to Tor controller using socket file {}.",
|
"settings_error_socket_file": "Can't connect to the Tor controller using socket file {}.",
|
||||||
"settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
|
"settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
|
||||||
"settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
|
"settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
|
||||||
"settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.",
|
"settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.",
|
||||||
"settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.",
|
"settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.",
|
||||||
"settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.",
|
"settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?",
|
||||||
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}",
|
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}",
|
||||||
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
|
"settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.",
|
||||||
"error_tor_protocol_error": "There was an error with Tor: {}",
|
"error_tor_protocol_error": "There was an error with Tor: {}",
|
||||||
"error_tor_protocol_error_unknown": "There was an unknown error with Tor",
|
"error_tor_protocol_error_unknown": "There was an unknown error with Tor",
|
||||||
"error_invalid_private_key": "This private key type is unsupported",
|
"error_invalid_private_key": "This private key type is unsupported",
|
||||||
"connecting_to_tor": "Connecting to the Tor network",
|
"connecting_to_tor": "Connecting to the Tor network",
|
||||||
"update_available": "A new version of OnionShare is available. <a href='{}'>Click here</a> to download it.<br><br>Installed version: {}<br>Latest version: {}",
|
"update_available": "New OnionShare out. <a href='{}'>Click here</a> to get it.<br><br>You are using {} and the latest is {}.",
|
||||||
"update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.",
|
"update_error_check_error": "Could not check for new versions: The OnionShare website is saying the latest version is the unrecognizable '{}'…",
|
||||||
"update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.",
|
"update_error_invalid_latest_version": "Could not check for new version: Maybe you're not connected to Tor, or the OnionShare website is down?",
|
||||||
"update_not_available": "You are running the latest version of OnionShare.",
|
"update_not_available": "You are running the latest OnionShare.",
|
||||||
"gui_tor_connection_ask": "Would you like to open OnionShare settings to troubleshoot connecting to Tor?",
|
"gui_tor_connection_ask": "Open the settings to sort out connection to Tor?",
|
||||||
"gui_tor_connection_ask_open_settings": "Open Settings",
|
"gui_tor_connection_ask_open_settings": "Yes",
|
||||||
"gui_tor_connection_ask_quit": "Quit",
|
"gui_tor_connection_ask_quit": "Quit",
|
||||||
"gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.",
|
"gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.",
|
||||||
"gui_tor_connection_canceled": "OnionShare could not connect to Tor.\n\nMake sure you're connected to the Internet, then re-open OnionShare to set up the Tor connection.",
|
"gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.",
|
||||||
"gui_tor_connection_lost": "Disconnected from Tor.",
|
"gui_tor_connection_lost": "Disconnected from Tor.",
|
||||||
"gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
|
"gui_server_started_after_timeout": "The auto-stop timer ran out before the server started.\nPlease make a new share.",
|
||||||
"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 auto-stop timer already ran out.\nPlease update it to start sharing.",
|
||||||
"share_via_onionshare": "Share via OnionShare",
|
"share_via_onionshare": "OnionShare it",
|
||||||
"gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
|
"gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
|
||||||
"gui_save_private_key_checkbox": "Use a persistent address (legacy)",
|
"gui_save_private_key_checkbox": "Use a persistent address (legacy)",
|
||||||
"gui_share_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
|
"gui_share_url_description": "<b>Anyone</b> with this OnionShare address can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
|
||||||
"gui_receive_url_description": "<b>Anyone</b> with this link can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
|
"gui_receive_url_description": "<b>Anyone</b> with this OnionShare address can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
|
||||||
"gui_url_label_persistent": "This share will not expire automatically unless a timer is set.<br><br>Every share will have the same address (to use one-time addresses, disable persistence in Settings)",
|
"gui_url_label_persistent": "This share will not auto-stop.<br><br>Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
|
||||||
"gui_url_label_stay_open": "This share will not expire automatically unless a timer is set.",
|
"gui_url_label_stay_open": "This share will not auto-stop.",
|
||||||
"gui_url_label_onetime": "This share will expire after the first download",
|
"gui_url_label_onetime": "This share will stop after first completion.",
|
||||||
"gui_url_label_onetime_and_persistent": "This share will expire after the first download<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
|
"gui_url_label_onetime_and_persistent": "This share will not auto-stop.<br><br>Every subsequent share will reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
|
||||||
"gui_status_indicator_share_stopped": "Ready to Share",
|
"gui_status_indicator_share_stopped": "Ready to share",
|
||||||
"gui_status_indicator_share_working": "Starting…",
|
"gui_status_indicator_share_working": "Starting…",
|
||||||
"gui_status_indicator_share_started": "Sharing",
|
"gui_status_indicator_share_started": "Sharing",
|
||||||
"gui_status_indicator_receive_stopped": "Ready to Receive",
|
"gui_status_indicator_receive_stopped": "Ready to receive",
|
||||||
"gui_status_indicator_receive_working": "Starting…",
|
"gui_status_indicator_receive_working": "Starting…",
|
||||||
"gui_status_indicator_receive_started": "Receiving",
|
"gui_status_indicator_receive_started": "Receiving",
|
||||||
"gui_file_info": "{} Files, {}",
|
"gui_file_info": "{} files, {}",
|
||||||
"gui_file_info_single": "{} File, {}",
|
"gui_file_info_single": "{} file, {}",
|
||||||
"info_in_progress_downloads_tooltip": "{} download(s) in progress",
|
"history_in_progress_tooltip": "{} in progress",
|
||||||
"info_completed_downloads_tooltip": "{} download(s) completed",
|
"history_completed_tooltip": "{} completed",
|
||||||
"info_in_progress_uploads_tooltip": "{} upload(s) in progress",
|
"info_in_progress_uploads_tooltip": "{} upload(s) in progress",
|
||||||
"info_completed_uploads_tooltip": "{} upload(s) completed",
|
"info_completed_uploads_tooltip": "{} upload(s) completed",
|
||||||
"error_cannot_create_downloads_dir": "Error creating downloads folder: {}",
|
"error_cannot_create_downloads_dir": "Could not create receive mode folder: {}",
|
||||||
"error_downloads_dir_not_writable": "The downloads folder isn't writable: {}",
|
"error_downloads_dir_not_writable": "The receive mode folder is write protected: {}",
|
||||||
"receive_mode_downloads_dir": "Files people send you will appear in this folder: {}",
|
"receive_mode_downloads_dir": "Files sent to you appear in this folder: {}",
|
||||||
"receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.",
|
"receive_mode_warning": "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.",
|
||||||
"gui_receive_mode_warning": "<b>Some files can hack your computer if you open them!</b><br>Only open files from people you trust, or if you know what you're doing.",
|
"gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
|
||||||
"receive_mode_upload_starting": "Upload of total size {} is starting",
|
"receive_mode_upload_starting": "Upload of total size {} is starting",
|
||||||
"receive_mode_received_file": "Received file: {}",
|
"receive_mode_received_file": "Received: {}",
|
||||||
"gui_mode_share_button": "Share Files",
|
"gui_mode_share_button": "Share Files",
|
||||||
"gui_mode_receive_button": "Receive Files",
|
"gui_mode_receive_button": "Receive Files",
|
||||||
"gui_settings_receiving_label": "Receiving settings",
|
"gui_settings_receiving_label": "Receiving settings",
|
||||||
@ -179,10 +175,11 @@
|
|||||||
"systray_download_page_loaded_message": "A user loaded the download page",
|
"systray_download_page_loaded_message": "A user loaded the download page",
|
||||||
"systray_upload_page_loaded_message": "A user loaded the upload page",
|
"systray_upload_page_loaded_message": "A user loaded the upload page",
|
||||||
"gui_uploads": "Upload History",
|
"gui_uploads": "Upload History",
|
||||||
"gui_uploads_window_tooltip": "Show/hide uploads",
|
"gui_no_uploads": "No Uploads Yet",
|
||||||
"gui_no_uploads": "No uploads yet.",
|
"gui_clear_history": "Clear All",
|
||||||
"gui_upload_in_progress": "Upload Started {}",
|
"gui_upload_in_progress": "Upload Started {}",
|
||||||
"gui_upload_finished_range": "Uploaded {} to {}",
|
"gui_upload_finished_range": "Uploaded {} to {}",
|
||||||
"gui_upload_finished": "Uploaded {}",
|
"gui_upload_finished": "Uploaded {}",
|
||||||
"gui_open_folder_error_nautilus": "Cannot open folder the because nautilus is not available. You can find this file here: {}"
|
"gui_download_in_progress": "Download Started {}",
|
||||||
|
"gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"config_onion_service": "Agordas onion service je pordo {0:d}.",
|
"config_onion_service": "Agordas onion service je pordo {0:d}.",
|
||||||
"preparing_files": "Preparas dosierojn por kundivido.",
|
"preparing_files": "Preparas dosierojn por kundivido.",
|
||||||
"wait_for_hs": "Atendas al hidden sevice por esti preta:",
|
|
||||||
"give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:",
|
"give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:",
|
||||||
"give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
|
"give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
|
||||||
"ctrlc_to_stop": "Presu Ctrl-C por halti la servilon",
|
"ctrlc_to_stop": "Presu Ctrl-C por halti la servilon",
|
||||||
@ -27,7 +26,6 @@
|
|||||||
"gui_copied_url": "URL kopiita en tondujon",
|
"gui_copied_url": "URL kopiita en tondujon",
|
||||||
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
|
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
|
||||||
"gui_please_wait": "Bonvolu atendi...",
|
"gui_please_wait": "Bonvolu atendi...",
|
||||||
"using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
|
|
||||||
"gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}",
|
"gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}",
|
||||||
"gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
|
"gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)",
|
||||||
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preparing_files": "Preparando los archivos para compartir.",
|
"preparing_files": "Preparando los archivos para compartir.",
|
||||||
"wait_for_hs": "Esperando a que HS esté listo:",
|
|
||||||
"give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:",
|
"give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:",
|
||||||
"ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor",
|
"ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor",
|
||||||
"not_a_file": "{0:s} no es un archivo.",
|
"not_a_file": "{0:s} no es un archivo.",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preparing_files": "Valmistellaan tiedostoja jaettavaksi.",
|
"preparing_files": "Valmistellaan tiedostoja jaettavaksi.",
|
||||||
"wait_for_hs": "Odotetaan piilopalvelun valmistumista:",
|
|
||||||
"give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:",
|
"give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:",
|
||||||
"ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen",
|
"ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen",
|
||||||
"not_a_file": "{0:s} Ei ole tiedosto.",
|
"not_a_file": "{0:s} Ei ole tiedosto.",
|
||||||
@ -22,6 +21,5 @@
|
|||||||
"gui_canceled": "Peruutettu",
|
"gui_canceled": "Peruutettu",
|
||||||
"gui_copied_url": "URL-osoite kopioitu leikepöydälle",
|
"gui_copied_url": "URL-osoite kopioitu leikepöydälle",
|
||||||
"gui_please_wait": "Odota...",
|
"gui_please_wait": "Odota...",
|
||||||
"using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua",
|
|
||||||
"zip_progress_bar_format": "Tiivistän tiedostoja: %p%"
|
"zip_progress_bar_format": "Tiivistän tiedostoja: %p%"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preparing_files": "Préparation des fichiers à partager.",
|
"preparing_files": "Préparation des fichiers à partager.",
|
||||||
"wait_for_hs": "En attente du HS:",
|
|
||||||
"give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :",
|
"give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :",
|
||||||
"ctrlc_to_stop": "Ctrl-C arrête le serveur",
|
"ctrlc_to_stop": "Ctrl-C arrête le serveur",
|
||||||
"not_a_file": "{0:s} n'est pas un fichier.",
|
"not_a_file": "{0:s} n'est pas un fichier.",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preparing_files": "Preparazione dei files da condividere.",
|
"preparing_files": "Preparazione dei files da condividere.",
|
||||||
"wait_for_hs": "In attesa che l'HS sia pronto:",
|
|
||||||
"give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:",
|
"give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:",
|
||||||
"ctrlc_to_stop": "Premi Ctrl-C per fermare il server",
|
"ctrlc_to_stop": "Premi Ctrl-C per fermare il server",
|
||||||
"not_a_file": "{0:s} non è un file.",
|
"not_a_file": "{0:s} non è un file.",
|
||||||
@ -22,6 +21,5 @@
|
|||||||
"gui_canceled": "Cancellati",
|
"gui_canceled": "Cancellati",
|
||||||
"gui_copied_url": "URL Copiato nella clipboard",
|
"gui_copied_url": "URL Copiato nella clipboard",
|
||||||
"gui_please_wait": "Attendere prego...",
|
"gui_please_wait": "Attendere prego...",
|
||||||
"using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione",
|
|
||||||
"zip_progress_bar_format": "Elaborazione files: %p%"
|
"zip_progress_bar_format": "Elaborazione files: %p%"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"config_onion_service": "Onion service configureren op poort {0:d}.",
|
"config_onion_service": "Onion service configureren op poort {0:d}.",
|
||||||
"preparing_files": "Bestanden om te delen aan het voorbereiden.",
|
"preparing_files": "Bestanden om te delen aan het voorbereiden.",
|
||||||
"wait_for_hs": "Wachten op gereed zijn van HS:",
|
|
||||||
"give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:",
|
"give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:",
|
||||||
"give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:",
|
"give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:",
|
||||||
"ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen",
|
"ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen",
|
||||||
@ -38,7 +37,6 @@
|
|||||||
"gui_copied_url": "URL gekopieerd naar klembord",
|
"gui_copied_url": "URL gekopieerd naar klembord",
|
||||||
"gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord",
|
"gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord",
|
||||||
"gui_please_wait": "Moment geduld...",
|
"gui_please_wait": "Moment geduld...",
|
||||||
"using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie",
|
|
||||||
"gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}",
|
"gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}",
|
||||||
"gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)",
|
"gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)",
|
||||||
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
"gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||||
@ -51,9 +49,7 @@
|
|||||||
"error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.",
|
"error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.",
|
||||||
"error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.",
|
"error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.",
|
||||||
"gui_settings_window_title": "Instellingen",
|
"gui_settings_window_title": "Instellingen",
|
||||||
"gui_settings_stealth_label": "Stealth (geavanceerd)",
|
|
||||||
"gui_settings_stealth_option": "Maak stealth onion services",
|
"gui_settings_stealth_option": "Maak stealth onion services",
|
||||||
"gui_settings_stealth_option_details": "Dit maakt OnionShare veiliger, maar ook lastiger voor de ontvanger om te verbinden.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">Meer informatie</a>.",
|
|
||||||
"gui_settings_autoupdate_label": "Controleer voor updates",
|
"gui_settings_autoupdate_label": "Controleer voor updates",
|
||||||
"gui_settings_autoupdate_option": "Notificeer me als er updates beschikbaar zijn",
|
"gui_settings_autoupdate_option": "Notificeer me als er updates beschikbaar zijn",
|
||||||
"gui_settings_autoupdate_timestamp": "Laatste controle: {}",
|
"gui_settings_autoupdate_timestamp": "Laatste controle: {}",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preparing_files": "Paylaşmak için dosyalar hazırlanıyor.",
|
"preparing_files": "Paylaşmak için dosyalar hazırlanıyor.",
|
||||||
"wait_for_hs": "GH hazır olması bekleniyor:",
|
|
||||||
"give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:",
|
"give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:",
|
||||||
"ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın",
|
"ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın",
|
||||||
"not_a_file": "{0:s} dosya değil.",
|
"not_a_file": "{0:s} dosya değil.",
|
||||||
@ -22,6 +21,5 @@
|
|||||||
"gui_canceled": "İptal edilen",
|
"gui_canceled": "İptal edilen",
|
||||||
"gui_copied_url": "Panoya kopyalanan URL",
|
"gui_copied_url": "Panoya kopyalanan URL",
|
||||||
"gui_please_wait": "Lütfen bekleyin...",
|
"gui_please_wait": "Lütfen bekleyin...",
|
||||||
"using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor",
|
|
||||||
"zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%"
|
"zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%"
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<header class="clearfix">
|
<header class="clearfix">
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li>
|
<li>Total size: <strong>{{ filesize_human }}</strong> {% if is_zipped %} (compressed){% endif %}</li>
|
||||||
{% if slug %}
|
{% if slug %}
|
||||||
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
|
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
Package3: onionshare
|
Package3: onionshare
|
||||||
Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy
|
Depends3: python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy
|
||||||
Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5
|
Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy
|
||||||
Suite: bionic
|
Suite: bionic
|
||||||
X-Python3-Version: >= 3.6
|
X-Python3-Version: >= 3.6
|
||||||
|
@ -64,7 +64,7 @@ def temp_file_1024_delete():
|
|||||||
# pytest > 2.9 only needs @pytest.fixture
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
@pytest.yield_fixture(scope='session')
|
@pytest.yield_fixture(scope='session')
|
||||||
def custom_zw():
|
def custom_zw():
|
||||||
zw = web.ZipWriter(
|
zw = web.share_mode.ZipWriter(
|
||||||
common.Common(),
|
common.Common(),
|
||||||
zip_filename=common.Common.random_string(4, 6),
|
zip_filename=common.Common.random_string(4, 6),
|
||||||
processed_size_callback=lambda _: 'custom_callback'
|
processed_size_callback=lambda _: 'custom_callback'
|
||||||
@ -77,7 +77,7 @@ def custom_zw():
|
|||||||
# pytest > 2.9 only needs @pytest.fixture
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
@pytest.yield_fixture(scope='session')
|
@pytest.yield_fixture(scope='session')
|
||||||
def default_zw():
|
def default_zw():
|
||||||
zw = web.ZipWriter(common.Common())
|
zw = web.share_mode.ZipWriter(common.Common())
|
||||||
yield zw
|
yield zw
|
||||||
zw.close()
|
zw.close()
|
||||||
tmp_dir = os.path.dirname(zw.zip_filename)
|
tmp_dir = os.path.dirname(zw.zip_filename)
|
@ -47,22 +47,14 @@ class TestLoadStrings:
|
|||||||
self, common_obj, locale_en, sys_onionshare_dev_mode):
|
self, common_obj, locale_en, sys_onionshare_dev_mode):
|
||||||
""" load_strings() loads English by default """
|
""" load_strings() loads English by default """
|
||||||
strings.load_strings(common_obj)
|
strings.load_strings(common_obj)
|
||||||
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
|
assert strings._('preparing_files') == "Compressing files."
|
||||||
|
|
||||||
|
|
||||||
def test_load_strings_loads_other_languages(
|
def test_load_strings_loads_other_languages(
|
||||||
self, common_obj, locale_fr, sys_onionshare_dev_mode):
|
self, common_obj, locale_fr, sys_onionshare_dev_mode):
|
||||||
""" load_strings() loads other languages in different locales """
|
""" load_strings() loads other languages in different locales """
|
||||||
strings.load_strings(common_obj, "fr")
|
strings.load_strings(common_obj, "fr")
|
||||||
assert strings._('wait_for_hs') == "En attente du HS:"
|
assert strings._('preparing_files') == "Préparation des fichiers à partager."
|
||||||
|
|
||||||
def test_load_partial_strings(
|
|
||||||
self, common_obj, locale_ru, sys_onionshare_dev_mode):
|
|
||||||
strings.load_strings(common_obj)
|
|
||||||
assert strings._("give_this_url") == (
|
|
||||||
"Отправьте эту ссылку тому человеку, "
|
|
||||||
"которому вы хотите передать файл:")
|
|
||||||
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
|
|
||||||
|
|
||||||
def test_load_invalid_locale(
|
def test_load_invalid_locale(
|
||||||
self, common_obj, locale_invalid, sys_onionshare_dev_mode):
|
self, common_obj, locale_invalid, sys_onionshare_dev_mode):
|
@ -38,11 +38,11 @@ DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$')
|
|||||||
RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$')
|
RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$')
|
||||||
|
|
||||||
|
|
||||||
def web_obj(common_obj, receive_mode, num_files=0):
|
def web_obj(common_obj, mode, num_files=0):
|
||||||
""" Creates a Web object, in either share mode or receive mode, ready for testing """
|
""" Creates a Web object, in either share mode or receive mode, ready for testing """
|
||||||
common_obj.load_settings()
|
common_obj.load_settings()
|
||||||
|
|
||||||
web = Web(common_obj, False, receive_mode)
|
web = Web(common_obj, False, mode)
|
||||||
web.generate_slug()
|
web.generate_slug()
|
||||||
web.stay_open = True
|
web.stay_open = True
|
||||||
web.running = True
|
web.running = True
|
||||||
@ -50,14 +50,14 @@ def web_obj(common_obj, receive_mode, num_files=0):
|
|||||||
web.app.testing = True
|
web.app.testing = True
|
||||||
|
|
||||||
# Share mode
|
# Share mode
|
||||||
if not receive_mode:
|
if mode == 'share':
|
||||||
# Add files
|
# Add files
|
||||||
files = []
|
files = []
|
||||||
for i in range(num_files):
|
for i in range(num_files):
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
||||||
tmp_file.write(b'*' * 1024)
|
tmp_file.write(b'*' * 1024)
|
||||||
files.append(tmp_file.name)
|
files.append(tmp_file.name)
|
||||||
web.set_file_info(files)
|
web.share_mode.set_file_info(files)
|
||||||
# Receive mode
|
# Receive mode
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
@ -67,8 +67,8 @@ def web_obj(common_obj, receive_mode, num_files=0):
|
|||||||
|
|
||||||
class TestWeb:
|
class TestWeb:
|
||||||
def test_share_mode(self, common_obj):
|
def test_share_mode(self, common_obj):
|
||||||
web = web_obj(common_obj, False, 3)
|
web = web_obj(common_obj, 'share', 3)
|
||||||
assert web.receive_mode is False
|
assert web.mode is 'share'
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Load 404 pages
|
# Load 404 pages
|
||||||
res = c.get('/')
|
res = c.get('/')
|
||||||
@ -91,7 +91,7 @@ class TestWeb:
|
|||||||
assert res.mimetype == 'application/zip'
|
assert res.mimetype == 'application/zip'
|
||||||
|
|
||||||
def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024):
|
def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024):
|
||||||
web = web_obj(common_obj, False, 3)
|
web = web_obj(common_obj, 'share', 3)
|
||||||
web.stay_open = False
|
web.stay_open = False
|
||||||
|
|
||||||
assert web.running == True
|
assert web.running == True
|
||||||
@ -106,7 +106,7 @@ class TestWeb:
|
|||||||
assert web.running == False
|
assert web.running == False
|
||||||
|
|
||||||
def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024):
|
def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024):
|
||||||
web = web_obj(common_obj, False, 3)
|
web = web_obj(common_obj, 'share', 3)
|
||||||
web.stay_open = True
|
web.stay_open = True
|
||||||
|
|
||||||
assert web.running == True
|
assert web.running == True
|
||||||
@ -120,8 +120,8 @@ class TestWeb:
|
|||||||
assert web.running == True
|
assert web.running == True
|
||||||
|
|
||||||
def test_receive_mode(self, common_obj):
|
def test_receive_mode(self, common_obj):
|
||||||
web = web_obj(common_obj, True)
|
web = web_obj(common_obj, 'receive')
|
||||||
assert web.receive_mode is True
|
assert web.mode is 'receive'
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Load 404 pages
|
# Load 404 pages
|
||||||
@ -139,7 +139,7 @@ class TestWeb:
|
|||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
def test_receive_mode_allow_receiver_shutdown_on(self, common_obj):
|
def test_receive_mode_allow_receiver_shutdown_on(self, common_obj):
|
||||||
web = web_obj(common_obj, True)
|
web = web_obj(common_obj, 'receive')
|
||||||
|
|
||||||
common_obj.settings.set('receive_allow_receiver_shutdown', True)
|
common_obj.settings.set('receive_allow_receiver_shutdown', True)
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ class TestWeb:
|
|||||||
assert web.running == False
|
assert web.running == False
|
||||||
|
|
||||||
def test_receive_mode_allow_receiver_shutdown_off(self, common_obj):
|
def test_receive_mode_allow_receiver_shutdown_off(self, common_obj):
|
||||||
web = web_obj(common_obj, True)
|
web = web_obj(common_obj, 'receive')
|
||||||
|
|
||||||
common_obj.settings.set('receive_allow_receiver_shutdown', False)
|
common_obj.settings.set('receive_allow_receiver_shutdown', False)
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ class TestWeb:
|
|||||||
assert web.running == True
|
assert web.running == True
|
||||||
|
|
||||||
def test_public_mode_on(self, common_obj):
|
def test_public_mode_on(self, common_obj):
|
||||||
web = web_obj(common_obj, True)
|
web = web_obj(common_obj, 'receive')
|
||||||
common_obj.settings.set('public_mode', True)
|
common_obj.settings.set('public_mode', True)
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
@ -184,7 +184,7 @@ class TestWeb:
|
|||||||
assert res.status_code == 404
|
assert res.status_code == 404
|
||||||
|
|
||||||
def test_public_mode_off(self, common_obj):
|
def test_public_mode_off(self, common_obj):
|
||||||
web = web_obj(common_obj, True)
|
web = web_obj(common_obj, 'receive')
|
||||||
common_obj.settings.set('public_mode', False)
|
common_obj.settings.set('public_mode', False)
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
1
tests_gui_local/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .commontests import CommonTests
|
291
tests_gui_local/commontests.py
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import socket
|
||||||
|
import socks
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtTest
|
||||||
|
from onionshare import strings
|
||||||
|
from onionshare_gui.mode.receive_mode import ReceiveMode
|
||||||
|
from onionshare_gui.mode.share_mode import ShareMode
|
||||||
|
|
||||||
|
|
||||||
|
class CommonTests(object):
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
'''Test that the GUI actually is shown'''
|
||||||
|
self.assertTrue(self.gui.show)
|
||||||
|
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
'''Test that the window title is OnionShare'''
|
||||||
|
self.assertEqual(self.gui.windowTitle(), 'OnionShare')
|
||||||
|
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
'''Test that the settings button is visible'''
|
||||||
|
self.assertTrue(self.gui.settings_button.isVisible())
|
||||||
|
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
'''Test that the status bar is visible'''
|
||||||
|
self.assertTrue(self.gui.status_bar.isVisible())
|
||||||
|
|
||||||
|
def test_click_mode(self, mode):
|
||||||
|
'''Test that we can switch Mode by clicking the button'''
|
||||||
|
if type(mode) == ReceiveMode:
|
||||||
|
QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE)
|
||||||
|
if type(mode) == ShareMode:
|
||||||
|
QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertTrue(self.gui.mode, self.gui.MODE_SHARE)
|
||||||
|
|
||||||
|
def test_click_toggle_history(self, mode):
|
||||||
|
'''Test that we can toggle Download or Upload history by clicking the toggle button'''
|
||||||
|
currently_visible = mode.history.isVisible()
|
||||||
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
|
self.assertEqual(mode.history.isVisible(), not currently_visible)
|
||||||
|
|
||||||
|
def test_history_indicator(self, mode, public_mode):
|
||||||
|
'''Test that we can make sure the history is toggled off, do an action, and the indiciator works'''
|
||||||
|
# Make sure history is toggled off
|
||||||
|
if mode.history.isVisible():
|
||||||
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
|
self.assertFalse(mode.history.isVisible())
|
||||||
|
|
||||||
|
# Indicator should not be visible yet
|
||||||
|
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||||
|
|
||||||
|
if type(mode) == ReceiveMode:
|
||||||
|
# Upload a file
|
||||||
|
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
||||||
|
if not public_mode:
|
||||||
|
path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug)
|
||||||
|
else:
|
||||||
|
path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
||||||
|
response = requests.post(path, files=files)
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
|
if type(mode) == ShareMode:
|
||||||
|
# Download files
|
||||||
|
if public_mode:
|
||||||
|
url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
|
||||||
|
else:
|
||||||
|
url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug)
|
||||||
|
r = requests.get(url)
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
|
# Indicator should be visible, have a value of "1"
|
||||||
|
self.assertTrue(mode.toggle_history.indicator_label.isVisible())
|
||||||
|
self.assertEqual(mode.toggle_history.indicator_label.text(), "1")
|
||||||
|
|
||||||
|
# Toggle history back on, indicator should be hidden again
|
||||||
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
|
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||||
|
|
||||||
|
def test_history_is_not_visible(self, mode):
|
||||||
|
'''Test that the History section is not visible'''
|
||||||
|
self.assertFalse(mode.history.isVisible())
|
||||||
|
|
||||||
|
def test_history_is_visible(self, mode):
|
||||||
|
'''Test that the History section is visible'''
|
||||||
|
self.assertTrue(mode.history.isVisible())
|
||||||
|
|
||||||
|
def test_server_working_on_start_button_pressed(self, mode):
|
||||||
|
'''Test we can start the service'''
|
||||||
|
# Should be in SERVER_WORKING state
|
||||||
|
QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertEqual(mode.server_status.status, 1)
|
||||||
|
|
||||||
|
def test_server_status_indicator_says_starting(self, mode):
|
||||||
|
'''Test that the Server Status indicator shows we are Starting'''
|
||||||
|
self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working'))
|
||||||
|
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
'''Test that the settings button is hidden when the server starts'''
|
||||||
|
self.assertFalse(self.gui.settings_button.isVisible())
|
||||||
|
|
||||||
|
def test_a_server_is_started(self, mode):
|
||||||
|
'''Test that the server has started'''
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
# Should now be in SERVER_STARTED state
|
||||||
|
self.assertEqual(mode.server_status.status, 2)
|
||||||
|
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
'''Test that the web server has started'''
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
|
||||||
|
|
||||||
|
def test_have_a_slug(self, mode, public_mode):
|
||||||
|
'''Test that we have a valid slug'''
|
||||||
|
if not public_mode:
|
||||||
|
self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)')
|
||||||
|
else:
|
||||||
|
self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)')
|
||||||
|
|
||||||
|
|
||||||
|
def test_url_description_shown(self, mode):
|
||||||
|
'''Test that the URL label is showing'''
|
||||||
|
self.assertTrue(mode.server_status.url_description.isVisible())
|
||||||
|
|
||||||
|
def test_have_copy_url_button(self, mode):
|
||||||
|
'''Test that the Copy URL button is shown'''
|
||||||
|
self.assertTrue(mode.server_status.copy_url_button.isVisible())
|
||||||
|
|
||||||
|
def test_server_status_indicator_says_started(self, mode):
|
||||||
|
'''Test that the Server Status indicator shows we are started'''
|
||||||
|
if type(mode) == ReceiveMode:
|
||||||
|
self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started'))
|
||||||
|
if type(mode) == ShareMode:
|
||||||
|
self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started'))
|
||||||
|
|
||||||
|
def test_web_page(self, mode, string, public_mode):
|
||||||
|
'''Test that the web page contains a string'''
|
||||||
|
s = socks.socksocket()
|
||||||
|
s.settimeout(60)
|
||||||
|
s.connect(('127.0.0.1', self.gui.app.port))
|
||||||
|
|
||||||
|
if not public_mode:
|
||||||
|
path = '/{}'.format(mode.server_status.web.slug)
|
||||||
|
else:
|
||||||
|
path = '/'
|
||||||
|
|
||||||
|
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
||||||
|
http_request += 'Host: 127.0.0.1\r\n'
|
||||||
|
http_request += '\r\n'
|
||||||
|
s.sendall(http_request.encode('utf-8'))
|
||||||
|
|
||||||
|
with open('/tmp/webpage', 'wb') as file_to_write:
|
||||||
|
while True:
|
||||||
|
data = s.recv(1024)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
file_to_write.write(data)
|
||||||
|
file_to_write.close()
|
||||||
|
|
||||||
|
f = open('/tmp/webpage')
|
||||||
|
self.assertTrue(string in f.read())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def test_history_widgets_present(self, mode):
|
||||||
|
'''Test that the relevant widgets are present in the history view after activity has taken place'''
|
||||||
|
self.assertFalse(mode.history.empty.isVisible())
|
||||||
|
self.assertTrue(mode.history.not_empty.isVisible())
|
||||||
|
|
||||||
|
def test_counter_incremented(self, mode, count):
|
||||||
|
'''Test that the counter has incremented'''
|
||||||
|
self.assertEquals(mode.history.completed_count, count)
|
||||||
|
|
||||||
|
def test_server_is_stopped(self, mode, stay_open):
|
||||||
|
'''Test that the server stops when we click Stop'''
|
||||||
|
if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open):
|
||||||
|
QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertEquals(mode.server_status.status, 0)
|
||||||
|
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
'''Test that the web server also stopped'''
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# We should be closed by now. Fail if not!
|
||||||
|
self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
|
||||||
|
|
||||||
|
def test_server_status_indicator_says_closed(self, mode, stay_open):
|
||||||
|
'''Test that the Server Status indicator shows we closed'''
|
||||||
|
if type(mode) == ReceiveMode:
|
||||||
|
self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True))
|
||||||
|
if type(mode) == ShareMode:
|
||||||
|
if stay_open:
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True))
|
||||||
|
else:
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True))
|
||||||
|
|
||||||
|
# Auto-stop timer tests
|
||||||
|
def test_set_timeout(self, mode, timeout):
|
||||||
|
'''Test that the timeout can be set'''
|
||||||
|
timer = QtCore.QDateTime.currentDateTime().addSecs(timeout)
|
||||||
|
mode.server_status.shutdown_timeout.setDateTime(timer)
|
||||||
|
self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer)
|
||||||
|
|
||||||
|
def test_timeout_widget_hidden(self, mode):
|
||||||
|
'''Test that the timeout widget is hidden when share has started'''
|
||||||
|
self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible())
|
||||||
|
|
||||||
|
def test_server_timed_out(self, mode, wait):
|
||||||
|
'''Test that the server has timed out after the timer ran out'''
|
||||||
|
QtTest.QTest.qWait(wait)
|
||||||
|
# We should have timed out now
|
||||||
|
self.assertEqual(mode.server_status.status, 0)
|
||||||
|
|
||||||
|
# Receive-specific tests
|
||||||
|
def test_upload_file(self, public_mode, expected_file):
|
||||||
|
'''Test that we can upload the file'''
|
||||||
|
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
||||||
|
if not public_mode:
|
||||||
|
path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
|
||||||
|
else:
|
||||||
|
path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
||||||
|
response = requests.post(path, files=files)
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
self.assertTrue(os.path.isfile(expected_file))
|
||||||
|
|
||||||
|
# Share-specific tests
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
'''Test that the number of files in the list is 1'''
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1)
|
||||||
|
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
'''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button'''
|
||||||
|
rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0))
|
||||||
|
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center())
|
||||||
|
# Delete button should be visible
|
||||||
|
self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
||||||
|
# Click delete, and since there's no more files, the delete button should be hidden
|
||||||
|
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
||||||
|
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
'''Test that we can also delete a file by clicking on its [X] widget'''
|
||||||
|
self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
|
||||||
|
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0)
|
||||||
|
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
'''Re-add some files to the list so we can share'''
|
||||||
|
self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts')
|
||||||
|
self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt')
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2)
|
||||||
|
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
'''Test that the add and delete buttons are hidden when the server starts'''
|
||||||
|
self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible())
|
||||||
|
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
||||||
|
|
||||||
|
def test_download_share(self, public_mode):
|
||||||
|
'''Test that we can download the share'''
|
||||||
|
s = socks.socksocket()
|
||||||
|
s.settimeout(60)
|
||||||
|
s.connect(('127.0.0.1', self.gui.app.port))
|
||||||
|
|
||||||
|
if public_mode:
|
||||||
|
path = '/download'
|
||||||
|
else:
|
||||||
|
path = '{}/download'.format(self.gui.share_mode.web.slug)
|
||||||
|
|
||||||
|
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
||||||
|
http_request += 'Host: 127.0.0.1\r\n'
|
||||||
|
http_request += '\r\n'
|
||||||
|
s.sendall(http_request.encode('utf-8'))
|
||||||
|
|
||||||
|
with open('/tmp/download.zip', 'wb') as file_to_write:
|
||||||
|
while True:
|
||||||
|
data = s.recv(1024)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
file_to_write.write(data)
|
||||||
|
file_to_write.close()
|
||||||
|
|
||||||
|
zip = zipfile.ZipFile('/tmp/download.zip')
|
||||||
|
QtTest.QTest.qWait(2000)
|
||||||
|
self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
|
||||||
|
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
'''Test that the add button should be visible'''
|
||||||
|
self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible())
|
160
tests_gui_local/conftest.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import sys
|
||||||
|
# Force tests to look for resources in the source code tree
|
||||||
|
sys.onionshare_dev_mode = True
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from onionshare import common, web, settings
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_dir_1024():
|
||||||
|
""" Create a temporary directory that has a single file of a
|
||||||
|
particular size (1024 bytes).
|
||||||
|
"""
|
||||||
|
|
||||||
|
tmp_dir = tempfile.mkdtemp()
|
||||||
|
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||||
|
with open(tmp_file, 'wb') as f:
|
||||||
|
f.write(b'*' * 1024)
|
||||||
|
return tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture
|
||||||
|
def temp_dir_1024_delete():
|
||||||
|
""" Create a temporary directory that has a single file of a
|
||||||
|
particular size (1024 bytes). The temporary directory (including
|
||||||
|
the file inside) will be deleted after fixture usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||||
|
with open(tmp_file, 'wb') as f:
|
||||||
|
f.write(b'*' * 1024)
|
||||||
|
yield tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_file_1024():
|
||||||
|
""" Create a temporary file of a particular size (1024 bytes). """
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
||||||
|
tmp_file.write(b'*' * 1024)
|
||||||
|
return tmp_file.name
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture
|
||||||
|
def temp_file_1024_delete():
|
||||||
|
"""
|
||||||
|
Create a temporary file of a particular size (1024 bytes).
|
||||||
|
The temporary file will be deleted after fixture usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||||
|
tmp_file.write(b'*' * 1024)
|
||||||
|
tmp_file.flush()
|
||||||
|
yield tmp_file.name
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture(scope='session')
|
||||||
|
def custom_zw():
|
||||||
|
zw = web.share_mode.ZipWriter(
|
||||||
|
common.Common(),
|
||||||
|
zip_filename=common.Common.random_string(4, 6),
|
||||||
|
processed_size_callback=lambda _: 'custom_callback'
|
||||||
|
)
|
||||||
|
yield zw
|
||||||
|
zw.close()
|
||||||
|
os.remove(zw.zip_filename)
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture(scope='session')
|
||||||
|
def default_zw():
|
||||||
|
zw = web.share_mode.ZipWriter(common.Common())
|
||||||
|
yield zw
|
||||||
|
zw.close()
|
||||||
|
tmp_dir = os.path.dirname(zw.zip_filename)
|
||||||
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_en(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_fr(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_invalid(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_ru(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def platform_darwin(monkeypatch):
|
||||||
|
monkeypatch.setattr('platform.system', lambda: 'Darwin')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture # (scope="session")
|
||||||
|
def platform_linux(monkeypatch):
|
||||||
|
monkeypatch.setattr('platform.system', lambda: 'Linux')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def platform_windows(monkeypatch):
|
||||||
|
monkeypatch.setattr('platform.system', lambda: 'Windows')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sys_argv_sys_prefix(monkeypatch):
|
||||||
|
monkeypatch.setattr('sys.argv', [sys.prefix])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sys_frozen(monkeypatch):
|
||||||
|
monkeypatch.setattr('sys.frozen', True, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sys_meipass(monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
'sys._MEIPASS', os.path.expanduser('~'), raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture # (scope="session")
|
||||||
|
def sys_onionshare_dev_mode(monkeypatch):
|
||||||
|
monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def time_time_100(monkeypatch):
|
||||||
|
monkeypatch.setattr('time.time', lambda: 100)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def time_strftime(monkeypatch):
|
||||||
|
monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def common_obj():
|
||||||
|
return common.Common()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
||||||
|
_common = common.Common()
|
||||||
|
_common.version = 'DUMMY_VERSION_1.2.3'
|
||||||
|
return settings.Settings(_common)
|
190
tests_gui_local/onionshare_receive_mode_upload_test.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_click_mode(self):
|
||||||
|
CommonTests.test_click_mode(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, self.gui.receive_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_upload_file(self):
|
||||||
|
CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_counter_incremented(self):
|
||||||
|
CommonTests.test_counter_incremented(self, self.gui.receive_mode, 1)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_upload_same_file_is_renamed(self):
|
||||||
|
CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_upload_count_incremented_again(self):
|
||||||
|
CommonTests.test_counter_incremented(self, self.gui.receive_mode, 2)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_history_indicator(self):
|
||||||
|
CommonTests.test_history_indicator(self, self.gui.receive_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.receive_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": True,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
try:
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test-2.txt')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_click_mode(self):
|
||||||
|
CommonTests.test_click_mode(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, self.gui.receive_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_upload_file(self):
|
||||||
|
CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, self.gui.receive_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_counter_incremented(self):
|
||||||
|
CommonTests.test_counter_incremented(self, self.gui.receive_mode, 1)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_upload_same_file_is_renamed(self):
|
||||||
|
CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_upload_count_incremented_again(self):
|
||||||
|
CommonTests.test_counter_incremented(self, self.gui.receive_mode, 2)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_history_indicator(self):
|
||||||
|
CommonTests.test_history_indicator(self, self.gui.receive_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.receive_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
199
tests_gui_local/onionshare_share_mode_download_test.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_download_share(self):
|
||||||
|
CommonTests.test_download_share(self, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_history_indicator(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_history_indicator(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": True,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_download_share(self):
|
||||||
|
CommonTests.test_download_share(self, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_history_indicator(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_history_indicator(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
211
tests_gui_local/onionshare_share_mode_download_test_stay_open.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": False,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": True,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_download_share(self):
|
||||||
|
CommonTests.test_download_share(self, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_counter_incremented(self):
|
||||||
|
CommonTests.test_counter_incremented(self, self.gui.share_mode, 1)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_download_share_again(self):
|
||||||
|
CommonTests.test_download_share(self, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_counter_incremented_again(self):
|
||||||
|
CommonTests.test_counter_incremented(self, self.gui.share_mode, 2)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=31)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=32)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=33)
|
||||||
|
def test_history_indicator(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_history_indicator(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
175
tests_gui_local/onionshare_slug_persistent_test.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
slug = ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": True,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, self.gui.share_mode, False)
|
||||||
|
global slug
|
||||||
|
slug = self.gui.share_mode.server_status.web.slug
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_server_started_again(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_have_same_slug(self):
|
||||||
|
'''Test that we have the same slug'''
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.web.slug, slug)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_is_stopped_again(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, self.gui.share_mode, True)
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_history_indicator(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
CommonTests.test_history_indicator(self, self.gui.share_mode, False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
136
tests_gui_local/onionshare_timer_test.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": True,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_set_timeout(self):
|
||||||
|
CommonTests.test_set_timeout(self, self.gui.share_mode, 5)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_timeout_widget_hidden(self):
|
||||||
|
CommonTests.test_timeout_widget_hidden(self, self.gui.share_mode)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_timeout(self):
|
||||||
|
CommonTests.test_server_timed_out(self, self.gui.share_mode, 10000)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
5
tests_gui_local/run_unit_tests.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for test in `ls -1 | egrep ^onionshare_`; do
|
||||||
|
pytest $test -vvv || exit 1
|
||||||
|
done
|
0
tests_gui_tor/__init__.py
Normal file
61
tests_gui_tor/commontests.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import socket
|
||||||
|
import socks
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtTest
|
||||||
|
from onionshare import strings
|
||||||
|
|
||||||
|
from tests_gui_local import CommonTests as LocalCommonTests
|
||||||
|
|
||||||
|
class CommonTests(LocalCommonTests):
|
||||||
|
def test_a_server_is_started(self, mode):
|
||||||
|
'''Test that the server has started (overriding from local tests to wait for longer)'''
|
||||||
|
QtTest.QTest.qWait(45000)
|
||||||
|
# Should now be in SERVER_STARTED state
|
||||||
|
if mode == 'receive':
|
||||||
|
self.assertEqual(self.gui.receive_mode.server_status.status, 2)
|
||||||
|
if mode == 'share':
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.status, 2)
|
||||||
|
|
||||||
|
def test_have_an_onion_service(self):
|
||||||
|
'''Test that we have a valid Onion URL'''
|
||||||
|
self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion')
|
||||||
|
|
||||||
|
def test_cancel_the_share(self, mode):
|
||||||
|
'''Test that we can cancel this share before it's started up '''
|
||||||
|
if mode == 'share':
|
||||||
|
QtTest.QTest.mousePress(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
|
QtTest.QTest.qWait(1000)
|
||||||
|
QtTest.QTest.mouseRelease(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
||||||
|
|
||||||
|
if mode == 'receive':
|
||||||
|
QtTest.QTest.mousePress(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
|
QtTest.QTest.qWait(1000)
|
||||||
|
QtTest.QTest.mouseRelease(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
|
self.assertEqual(self.gui.receive_mode.server_status.status, 0)
|
||||||
|
|
||||||
|
# Stealth tests
|
||||||
|
def test_copy_have_hidserv_auth_button(self, mode):
|
||||||
|
'''Test that the Copy HidservAuth button is shown'''
|
||||||
|
if mode == 'share':
|
||||||
|
self.assertTrue(self.gui.share_mode.server_status.copy_hidservauth_button.isVisible())
|
||||||
|
if mode == 'receive':
|
||||||
|
self.assertTrue(self.gui.receive_mode.server_status.copy_hidservauth_button.isVisible())
|
||||||
|
|
||||||
|
def test_hidserv_auth_string(self):
|
||||||
|
'''Test the validity of the HidservAuth string'''
|
||||||
|
self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host)
|
||||||
|
|
||||||
|
|
||||||
|
# Miscellaneous tests
|
||||||
|
def test_tor_killed_statusbar_message_shown(self, mode):
|
||||||
|
'''Test that the status bar message shows Tor was disconnected'''
|
||||||
|
self.gui.app.onion.cleanup(stop_tor=True)
|
||||||
|
QtTest.QTest.qWait(2500)
|
||||||
|
if mode == 'share':
|
||||||
|
self.assertTrue(self.gui.share_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost', True))
|
||||||
|
if mode == 'receive':
|
||||||
|
self.assertTrue(self.gui.receive_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost', True))
|
160
tests_gui_tor/conftest.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import sys
|
||||||
|
# Force tests to look for resources in the source code tree
|
||||||
|
sys.onionshare_dev_mode = True
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from onionshare import common, web, settings
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_dir_1024():
|
||||||
|
""" Create a temporary directory that has a single file of a
|
||||||
|
particular size (1024 bytes).
|
||||||
|
"""
|
||||||
|
|
||||||
|
tmp_dir = tempfile.mkdtemp()
|
||||||
|
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||||
|
with open(tmp_file, 'wb') as f:
|
||||||
|
f.write(b'*' * 1024)
|
||||||
|
return tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture
|
||||||
|
def temp_dir_1024_delete():
|
||||||
|
""" Create a temporary directory that has a single file of a
|
||||||
|
particular size (1024 bytes). The temporary directory (including
|
||||||
|
the file inside) will be deleted after fixture usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||||
|
with open(tmp_file, 'wb') as f:
|
||||||
|
f.write(b'*' * 1024)
|
||||||
|
yield tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_file_1024():
|
||||||
|
""" Create a temporary file of a particular size (1024 bytes). """
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
||||||
|
tmp_file.write(b'*' * 1024)
|
||||||
|
return tmp_file.name
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture
|
||||||
|
def temp_file_1024_delete():
|
||||||
|
"""
|
||||||
|
Create a temporary file of a particular size (1024 bytes).
|
||||||
|
The temporary file will be deleted after fixture usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||||
|
tmp_file.write(b'*' * 1024)
|
||||||
|
tmp_file.flush()
|
||||||
|
yield tmp_file.name
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture(scope='session')
|
||||||
|
def custom_zw():
|
||||||
|
zw = web.share_mode.ZipWriter(
|
||||||
|
common.Common(),
|
||||||
|
zip_filename=common.Common.random_string(4, 6),
|
||||||
|
processed_size_callback=lambda _: 'custom_callback'
|
||||||
|
)
|
||||||
|
yield zw
|
||||||
|
zw.close()
|
||||||
|
os.remove(zw.zip_filename)
|
||||||
|
|
||||||
|
|
||||||
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
|
@pytest.yield_fixture(scope='session')
|
||||||
|
def default_zw():
|
||||||
|
zw = web.share_mode.ZipWriter(common.Common())
|
||||||
|
yield zw
|
||||||
|
zw.close()
|
||||||
|
tmp_dir = os.path.dirname(zw.zip_filename)
|
||||||
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_en(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_fr(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_invalid(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def locale_ru(monkeypatch):
|
||||||
|
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def platform_darwin(monkeypatch):
|
||||||
|
monkeypatch.setattr('platform.system', lambda: 'Darwin')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture # (scope="session")
|
||||||
|
def platform_linux(monkeypatch):
|
||||||
|
monkeypatch.setattr('platform.system', lambda: 'Linux')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def platform_windows(monkeypatch):
|
||||||
|
monkeypatch.setattr('platform.system', lambda: 'Windows')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sys_argv_sys_prefix(monkeypatch):
|
||||||
|
monkeypatch.setattr('sys.argv', [sys.prefix])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sys_frozen(monkeypatch):
|
||||||
|
monkeypatch.setattr('sys.frozen', True, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sys_meipass(monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
'sys._MEIPASS', os.path.expanduser('~'), raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture # (scope="session")
|
||||||
|
def sys_onionshare_dev_mode(monkeypatch):
|
||||||
|
monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def time_time_100(monkeypatch):
|
||||||
|
monkeypatch.setattr('time.time', lambda: 100)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def time_strftime(monkeypatch):
|
||||||
|
monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def common_obj():
|
||||||
|
return common.Common()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
||||||
|
_common = common.Common()
|
||||||
|
_common.version = 'DUMMY_VERSION_1.2.3'
|
||||||
|
return settings.Settings(_common)
|
197
tests_gui_tor/onionshare_790_cancel_on_second_share_test.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_is_visible(self):
|
||||||
|
CommonTests.test_info_widget_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_server_working_on_start_button_pressed_round2(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_server_status_indicator_says_starting_round2(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_cancel_the_share(self):
|
||||||
|
CommonTests.test_cancel_the_share(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_server_is_stopped_round2(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_web_service_is_stopped_round2(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
190
tests_gui_tor/onionshare_receive_mode_upload_test.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_click_mode(self):
|
||||||
|
CommonTests.test_click_mode(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'receive', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_upload_file(self):
|
||||||
|
CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_counter_incremented(self):
|
||||||
|
CommonTests.test_counter_incremented(self, 'receive', 1)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_upload_same_file_is_renamed(self):
|
||||||
|
CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_upload_count_incremented_again(self):
|
||||||
|
CommonTests.test_counter_incremented(self, 'receive', 2)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'receive', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=31)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, 'receive', False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
190
tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": True,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test.txt')
|
||||||
|
os.remove('/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_click_mode(self):
|
||||||
|
CommonTests.test_click_mode(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'receive', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_upload_file(self):
|
||||||
|
CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, 'receive')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_counter_incremented(self):
|
||||||
|
CommonTests.test_counter_incremented(self, 'receive', 1)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_upload_same_file_is_renamed(self):
|
||||||
|
CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_upload_count_incremented_again(self):
|
||||||
|
CommonTests.test_counter_incremented(self, 'receive', 2)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'receive', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=31)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, 'receive', False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
149
tests_gui_tor/onionshare_share_mode_cancel_share_test.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_cancel_the_share(self):
|
||||||
|
CommonTests.test_cancel_the_share(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
193
tests_gui_tor/onionshare_share_mode_download_test.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, 'share', 'Total size', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_download_share(self):
|
||||||
|
CommonTests.test_download_share(self, False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
201
tests_gui_tor/onionshare_share_mode_download_test_public_mode.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": True,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, 'share', 'Total size', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_download_share(self):
|
||||||
|
CommonTests.test_download_share(self, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
213
tests_gui_tor/onionshare_share_mode_download_test_stay_open.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": False,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": True,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_web_page(self):
|
||||||
|
CommonTests.test_web_page(self, 'share', 'Total size', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_download_share(self):
|
||||||
|
CommonTests.test_download_share(self, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_history_widgets_present(self):
|
||||||
|
CommonTests.test_history_widgets_present(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=27)
|
||||||
|
def test_counter_incremented(self):
|
||||||
|
CommonTests.test_counter_incremented(self, 'share', 1)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=28)
|
||||||
|
def test_download_share_again(self):
|
||||||
|
CommonTests.test_download_share(self, True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=29)
|
||||||
|
def test_counter_incremented_again(self):
|
||||||
|
CommonTests.test_counter_incremented(self, 'share', 2)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=30)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=31)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=32)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=33)
|
||||||
|
def test_add_button_visible(self):
|
||||||
|
CommonTests.test_add_button_visible(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
185
tests_gui_tor/onionshare_share_mode_persistent_test.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
slug = ''
|
||||||
|
onion_host = ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": True,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', False)
|
||||||
|
global slug
|
||||||
|
slug = self.gui.share_mode.server_status.web.slug
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
global onion_host
|
||||||
|
onion_host = self.gui.app.onion_host
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_server_status_indicator_says_closed(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_closed(self, 'share', True)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_server_started_again(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_have_same_slug(self):
|
||||||
|
'''Test that we have the same slug'''
|
||||||
|
self.assertEqual(self.gui.share_mode.server_status.web.slug, slug)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_have_same_onion(self):
|
||||||
|
'''Test that we have the same onion'''
|
||||||
|
self.assertEqual(self.gui.app.onion_host, onion_host)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_server_is_stopped_again(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', True)
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
180
tests_gui_tor/onionshare_share_mode_stealth_test.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": True,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_copy_have_hidserv_auth_button(self):
|
||||||
|
CommonTests.test_copy_have_hidserv_auth_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_hidserv_auth_string(self):
|
||||||
|
CommonTests.test_hidserv_auth_string(self)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_tor_killed_statusbar_message_shown(self):
|
||||||
|
CommonTests.test_tor_killed_statusbar_message_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
148
tests_gui_tor/onionshare_timer_test.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": True,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_set_timeout(self):
|
||||||
|
CommonTests.test_set_timeout(self, 'share', 120)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_timeout_widget_hidden(self):
|
||||||
|
CommonTests.test_timeout_widget_hidden(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_timeout(self):
|
||||||
|
CommonTests.test_server_timed_out(self, 'share', 125000)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
185
tests_gui_tor/onionshare_tor_connection_killed_test.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
|
from onionshare import onion, strings
|
||||||
|
from onionshare_gui import *
|
||||||
|
|
||||||
|
from .commontests import CommonTests
|
||||||
|
|
||||||
|
class OnionShareGuiTest(unittest.TestCase):
|
||||||
|
'''Test the OnionShare GUI'''
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
'''Create the GUI'''
|
||||||
|
# Create our test file
|
||||||
|
testfile = open('/tmp/test.txt', 'w')
|
||||||
|
testfile.write('onionshare')
|
||||||
|
testfile.close()
|
||||||
|
common = Common()
|
||||||
|
common.define_css()
|
||||||
|
|
||||||
|
# Start the Onion
|
||||||
|
strings.load_strings(common)
|
||||||
|
|
||||||
|
testonion = onion.Onion(common)
|
||||||
|
global qtapp
|
||||||
|
qtapp = Application(common)
|
||||||
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
|
web = Web(common, False, True)
|
||||||
|
|
||||||
|
test_settings = {
|
||||||
|
"auth_password": "",
|
||||||
|
"auth_type": "no_auth",
|
||||||
|
"autoupdate_timestamp": "",
|
||||||
|
"close_after_first_download": True,
|
||||||
|
"connection_type": "bundled",
|
||||||
|
"control_port_address": "127.0.0.1",
|
||||||
|
"control_port_port": 9051,
|
||||||
|
"downloads_dir": "/tmp/OnionShare",
|
||||||
|
"hidservauth_string": "",
|
||||||
|
"no_bridges": True,
|
||||||
|
"private_key": "",
|
||||||
|
"public_mode": False,
|
||||||
|
"receive_allow_receiver_shutdown": True,
|
||||||
|
"save_private_key": False,
|
||||||
|
"shutdown_timeout": False,
|
||||||
|
"slug": "",
|
||||||
|
"socks_address": "127.0.0.1",
|
||||||
|
"socks_port": 9050,
|
||||||
|
"socket_file_path": "/var/run/tor/control",
|
||||||
|
"systray_notifications": True,
|
||||||
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
|
"tor_bridges_use_meek_lite_amazon": False,
|
||||||
|
"tor_bridges_use_custom_bridges": "",
|
||||||
|
"tor_bridges_use_obfs4": False,
|
||||||
|
"use_stealth": False,
|
||||||
|
"use_legacy_v2_onions": False,
|
||||||
|
"use_autoupdate": True,
|
||||||
|
"version": "1.3.1"
|
||||||
|
}
|
||||||
|
testsettings = '/tmp/testsettings.json'
|
||||||
|
open(testsettings, 'w').write(json.dumps(test_settings))
|
||||||
|
|
||||||
|
cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
'''Clean up after tests'''
|
||||||
|
os.remove('/tmp/test.txt')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=1)
|
||||||
|
def test_gui_loaded(self):
|
||||||
|
CommonTests.test_gui_loaded(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=2)
|
||||||
|
def test_windowTitle_seen(self):
|
||||||
|
CommonTests.test_windowTitle_seen(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=3)
|
||||||
|
def test_settings_button_is_visible(self):
|
||||||
|
CommonTests.test_settings_button_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=4)
|
||||||
|
def test_server_status_bar_is_visible(self):
|
||||||
|
CommonTests.test_server_status_bar_is_visible(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=5)
|
||||||
|
def test_file_selection_widget_has_a_file(self):
|
||||||
|
CommonTests.test_file_selection_widget_has_a_file(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=6)
|
||||||
|
def test_info_widget_shows_less(self):
|
||||||
|
CommonTests.test_info_widget_shows_less(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=7)
|
||||||
|
def test_history_is_not_visible(self):
|
||||||
|
CommonTests.test_history_is_not_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=8)
|
||||||
|
def test_click_toggle_history(self):
|
||||||
|
CommonTests.test_click_toggle_history(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=9)
|
||||||
|
def test_history_is_visible(self):
|
||||||
|
CommonTests.test_history_is_visible(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=10)
|
||||||
|
def test_deleting_only_file_hides_delete_button(self):
|
||||||
|
CommonTests.test_deleting_only_file_hides_delete_button(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=11)
|
||||||
|
def test_add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
|
CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=12)
|
||||||
|
def test_file_selection_widget_readd_files(self):
|
||||||
|
CommonTests.test_file_selection_widget_readd_files(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=13)
|
||||||
|
def test_server_working_on_start_button_pressed(self):
|
||||||
|
CommonTests.test_server_working_on_start_button_pressed(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=14)
|
||||||
|
def test_server_status_indicator_says_starting(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_starting(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=15)
|
||||||
|
def test_add_delete_buttons_hidden(self):
|
||||||
|
CommonTests.test_add_delete_buttons_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=16)
|
||||||
|
def test_settings_button_is_hidden(self):
|
||||||
|
CommonTests.test_settings_button_is_hidden(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=17)
|
||||||
|
def test_a_server_is_started(self):
|
||||||
|
CommonTests.test_a_server_is_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=18)
|
||||||
|
def test_a_web_server_is_running(self):
|
||||||
|
CommonTests.test_a_web_server_is_running(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=19)
|
||||||
|
def test_have_a_slug(self):
|
||||||
|
CommonTests.test_have_a_slug(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=20)
|
||||||
|
def test_have_an_onion(self):
|
||||||
|
CommonTests.test_have_an_onion_service(self)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=21)
|
||||||
|
def test_url_description_shown(self):
|
||||||
|
CommonTests.test_url_description_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=22)
|
||||||
|
def test_have_copy_url_button(self):
|
||||||
|
CommonTests.test_have_copy_url_button(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=23)
|
||||||
|
def test_server_status_indicator_says_started(self):
|
||||||
|
CommonTests.test_server_status_indicator_says_started(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=24)
|
||||||
|
def test_tor_killed_statusbar_message_shown(self):
|
||||||
|
CommonTests.test_tor_killed_statusbar_message_shown(self, 'share')
|
||||||
|
|
||||||
|
@pytest.mark.run(order=25)
|
||||||
|
def test_server_is_stopped(self):
|
||||||
|
CommonTests.test_server_is_stopped(self, 'share', False)
|
||||||
|
|
||||||
|
@pytest.mark.run(order=26)
|
||||||
|
def test_web_service_is_stopped(self):
|
||||||
|
CommonTests.test_web_service_is_stopped(self)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
5
tests_gui_tor/run_unit_tests.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
for test in `ls -1 | egrep ^onionshare_`; do
|
||||||
|
pytest $test -vvv || exit 1
|
||||||
|
done
|