mirror of
https://github.com/onionshare/onionshare.git
synced 2025-05-17 13:50:28 -04:00
Format all code using black
This commit is contained in:
parent
90c244ee2f
commit
3037727890
87 changed files with 4293 additions and 2374 deletions
|
@ -33,12 +33,25 @@ import fileinput, argparse, re, os, codecs, json, sys
|
||||||
def arg_parser():
|
def arg_parser():
|
||||||
desc = __doc__.strip().splitlines()[0]
|
desc = __doc__.strip().splitlines()[0]
|
||||||
p = argparse.ArgumentParser(description=desc)
|
p = argparse.ArgumentParser(description=desc)
|
||||||
p.add_argument('-d', default='.', help='onionshare directory',
|
p.add_argument(
|
||||||
metavar='ONIONSHARE_DIR', dest='onionshare_dir')
|
"-d",
|
||||||
p.add_argument('--show-all-keys', action='store_true',
|
default=".",
|
||||||
help='show translation key in source and exit'),
|
help="onionshare directory",
|
||||||
p.add_argument('-l', default='all', help='language code (default: all)',
|
metavar="ONIONSHARE_DIR",
|
||||||
metavar='LANG_CODE', dest='lang_code')
|
dest="onionshare_dir",
|
||||||
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--show-all-keys",
|
||||||
|
action="store_true",
|
||||||
|
help="show translation key in source and exit",
|
||||||
|
),
|
||||||
|
p.add_argument(
|
||||||
|
"-l",
|
||||||
|
default="all",
|
||||||
|
help="language code (default: all)",
|
||||||
|
metavar="LANG_CODE",
|
||||||
|
dest="lang_code",
|
||||||
|
)
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,27 +67,29 @@ def main():
|
||||||
|
|
||||||
dir = args.onionshare_dir
|
dir = args.onionshare_dir
|
||||||
|
|
||||||
src = files_in(dir, 'onionshare') + \
|
src = (
|
||||||
files_in(dir, 'onionshare_gui') + \
|
files_in(dir, "onionshare")
|
||||||
files_in(dir, 'onionshare_gui/mode') + \
|
+ files_in(dir, "onionshare_gui")
|
||||||
files_in(dir, 'onionshare_gui/mode/share_mode') + \
|
+ files_in(dir, "onionshare_gui/mode")
|
||||||
files_in(dir, 'onionshare_gui/mode/receive_mode') + \
|
+ files_in(dir, "onionshare_gui/mode/share_mode")
|
||||||
files_in(dir, 'onionshare_gui/mode/website_mode') + \
|
+ files_in(dir, "onionshare_gui/mode/receive_mode")
|
||||||
files_in(dir, 'install/scripts') + \
|
+ files_in(dir, "onionshare_gui/mode/website_mode")
|
||||||
files_in(dir, 'tests')
|
+ files_in(dir, "install/scripts")
|
||||||
pysrc = [p for p in src if p.endswith('.py')]
|
+ files_in(dir, "tests")
|
||||||
|
)
|
||||||
|
pysrc = [p for p in src if p.endswith(".py")]
|
||||||
|
|
||||||
lang_code = args.lang_code
|
lang_code = args.lang_code
|
||||||
|
|
||||||
translate_keys = set()
|
translate_keys = set()
|
||||||
# load translate key from python source
|
# load translate key from python source
|
||||||
for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded('utf-8')):
|
for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded("utf-8")):
|
||||||
# search `strings._('translate_key')`
|
# search `strings._('translate_key')`
|
||||||
# `strings._('translate_key', True)`
|
# `strings._('translate_key', True)`
|
||||||
m = re.findall(r'strings\._\((.*?)\)', line)
|
m = re.findall(r"strings\._\((.*?)\)", line)
|
||||||
if m:
|
if m:
|
||||||
for match in m:
|
for match in m:
|
||||||
key = match.split(',')[0].strip('''"' ''')
|
key = match.split(",")[0].strip(""""' """)
|
||||||
translate_keys.add(key)
|
translate_keys.add(key)
|
||||||
|
|
||||||
if args.show_all_keys:
|
if args.show_all_keys:
|
||||||
|
@ -82,12 +97,16 @@ def main():
|
||||||
print(k)
|
print(k)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if lang_code == 'all':
|
if lang_code == "all":
|
||||||
locale_files = [f for f in files_in(dir, 'share/locale') if f.endswith('.json')]
|
locale_files = [f for f in files_in(dir, "share/locale") if f.endswith(".json")]
|
||||||
else:
|
else:
|
||||||
locale_files = [f for f in files_in(dir, 'share/locale') if f.endswith('%s.json' % lang_code)]
|
locale_files = [
|
||||||
|
f
|
||||||
|
for f in files_in(dir, "share/locale")
|
||||||
|
if f.endswith("%s.json" % lang_code)
|
||||||
|
]
|
||||||
for locale_file in locale_files:
|
for locale_file in locale_files:
|
||||||
with codecs.open(locale_file, 'r', encoding='utf-8') as f:
|
with codecs.open(locale_file, "r", encoding="utf-8") as f:
|
||||||
trans = json.load(f)
|
trans = json.load(f)
|
||||||
# trans -> {"key1": "translate-text1", "key2": "translate-text2", ...}
|
# trans -> {"key1": "translate-text1", "key2": "translate-text2", ...}
|
||||||
locale_keys = set(trans.keys())
|
locale_keys = set(trans.keys())
|
||||||
|
@ -97,11 +116,11 @@ def main():
|
||||||
|
|
||||||
locale, ext = os.path.splitext(os.path.basename(locale_file))
|
locale, ext = os.path.splitext(os.path.basename(locale_file))
|
||||||
for k in sorted(disused):
|
for k in sorted(disused):
|
||||||
print(locale, 'disused', k)
|
print(locale, "disused", k)
|
||||||
|
|
||||||
for k in sorted(lacked):
|
for k in sorted(lacked):
|
||||||
print(locale, 'lacked', k)
|
print(locale, "lacked", k)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -34,17 +34,24 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg'
|
dmg_url = "https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg"
|
||||||
dmg_filename = 'TorBrowser-8.5.5-osx64_en-US.dmg'
|
dmg_filename = "TorBrowser-8.5.5-osx64_en-US.dmg"
|
||||||
expected_dmg_sha256 = '9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511'
|
expected_dmg_sha256 = (
|
||||||
|
"9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511"
|
||||||
|
)
|
||||||
|
|
||||||
# Build paths
|
# Build paths
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
root_path = os.path.dirname(
|
||||||
working_path = os.path.join(root_path, 'build', 'tor')
|
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'Tor Browser.app', 'Contents')
|
)
|
||||||
|
working_path = os.path.join(root_path, "build", "tor")
|
||||||
|
dmg_tor_path = os.path.join(
|
||||||
|
"/Volumes", "Tor Browser", "Tor Browser.app", "Contents"
|
||||||
|
)
|
||||||
dmg_path = os.path.join(working_path, dmg_filename)
|
dmg_path = os.path.join(working_path, dmg_filename)
|
||||||
dist_path = os.path.join(root_path, 'dist', 'OnionShare.app', 'Contents')
|
dist_path = os.path.join(root_path, "dist", "OnionShare.app", "Contents")
|
||||||
|
|
||||||
# Make sure the working folder exists
|
# Make sure the working folder exists
|
||||||
if not os.path.exists(working_path):
|
if not os.path.exists(working_path):
|
||||||
|
@ -54,10 +61,10 @@ def main():
|
||||||
if not os.path.exists(dmg_path):
|
if not os.path.exists(dmg_path):
|
||||||
print("Downloading {}".format(dmg_url))
|
print("Downloading {}".format(dmg_url))
|
||||||
r = requests.get(dmg_url)
|
r = requests.get(dmg_url)
|
||||||
open(dmg_path, 'wb').write(r.content)
|
open(dmg_path, "wb").write(r.content)
|
||||||
dmg_sha256 = hashlib.sha256(r.content).hexdigest()
|
dmg_sha256 = hashlib.sha256(r.content).hexdigest()
|
||||||
else:
|
else:
|
||||||
dmg_data = open(dmg_path, 'rb').read()
|
dmg_data = open(dmg_path, "rb").read()
|
||||||
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
|
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
|
||||||
|
|
||||||
# Compare the hash
|
# Compare the hash
|
||||||
|
@ -68,34 +75,52 @@ def main():
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Mount the dmg, copy data to the working path
|
# Mount the dmg, copy data to the working path
|
||||||
subprocess.call(['hdiutil', 'attach', dmg_path])
|
subprocess.call(["hdiutil", "attach", dmg_path])
|
||||||
|
|
||||||
# Make sure Resources/tor exists before copying files
|
# Make sure Resources/tor exists before copying files
|
||||||
if os.path.exists(os.path.join(dist_path, 'Resources', 'Tor')):
|
if os.path.exists(os.path.join(dist_path, "Resources", "Tor")):
|
||||||
shutil.rmtree(os.path.join(dist_path, 'Resources', 'Tor'))
|
shutil.rmtree(os.path.join(dist_path, "Resources", "Tor"))
|
||||||
os.makedirs(os.path.join(dist_path, 'Resources', 'Tor'))
|
os.makedirs(os.path.join(dist_path, "Resources", "Tor"))
|
||||||
if os.path.exists(os.path.join(dist_path, 'MacOS', 'Tor')):
|
if os.path.exists(os.path.join(dist_path, "MacOS", "Tor")):
|
||||||
shutil.rmtree(os.path.join(dist_path, 'MacOS', 'Tor'))
|
shutil.rmtree(os.path.join(dist_path, "MacOS", "Tor"))
|
||||||
os.makedirs(os.path.join(dist_path, 'MacOS', 'Tor'))
|
os.makedirs(os.path.join(dist_path, "MacOS", "Tor"))
|
||||||
|
|
||||||
# Modify the tor script to adjust the path
|
# Modify the tor script to adjust the path
|
||||||
tor_script = open(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'tor'), 'r').read()
|
tor_script = open(
|
||||||
tor_script = tor_script.replace('../../../MacOS/Tor', '../../MacOS/Tor')
|
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "tor"), "r"
|
||||||
open(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 'w').write(tor_script)
|
).read()
|
||||||
|
tor_script = tor_script.replace("../../../MacOS/Tor", "../../MacOS/Tor")
|
||||||
|
open(os.path.join(dist_path, "Resources", "Tor", "tor"), "w").write(tor_script)
|
||||||
|
|
||||||
# Copy into dist
|
# Copy into dist
|
||||||
shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip'))
|
shutil.copyfile(
|
||||||
shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip6'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip6'))
|
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip"),
|
||||||
os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 0o755)
|
os.path.join(dist_path, "Resources", "Tor", "geoip"),
|
||||||
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'))
|
)
|
||||||
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'))
|
shutil.copyfile(
|
||||||
os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755)
|
os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip6"),
|
||||||
|
os.path.join(dist_path, "Resources", "Tor", "geoip6"),
|
||||||
|
)
|
||||||
|
os.chmod(os.path.join(dist_path, "Resources", "Tor", "tor"), 0o755)
|
||||||
|
shutil.copyfile(
|
||||||
|
os.path.join(dmg_tor_path, "MacOS", "Tor", "tor.real"),
|
||||||
|
os.path.join(dist_path, "MacOS", "Tor", "tor.real"),
|
||||||
|
)
|
||||||
|
shutil.copyfile(
|
||||||
|
os.path.join(dmg_tor_path, "MacOS", "Tor", "libevent-2.1.6.dylib"),
|
||||||
|
os.path.join(dist_path, "MacOS", "Tor", "libevent-2.1.6.dylib"),
|
||||||
|
)
|
||||||
|
os.chmod(os.path.join(dist_path, "MacOS", "Tor", "tor.real"), 0o755)
|
||||||
# obfs4proxy binary
|
# obfs4proxy binary
|
||||||
shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'PluggableTransports', 'obfs4proxy'), os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'))
|
shutil.copyfile(
|
||||||
os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'), 0o755)
|
os.path.join(dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "obfs4proxy"),
|
||||||
|
os.path.join(dist_path, "Resources", "Tor", "obfs4proxy"),
|
||||||
|
)
|
||||||
|
os.chmod(os.path.join(dist_path, "Resources", "Tor", "obfs4proxy"), 0o755)
|
||||||
|
|
||||||
# Eject dmg
|
# Eject dmg
|
||||||
subprocess.call(['diskutil', 'eject', '/Volumes/Tor Browser'])
|
subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -32,15 +32,22 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe'
|
exe_url = "https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe"
|
||||||
exe_filename = 'torbrowser-install-8.5.5_en-US.exe'
|
exe_filename = "torbrowser-install-8.5.5_en-US.exe"
|
||||||
expected_exe_sha256 = 'a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0'
|
expected_exe_sha256 = (
|
||||||
|
"a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0"
|
||||||
|
)
|
||||||
# Build paths
|
# Build paths
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
root_path = os.path.dirname(
|
||||||
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
|
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
|
)
|
||||||
|
working_path = os.path.join(os.path.join(root_path, "build"), "tor")
|
||||||
exe_path = os.path.join(working_path, exe_filename)
|
exe_path = os.path.join(working_path, exe_filename)
|
||||||
dist_path = os.path.join(os.path.join(os.path.join(root_path, 'dist'), 'onionshare'), 'tor')
|
dist_path = os.path.join(
|
||||||
|
os.path.join(os.path.join(root_path, "dist"), "onionshare"), "tor"
|
||||||
|
)
|
||||||
|
|
||||||
# Make sure the working folder exists
|
# Make sure the working folder exists
|
||||||
if not os.path.exists(working_path):
|
if not os.path.exists(working_path):
|
||||||
|
@ -50,10 +57,10 @@ def main():
|
||||||
if not os.path.exists(exe_path):
|
if not os.path.exists(exe_path):
|
||||||
print("Downloading {}".format(exe_url))
|
print("Downloading {}".format(exe_url))
|
||||||
r = requests.get(exe_url)
|
r = requests.get(exe_url)
|
||||||
open(exe_path, 'wb').write(r.content)
|
open(exe_path, "wb").write(r.content)
|
||||||
exe_sha256 = hashlib.sha256(r.content).hexdigest()
|
exe_sha256 = hashlib.sha256(r.content).hexdigest()
|
||||||
else:
|
else:
|
||||||
exe_data = open(exe_path, 'rb').read()
|
exe_data = open(exe_path, "rb").read()
|
||||||
exe_sha256 = hashlib.sha256(exe_data).hexdigest()
|
exe_sha256 = hashlib.sha256(exe_data).hexdigest()
|
||||||
|
|
||||||
# Compare the hash
|
# Compare the hash
|
||||||
|
@ -64,8 +71,22 @@ def main():
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
# Extract the bits we need from the exe
|
# Extract the bits we need from the exe
|
||||||
cmd = ['7z', 'e', '-y', exe_path, 'Browser\TorBrowser\Tor', '-o%s' % os.path.join(working_path, 'Tor')]
|
cmd = [
|
||||||
cmd2 = ['7z', 'e', '-y', exe_path, 'Browser\TorBrowser\Data\Tor\geoip*', '-o%s' % os.path.join(working_path, 'Data')]
|
"7z",
|
||||||
|
"e",
|
||||||
|
"-y",
|
||||||
|
exe_path,
|
||||||
|
"Browser\TorBrowser\Tor",
|
||||||
|
"-o%s" % os.path.join(working_path, "Tor"),
|
||||||
|
]
|
||||||
|
cmd2 = [
|
||||||
|
"7z",
|
||||||
|
"e",
|
||||||
|
"-y",
|
||||||
|
exe_path,
|
||||||
|
"Browser\TorBrowser\Data\Tor\geoip*",
|
||||||
|
"-o%s" % os.path.join(working_path, "Data"),
|
||||||
|
]
|
||||||
subprocess.Popen(cmd).wait()
|
subprocess.Popen(cmd).wait()
|
||||||
subprocess.Popen(cmd2).wait()
|
subprocess.Popen(cmd2).wait()
|
||||||
|
|
||||||
|
@ -73,8 +94,11 @@ def main():
|
||||||
if os.path.exists(dist_path):
|
if os.path.exists(dist_path):
|
||||||
shutil.rmtree(dist_path)
|
shutil.rmtree(dist_path)
|
||||||
os.makedirs(dist_path)
|
os.makedirs(dist_path)
|
||||||
shutil.copytree( os.path.join(working_path, 'Tor'), os.path.join(dist_path, 'Tor') )
|
shutil.copytree(os.path.join(working_path, "Tor"), os.path.join(dist_path, "Tor"))
|
||||||
shutil.copytree( os.path.join(working_path, 'Data'), os.path.join(dist_path, 'Data', 'Tor') )
|
shutil.copytree(
|
||||||
|
os.path.join(working_path, "Data"), os.path.join(dist_path, "Data", "Tor")
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -5,7 +5,8 @@ import locale
|
||||||
import subprocess
|
import subprocess
|
||||||
import urllib
|
import urllib
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Nautilus', '3.0')
|
|
||||||
|
gi.require_version("Nautilus", "3.0")
|
||||||
|
|
||||||
from gi.repository import Nautilus
|
from gi.repository import Nautilus
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
@ -15,12 +16,12 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Get the localized string for "Share via OnionShare" label
|
# Get the localized string for "Share via OnionShare" label
|
||||||
self.label = None
|
self.label = None
|
||||||
default_label = 'Share via OnionShare'
|
default_label = "Share via OnionShare"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Re-implement localization in python2
|
# Re-implement localization in python2
|
||||||
default_locale = 'en'
|
default_locale = "en"
|
||||||
locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale')
|
locale_dir = os.path.join(sys.prefix, "share/onionshare/locale")
|
||||||
if os.path.exists(locale_dir):
|
if os.path.exists(locale_dir):
|
||||||
# Load all translations
|
# Load all translations
|
||||||
strings = {}
|
strings = {}
|
||||||
|
@ -28,7 +29,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||||
for filename in os.listdir(locale_dir):
|
for filename in os.listdir(locale_dir):
|
||||||
abs_filename = os.path.join(locale_dir, filename)
|
abs_filename = os.path.join(locale_dir, filename)
|
||||||
lang, ext = os.path.splitext(filename)
|
lang, ext = os.path.splitext(filename)
|
||||||
if ext == '.json':
|
if ext == ".json":
|
||||||
with open(abs_filename) as f:
|
with open(abs_filename) as f:
|
||||||
translations[lang] = json.load(f)
|
translations[lang] = json.load(f)
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||||
if key in translations[lang]:
|
if key in translations[lang]:
|
||||||
strings[key] = translations[lang][key]
|
strings[key] = translations[lang][key]
|
||||||
|
|
||||||
self.label = strings['share_via_onionshare']
|
self.label = strings["share_via_onionshare"]
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.label = default_label
|
self.label = default_label
|
||||||
|
@ -74,19 +75,18 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||||
# (nautilus:3090): Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported
|
# (nautilus:3090): Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported
|
||||||
# sys.argv = ["", "--filenames"] + filenames
|
# sys.argv = ["", "--filenames"] + filenames
|
||||||
# sys.exit(onionshare_gui.main())
|
# sys.exit(onionshare_gui.main())
|
||||||
path = os.path.join(os.sep, 'usr', 'bin', 'onionshare-gui')
|
path = os.path.join(os.sep, "usr", "bin", "onionshare-gui")
|
||||||
cmd = [path, "--filenames"] + filenames
|
cmd = [path, "--filenames"] + filenames
|
||||||
subprocess.Popen(cmd)
|
subprocess.Popen(cmd)
|
||||||
|
|
||||||
def get_file_items(self, window, files):
|
def get_file_items(self, window, files):
|
||||||
menuitem = Nautilus.MenuItem(name='OnionShare::Nautilus',
|
menuitem = Nautilus.MenuItem(
|
||||||
label=self.label,
|
name="OnionShare::Nautilus", label=self.label, tip="", icon=""
|
||||||
tip='',
|
)
|
||||||
icon='')
|
|
||||||
menu = Nautilus.Menu()
|
menu = Nautilus.Menu()
|
||||||
menu.append_item(menuitem)
|
menu.append_item(menuitem)
|
||||||
menuitem.connect("activate", self.menu_activate_cb, files)
|
menuitem.connect("activate", self.menu_activate_cb, files)
|
||||||
return menuitem,
|
return (menuitem,)
|
||||||
|
|
||||||
def menu_activate_cb(self, menu, files):
|
def menu_activate_cb(self, menu, files):
|
||||||
file_list = []
|
file_list = []
|
||||||
|
|
|
@ -30,10 +30,10 @@ from .onionshare import OnionShare
|
||||||
|
|
||||||
def build_url(common, app, web):
|
def build_url(common, app, web):
|
||||||
# Build the URL
|
# Build the URL
|
||||||
if common.settings.get('public_mode'):
|
if common.settings.get("public_mode"):
|
||||||
return 'http://{0:s}'.format(app.onion_host)
|
return "http://{0:s}".format(app.onion_host)
|
||||||
else:
|
else:
|
||||||
return 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host)
|
return "http://onionshare:{0:s}@{1:s}".format(web.password, app.onion_host)
|
||||||
|
|
||||||
|
|
||||||
def main(cwd=None):
|
def main(cwd=None):
|
||||||
|
@ -47,23 +47,84 @@ def main(cwd=None):
|
||||||
print("OnionShare {0:s} | https://onionshare.org/".format(common.version))
|
print("OnionShare {0:s} | https://onionshare.org/".format(common.version))
|
||||||
|
|
||||||
# OnionShare CLI in OSX needs to change current working directory (#132)
|
# OnionShare CLI in OSX needs to change current working directory (#132)
|
||||||
if common.platform == 'Darwin':
|
if common.platform == "Darwin":
|
||||||
if cwd:
|
if cwd:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28))
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)")
|
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=28)
|
||||||
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help="Continue sharing after files have been sent")
|
)
|
||||||
parser.add_argument('--auto-start-timer', metavar='<int>', dest='autostart_timer', default=0, help="Schedule this share to start N seconds from now")
|
parser.add_argument(
|
||||||
parser.add_argument('--auto-stop-timer', metavar='<int>', dest='autostop_timer', default=0, help="Stop sharing after a given amount of seconds")
|
"--local-only",
|
||||||
parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)")
|
action="store_true",
|
||||||
parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)")
|
dest="local_only",
|
||||||
parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them")
|
help="Don't use Tor (only for development)",
|
||||||
parser.add_argument('--website', action='store_true', dest='website', help="Publish a static website")
|
)
|
||||||
parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
|
parser.add_argument(
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
|
"--stay-open",
|
||||||
parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share")
|
action="store_true",
|
||||||
|
dest="stay_open",
|
||||||
|
help="Continue sharing after files have been sent",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--auto-start-timer",
|
||||||
|
metavar="<int>",
|
||||||
|
dest="autostart_timer",
|
||||||
|
default=0,
|
||||||
|
help="Schedule this share to start N seconds from now",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--auto-stop-timer",
|
||||||
|
metavar="<int>",
|
||||||
|
dest="autostop_timer",
|
||||||
|
default=0,
|
||||||
|
help="Stop sharing after a given amount of seconds",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--connect-timeout",
|
||||||
|
metavar="<int>",
|
||||||
|
dest="connect_timeout",
|
||||||
|
default=120,
|
||||||
|
help="Give up connecting to Tor after a given amount of seconds (default: 120)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stealth",
|
||||||
|
action="store_true",
|
||||||
|
dest="stealth",
|
||||||
|
help="Use client authorization (advanced)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--receive",
|
||||||
|
action="store_true",
|
||||||
|
dest="receive",
|
||||||
|
help="Receive shares instead of sending them",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--website",
|
||||||
|
action="store_true",
|
||||||
|
dest="website",
|
||||||
|
help="Publish a static website",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
metavar="config",
|
||||||
|
default=False,
|
||||||
|
help="Custom JSON config file location (optional)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v",
|
||||||
|
"--verbose",
|
||||||
|
action="store_true",
|
||||||
|
dest="verbose",
|
||||||
|
help="Log OnionShare errors to stdout, and web errors to disk",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"filename",
|
||||||
|
metavar="filename",
|
||||||
|
nargs="*",
|
||||||
|
help="List of files or folders to share",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
filenames = args.filename
|
filenames = args.filename
|
||||||
|
@ -82,14 +143,14 @@ def main(cwd=None):
|
||||||
config = args.config
|
config = args.config
|
||||||
|
|
||||||
if receive:
|
if receive:
|
||||||
mode = 'receive'
|
mode = "receive"
|
||||||
elif website:
|
elif website:
|
||||||
mode = 'website'
|
mode = "website"
|
||||||
else:
|
else:
|
||||||
mode = 'share'
|
mode = "share"
|
||||||
|
|
||||||
# In share an website mode, you must supply a list of filenames
|
# In share an website mode, you must supply a list of filenames
|
||||||
if mode == 'share' or mode == 'website':
|
if mode == "share" or mode == "website":
|
||||||
# Make sure filenames given if not using receiver mode
|
# Make sure filenames given if not using receiver mode
|
||||||
if len(filenames) == 0:
|
if len(filenames) == 0:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
@ -122,7 +183,9 @@ def main(cwd=None):
|
||||||
# Start the Onion object
|
# Start the Onion object
|
||||||
onion = Onion(common)
|
onion = Onion(common)
|
||||||
try:
|
try:
|
||||||
onion.connect(custom_settings=False, config=config, connect_timeout=connect_timeout)
|
onion.connect(
|
||||||
|
custom_settings=False, config=config, connect_timeout=connect_timeout
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("")
|
print("")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
@ -132,8 +195,8 @@ def main(cwd=None):
|
||||||
# Start the onionshare app
|
# Start the onionshare app
|
||||||
try:
|
try:
|
||||||
common.settings.load()
|
common.settings.load()
|
||||||
if not common.settings.get('public_mode'):
|
if not common.settings.get("public_mode"):
|
||||||
web.generate_password(common.settings.get('password'))
|
web.generate_password(common.settings.get("password"))
|
||||||
else:
|
else:
|
||||||
web.password = None
|
web.password = None
|
||||||
app = OnionShare(common, onion, local_only, autostop_timer)
|
app = OnionShare(common, onion, local_only, autostop_timer)
|
||||||
|
@ -144,30 +207,54 @@ def main(cwd=None):
|
||||||
if autostart_timer > 0:
|
if autostart_timer > 0:
|
||||||
# Can't set a schedule that is later than the auto-stop timer
|
# Can't set a schedule that is later than the auto-stop timer
|
||||||
if app.autostop_timer > 0 and app.autostop_timer < autostart_timer:
|
if app.autostop_timer > 0 and app.autostop_timer < autostart_timer:
|
||||||
print("The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing.")
|
print(
|
||||||
|
"The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing."
|
||||||
|
)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
app.start_onion_service(False, True)
|
app.start_onion_service(False, True)
|
||||||
url = build_url(common, app, web)
|
url = build_url(common, app, web)
|
||||||
schedule = datetime.now() + timedelta(seconds=autostart_timer)
|
schedule = datetime.now() + timedelta(seconds=autostart_timer)
|
||||||
if mode == 'receive':
|
if mode == "receive":
|
||||||
print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir')))
|
print(
|
||||||
print('')
|
"Files sent to you appear in this folder: {}".format(
|
||||||
print("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.")
|
common.settings.get("data_dir")
|
||||||
print('')
|
)
|
||||||
|
)
|
||||||
|
print("")
|
||||||
|
print(
|
||||||
|
"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."
|
||||||
|
)
|
||||||
|
print("")
|
||||||
if stealth:
|
if stealth:
|
||||||
print("Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y")))
|
print(
|
||||||
|
"Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}".format(
|
||||||
|
schedule.strftime("%I:%M:%S%p, %b %d, %y")
|
||||||
|
)
|
||||||
|
)
|
||||||
print(app.auth_string)
|
print(app.auth_string)
|
||||||
else:
|
else:
|
||||||
print("Give this address to your sender, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y")))
|
print(
|
||||||
|
"Give this address to your sender, and tell them it won't be accessible until: {}".format(
|
||||||
|
schedule.strftime("%I:%M:%S%p, %b %d, %y")
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if stealth:
|
if stealth:
|
||||||
print("Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y")))
|
print(
|
||||||
|
"Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}".format(
|
||||||
|
schedule.strftime("%I:%M:%S%p, %b %d, %y")
|
||||||
|
)
|
||||||
|
)
|
||||||
print(app.auth_string)
|
print(app.auth_string)
|
||||||
else:
|
else:
|
||||||
print("Give this address to your recipient, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y")))
|
print(
|
||||||
|
"Give this address to your recipient, and tell them it won't be accessible until: {}".format(
|
||||||
|
schedule.strftime("%I:%M:%S%p, %b %d, %y")
|
||||||
|
)
|
||||||
|
)
|
||||||
print(url)
|
print(url)
|
||||||
print('')
|
print("")
|
||||||
print("Waiting for the scheduled time before starting...")
|
print("Waiting for the scheduled time before starting...")
|
||||||
app.onion.cleanup(False)
|
app.onion.cleanup(False)
|
||||||
time.sleep(autostart_timer)
|
time.sleep(autostart_timer)
|
||||||
|
@ -182,7 +269,7 @@ def main(cwd=None):
|
||||||
print(e.args[0])
|
print(e.args[0])
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if mode == 'website':
|
if mode == "website":
|
||||||
# Prepare files to share
|
# Prepare files to share
|
||||||
try:
|
try:
|
||||||
web.website_mode.set_file_info(filenames)
|
web.website_mode.set_file_info(filenames)
|
||||||
|
@ -190,7 +277,7 @@ def main(cwd=None):
|
||||||
print(e.strerror)
|
print(e.strerror)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if mode == 'share':
|
if mode == "share":
|
||||||
# Prepare files to share
|
# Prepare files to share
|
||||||
print("Compressing files.")
|
print("Compressing files.")
|
||||||
try:
|
try:
|
||||||
|
@ -202,12 +289,15 @@ def main(cwd=None):
|
||||||
|
|
||||||
# Warn about sending large files over Tor
|
# Warn about sending large files over Tor
|
||||||
if web.share_mode.download_filesize >= 157286400: # 150mb
|
if web.share_mode.download_filesize >= 157286400: # 150mb
|
||||||
print('')
|
print("")
|
||||||
print("Warning: Sending a large share could take hours")
|
print("Warning: Sending a large share could take hours")
|
||||||
print('')
|
print("")
|
||||||
|
|
||||||
# Start OnionShare http service in new thread
|
# Start OnionShare http service in new thread
|
||||||
t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.password))
|
t = threading.Thread(
|
||||||
|
target=web.start,
|
||||||
|
args=(app.port, stay_open, common.settings.get("public_mode"), web.password),
|
||||||
|
)
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
@ -220,23 +310,29 @@ def main(cwd=None):
|
||||||
app.autostop_timer_thread.start()
|
app.autostop_timer_thread.start()
|
||||||
|
|
||||||
# Save the web password if we are using a persistent private key
|
# Save the web password if we are using a persistent private key
|
||||||
if common.settings.get('save_private_key'):
|
if common.settings.get("save_private_key"):
|
||||||
if not common.settings.get('password'):
|
if not common.settings.get("password"):
|
||||||
common.settings.set('password', web.password)
|
common.settings.set("password", web.password)
|
||||||
common.settings.save()
|
common.settings.save()
|
||||||
|
|
||||||
# Build the URL
|
# Build the URL
|
||||||
url = build_url(common, app, web)
|
url = build_url(common, app, web)
|
||||||
|
|
||||||
print('')
|
print("")
|
||||||
if autostart_timer > 0:
|
if autostart_timer > 0:
|
||||||
print("Server started")
|
print("Server started")
|
||||||
else:
|
else:
|
||||||
if mode == 'receive':
|
if mode == "receive":
|
||||||
print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir')))
|
print(
|
||||||
print('')
|
"Files sent to you appear in this folder: {}".format(
|
||||||
print("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.")
|
common.settings.get("data_dir")
|
||||||
print('')
|
)
|
||||||
|
)
|
||||||
|
print("")
|
||||||
|
print(
|
||||||
|
"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."
|
||||||
|
)
|
||||||
|
print("")
|
||||||
|
|
||||||
if stealth:
|
if stealth:
|
||||||
print("Give this address and HidServAuth to the sender:")
|
print("Give this address and HidServAuth to the sender:")
|
||||||
|
@ -253,7 +349,7 @@ def main(cwd=None):
|
||||||
else:
|
else:
|
||||||
print("Give this address to the recipient:")
|
print("Give this address to the recipient:")
|
||||||
print(url)
|
print(url)
|
||||||
print('')
|
print("")
|
||||||
print("Press Ctrl+C to stop the server")
|
print("Press Ctrl+C to stop the server")
|
||||||
|
|
||||||
# Wait for app to close
|
# Wait for app to close
|
||||||
|
@ -261,14 +357,17 @@ def main(cwd=None):
|
||||||
if app.autostop_timer > 0:
|
if app.autostop_timer > 0:
|
||||||
# if the auto-stop timer was set and has run out, stop the server
|
# if the auto-stop timer was set and has run out, stop the server
|
||||||
if not app.autostop_timer_thread.is_alive():
|
if not app.autostop_timer_thread.is_alive():
|
||||||
if mode == 'share' or (mode == 'website'):
|
if mode == "share" or (mode == "website"):
|
||||||
# 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.share_mode.cur_history_id == 0 or web.done:
|
if web.share_mode.cur_history_id == 0 or web.done:
|
||||||
print("Stopped because auto-stop timer ran out")
|
print("Stopped because auto-stop timer ran out")
|
||||||
web.stop(app.port)
|
web.stop(app.port)
|
||||||
break
|
break
|
||||||
if mode == 'receive':
|
if mode == "receive":
|
||||||
if web.receive_mode.cur_history_id == 0 or not web.receive_mode.uploads_in_progress:
|
if (
|
||||||
|
web.receive_mode.cur_history_id == 0
|
||||||
|
or not web.receive_mode.uploads_in_progress
|
||||||
|
):
|
||||||
print("Stopped because auto-stop timer ran out")
|
print("Stopped because auto-stop timer ran out")
|
||||||
web.stop(app.port)
|
web.stop(app.port)
|
||||||
break
|
break
|
||||||
|
@ -284,5 +383,6 @@ def main(cwd=None):
|
||||||
app.cleanup()
|
app.cleanup()
|
||||||
onion.cleanup()
|
onion.cleanup()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -36,16 +36,17 @@ class Common(object):
|
||||||
"""
|
"""
|
||||||
The Common object is shared amongst all parts of OnionShare.
|
The Common object is shared amongst all parts of OnionShare.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, verbose=False):
|
def __init__(self, verbose=False):
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
|
|
||||||
# The platform OnionShare is running on
|
# The platform OnionShare is running on
|
||||||
self.platform = platform.system()
|
self.platform = platform.system()
|
||||||
if self.platform.endswith('BSD') or self.platform == 'DragonFly':
|
if self.platform.endswith("BSD") or self.platform == "DragonFly":
|
||||||
self.platform = 'BSD'
|
self.platform = "BSD"
|
||||||
|
|
||||||
# The current version of OnionShare
|
# The current version of OnionShare
|
||||||
with open(self.get_resource_path('version.txt')) as f:
|
with open(self.get_resource_path("version.txt")) as f:
|
||||||
self.version = f.read().strip()
|
self.version = f.read().strip()
|
||||||
|
|
||||||
def load_settings(self, config=None):
|
def load_settings(self, config=None):
|
||||||
|
@ -64,7 +65,7 @@ class Common(object):
|
||||||
|
|
||||||
final_msg = "[{}] {}.{}".format(timestamp, module, func)
|
final_msg = "[{}] {}.{}".format(timestamp, module, func)
|
||||||
if msg:
|
if msg:
|
||||||
final_msg = '{}: {}'.format(final_msg, msg)
|
final_msg = "{}: {}".format(final_msg, msg)
|
||||||
print(final_msg)
|
print(final_msg)
|
||||||
|
|
||||||
def get_resource_path(self, filename):
|
def get_resource_path(self, filename):
|
||||||
|
@ -73,72 +74,105 @@ class Common(object):
|
||||||
systemwide, and whether regardless of platform
|
systemwide, and whether regardless of platform
|
||||||
"""
|
"""
|
||||||
# On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
|
# On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
|
||||||
if self.platform == 'Windows':
|
if self.platform == "Windows":
|
||||||
filename = filename.replace('/', '\\')
|
filename = filename.replace("/", "\\")
|
||||||
|
|
||||||
if getattr(sys, 'onionshare_dev_mode', False):
|
if getattr(sys, "onionshare_dev_mode", False):
|
||||||
# Look for resources directory relative to python file
|
# Look for resources directory relative to python file
|
||||||
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share')
|
prefix = os.path.join(
|
||||||
|
os.path.dirname(
|
||||||
|
os.path.dirname(
|
||||||
|
os.path.abspath(inspect.getfile(inspect.currentframe()))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"share",
|
||||||
|
)
|
||||||
if not os.path.exists(prefix):
|
if not os.path.exists(prefix):
|
||||||
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
|
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
|
||||||
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
|
prefix = os.path.join(
|
||||||
|
os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(prefix)))
|
||||||
|
),
|
||||||
|
"share",
|
||||||
|
)
|
||||||
|
|
||||||
elif self.platform == 'BSD' or self.platform == 'Linux':
|
elif self.platform == "BSD" or self.platform == "Linux":
|
||||||
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
|
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
|
||||||
prefix = os.path.join(sys.prefix, 'share/onionshare')
|
prefix = os.path.join(sys.prefix, "share/onionshare")
|
||||||
|
|
||||||
elif getattr(sys, 'frozen', False):
|
elif getattr(sys, "frozen", False):
|
||||||
# Check if app is "frozen"
|
# Check if app is "frozen"
|
||||||
# https://pythonhosted.org/PyInstaller/#run-time-information
|
# https://pythonhosted.org/PyInstaller/#run-time-information
|
||||||
if self.platform == 'Darwin':
|
if self.platform == "Darwin":
|
||||||
prefix = os.path.join(sys._MEIPASS, 'share')
|
prefix = os.path.join(sys._MEIPASS, "share")
|
||||||
elif self.platform == 'Windows':
|
elif self.platform == "Windows":
|
||||||
prefix = os.path.join(os.path.dirname(sys.executable), 'share')
|
prefix = os.path.join(os.path.dirname(sys.executable), "share")
|
||||||
|
|
||||||
return os.path.join(prefix, filename)
|
return os.path.join(prefix, filename)
|
||||||
|
|
||||||
def get_tor_paths(self):
|
def get_tor_paths(self):
|
||||||
if self.platform == 'Linux':
|
if self.platform == "Linux":
|
||||||
tor_path = '/usr/bin/tor'
|
tor_path = "/usr/bin/tor"
|
||||||
tor_geo_ip_file_path = '/usr/share/tor/geoip'
|
tor_geo_ip_file_path = "/usr/share/tor/geoip"
|
||||||
tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
|
tor_geo_ipv6_file_path = "/usr/share/tor/geoip6"
|
||||||
obfs4proxy_file_path = '/usr/bin/obfs4proxy'
|
obfs4proxy_file_path = "/usr/bin/obfs4proxy"
|
||||||
elif self.platform == 'Windows':
|
elif self.platform == "Windows":
|
||||||
base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor')
|
base_path = os.path.join(
|
||||||
tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
|
os.path.dirname(os.path.dirname(self.get_resource_path(""))), "tor"
|
||||||
obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
|
)
|
||||||
tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
|
tor_path = os.path.join(os.path.join(base_path, "Tor"), "tor.exe")
|
||||||
tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
|
obfs4proxy_file_path = os.path.join(
|
||||||
elif self.platform == 'Darwin':
|
os.path.join(base_path, "Tor"), "obfs4proxy.exe"
|
||||||
base_path = os.path.dirname(os.path.dirname(os.path.dirname(self.get_resource_path(''))))
|
)
|
||||||
tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor')
|
tor_geo_ip_file_path = os.path.join(
|
||||||
tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
|
os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip"
|
||||||
tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
|
)
|
||||||
obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
|
tor_geo_ipv6_file_path = os.path.join(
|
||||||
elif self.platform == 'BSD':
|
os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip6"
|
||||||
tor_path = '/usr/local/bin/tor'
|
)
|
||||||
tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
|
elif self.platform == "Darwin":
|
||||||
tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
|
base_path = os.path.dirname(
|
||||||
obfs4proxy_file_path = '/usr/local/bin/obfs4proxy'
|
os.path.dirname(os.path.dirname(self.get_resource_path("")))
|
||||||
|
)
|
||||||
|
tor_path = os.path.join(base_path, "Resources", "Tor", "tor")
|
||||||
|
tor_geo_ip_file_path = os.path.join(base_path, "Resources", "Tor", "geoip")
|
||||||
|
tor_geo_ipv6_file_path = os.path.join(
|
||||||
|
base_path, "Resources", "Tor", "geoip6"
|
||||||
|
)
|
||||||
|
obfs4proxy_file_path = os.path.join(
|
||||||
|
base_path, "Resources", "Tor", "obfs4proxy"
|
||||||
|
)
|
||||||
|
elif self.platform == "BSD":
|
||||||
|
tor_path = "/usr/local/bin/tor"
|
||||||
|
tor_geo_ip_file_path = "/usr/local/share/tor/geoip"
|
||||||
|
tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
|
||||||
|
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
|
||||||
|
|
||||||
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
|
return (
|
||||||
|
tor_path,
|
||||||
|
tor_geo_ip_file_path,
|
||||||
|
tor_geo_ipv6_file_path,
|
||||||
|
obfs4proxy_file_path,
|
||||||
|
)
|
||||||
|
|
||||||
def build_data_dir(self):
|
def build_data_dir(self):
|
||||||
"""
|
"""
|
||||||
Returns the path of the OnionShare data directory.
|
Returns the path of the OnionShare data directory.
|
||||||
"""
|
"""
|
||||||
if self.platform == 'Windows':
|
if self.platform == "Windows":
|
||||||
try:
|
try:
|
||||||
appdata = os.environ['APPDATA']
|
appdata = os.environ["APPDATA"]
|
||||||
onionshare_data_dir = '{}\\OnionShare'.format(appdata)
|
onionshare_data_dir = "{}\\OnionShare".format(appdata)
|
||||||
except:
|
except:
|
||||||
# If for some reason we don't have the 'APPDATA' environment variable
|
# If for some reason we don't have the 'APPDATA' environment variable
|
||||||
# (like running tests in Linux while pretending to be in Windows)
|
# (like running tests in Linux while pretending to be in Windows)
|
||||||
onionshare_data_dir = os.path.expanduser('~/.config/onionshare')
|
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
||||||
elif self.platform == 'Darwin':
|
elif self.platform == "Darwin":
|
||||||
onionshare_data_dir = os.path.expanduser('~/Library/Application Support/OnionShare')
|
onionshare_data_dir = os.path.expanduser(
|
||||||
|
"~/Library/Application Support/OnionShare"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
onionshare_data_dir = os.path.expanduser('~/.config/onionshare')
|
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
||||||
|
|
||||||
os.makedirs(onionshare_data_dir, 0o700, True)
|
os.makedirs(onionshare_data_dir, 0o700, True)
|
||||||
return onionshare_data_dir
|
return onionshare_data_dir
|
||||||
|
@ -147,11 +181,11 @@ class Common(object):
|
||||||
"""
|
"""
|
||||||
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
||||||
"""
|
"""
|
||||||
with open(self.get_resource_path('wordlist.txt')) as f:
|
with open(self.get_resource_path("wordlist.txt")) as f:
|
||||||
wordlist = f.read().split()
|
wordlist = f.read().split()
|
||||||
|
|
||||||
r = random.SystemRandom()
|
r = random.SystemRandom()
|
||||||
return '-'.join(r.choice(wordlist) for _ in range(2))
|
return "-".join(r.choice(wordlist) for _ in range(2))
|
||||||
|
|
||||||
def define_css(self):
|
def define_css(self):
|
||||||
"""
|
"""
|
||||||
|
@ -160,7 +194,7 @@ class Common(object):
|
||||||
"""
|
"""
|
||||||
self.css = {
|
self.css = {
|
||||||
# OnionShareGui styles
|
# OnionShareGui styles
|
||||||
'mode_switcher_selected_style': """
|
"mode_switcher_selected_style": """
|
||||||
QPushButton {
|
QPushButton {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #4e064f;
|
background-color: #4e064f;
|
||||||
|
@ -169,8 +203,7 @@ class Common(object):
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}""",
|
}""",
|
||||||
|
"mode_switcher_unselected_style": """
|
||||||
'mode_switcher_unselected_style': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #601f61;
|
background-color: #601f61;
|
||||||
|
@ -178,23 +211,20 @@ class Common(object):
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}""",
|
}""",
|
||||||
|
"settings_button": """
|
||||||
'settings_button': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #601f61;
|
background-color: #601f61;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-left: 1px solid #69266b;
|
border-left: 1px solid #69266b;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}""",
|
}""",
|
||||||
|
"server_status_indicator_label": """
|
||||||
'server_status_indicator_label': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}""",
|
}""",
|
||||||
|
"status_bar": """
|
||||||
'status_bar': """
|
|
||||||
QStatusBar {
|
QStatusBar {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
|
@ -202,16 +232,14 @@ class Common(object):
|
||||||
QStatusBar::item {
|
QStatusBar::item {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
}""",
|
}""",
|
||||||
|
|
||||||
# Common styles between modes and their child widgets
|
# Common styles between modes and their child widgets
|
||||||
'mode_info_label': """
|
"mode_info_label": """
|
||||||
QLabel {
|
QLabel {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"server_status_url": """
|
||||||
'server_status_url': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
@ -220,14 +248,12 @@ class Common(object):
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"server_status_url_buttons": """
|
||||||
'server_status_url_buttons': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
color: #3f7fcf;
|
color: #3f7fcf;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"server_status_button_stopped": """
|
||||||
'server_status_button_stopped': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #5fa416;
|
background-color: #5fa416;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
@ -235,8 +261,7 @@ class Common(object):
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}""",
|
}""",
|
||||||
|
"server_status_button_working": """
|
||||||
'server_status_button_working': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #4c8211;
|
background-color: #4c8211;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
@ -245,8 +270,7 @@ class Common(object):
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}""",
|
}""",
|
||||||
|
"server_status_button_started": """
|
||||||
'server_status_button_started': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #d0011b;
|
background-color: #d0011b;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
@ -254,8 +278,7 @@ class Common(object):
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}""",
|
}""",
|
||||||
|
"downloads_uploads_empty": """
|
||||||
'downloads_uploads_empty': """
|
|
||||||
QWidget {
|
QWidget {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid #999999;
|
border: 1px solid #999999;
|
||||||
|
@ -265,13 +288,11 @@ class Common(object):
|
||||||
border: 0px;
|
border: 0px;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"downloads_uploads_empty_text": """
|
||||||
'downloads_uploads_empty_text': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #999999;
|
color: #999999;
|
||||||
}""",
|
}""",
|
||||||
|
"downloads_uploads_label": """
|
||||||
'downloads_uploads_label': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size 14px;
|
font-size 14px;
|
||||||
|
@ -279,14 +300,12 @@ class Common(object):
|
||||||
background-color: none;
|
background-color: none;
|
||||||
border: none;
|
border: none;
|
||||||
}""",
|
}""",
|
||||||
|
"downloads_uploads_clear": """
|
||||||
'downloads_uploads_clear': """
|
|
||||||
QPushButton {
|
QPushButton {
|
||||||
color: #3f7fcf;
|
color: #3f7fcf;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"download_uploads_indicator": """
|
||||||
'download_uploads_indicator': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #f44449;
|
background-color: #f44449;
|
||||||
|
@ -296,8 +315,7 @@ class Common(object):
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}""",
|
}""",
|
||||||
|
"downloads_uploads_progress_bar": """
|
||||||
'downloads_uploads_progress_bar': """
|
|
||||||
QProgressBar {
|
QProgressBar {
|
||||||
border: 1px solid #4e064f;
|
border: 1px solid #4e064f;
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
|
@ -309,24 +327,20 @@ class Common(object):
|
||||||
background-color: #4e064f;
|
background-color: #4e064f;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}""",
|
}""",
|
||||||
|
"history_individual_file_timestamp_label": """
|
||||||
'history_individual_file_timestamp_label': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}""",
|
}""",
|
||||||
|
"history_individual_file_status_code_label_2xx": """
|
||||||
'history_individual_file_status_code_label_2xx': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #008800;
|
color: #008800;
|
||||||
}""",
|
}""",
|
||||||
|
"history_individual_file_status_code_label_4xx": """
|
||||||
'history_individual_file_status_code_label_4xx': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #cc0000;
|
color: #cc0000;
|
||||||
}""",
|
}""",
|
||||||
|
|
||||||
# Share mode and child widget styles
|
# Share mode and child widget styles
|
||||||
'share_zip_progess_bar': """
|
"share_zip_progess_bar": """
|
||||||
QProgressBar {
|
QProgressBar {
|
||||||
border: 1px solid #4e064f;
|
border: 1px solid #4e064f;
|
||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
|
@ -338,21 +352,18 @@ class Common(object):
|
||||||
background-color: #4e064f;
|
background-color: #4e064f;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}""",
|
}""",
|
||||||
|
"share_filesize_warning": """
|
||||||
'share_filesize_warning': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"share_file_selection_drop_here_label": """
|
||||||
'share_file_selection_drop_here_label': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #999999;
|
color: #999999;
|
||||||
}""",
|
}""",
|
||||||
|
"share_file_selection_drop_count_label": """
|
||||||
'share_file_selection_drop_count_label': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #f44449;
|
background-color: #f44449;
|
||||||
|
@ -360,60 +371,51 @@ class Common(object):
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}""",
|
}""",
|
||||||
|
"share_file_list_drag_enter": """
|
||||||
'share_file_list_drag_enter': """
|
|
||||||
FileList {
|
FileList {
|
||||||
border: 3px solid #538ad0;
|
border: 3px solid #538ad0;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"share_file_list_drag_leave": """
|
||||||
'share_file_list_drag_leave': """
|
|
||||||
FileList {
|
FileList {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"share_file_list_item_size": """
|
||||||
'share_file_list_item_size': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}""",
|
}""",
|
||||||
|
|
||||||
# Receive mode and child widget styles
|
# Receive mode and child widget styles
|
||||||
'receive_file': """
|
"receive_file": """
|
||||||
QWidget {
|
QWidget {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
|
"receive_file_size": """
|
||||||
'receive_file_size': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}""",
|
}""",
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
'settings_version': """
|
"settings_version": """
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}""",
|
}""",
|
||||||
|
"settings_tor_status": """
|
||||||
'settings_tor_status': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}""",
|
}""",
|
||||||
|
"settings_whats_this": """
|
||||||
'settings_whats_this': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}""",
|
}""",
|
||||||
|
"settings_connect_to_tor": """
|
||||||
'settings_connect_to_tor': """
|
|
||||||
QLabel {
|
QLabel {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}"""
|
}""",
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -423,7 +425,7 @@ class Common(object):
|
||||||
"""
|
"""
|
||||||
b = os.urandom(num_bytes)
|
b = os.urandom(num_bytes)
|
||||||
h = hashlib.sha256(b).digest()[:16]
|
h = hashlib.sha256(b).digest()[:16]
|
||||||
s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8')
|
s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8")
|
||||||
if not output_len:
|
if not output_len:
|
||||||
return s
|
return s
|
||||||
return s[:output_len]
|
return s[:output_len]
|
||||||
|
@ -435,14 +437,14 @@ class Common(object):
|
||||||
"""
|
"""
|
||||||
thresh = 1024.0
|
thresh = 1024.0
|
||||||
if b < thresh:
|
if b < thresh:
|
||||||
return '{:.1f} B'.format(b)
|
return "{:.1f} B".format(b)
|
||||||
units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
|
units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
||||||
u = 0
|
u = 0
|
||||||
b /= thresh
|
b /= thresh
|
||||||
while b >= thresh:
|
while b >= thresh:
|
||||||
b /= thresh
|
b /= thresh
|
||||||
u += 1
|
u += 1
|
||||||
return '{:.1f} {}'.format(b, units[u])
|
return "{:.1f} {}".format(b, units[u])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_seconds(seconds):
|
def format_seconds(seconds):
|
||||||
|
@ -460,7 +462,7 @@ class Common(object):
|
||||||
human_readable.append("{:.0f}m".format(minutes))
|
human_readable.append("{:.0f}m".format(minutes))
|
||||||
if seconds or not human_readable:
|
if seconds or not human_readable:
|
||||||
human_readable.append("{:.0f}s".format(seconds))
|
human_readable.append("{:.0f}s".format(seconds))
|
||||||
return ''.join(human_readable)
|
return "".join(human_readable)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def estimated_time_remaining(bytes_downloaded, total_bytes, started):
|
def estimated_time_remaining(bytes_downloaded, total_bytes, started):
|
||||||
|
@ -504,6 +506,7 @@ class AutoStopTimer(threading.Thread):
|
||||||
"""
|
"""
|
||||||
Background thread sleeps t hours and returns.
|
Background thread sleeps t hours and returns.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, time):
|
def __init__(self, common, time):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
@ -513,6 +516,8 @@ class AutoStopTimer(threading.Thread):
|
||||||
self.time = time
|
self.time = time
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.common.log('AutoStopTimer', 'Server will shut down after {} seconds'.format(self.time))
|
self.common.log(
|
||||||
|
"AutoStopTimer", "Server will shut down after {} seconds".format(self.time)
|
||||||
|
)
|
||||||
time.sleep(self.time)
|
time.sleep(self.time)
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -28,90 +28,113 @@ from distutils.version import LooseVersion as Version
|
||||||
from . import common, strings
|
from . import common, strings
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
|
|
||||||
|
|
||||||
class TorErrorAutomatic(Exception):
|
class TorErrorAutomatic(Exception):
|
||||||
"""
|
"""
|
||||||
OnionShare is failing to connect and authenticate to the Tor controller,
|
OnionShare is failing to connect and authenticate to the Tor controller,
|
||||||
using automatic settings that should work with Tor Browser.
|
using automatic settings that should work with Tor Browser.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorInvalidSetting(Exception):
|
class TorErrorInvalidSetting(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if the settings just don't make sense.
|
This exception is raised if the settings just don't make sense.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorSocketPort(Exception):
|
class TorErrorSocketPort(Exception):
|
||||||
"""
|
"""
|
||||||
OnionShare can't connect to the Tor controller using the supplied address and port.
|
OnionShare can't connect to the Tor controller using the supplied address and port.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorSocketFile(Exception):
|
class TorErrorSocketFile(Exception):
|
||||||
"""
|
"""
|
||||||
OnionShare can't connect to the Tor controller using the supplied socket file.
|
OnionShare can't connect to the Tor controller using the supplied socket file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorMissingPassword(Exception):
|
class TorErrorMissingPassword(Exception):
|
||||||
"""
|
"""
|
||||||
OnionShare connected to the Tor controller, but it requires a password.
|
OnionShare connected to the Tor controller, but it requires a password.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorUnreadableCookieFile(Exception):
|
class TorErrorUnreadableCookieFile(Exception):
|
||||||
"""
|
"""
|
||||||
OnionShare connected to the Tor controller, but your user does not have permission
|
OnionShare connected to the Tor controller, but your user does not have permission
|
||||||
to access the cookie file.
|
to access the cookie file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorAuthError(Exception):
|
class TorErrorAuthError(Exception):
|
||||||
"""
|
"""
|
||||||
OnionShare connected to the address and port, but can't authenticate. It's possible
|
OnionShare connected to the address and port, but can't authenticate. It's possible
|
||||||
that a Tor controller isn't listening on this port.
|
that a Tor controller isn't listening on this port.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorErrorProtocolError(Exception):
|
class TorErrorProtocolError(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if onionshare connects to the Tor controller, but it
|
This exception is raised if onionshare connects to the Tor controller, but it
|
||||||
isn't acting like a Tor controller (such as in Whonix).
|
isn't acting like a Tor controller (such as in Whonix).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TorTooOld(Exception):
|
class TorTooOld(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if onionshare needs to use a feature of Tor or stem
|
This exception is raised if onionshare needs to use a feature of Tor or stem
|
||||||
(like stealth ephemeral onion services) but the version you have installed
|
(like stealth ephemeral onion services) but the version you have installed
|
||||||
is too old.
|
is too old.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BundledTorNotSupported(Exception):
|
class BundledTorNotSupported(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if onionshare is set to use the bundled Tor binary,
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
||||||
but it's not supported on that platform, or in dev mode.
|
but it's not supported on that platform, or in dev mode.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BundledTorTimeout(Exception):
|
class BundledTorTimeout(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if onionshare is set to use the bundled Tor binary,
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
||||||
but Tor doesn't finish connecting promptly.
|
but Tor doesn't finish connecting promptly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BundledTorCanceled(Exception):
|
class BundledTorCanceled(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if onionshare is set to use the bundled Tor binary,
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
||||||
and the user cancels connecting to Tor
|
and the user cancels connecting to Tor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BundledTorBroken(Exception):
|
class BundledTorBroken(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised if onionshare is set to use the bundled Tor binary,
|
This exception is raised if onionshare is set to use the bundled Tor binary,
|
||||||
but the process seems to fail to run.
|
but the process seems to fail to run.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Onion(object):
|
class Onion(object):
|
||||||
"""
|
"""
|
||||||
Onion is an abstraction layer for connecting to the Tor control port and
|
Onion is an abstraction layer for connecting to the Tor control port and
|
||||||
|
@ -126,10 +149,11 @@ class Onion(object):
|
||||||
call this function and pass in a status string while connecting to tor. This
|
call this function and pass in a status string while connecting to tor. This
|
||||||
is necessary for status updates to reach the GUI.
|
is necessary for status updates to reach the GUI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common):
|
def __init__(self, common):
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('Onion', '__init__')
|
self.common.log("Onion", "__init__")
|
||||||
|
|
||||||
self.stealth = False
|
self.stealth = False
|
||||||
self.service_id = None
|
self.service_id = None
|
||||||
|
@ -137,13 +161,20 @@ class Onion(object):
|
||||||
self.scheduled_auth_cookie = None
|
self.scheduled_auth_cookie = None
|
||||||
|
|
||||||
# Is bundled tor supported?
|
# Is bundled tor supported?
|
||||||
if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
|
if (
|
||||||
|
self.common.platform == "Windows" or self.common.platform == "Darwin"
|
||||||
|
) and getattr(sys, "onionshare_dev_mode", False):
|
||||||
self.bundle_tor_supported = False
|
self.bundle_tor_supported = False
|
||||||
else:
|
else:
|
||||||
self.bundle_tor_supported = True
|
self.bundle_tor_supported = True
|
||||||
|
|
||||||
# Set the path of the tor binary, for bundled tor
|
# Set the path of the tor binary, for bundled tor
|
||||||
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
|
(
|
||||||
|
self.tor_path,
|
||||||
|
self.tor_geo_ip_file_path,
|
||||||
|
self.tor_geo_ipv6_file_path,
|
||||||
|
self.obfs4proxy_file_path,
|
||||||
|
) = self.common.get_tor_paths()
|
||||||
|
|
||||||
# The tor process
|
# The tor process
|
||||||
self.tor_proc = None
|
self.tor_proc = None
|
||||||
|
@ -154,8 +185,14 @@ class Onion(object):
|
||||||
# Start out not connected to Tor
|
# Start out not connected to Tor
|
||||||
self.connected_to_tor = False
|
self.connected_to_tor = False
|
||||||
|
|
||||||
def connect(self, custom_settings=False, config=False, tor_status_update_func=None, connect_timeout=120):
|
def connect(
|
||||||
self.common.log('Onion', 'connect')
|
self,
|
||||||
|
custom_settings=False,
|
||||||
|
config=False,
|
||||||
|
tor_status_update_func=None,
|
||||||
|
connect_timeout=120,
|
||||||
|
):
|
||||||
|
self.common.log("Onion", "connect")
|
||||||
|
|
||||||
# Either use settings that are passed in, or use them from common
|
# Either use settings that are passed in, or use them from common
|
||||||
if custom_settings:
|
if custom_settings:
|
||||||
|
@ -171,95 +208,157 @@ class Onion(object):
|
||||||
# The Tor controller
|
# The Tor controller
|
||||||
self.c = None
|
self.c = None
|
||||||
|
|
||||||
if self.settings.get('connection_type') == 'bundled':
|
if self.settings.get("connection_type") == "bundled":
|
||||||
if not self.bundle_tor_supported:
|
if not self.bundle_tor_supported:
|
||||||
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
|
raise BundledTorNotSupported(
|
||||||
|
strings._("settings_error_bundled_tor_not_supported")
|
||||||
|
)
|
||||||
|
|
||||||
# Create a torrc for this session
|
# Create a torrc for this session
|
||||||
self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir())
|
self.tor_data_directory = tempfile.TemporaryDirectory(
|
||||||
self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name))
|
dir=self.common.build_data_dir()
|
||||||
|
)
|
||||||
|
self.common.log(
|
||||||
|
"Onion",
|
||||||
|
"connect",
|
||||||
|
"tor_data_directory={}".format(self.tor_data_directory.name),
|
||||||
|
)
|
||||||
|
|
||||||
# Create the torrc
|
# Create the torrc
|
||||||
with open(self.common.get_resource_path('torrc_template')) as f:
|
with open(self.common.get_resource_path("torrc_template")) as f:
|
||||||
torrc_template = f.read()
|
torrc_template = f.read()
|
||||||
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
self.tor_cookie_auth_file = os.path.join(
|
||||||
|
self.tor_data_directory.name, "cookie"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._("no_available_port"))
|
||||||
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
self.tor_torrc = os.path.join(self.tor_data_directory.name, "torrc")
|
||||||
|
|
||||||
if self.common.platform == 'Windows' or self.common.platform == "Darwin":
|
if self.common.platform == "Windows" or self.common.platform == "Darwin":
|
||||||
# Windows doesn't support unix sockets, so it must use a network port.
|
# Windows doesn't support unix sockets, so it must use a network port.
|
||||||
# macOS can't use unix sockets either because socket filenames are limited to
|
# macOS can't use unix sockets either because socket filenames are limited to
|
||||||
# 100 chars, and the macOS sandbox forces us to put the socket file in a place
|
# 100 chars, and the macOS sandbox forces us to put the socket file in a place
|
||||||
# with a really long path.
|
# with a really long path.
|
||||||
torrc_template += 'ControlPort {{control_port}}\n'
|
torrc_template += "ControlPort {{control_port}}\n"
|
||||||
try:
|
try:
|
||||||
self.tor_control_port = self.common.get_available_port(1000, 65535)
|
self.tor_control_port = self.common.get_available_port(1000, 65535)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._("no_available_port"))
|
||||||
self.tor_control_socket = None
|
self.tor_control_socket = None
|
||||||
else:
|
else:
|
||||||
# Linux and BSD can use unix sockets
|
# Linux and BSD can use unix sockets
|
||||||
torrc_template += 'ControlSocket {{control_socket}}\n'
|
torrc_template += "ControlSocket {{control_socket}}\n"
|
||||||
self.tor_control_port = None
|
self.tor_control_port = None
|
||||||
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
|
self.tor_control_socket = os.path.join(
|
||||||
|
self.tor_data_directory.name, "control_socket"
|
||||||
|
)
|
||||||
|
|
||||||
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
|
torrc_template = torrc_template.replace(
|
||||||
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
|
"{{data_directory}}", self.tor_data_directory.name
|
||||||
torrc_template = torrc_template.replace('{{control_socket}}', str(self.tor_control_socket))
|
)
|
||||||
torrc_template = torrc_template.replace('{{cookie_auth_file}}', self.tor_cookie_auth_file)
|
torrc_template = torrc_template.replace(
|
||||||
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
|
"{{control_port}}", str(self.tor_control_port)
|
||||||
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
|
)
|
||||||
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
|
torrc_template = torrc_template.replace(
|
||||||
|
"{{control_socket}}", str(self.tor_control_socket)
|
||||||
|
)
|
||||||
|
torrc_template = torrc_template.replace(
|
||||||
|
"{{cookie_auth_file}}", self.tor_cookie_auth_file
|
||||||
|
)
|
||||||
|
torrc_template = torrc_template.replace(
|
||||||
|
"{{geo_ip_file}}", self.tor_geo_ip_file_path
|
||||||
|
)
|
||||||
|
torrc_template = torrc_template.replace(
|
||||||
|
"{{geo_ipv6_file}}", self.tor_geo_ipv6_file_path
|
||||||
|
)
|
||||||
|
torrc_template = torrc_template.replace(
|
||||||
|
"{{socks_port}}", str(self.tor_socks_port)
|
||||||
|
)
|
||||||
|
|
||||||
with open(self.tor_torrc, 'w') as f:
|
with open(self.tor_torrc, "w") as f:
|
||||||
f.write(torrc_template)
|
f.write(torrc_template)
|
||||||
|
|
||||||
# Bridge support
|
# Bridge support
|
||||||
if self.settings.get('tor_bridges_use_obfs4'):
|
if self.settings.get("tor_bridges_use_obfs4"):
|
||||||
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
|
f.write(
|
||||||
with open(self.common.get_resource_path('torrc_template-obfs4')) as o:
|
"ClientTransportPlugin obfs4 exec {}\n".format(
|
||||||
|
self.obfs4proxy_file_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with open(
|
||||||
|
self.common.get_resource_path("torrc_template-obfs4")
|
||||||
|
) as o:
|
||||||
for line in o:
|
for line in o:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
elif self.settings.get('tor_bridges_use_meek_lite_azure'):
|
elif self.settings.get("tor_bridges_use_meek_lite_azure"):
|
||||||
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
f.write(
|
||||||
with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o:
|
"ClientTransportPlugin meek_lite exec {}\n".format(
|
||||||
|
self.obfs4proxy_file_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with open(
|
||||||
|
self.common.get_resource_path("torrc_template-meek_lite_azure")
|
||||||
|
) as o:
|
||||||
for line in o:
|
for line in o:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
|
|
||||||
if self.settings.get('tor_bridges_use_custom_bridges'):
|
if self.settings.get("tor_bridges_use_custom_bridges"):
|
||||||
if 'obfs4' in self.settings.get('tor_bridges_use_custom_bridges'):
|
if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"):
|
||||||
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
|
f.write(
|
||||||
elif 'meek_lite' in self.settings.get('tor_bridges_use_custom_bridges'):
|
"ClientTransportPlugin obfs4 exec {}\n".format(
|
||||||
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
self.obfs4proxy_file_path
|
||||||
f.write(self.settings.get('tor_bridges_use_custom_bridges'))
|
)
|
||||||
f.write('\nUseBridges 1')
|
)
|
||||||
|
elif "meek_lite" in self.settings.get(
|
||||||
|
"tor_bridges_use_custom_bridges"
|
||||||
|
):
|
||||||
|
f.write(
|
||||||
|
"ClientTransportPlugin meek_lite exec {}\n".format(
|
||||||
|
self.obfs4proxy_file_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
f.write(self.settings.get("tor_bridges_use_custom_bridges"))
|
||||||
|
f.write("\nUseBridges 1")
|
||||||
|
|
||||||
# Execute a tor subprocess
|
# Execute a tor subprocess
|
||||||
start_ts = time.time()
|
start_ts = time.time()
|
||||||
if self.common.platform == 'Windows':
|
if self.common.platform == "Windows":
|
||||||
# In Windows, hide console window when opening tor.exe subprocess
|
# In Windows, hide console window when opening tor.exe subprocess
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
|
self.tor_proc = subprocess.Popen(
|
||||||
|
[self.tor_path, "-f", self.tor_torrc],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
startupinfo=startupinfo,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
self.tor_proc = subprocess.Popen(
|
||||||
|
[self.tor_path, "-f", self.tor_torrc],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for the tor controller to start
|
# Wait for the tor controller to start
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
# Connect to the controller
|
# Connect to the controller
|
||||||
try:
|
try:
|
||||||
if self.common.platform == 'Windows' or self.common.platform == "Darwin":
|
if (
|
||||||
|
self.common.platform == "Windows"
|
||||||
|
or self.common.platform == "Darwin"
|
||||||
|
):
|
||||||
self.c = Controller.from_port(port=self.tor_control_port)
|
self.c = Controller.from_port(port=self.tor_control_port)
|
||||||
self.c.authenticate()
|
self.c.authenticate()
|
||||||
else:
|
else:
|
||||||
self.c = Controller.from_socket_file(path=self.tor_control_socket)
|
self.c = Controller.from_socket_file(path=self.tor_control_socket)
|
||||||
self.c.authenticate()
|
self.c.authenticate()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise BundledTorBroken(strings._('settings_error_bundled_tor_broken').format(e.args[0]))
|
raise BundledTorBroken(
|
||||||
|
strings._("settings_error_bundled_tor_broken").format(e.args[0])
|
||||||
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -268,28 +367,39 @@ class Onion(object):
|
||||||
raise BundledTorCanceled()
|
raise BundledTorCanceled()
|
||||||
|
|
||||||
res_parts = shlex.split(res)
|
res_parts = shlex.split(res)
|
||||||
progress = res_parts[2].split('=')[1]
|
progress = res_parts[2].split("=")[1]
|
||||||
summary = res_parts[4].split('=')[1]
|
summary = res_parts[4].split("=")[1]
|
||||||
|
|
||||||
# "\033[K" clears the rest of the line
|
# "\033[K" clears the rest of the line
|
||||||
print("Connecting to the Tor network: {}% - {}{}".format(progress, summary, "\033[K"), end="\r")
|
print(
|
||||||
|
"Connecting to the Tor network: {}% - {}{}".format(
|
||||||
|
progress, summary, "\033[K"
|
||||||
|
),
|
||||||
|
end="\r",
|
||||||
|
)
|
||||||
|
|
||||||
if callable(tor_status_update_func):
|
if callable(tor_status_update_func):
|
||||||
if not tor_status_update_func(progress, summary):
|
if not tor_status_update_func(progress, summary):
|
||||||
# If the dialog was canceled, stop connecting to Tor
|
# If the dialog was canceled, stop connecting to Tor
|
||||||
self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
|
self.common.log(
|
||||||
|
"Onion",
|
||||||
|
"connect",
|
||||||
|
"tor_status_update_func returned false, canceling connecting to Tor",
|
||||||
|
)
|
||||||
print()
|
print()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if summary == 'Done':
|
if summary == "Done":
|
||||||
print("")
|
print("")
|
||||||
break
|
break
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
# If using bridges, it might take a bit longer to connect to Tor
|
# If using bridges, it might take a bit longer to connect to Tor
|
||||||
if self.settings.get('tor_bridges_use_custom_bridges') or \
|
if (
|
||||||
self.settings.get('tor_bridges_use_obfs4') or \
|
self.settings.get("tor_bridges_use_custom_bridges")
|
||||||
self.settings.get('tor_bridges_use_meek_lite_azure'):
|
or self.settings.get("tor_bridges_use_obfs4")
|
||||||
|
or self.settings.get("tor_bridges_use_meek_lite_azure")
|
||||||
|
):
|
||||||
# Only override timeout if a custom timeout has not been passed in
|
# Only override timeout if a custom timeout has not been passed in
|
||||||
if connect_timeout == 120:
|
if connect_timeout == 120:
|
||||||
connect_timeout = 150
|
connect_timeout = 150
|
||||||
|
@ -297,18 +407,20 @@ class Onion(object):
|
||||||
print("")
|
print("")
|
||||||
try:
|
try:
|
||||||
self.tor_proc.terminate()
|
self.tor_proc.terminate()
|
||||||
raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout'))
|
raise BundledTorTimeout(
|
||||||
|
strings._("settings_error_bundled_tor_timeout")
|
||||||
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif self.settings.get('connection_type') == 'automatic':
|
elif self.settings.get("connection_type") == "automatic":
|
||||||
# Automatically try to guess the right way to connect to Tor Browser
|
# Automatically try to guess the right way to connect to Tor Browser
|
||||||
|
|
||||||
# Try connecting to control port
|
# Try connecting to control port
|
||||||
found_tor = False
|
found_tor = False
|
||||||
|
|
||||||
# If the TOR_CONTROL_PORT environment variable is set, use that
|
# If the TOR_CONTROL_PORT environment variable is set, use that
|
||||||
env_port = os.environ.get('TOR_CONTROL_PORT')
|
env_port = os.environ.get("TOR_CONTROL_PORT")
|
||||||
if env_port:
|
if env_port:
|
||||||
try:
|
try:
|
||||||
self.c = Controller.from_port(port=int(env_port))
|
self.c = Controller.from_port(port=int(env_port))
|
||||||
|
@ -327,11 +439,13 @@ class Onion(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# If this still didn't work, try guessing the default socket file path
|
# If this still didn't work, try guessing the default socket file path
|
||||||
socket_file_path = ''
|
socket_file_path = ""
|
||||||
if not found_tor:
|
if not found_tor:
|
||||||
try:
|
try:
|
||||||
if self.common.platform == 'Darwin':
|
if self.common.platform == "Darwin":
|
||||||
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
|
socket_file_path = os.path.expanduser(
|
||||||
|
"~/Library/Application Support/TorBrowser-Data/Tor/control.socket"
|
||||||
|
)
|
||||||
|
|
||||||
self.c = Controller.from_socket_file(path=socket_file_path)
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
||||||
found_tor = True
|
found_tor = True
|
||||||
|
@ -342,74 +456,108 @@ class Onion(object):
|
||||||
# guessing the socket file name next
|
# guessing the socket file name next
|
||||||
if not found_tor:
|
if not found_tor:
|
||||||
try:
|
try:
|
||||||
if self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
if self.common.platform == "Linux" or self.common.platform == "BSD":
|
||||||
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
socket_file_path = "/run/user/{}/Tor/control.socket".format(
|
||||||
elif self.common.platform == 'Darwin':
|
os.geteuid()
|
||||||
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
)
|
||||||
elif self.common.platform == 'Windows':
|
elif self.common.platform == "Darwin":
|
||||||
|
socket_file_path = "/run/user/{}/Tor/control.socket".format(
|
||||||
|
os.geteuid()
|
||||||
|
)
|
||||||
|
elif self.common.platform == "Windows":
|
||||||
# Windows doesn't support unix sockets
|
# Windows doesn't support unix sockets
|
||||||
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
raise TorErrorAutomatic(strings._("settings_error_automatic"))
|
||||||
|
|
||||||
self.c = Controller.from_socket_file(path=socket_file_path)
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
raise TorErrorAutomatic(strings._("settings_error_automatic"))
|
||||||
|
|
||||||
# Try authenticating
|
# Try authenticating
|
||||||
try:
|
try:
|
||||||
self.c.authenticate()
|
self.c.authenticate()
|
||||||
except:
|
except:
|
||||||
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
raise TorErrorAutomatic(strings._("settings_error_automatic"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Use specific settings to connect to tor
|
# Use specific settings to connect to tor
|
||||||
|
|
||||||
# Try connecting
|
# Try connecting
|
||||||
try:
|
try:
|
||||||
if self.settings.get('connection_type') == 'control_port':
|
if self.settings.get("connection_type") == "control_port":
|
||||||
self.c = Controller.from_port(address=self.settings.get('control_port_address'), port=self.settings.get('control_port_port'))
|
self.c = Controller.from_port(
|
||||||
elif self.settings.get('connection_type') == 'socket_file':
|
address=self.settings.get("control_port_address"),
|
||||||
self.c = Controller.from_socket_file(path=self.settings.get('socket_file_path'))
|
port=self.settings.get("control_port_port"),
|
||||||
|
)
|
||||||
|
elif self.settings.get("connection_type") == "socket_file":
|
||||||
|
self.c = Controller.from_socket_file(
|
||||||
|
path=self.settings.get("socket_file_path")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
|
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
|
||||||
|
|
||||||
except:
|
except:
|
||||||
if self.settings.get('connection_type') == 'control_port':
|
if self.settings.get("connection_type") == "control_port":
|
||||||
raise TorErrorSocketPort(strings._("settings_error_socket_port").format(self.settings.get('control_port_address'), self.settings.get('control_port_port')))
|
raise TorErrorSocketPort(
|
||||||
|
strings._("settings_error_socket_port").format(
|
||||||
|
self.settings.get("control_port_address"),
|
||||||
|
self.settings.get("control_port_port"),
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise TorErrorSocketFile(strings._("settings_error_socket_file").format(self.settings.get('socket_file_path')))
|
raise TorErrorSocketFile(
|
||||||
|
strings._("settings_error_socket_file").format(
|
||||||
|
self.settings.get("socket_file_path")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Try authenticating
|
# Try authenticating
|
||||||
try:
|
try:
|
||||||
if self.settings.get('auth_type') == 'no_auth':
|
if self.settings.get("auth_type") == "no_auth":
|
||||||
self.c.authenticate()
|
self.c.authenticate()
|
||||||
elif self.settings.get('auth_type') == 'password':
|
elif self.settings.get("auth_type") == "password":
|
||||||
self.c.authenticate(self.settings.get('auth_password'))
|
self.c.authenticate(self.settings.get("auth_password"))
|
||||||
else:
|
else:
|
||||||
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
|
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
|
||||||
|
|
||||||
except MissingPassword:
|
except MissingPassword:
|
||||||
raise TorErrorMissingPassword(strings._('settings_error_missing_password'))
|
raise TorErrorMissingPassword(
|
||||||
|
strings._("settings_error_missing_password")
|
||||||
|
)
|
||||||
except UnreadableCookieFile:
|
except UnreadableCookieFile:
|
||||||
raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file'))
|
raise TorErrorUnreadableCookieFile(
|
||||||
|
strings._("settings_error_unreadable_cookie_file")
|
||||||
|
)
|
||||||
except AuthenticationFailure:
|
except AuthenticationFailure:
|
||||||
raise TorErrorAuthError(strings._('settings_error_auth').format(self.settings.get('control_port_address'), self.settings.get('control_port_port')))
|
raise TorErrorAuthError(
|
||||||
|
strings._("settings_error_auth").format(
|
||||||
|
self.settings.get("control_port_address"),
|
||||||
|
self.settings.get("control_port_port"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# If we made it this far, we should be connected to Tor
|
# If we made it this far, we should be connected to Tor
|
||||||
self.connected_to_tor = True
|
self.connected_to_tor = True
|
||||||
|
|
||||||
# Get the tor version
|
# Get the tor version
|
||||||
self.tor_version = self.c.get_version().version_str
|
self.tor_version = self.c.get_version().version_str
|
||||||
self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version))
|
self.common.log(
|
||||||
|
"Onion", "connect", "Connected to tor {}".format(self.tor_version)
|
||||||
|
)
|
||||||
|
|
||||||
# Do the versions of stem and tor that I'm using support ephemeral onion services?
|
# Do the versions of stem and tor that I'm using support ephemeral onion services?
|
||||||
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
|
list_ephemeral_hidden_services = getattr(
|
||||||
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and self.tor_version >= '0.2.7.1'
|
self.c, "list_ephemeral_hidden_services", None
|
||||||
|
)
|
||||||
|
self.supports_ephemeral = (
|
||||||
|
callable(list_ephemeral_hidden_services) and self.tor_version >= "0.2.7.1"
|
||||||
|
)
|
||||||
|
|
||||||
# Do the versions of stem and tor that I'm using support stealth onion services?
|
# Do the versions of stem and tor that I'm using support stealth onion services?
|
||||||
try:
|
try:
|
||||||
res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False)
|
res = self.c.create_ephemeral_hidden_service(
|
||||||
|
{1: 1}, basic_auth={"onionshare": None}, await_publication=False
|
||||||
|
)
|
||||||
tmp_service_id = res.service_id
|
tmp_service_id = res.service_id
|
||||||
self.c.remove_ephemeral_hidden_service(tmp_service_id)
|
self.c.remove_ephemeral_hidden_service(tmp_service_id)
|
||||||
self.supports_stealth = True
|
self.supports_stealth = True
|
||||||
|
@ -420,7 +568,7 @@ class Onion(object):
|
||||||
# Does this version of Tor support next-gen ('v3') onions?
|
# Does this version of Tor support next-gen ('v3') onions?
|
||||||
# Note, this is the version of Tor where this bug was fixed:
|
# Note, this is the version of Tor where this bug was fixed:
|
||||||
# https://trac.torproject.org/projects/tor/ticket/28619
|
# https://trac.torproject.org/projects/tor/ticket/28619
|
||||||
self.supports_v3_onions = self.tor_version >= Version('0.3.5.7')
|
self.supports_v3_onions = self.tor_version >= Version("0.3.5.7")
|
||||||
|
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
"""
|
"""
|
||||||
|
@ -431,13 +579,12 @@ class Onion(object):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def start_onion_service(self, port, await_publication, save_scheduled_key=False):
|
def start_onion_service(self, port, await_publication, save_scheduled_key=False):
|
||||||
"""
|
"""
|
||||||
Start a onion service on port 80, pointing to the given port, and
|
Start a onion service on port 80, pointing to the given port, and
|
||||||
return the onion hostname.
|
return the onion hostname.
|
||||||
"""
|
"""
|
||||||
self.common.log('Onion', 'start_onion_service')
|
self.common.log("Onion", "start_onion_service")
|
||||||
# Settings may have changed in the frontend but not updated in our settings object,
|
# Settings may have changed in the frontend but not updated in our settings object,
|
||||||
# such as persistence. Reload the settings now just to be sure.
|
# such as persistence. Reload the settings now just to be sure.
|
||||||
self.settings.load()
|
self.settings.load()
|
||||||
|
@ -445,27 +592,27 @@ class Onion(object):
|
||||||
self.auth_string = None
|
self.auth_string = None
|
||||||
|
|
||||||
if not self.supports_ephemeral:
|
if not self.supports_ephemeral:
|
||||||
raise TorTooOld(strings._('error_ephemeral_not_supported'))
|
raise TorTooOld(strings._("error_ephemeral_not_supported"))
|
||||||
if self.stealth and not self.supports_stealth:
|
if self.stealth and not self.supports_stealth:
|
||||||
raise TorTooOld(strings._('error_stealth_not_supported'))
|
raise TorTooOld(strings._("error_stealth_not_supported"))
|
||||||
|
|
||||||
if not save_scheduled_key:
|
if not save_scheduled_key:
|
||||||
print("Setting up onion service on port {0:d}.".format(int(port)))
|
print("Setting up onion service on port {0:d}.".format(int(port)))
|
||||||
|
|
||||||
if self.stealth:
|
if self.stealth:
|
||||||
if self.settings.get('hidservauth_string'):
|
if self.settings.get("hidservauth_string"):
|
||||||
hidservauth_string = self.settings.get('hidservauth_string').split()[2]
|
hidservauth_string = self.settings.get("hidservauth_string").split()[2]
|
||||||
basic_auth = {'onionshare':hidservauth_string}
|
basic_auth = {"onionshare": hidservauth_string}
|
||||||
else:
|
else:
|
||||||
if self.scheduled_auth_cookie:
|
if self.scheduled_auth_cookie:
|
||||||
basic_auth = {'onionshare':self.scheduled_auth_cookie}
|
basic_auth = {"onionshare": self.scheduled_auth_cookie}
|
||||||
else:
|
else:
|
||||||
basic_auth = {'onionshare':None}
|
basic_auth = {"onionshare": None}
|
||||||
else:
|
else:
|
||||||
basic_auth = None
|
basic_auth = None
|
||||||
|
|
||||||
if self.settings.get('private_key'):
|
if self.settings.get("private_key"):
|
||||||
key_content = self.settings.get('private_key')
|
key_content = self.settings.get("private_key")
|
||||||
if self.is_v2_key(key_content):
|
if self.is_v2_key(key_content):
|
||||||
key_type = "RSA1024"
|
key_type = "RSA1024"
|
||||||
else:
|
else:
|
||||||
|
@ -483,7 +630,9 @@ class Onion(object):
|
||||||
else:
|
else:
|
||||||
key_type = "NEW"
|
key_type = "NEW"
|
||||||
# Work out if we can support v3 onion services, which are preferred
|
# Work out if we can support v3 onion services, which are preferred
|
||||||
if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'):
|
if self.supports_v3_onions and not self.settings.get(
|
||||||
|
"use_legacy_v2_onions"
|
||||||
|
):
|
||||||
key_content = "ED25519-V3"
|
key_content = "ED25519-V3"
|
||||||
else:
|
else:
|
||||||
# fall back to v2 onion services
|
# fall back to v2 onion services
|
||||||
|
@ -491,31 +640,48 @@ class Onion(object):
|
||||||
|
|
||||||
# v3 onions don't yet support basic auth. Our ticket:
|
# v3 onions don't yet support basic auth. Our ticket:
|
||||||
# https://github.com/micahflee/onionshare/issues/697
|
# https://github.com/micahflee/onionshare/issues/697
|
||||||
if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'):
|
if (
|
||||||
|
key_type == "NEW"
|
||||||
|
and key_content == "ED25519-V3"
|
||||||
|
and not self.settings.get("use_legacy_v2_onions")
|
||||||
|
):
|
||||||
basic_auth = None
|
basic_auth = None
|
||||||
self.stealth = False
|
self.stealth = False
|
||||||
|
|
||||||
debug_message = 'key_type={}'.format(key_type)
|
debug_message = "key_type={}".format(key_type)
|
||||||
if key_type == "NEW":
|
if key_type == "NEW":
|
||||||
debug_message += ', key_content={}'.format(key_content)
|
debug_message += ", key_content={}".format(key_content)
|
||||||
self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message))
|
self.common.log("Onion", "start_onion_service", "{}".format(debug_message))
|
||||||
try:
|
try:
|
||||||
if basic_auth != None:
|
if basic_auth != None:
|
||||||
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content)
|
res = self.c.create_ephemeral_hidden_service(
|
||||||
|
{80: port},
|
||||||
|
await_publication=await_publication,
|
||||||
|
basic_auth=basic_auth,
|
||||||
|
key_type=key_type,
|
||||||
|
key_content=key_content,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
# if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg
|
||||||
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content)
|
res = self.c.create_ephemeral_hidden_service(
|
||||||
|
{80: port},
|
||||||
|
await_publication=await_publication,
|
||||||
|
key_type=key_type,
|
||||||
|
key_content=key_content,
|
||||||
|
)
|
||||||
|
|
||||||
except ProtocolError as e:
|
except ProtocolError as e:
|
||||||
raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0]))
|
raise TorErrorProtocolError(
|
||||||
|
strings._("error_tor_protocol_error").format(e.args[0])
|
||||||
|
)
|
||||||
|
|
||||||
self.service_id = res.service_id
|
self.service_id = res.service_id
|
||||||
onion_host = self.service_id + '.onion'
|
onion_host = self.service_id + ".onion"
|
||||||
|
|
||||||
# A new private key was generated and is in the Control port response.
|
# A new private key was generated and is in the Control port response.
|
||||||
if self.settings.get('save_private_key'):
|
if self.settings.get("save_private_key"):
|
||||||
if not self.settings.get('private_key'):
|
if not self.settings.get("private_key"):
|
||||||
self.settings.set('private_key', res.private_key)
|
self.settings.set("private_key", res.private_key)
|
||||||
|
|
||||||
# If we were scheduling a future share, register the private key for later re-use
|
# If we were scheduling a future share, register the private key for later re-use
|
||||||
if save_scheduled_key:
|
if save_scheduled_key:
|
||||||
|
@ -529,24 +695,30 @@ class Onion(object):
|
||||||
# in the first place.
|
# in the first place.
|
||||||
# If we sent the basic_auth (due to a saved hidservauth_string in the settings),
|
# If we sent the basic_auth (due to a saved hidservauth_string in the settings),
|
||||||
# there is no response here, so use the saved value from settings.
|
# there is no response here, so use the saved value from settings.
|
||||||
if self.settings.get('save_private_key'):
|
if self.settings.get("save_private_key"):
|
||||||
if self.settings.get('hidservauth_string'):
|
if self.settings.get("hidservauth_string"):
|
||||||
self.auth_string = self.settings.get('hidservauth_string')
|
self.auth_string = self.settings.get("hidservauth_string")
|
||||||
else:
|
else:
|
||||||
auth_cookie = list(res.client_auth.values())[0]
|
auth_cookie = list(res.client_auth.values())[0]
|
||||||
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
|
self.auth_string = "HidServAuth {} {}".format(
|
||||||
self.settings.set('hidservauth_string', self.auth_string)
|
onion_host, auth_cookie
|
||||||
|
)
|
||||||
|
self.settings.set("hidservauth_string", self.auth_string)
|
||||||
else:
|
else:
|
||||||
if not self.scheduled_auth_cookie:
|
if not self.scheduled_auth_cookie:
|
||||||
auth_cookie = list(res.client_auth.values())[0]
|
auth_cookie = list(res.client_auth.values())[0]
|
||||||
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
|
self.auth_string = "HidServAuth {} {}".format(
|
||||||
|
onion_host, auth_cookie
|
||||||
|
)
|
||||||
if save_scheduled_key:
|
if save_scheduled_key:
|
||||||
# Register the HidServAuth for the scheduled share
|
# Register the HidServAuth for the scheduled share
|
||||||
self.scheduled_auth_cookie = auth_cookie
|
self.scheduled_auth_cookie = auth_cookie
|
||||||
else:
|
else:
|
||||||
self.scheduled_auth_cookie = None
|
self.scheduled_auth_cookie = None
|
||||||
else:
|
else:
|
||||||
self.auth_string = 'HidServAuth {} {}'.format(onion_host, self.scheduled_auth_cookie)
|
self.auth_string = "HidServAuth {} {}".format(
|
||||||
|
onion_host, self.scheduled_auth_cookie
|
||||||
|
)
|
||||||
if not save_scheduled_key:
|
if not save_scheduled_key:
|
||||||
# We've used the scheduled share's HidServAuth. Reset it to None for future shares
|
# We've used the scheduled share's HidServAuth. Reset it to None for future shares
|
||||||
self.scheduled_auth_cookie = None
|
self.scheduled_auth_cookie = None
|
||||||
|
@ -555,23 +727,29 @@ class Onion(object):
|
||||||
self.settings.save()
|
self.settings.save()
|
||||||
return onion_host
|
return onion_host
|
||||||
else:
|
else:
|
||||||
raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown'))
|
raise TorErrorProtocolError(strings._("error_tor_protocol_error_unknown"))
|
||||||
|
|
||||||
def cleanup(self, stop_tor=True):
|
def cleanup(self, stop_tor=True):
|
||||||
"""
|
"""
|
||||||
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
|
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
|
||||||
"""
|
"""
|
||||||
self.common.log('Onion', 'cleanup')
|
self.common.log("Onion", "cleanup")
|
||||||
|
|
||||||
# Cleanup the ephemeral onion services, if we have any
|
# Cleanup the ephemeral onion services, if we have any
|
||||||
try:
|
try:
|
||||||
onions = self.c.list_ephemeral_hidden_services()
|
onions = self.c.list_ephemeral_hidden_services()
|
||||||
for onion in onions:
|
for onion in onions:
|
||||||
try:
|
try:
|
||||||
self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
|
self.common.log(
|
||||||
|
"Onion", "cleanup", "trying to remove onion {}".format(onion)
|
||||||
|
)
|
||||||
self.c.remove_ephemeral_hidden_service(onion)
|
self.c.remove_ephemeral_hidden_service(onion)
|
||||||
except:
|
except:
|
||||||
self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
|
self.common.log(
|
||||||
|
"Onion",
|
||||||
|
"cleanup",
|
||||||
|
"could not remove onion {}.. moving on anyway".format(onion),
|
||||||
|
)
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -608,14 +786,14 @@ class Onion(object):
|
||||||
"""
|
"""
|
||||||
Returns a (address, port) tuple for the Tor SOCKS port
|
Returns a (address, port) tuple for the Tor SOCKS port
|
||||||
"""
|
"""
|
||||||
self.common.log('Onion', 'get_tor_socks_port')
|
self.common.log("Onion", "get_tor_socks_port")
|
||||||
|
|
||||||
if self.settings.get('connection_type') == 'bundled':
|
if self.settings.get("connection_type") == "bundled":
|
||||||
return ('127.0.0.1', self.tor_socks_port)
|
return ("127.0.0.1", self.tor_socks_port)
|
||||||
elif self.settings.get('connection_type') == 'automatic':
|
elif self.settings.get("connection_type") == "automatic":
|
||||||
return ('127.0.0.1', 9150)
|
return ("127.0.0.1", 9150)
|
||||||
else:
|
else:
|
||||||
return (self.settings.get('socks_address'), self.settings.get('socks_port'))
|
return (self.settings.get("socks_address"), self.settings.get("socks_port"))
|
||||||
|
|
||||||
def is_v2_key(self, key):
|
def is_v2_key(self, key):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -24,15 +24,17 @@ from . import common, strings
|
||||||
from .onion import TorTooOld, TorErrorProtocolError
|
from .onion import TorTooOld, TorErrorProtocolError
|
||||||
from .common import AutoStopTimer
|
from .common import AutoStopTimer
|
||||||
|
|
||||||
|
|
||||||
class OnionShare(object):
|
class OnionShare(object):
|
||||||
"""
|
"""
|
||||||
OnionShare is the main application class. Pass in options and run
|
OnionShare is the main application class. Pass in options and run
|
||||||
start_onion_service and it will do the magic.
|
start_onion_service and it will do the magic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, onion, local_only=False, autostop_timer=0):
|
def __init__(self, common, onion, local_only=False, autostop_timer=0):
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('OnionShare', '__init__')
|
self.common.log("OnionShare", "__init__")
|
||||||
|
|
||||||
# The Onion object
|
# The Onion object
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
|
@ -54,7 +56,7 @@ class OnionShare(object):
|
||||||
self.autostop_timer_thread = None
|
self.autostop_timer_thread = None
|
||||||
|
|
||||||
def set_stealth(self, stealth):
|
def set_stealth(self, stealth):
|
||||||
self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth))
|
self.common.log("OnionShare", "set_stealth", "stealth={}".format(stealth))
|
||||||
|
|
||||||
self.stealth = stealth
|
self.stealth = stealth
|
||||||
self.onion.stealth = stealth
|
self.onion.stealth = stealth
|
||||||
|
@ -66,13 +68,13 @@ class OnionShare(object):
|
||||||
try:
|
try:
|
||||||
self.port = self.common.get_available_port(17600, 17650)
|
self.port = self.common.get_available_port(17600, 17650)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._("no_available_port"))
|
||||||
|
|
||||||
def start_onion_service(self, await_publication=True, save_scheduled_key=False):
|
def start_onion_service(self, await_publication=True, save_scheduled_key=False):
|
||||||
"""
|
"""
|
||||||
Start the onionshare onion service.
|
Start the onionshare onion service.
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShare', 'start_onion_service')
|
self.common.log("OnionShare", "start_onion_service")
|
||||||
|
|
||||||
if not self.port:
|
if not self.port:
|
||||||
self.choose_port()
|
self.choose_port()
|
||||||
|
@ -81,10 +83,12 @@ class OnionShare(object):
|
||||||
self.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer)
|
self.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer)
|
||||||
|
|
||||||
if self.local_only:
|
if self.local_only:
|
||||||
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
|
self.onion_host = "127.0.0.1:{0:d}".format(self.port)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.onion_host = self.onion.start_onion_service(self.port, await_publication, save_scheduled_key)
|
self.onion_host = self.onion.start_onion_service(
|
||||||
|
self.port, await_publication, save_scheduled_key
|
||||||
|
)
|
||||||
|
|
||||||
if self.stealth:
|
if self.stealth:
|
||||||
self.auth_string = self.onion.auth_string
|
self.auth_string = self.onion.auth_string
|
||||||
|
@ -93,7 +97,7 @@ class OnionShare(object):
|
||||||
"""
|
"""
|
||||||
Shut everything down and clean up temporary files, etc.
|
Shut everything down and clean up temporary files, etc.
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShare', 'cleanup')
|
self.common.log("OnionShare", "cleanup")
|
||||||
|
|
||||||
# Cleanup files
|
# Cleanup files
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -39,17 +39,22 @@ class Settings(object):
|
||||||
which is to attempt to connect automatically using default Tor Browser
|
which is to attempt to connect automatically using default Tor Browser
|
||||||
settings.
|
settings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, config=False):
|
def __init__(self, common, config=False):
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('Settings', '__init__')
|
self.common.log("Settings", "__init__")
|
||||||
|
|
||||||
# If a readable config file was provided, use that instead
|
# If a readable config file was provided, use that instead
|
||||||
if config:
|
if config:
|
||||||
if os.path.isfile(config):
|
if os.path.isfile(config):
|
||||||
self.filename = config
|
self.filename = config
|
||||||
else:
|
else:
|
||||||
self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location')
|
self.common.log(
|
||||||
|
"Settings",
|
||||||
|
"__init__",
|
||||||
|
"Supplied config does not exist or is unreadable. Falling back to default location",
|
||||||
|
)
|
||||||
self.filename = self.build_filename()
|
self.filename = self.build_filename()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -60,62 +65,62 @@ class Settings(object):
|
||||||
# mapped to the language name, in that language
|
# mapped to the language name, in that language
|
||||||
self.available_locales = {
|
self.available_locales = {
|
||||||
#'bn': 'বাংলা', # Bengali (commented out because not at 90% translation)
|
#'bn': 'বাংলা', # Bengali (commented out because not at 90% translation)
|
||||||
'ca': 'Català', # Catalan
|
"ca": "Català", # Catalan
|
||||||
'zh_Hant': '正體中文 (繁體)', # Traditional Chinese
|
"zh_Hant": "正體中文 (繁體)", # Traditional Chinese
|
||||||
'zh_Hans': '中文 (简体)', # Simplified Chinese
|
"zh_Hans": "中文 (简体)", # Simplified Chinese
|
||||||
'da': 'Dansk', # Danish
|
"da": "Dansk", # Danish
|
||||||
'en': 'English', # English
|
"en": "English", # English
|
||||||
'fi': 'Suomi', # Finnish
|
"fi": "Suomi", # Finnish
|
||||||
'fr': 'Français', # French
|
"fr": "Français", # French
|
||||||
'de': 'Deutsch', # German
|
"de": "Deutsch", # German
|
||||||
'el': 'Ελληνικά', # Greek
|
"el": "Ελληνικά", # Greek
|
||||||
'is': 'Íslenska', # Icelandic
|
"is": "Íslenska", # Icelandic
|
||||||
'ga': 'Gaeilge', # Irish
|
"ga": "Gaeilge", # Irish
|
||||||
'it': 'Italiano', # Italian
|
"it": "Italiano", # Italian
|
||||||
'ja': '日本語', # Japanese
|
"ja": "日本語", # Japanese
|
||||||
'nb': 'Norsk Bokmål', # Norwegian Bokmål
|
"nb": "Norsk Bokmål", # Norwegian Bokmål
|
||||||
#'fa': 'فارسی', # Persian (commented out because not at 90% translation)
|
#'fa': 'فارسی', # Persian (commented out because not at 90% translation)
|
||||||
'pl': 'Polski', # Polish
|
"pl": "Polski", # Polish
|
||||||
'pt_BR': 'Português (Brasil)', # Portuguese Brazil
|
"pt_BR": "Português (Brasil)", # Portuguese Brazil
|
||||||
'pt_PT': 'Português (Portugal)', # Portuguese Portugal
|
"pt_PT": "Português (Portugal)", # Portuguese Portugal
|
||||||
'ru': 'Русский', # Russian
|
"ru": "Русский", # Russian
|
||||||
'es': 'Español', # Spanish
|
"es": "Español", # Spanish
|
||||||
'sv': 'Svenska', # Swedish
|
"sv": "Svenska", # Swedish
|
||||||
'te': 'తెలుగు', # Telugu
|
"te": "తెలుగు", # Telugu
|
||||||
'tr': 'Türkçe', # Turkish
|
"tr": "Türkçe", # Turkish
|
||||||
'uk': 'Українська', # Ukrainian
|
"uk": "Українська", # Ukrainian
|
||||||
}
|
}
|
||||||
|
|
||||||
# These are the default settings. They will get overwritten when loading from disk
|
# These are the default settings. They will get overwritten when loading from disk
|
||||||
self.default_settings = {
|
self.default_settings = {
|
||||||
'version': self.common.version,
|
"version": self.common.version,
|
||||||
'connection_type': 'bundled',
|
"connection_type": "bundled",
|
||||||
'control_port_address': '127.0.0.1',
|
"control_port_address": "127.0.0.1",
|
||||||
'control_port_port': 9051,
|
"control_port_port": 9051,
|
||||||
'socks_address': '127.0.0.1',
|
"socks_address": "127.0.0.1",
|
||||||
'socks_port': 9050,
|
"socks_port": 9050,
|
||||||
'socket_file_path': '/var/run/tor/control',
|
"socket_file_path": "/var/run/tor/control",
|
||||||
'auth_type': 'no_auth',
|
"auth_type": "no_auth",
|
||||||
'auth_password': '',
|
"auth_password": "",
|
||||||
'close_after_first_download': True,
|
"close_after_first_download": True,
|
||||||
'autostop_timer': False,
|
"autostop_timer": False,
|
||||||
'autostart_timer': False,
|
"autostart_timer": False,
|
||||||
'use_stealth': False,
|
"use_stealth": False,
|
||||||
'use_autoupdate': True,
|
"use_autoupdate": True,
|
||||||
'autoupdate_timestamp': None,
|
"autoupdate_timestamp": None,
|
||||||
'no_bridges': True,
|
"no_bridges": True,
|
||||||
'tor_bridges_use_obfs4': False,
|
"tor_bridges_use_obfs4": False,
|
||||||
'tor_bridges_use_meek_lite_azure': False,
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
'tor_bridges_use_custom_bridges': '',
|
"tor_bridges_use_custom_bridges": "",
|
||||||
'use_legacy_v2_onions': False,
|
"use_legacy_v2_onions": False,
|
||||||
'save_private_key': False,
|
"save_private_key": False,
|
||||||
'private_key': '',
|
"private_key": "",
|
||||||
'public_mode': False,
|
"public_mode": False,
|
||||||
'password': '',
|
"password": "",
|
||||||
'hidservauth_string': '',
|
"hidservauth_string": "",
|
||||||
'data_dir': self.build_default_data_dir(),
|
"data_dir": self.build_default_data_dir(),
|
||||||
'csp_header_disabled': False,
|
"csp_header_disabled": False,
|
||||||
'locale': None # this gets defined in fill_in_defaults()
|
"locale": None, # this gets defined in fill_in_defaults()
|
||||||
}
|
}
|
||||||
self._settings = {}
|
self._settings = {}
|
||||||
self.fill_in_defaults()
|
self.fill_in_defaults()
|
||||||
|
@ -130,14 +135,14 @@ class Settings(object):
|
||||||
self._settings[key] = self.default_settings[key]
|
self._settings[key] = self.default_settings[key]
|
||||||
|
|
||||||
# Choose the default locale based on the OS preference, and fall-back to English
|
# Choose the default locale based on the OS preference, and fall-back to English
|
||||||
if self._settings['locale'] is None:
|
if self._settings["locale"] is None:
|
||||||
language_code, encoding = locale.getdefaultlocale()
|
language_code, encoding = locale.getdefaultlocale()
|
||||||
|
|
||||||
# Default to English
|
# Default to English
|
||||||
if not language_code:
|
if not language_code:
|
||||||
language_code = 'en_US'
|
language_code = "en_US"
|
||||||
|
|
||||||
if language_code == 'pt_PT' and language_code == 'pt_BR':
|
if language_code == "pt_PT" and language_code == "pt_BR":
|
||||||
# Portuguese locales include country code
|
# Portuguese locales include country code
|
||||||
default_locale = language_code
|
default_locale = language_code
|
||||||
else:
|
else:
|
||||||
|
@ -145,14 +150,14 @@ class Settings(object):
|
||||||
default_locale = language_code[:2]
|
default_locale = language_code[:2]
|
||||||
|
|
||||||
if default_locale not in self.available_locales:
|
if default_locale not in self.available_locales:
|
||||||
default_locale = 'en'
|
default_locale = "en"
|
||||||
self._settings['locale'] = default_locale
|
self._settings["locale"] = default_locale
|
||||||
|
|
||||||
def build_filename(self):
|
def build_filename(self):
|
||||||
"""
|
"""
|
||||||
Returns the path of the settings file.
|
Returns the path of the settings file.
|
||||||
"""
|
"""
|
||||||
return os.path.join(self.common.build_data_dir(), 'onionshare.json')
|
return os.path.join(self.common.build_data_dir(), "onionshare.json")
|
||||||
|
|
||||||
def build_default_data_dir(self):
|
def build_default_data_dir(self):
|
||||||
"""
|
"""
|
||||||
|
@ -163,26 +168,28 @@ class Settings(object):
|
||||||
# We can't use os.path.expanduser() in macOS because in the sandbox it
|
# We can't use os.path.expanduser() in macOS because in the sandbox it
|
||||||
# returns the path to the sandboxed homedir
|
# returns the path to the sandboxed homedir
|
||||||
real_homedir = pwd.getpwuid(os.getuid()).pw_dir
|
real_homedir = pwd.getpwuid(os.getuid()).pw_dir
|
||||||
return os.path.join(real_homedir, 'OnionShare')
|
return os.path.join(real_homedir, "OnionShare")
|
||||||
elif self.common.platform == "Windows":
|
elif self.common.platform == "Windows":
|
||||||
# On Windows, os.path.expanduser() needs to use backslash, or else it
|
# On Windows, os.path.expanduser() needs to use backslash, or else it
|
||||||
# retains the forward slash, which breaks opening the folder in explorer.
|
# retains the forward slash, which breaks opening the folder in explorer.
|
||||||
return os.path.expanduser('~\OnionShare')
|
return os.path.expanduser("~\OnionShare")
|
||||||
else:
|
else:
|
||||||
# All other OSes
|
# All other OSes
|
||||||
return os.path.expanduser('~/OnionShare')
|
return os.path.expanduser("~/OnionShare")
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
Load the settings from file.
|
Load the settings from file.
|
||||||
"""
|
"""
|
||||||
self.common.log('Settings', 'load')
|
self.common.log("Settings", "load")
|
||||||
|
|
||||||
# If the settings file exists, load it
|
# If the settings file exists, load it
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
try:
|
try:
|
||||||
self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
|
self.common.log(
|
||||||
with open(self.filename, 'r') as f:
|
"Settings", "load", "Trying to load {}".format(self.filename)
|
||||||
|
)
|
||||||
|
with open(self.filename, "r") as f:
|
||||||
self._settings = json.load(f)
|
self._settings = json.load(f)
|
||||||
self.fill_in_defaults()
|
self.fill_in_defaults()
|
||||||
except:
|
except:
|
||||||
|
@ -190,7 +197,7 @@ class Settings(object):
|
||||||
|
|
||||||
# Make sure data_dir exists
|
# Make sure data_dir exists
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.get('data_dir'), exist_ok=True)
|
os.makedirs(self.get("data_dir"), exist_ok=True)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -198,22 +205,24 @@ class Settings(object):
|
||||||
"""
|
"""
|
||||||
Save settings to file.
|
Save settings to file.
|
||||||
"""
|
"""
|
||||||
self.common.log('Settings', 'save')
|
self.common.log("Settings", "save")
|
||||||
open(self.filename, 'w').write(json.dumps(self._settings, indent=2))
|
open(self.filename, "w").write(json.dumps(self._settings, indent=2))
|
||||||
self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename))
|
self.common.log(
|
||||||
|
"Settings", "save", "Settings saved in {}".format(self.filename)
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
return self._settings[key]
|
return self._settings[key]
|
||||||
|
|
||||||
def set(self, key, val):
|
def set(self, key, val):
|
||||||
# If typecasting int values fails, fallback to default values
|
# If typecasting int values fails, fallback to default values
|
||||||
if key == 'control_port_port' or key == 'socks_port':
|
if key == "control_port_port" or key == "socks_port":
|
||||||
try:
|
try:
|
||||||
val = int(val)
|
val = int(val)
|
||||||
except:
|
except:
|
||||||
if key == 'control_port_port':
|
if key == "control_port_port":
|
||||||
val = self.default_settings['control_port_port']
|
val = self.default_settings["control_port_port"]
|
||||||
elif key == 'socks_port':
|
elif key == "socks_port":
|
||||||
val = self.default_settings['socks_port']
|
val = self.default_settings["socks_port"]
|
||||||
|
|
||||||
self._settings[key] = val
|
self._settings[key] = val
|
||||||
|
|
|
@ -35,14 +35,14 @@ def load_strings(common):
|
||||||
# Load all translations
|
# Load all translations
|
||||||
translations = {}
|
translations = {}
|
||||||
for locale in common.settings.available_locales:
|
for locale in common.settings.available_locales:
|
||||||
locale_dir = common.get_resource_path('locale')
|
locale_dir = common.get_resource_path("locale")
|
||||||
filename = os.path.join(locale_dir, "{}.json".format(locale))
|
filename = os.path.join(locale_dir, "{}.json".format(locale))
|
||||||
with open(filename, encoding='utf-8') as f:
|
with open(filename, encoding="utf-8") as f:
|
||||||
translations[locale] = json.load(f)
|
translations[locale] = json.load(f)
|
||||||
|
|
||||||
# Build strings
|
# Build strings
|
||||||
default_locale = 'en'
|
default_locale = "en"
|
||||||
current_locale = common.settings.get('locale')
|
current_locale = common.settings.get("locale")
|
||||||
strings = {}
|
strings = {}
|
||||||
for s in translations[default_locale]:
|
for s in translations[default_locale]:
|
||||||
if s in translations[current_locale] and translations[current_locale][s] != "":
|
if s in translations[current_locale] and translations[current_locale][s] != "":
|
||||||
|
@ -57,4 +57,5 @@ def translated(k):
|
||||||
"""
|
"""
|
||||||
return strings[k]
|
return strings[k]
|
||||||
|
|
||||||
|
|
||||||
_ = translated
|
_ = translated
|
||||||
|
|
|
@ -12,9 +12,10 @@ class ReceiveModeWeb:
|
||||||
"""
|
"""
|
||||||
All of the web logic for receive mode
|
All of the web logic for receive mode
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, web):
|
def __init__(self, common, web):
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log('ReceiveModeWeb', '__init__')
|
self.common.log("ReceiveModeWeb", "__init__")
|
||||||
|
|
||||||
self.web = web
|
self.web = web
|
||||||
|
|
||||||
|
@ -30,59 +31,84 @@ class ReceiveModeWeb:
|
||||||
"""
|
"""
|
||||||
The web app routes for receiving files
|
The web app routes for receiving files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@self.web.app.route("/")
|
@self.web.app.route("/")
|
||||||
def index():
|
def index():
|
||||||
history_id = self.cur_history_id
|
history_id = self.cur_history_id
|
||||||
self.cur_history_id += 1
|
self.cur_history_id += 1
|
||||||
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), {
|
self.web.add_request(
|
||||||
'id': history_id,
|
self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
|
||||||
'status_code': 200
|
"{}".format(request.path),
|
||||||
})
|
{"id": history_id, "status_code": 200},
|
||||||
|
)
|
||||||
|
|
||||||
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
||||||
r = make_response(render_template('receive.html',
|
r = make_response(
|
||||||
static_url_path=self.web.static_url_path))
|
render_template(
|
||||||
|
"receive.html", static_url_path=self.web.static_url_path
|
||||||
|
)
|
||||||
|
)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
@self.web.app.route("/upload", methods=['POST'])
|
@self.web.app.route("/upload", methods=["POST"])
|
||||||
def upload(ajax=False):
|
def upload(ajax=False):
|
||||||
"""
|
"""
|
||||||
Handle the upload files POST request, though at this point, the files have
|
Handle the upload files POST request, though at this point, the files have
|
||||||
already been uploaded and saved to their correct locations.
|
already been uploaded and saved to their correct locations.
|
||||||
"""
|
"""
|
||||||
files = request.files.getlist('file[]')
|
files = request.files.getlist("file[]")
|
||||||
filenames = []
|
filenames = []
|
||||||
for f in files:
|
for f in files:
|
||||||
if f.filename != '':
|
if f.filename != "":
|
||||||
filename = secure_filename(f.filename)
|
filename = secure_filename(f.filename)
|
||||||
filenames.append(filename)
|
filenames.append(filename)
|
||||||
local_path = os.path.join(request.receive_mode_dir, filename)
|
local_path = os.path.join(request.receive_mode_dir, filename)
|
||||||
basename = os.path.basename(local_path)
|
basename = os.path.basename(local_path)
|
||||||
|
|
||||||
# Tell the GUI the receive mode directory for this file
|
# Tell the GUI the receive mode directory for this file
|
||||||
self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, {
|
self.web.add_request(
|
||||||
'id': request.history_id,
|
self.web.REQUEST_UPLOAD_SET_DIR,
|
||||||
'filename': basename,
|
request.path,
|
||||||
'dir': request.receive_mode_dir
|
{
|
||||||
})
|
"id": request.history_id,
|
||||||
|
"filename": basename,
|
||||||
|
"dir": request.receive_mode_dir,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path))
|
self.common.log(
|
||||||
print('\n' + "Received: {}".format(local_path))
|
"ReceiveModeWeb",
|
||||||
|
"define_routes",
|
||||||
|
"/upload, uploaded {}, saving to {}".format(
|
||||||
|
f.filename, local_path
|
||||||
|
),
|
||||||
|
)
|
||||||
|
print("\n" + "Received: {}".format(local_path))
|
||||||
|
|
||||||
if request.upload_error:
|
if request.upload_error:
|
||||||
self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error')
|
self.common.log(
|
||||||
|
"ReceiveModeWeb",
|
||||||
|
"define_routes",
|
||||||
|
"/upload, there was an upload error",
|
||||||
|
)
|
||||||
|
|
||||||
self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, {
|
self.web.add_request(
|
||||||
"receive_mode_dir": request.receive_mode_dir
|
self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE,
|
||||||
})
|
request.path,
|
||||||
print("Could not create OnionShare data folder: {}".format(request.receive_mode_dir))
|
{"receive_mode_dir": request.receive_mode_dir},
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Could not create OnionShare data folder: {}".format(
|
||||||
|
request.receive_mode_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
msg = 'Error uploading, please inform the OnionShare user'
|
msg = "Error uploading, please inform the OnionShare user"
|
||||||
if ajax:
|
if ajax:
|
||||||
return json.dumps({"error_flashes": [msg]})
|
return json.dumps({"error_flashes": [msg]})
|
||||||
else:
|
else:
|
||||||
flash(msg, 'error')
|
flash(msg, "error")
|
||||||
return redirect('/')
|
return redirect("/")
|
||||||
|
|
||||||
# Note that flash strings are in English, and not translated, on purpose,
|
# Note that flash strings are in English, and not translated, on purpose,
|
||||||
# to avoid leaking the locale of the OnionShare user
|
# to avoid leaking the locale of the OnionShare user
|
||||||
|
@ -90,37 +116,45 @@ class ReceiveModeWeb:
|
||||||
info_flashes = []
|
info_flashes = []
|
||||||
|
|
||||||
if len(filenames) == 0:
|
if len(filenames) == 0:
|
||||||
msg = 'No files uploaded'
|
msg = "No files uploaded"
|
||||||
if ajax:
|
if ajax:
|
||||||
info_flashes.append(msg)
|
info_flashes.append(msg)
|
||||||
else:
|
else:
|
||||||
flash(msg, 'info')
|
flash(msg, "info")
|
||||||
else:
|
else:
|
||||||
msg = 'Sent '
|
msg = "Sent "
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
msg += '{}, '.format(filename)
|
msg += "{}, ".format(filename)
|
||||||
msg = msg.rstrip(', ')
|
msg = msg.rstrip(", ")
|
||||||
if ajax:
|
if ajax:
|
||||||
info_flashes.append(msg)
|
info_flashes.append(msg)
|
||||||
else:
|
else:
|
||||||
flash(msg, 'info')
|
flash(msg, "info")
|
||||||
|
|
||||||
if self.can_upload:
|
if self.can_upload:
|
||||||
if ajax:
|
if ajax:
|
||||||
return json.dumps({"info_flashes": info_flashes})
|
return json.dumps({"info_flashes": info_flashes})
|
||||||
else:
|
else:
|
||||||
return redirect('/')
|
return redirect("/")
|
||||||
else:
|
else:
|
||||||
if ajax:
|
if ajax:
|
||||||
return json.dumps({
|
return json.dumps(
|
||||||
"new_body": render_template('thankyou.html', static_url_path=self.web.static_url_path)
|
{
|
||||||
})
|
"new_body": render_template(
|
||||||
|
"thankyou.html",
|
||||||
|
static_url_path=self.web.static_url_path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# It was the last upload and the timer ran out
|
# It was the last upload and the timer ran out
|
||||||
r = make_response(render_template('thankyou.html'), static_url_path=self.web.static_url_path)
|
r = make_response(
|
||||||
|
render_template("thankyou.html"),
|
||||||
|
static_url_path=self.web.static_url_path,
|
||||||
|
)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
@self.web.app.route("/upload-ajax", methods=['POST'])
|
@self.web.app.route("/upload-ajax", methods=["POST"])
|
||||||
def upload_ajax_public():
|
def upload_ajax_public():
|
||||||
if not self.can_upload:
|
if not self.can_upload:
|
||||||
return self.web.error403()
|
return self.web.error403()
|
||||||
|
@ -132,13 +166,14 @@ class ReceiveModeWSGIMiddleware(object):
|
||||||
Custom WSGI middleware in order to attach the Web object to environ, so
|
Custom WSGI middleware in order to attach the Web object to environ, so
|
||||||
ReceiveModeRequest can access it.
|
ReceiveModeRequest can access it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app, web):
|
def __init__(self, app, web):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.web = web
|
self.web = web
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
environ['web'] = self.web
|
environ["web"] = self.web
|
||||||
environ['stop_q'] = self.web.stop_q
|
environ["stop_q"] = self.web.stop_q
|
||||||
return self.app(environ, start_response)
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,6 +183,7 @@ class ReceiveModeFile(object):
|
||||||
written to it, in order to track the progress of uploads. It starts out with
|
written to it, in order to track the progress of uploads. It starts out with
|
||||||
a .part file extension, and when it's complete it removes that extension.
|
a .part file extension, and when it's complete it removes that extension.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request, filename, write_func, close_func):
|
def __init__(self, request, filename, write_func, close_func):
|
||||||
self.onionshare_request = request
|
self.onionshare_request = request
|
||||||
self.onionshare_filename = filename
|
self.onionshare_filename = filename
|
||||||
|
@ -155,24 +191,44 @@ class ReceiveModeFile(object):
|
||||||
self.onionshare_close_func = close_func
|
self.onionshare_close_func = close_func
|
||||||
|
|
||||||
self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename)
|
self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename)
|
||||||
self.filename_in_progress = '{}.part'.format(self.filename)
|
self.filename_in_progress = "{}.part".format(self.filename)
|
||||||
|
|
||||||
# Open the file
|
# Open the file
|
||||||
self.upload_error = False
|
self.upload_error = False
|
||||||
try:
|
try:
|
||||||
self.f = open(self.filename_in_progress, 'wb+')
|
self.f = open(self.filename_in_progress, "wb+")
|
||||||
except:
|
except:
|
||||||
# This will only happen if someone is messing with the data dir while
|
# This will only happen if someone is messing with the data dir while
|
||||||
# OnionShare is running, but if it does make sure to throw an error
|
# OnionShare is running, but if it does make sure to throw an error
|
||||||
self.upload_error = True
|
self.upload_error = True
|
||||||
self.f = tempfile.TemporaryFile('wb+')
|
self.f = tempfile.TemporaryFile("wb+")
|
||||||
|
|
||||||
# Make all the file-like methods and attributes actually access the
|
# Make all the file-like methods and attributes actually access the
|
||||||
# TemporaryFile, except for write
|
# TemporaryFile, except for write
|
||||||
attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode',
|
attrs = [
|
||||||
'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto',
|
"closed",
|
||||||
'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell',
|
"detach",
|
||||||
'truncate', 'writable', 'writelines']
|
"fileno",
|
||||||
|
"flush",
|
||||||
|
"isatty",
|
||||||
|
"mode",
|
||||||
|
"name",
|
||||||
|
"peek",
|
||||||
|
"raw",
|
||||||
|
"read",
|
||||||
|
"read1",
|
||||||
|
"readable",
|
||||||
|
"readinto",
|
||||||
|
"readinto1",
|
||||||
|
"readline",
|
||||||
|
"readlines",
|
||||||
|
"seek",
|
||||||
|
"seekable",
|
||||||
|
"tell",
|
||||||
|
"truncate",
|
||||||
|
"writable",
|
||||||
|
"writelines",
|
||||||
|
]
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
setattr(self, attr, getattr(self.f, attr))
|
setattr(self, attr, getattr(self.f, attr))
|
||||||
|
|
||||||
|
@ -214,20 +270,21 @@ class ReceiveModeRequest(Request):
|
||||||
A custom flask Request object that keeps track of how much data has been
|
A custom flask Request object that keeps track of how much data has been
|
||||||
uploaded for each file, for receive mode.
|
uploaded for each file, for receive mode.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, environ, populate_request=True, shallow=False):
|
def __init__(self, environ, populate_request=True, shallow=False):
|
||||||
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
|
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
|
||||||
self.web = environ['web']
|
self.web = environ["web"]
|
||||||
self.stop_q = environ['stop_q']
|
self.stop_q = environ["stop_q"]
|
||||||
|
|
||||||
self.web.common.log('ReceiveModeRequest', '__init__')
|
self.web.common.log("ReceiveModeRequest", "__init__")
|
||||||
|
|
||||||
# Prevent running the close() method more than once
|
# Prevent running the close() method more than once
|
||||||
self.closed = False
|
self.closed = False
|
||||||
|
|
||||||
# Is this a valid upload request?
|
# Is this a valid upload request?
|
||||||
self.upload_request = False
|
self.upload_request = False
|
||||||
if self.method == 'POST':
|
if self.method == "POST":
|
||||||
if self.path == '/upload' or self.path == '/upload-ajax':
|
if self.path == "/upload" or self.path == "/upload-ajax":
|
||||||
self.upload_request = True
|
self.upload_request = True
|
||||||
|
|
||||||
if self.upload_request:
|
if self.upload_request:
|
||||||
|
@ -238,7 +295,9 @@ class ReceiveModeRequest(Request):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
date_dir = now.strftime("%Y-%m-%d")
|
date_dir = now.strftime("%Y-%m-%d")
|
||||||
time_dir = now.strftime("%H.%M.%S")
|
time_dir = now.strftime("%H.%M.%S")
|
||||||
self.receive_mode_dir = os.path.join(self.web.common.settings.get('data_dir'), date_dir, time_dir)
|
self.receive_mode_dir = os.path.join(
|
||||||
|
self.web.common.settings.get("data_dir"), date_dir, time_dir
|
||||||
|
)
|
||||||
|
|
||||||
# Create that directory, which shouldn't exist yet
|
# Create that directory, which shouldn't exist yet
|
||||||
try:
|
try:
|
||||||
|
@ -250,7 +309,7 @@ class ReceiveModeRequest(Request):
|
||||||
# Keep going until we find a directory name that's available
|
# Keep going until we find a directory name that's available
|
||||||
i = 1
|
i = 1
|
||||||
while True:
|
while True:
|
||||||
new_receive_mode_dir = '{}-{}'.format(self.receive_mode_dir, i)
|
new_receive_mode_dir = "{}-{}".format(self.receive_mode_dir, i)
|
||||||
try:
|
try:
|
||||||
os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False)
|
os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False)
|
||||||
self.receive_mode_dir = new_receive_mode_dir
|
self.receive_mode_dir = new_receive_mode_dir
|
||||||
|
@ -260,15 +319,29 @@ class ReceiveModeRequest(Request):
|
||||||
i += 1
|
i += 1
|
||||||
# Failsafe
|
# Failsafe
|
||||||
if i == 100:
|
if i == 100:
|
||||||
self.web.common.log('ReceiveModeRequest', '__init__', 'Error finding available receive mode directory')
|
self.web.common.log(
|
||||||
|
"ReceiveModeRequest",
|
||||||
|
"__init__",
|
||||||
|
"Error finding available receive mode directory",
|
||||||
|
)
|
||||||
self.upload_error = True
|
self.upload_error = True
|
||||||
break
|
break
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, {
|
self.web.add_request(
|
||||||
"receive_mode_dir": self.receive_mode_dir
|
self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE,
|
||||||
})
|
request.path,
|
||||||
print("Could not create OnionShare data folder: {}".format(self.receive_mode_dir))
|
{"receive_mode_dir": self.receive_mode_dir},
|
||||||
self.web.common.log('ReceiveModeRequest', '__init__', 'Permission denied creating receive mode directory')
|
)
|
||||||
|
print(
|
||||||
|
"Could not create OnionShare data folder: {}".format(
|
||||||
|
self.receive_mode_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.web.common.log(
|
||||||
|
"ReceiveModeRequest",
|
||||||
|
"__init__",
|
||||||
|
"Permission denied creating receive mode directory",
|
||||||
|
)
|
||||||
self.upload_error = True
|
self.upload_error = True
|
||||||
|
|
||||||
# If there's an error so far, finish early
|
# If there's an error so far, finish early
|
||||||
|
@ -287,21 +360,27 @@ class ReceiveModeRequest(Request):
|
||||||
|
|
||||||
# Figure out the content length
|
# Figure out the content length
|
||||||
try:
|
try:
|
||||||
self.content_length = int(self.headers['Content-Length'])
|
self.content_length = int(self.headers["Content-Length"])
|
||||||
except:
|
except:
|
||||||
self.content_length = 0
|
self.content_length = 0
|
||||||
|
|
||||||
print("{}: {}".format(
|
print(
|
||||||
|
"{}: {}".format(
|
||||||
datetime.now().strftime("%b %d, %I:%M%p"),
|
datetime.now().strftime("%b %d, %I:%M%p"),
|
||||||
strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length))
|
strings._("receive_mode_upload_starting").format(
|
||||||
))
|
self.web.common.human_readable_filesize(self.content_length)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Don't tell the GUI that a request has started until we start receiving files
|
# Don't tell the GUI that a request has started until we start receiving files
|
||||||
self.told_gui_about_request = False
|
self.told_gui_about_request = False
|
||||||
|
|
||||||
self.previous_file = None
|
self.previous_file = None
|
||||||
|
|
||||||
def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=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
|
This gets called for each file that gets uploaded, and returns an file-like
|
||||||
writable stream.
|
writable stream.
|
||||||
|
@ -309,24 +388,26 @@ class ReceiveModeRequest(Request):
|
||||||
if self.upload_request:
|
if self.upload_request:
|
||||||
if not self.told_gui_about_request:
|
if not self.told_gui_about_request:
|
||||||
# Tell the GUI about the request
|
# Tell the GUI about the request
|
||||||
self.web.add_request(self.web.REQUEST_STARTED, self.path, {
|
self.web.add_request(
|
||||||
'id': self.history_id,
|
self.web.REQUEST_STARTED,
|
||||||
'content_length': self.content_length
|
self.path,
|
||||||
})
|
{"id": self.history_id, "content_length": self.content_length},
|
||||||
|
)
|
||||||
self.web.receive_mode.uploads_in_progress.append(self.history_id)
|
self.web.receive_mode.uploads_in_progress.append(self.history_id)
|
||||||
|
|
||||||
self.told_gui_about_request = True
|
self.told_gui_about_request = True
|
||||||
|
|
||||||
self.filename = secure_filename(filename)
|
self.filename = secure_filename(filename)
|
||||||
|
|
||||||
self.progress[self.filename] = {
|
self.progress[self.filename] = {"uploaded_bytes": 0, "complete": False}
|
||||||
'uploaded_bytes': 0,
|
|
||||||
'complete': False
|
|
||||||
}
|
|
||||||
|
|
||||||
f = ReceiveModeFile(self, self.filename, self.file_write_func, self.file_close_func)
|
f = ReceiveModeFile(
|
||||||
|
self, self.filename, self.file_write_func, self.file_close_func
|
||||||
|
)
|
||||||
if f.upload_error:
|
if f.upload_error:
|
||||||
self.web.common.log('ReceiveModeRequest', '_get_file_stream', 'Error creating file')
|
self.web.common.log(
|
||||||
|
"ReceiveModeRequest", "_get_file_stream", "Error creating file"
|
||||||
|
)
|
||||||
self.upload_error = True
|
self.upload_error = True
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
@ -341,22 +422,25 @@ class ReceiveModeRequest(Request):
|
||||||
return
|
return
|
||||||
self.closed = True
|
self.closed = True
|
||||||
|
|
||||||
self.web.common.log('ReceiveModeRequest', 'close')
|
self.web.common.log("ReceiveModeRequest", "close")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.told_gui_about_request:
|
if self.told_gui_about_request:
|
||||||
history_id = self.history_id
|
history_id = self.history_id
|
||||||
|
|
||||||
if not self.web.stop_q.empty() or not self.progress[self.filename]['complete']:
|
if (
|
||||||
|
not self.web.stop_q.empty()
|
||||||
|
or not self.progress[self.filename]["complete"]
|
||||||
|
):
|
||||||
# Inform the GUI that the upload has canceled
|
# Inform the GUI that the upload has canceled
|
||||||
self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, {
|
self.web.add_request(
|
||||||
'id': history_id
|
self.web.REQUEST_UPLOAD_CANCELED, self.path, {"id": history_id}
|
||||||
})
|
)
|
||||||
else:
|
else:
|
||||||
# Inform the GUI that the upload has finished
|
# Inform the GUI that the upload has finished
|
||||||
self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, {
|
self.web.add_request(
|
||||||
'id': history_id
|
self.web.REQUEST_UPLOAD_FINISHED, self.path, {"id": history_id}
|
||||||
})
|
)
|
||||||
self.web.receive_mode.uploads_in_progress.remove(history_id)
|
self.web.receive_mode.uploads_in_progress.remove(history_id)
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -370,28 +454,34 @@ class ReceiveModeRequest(Request):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.upload_request:
|
if self.upload_request:
|
||||||
self.progress[filename]['uploaded_bytes'] += length
|
self.progress[filename]["uploaded_bytes"] += length
|
||||||
|
|
||||||
if self.previous_file != filename:
|
if self.previous_file != filename:
|
||||||
self.previous_file = filename
|
self.previous_file = filename
|
||||||
|
|
||||||
print('\r=> {:15s} {}'.format(
|
print(
|
||||||
self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']),
|
"\r=> {:15s} {}".format(
|
||||||
filename
|
self.web.common.human_readable_filesize(
|
||||||
), end='')
|
self.progress[filename]["uploaded_bytes"]
|
||||||
|
),
|
||||||
|
filename,
|
||||||
|
),
|
||||||
|
end="",
|
||||||
|
)
|
||||||
|
|
||||||
# Update the GUI on the upload progress
|
# Update the GUI on the upload progress
|
||||||
if self.told_gui_about_request:
|
if self.told_gui_about_request:
|
||||||
self.web.add_request(self.web.REQUEST_PROGRESS, self.path, {
|
self.web.add_request(
|
||||||
'id': self.history_id,
|
self.web.REQUEST_PROGRESS,
|
||||||
'progress': self.progress
|
self.path,
|
||||||
})
|
{"id": self.history_id, "progress": self.progress},
|
||||||
|
)
|
||||||
|
|
||||||
def file_close_func(self, filename, upload_error=False):
|
def file_close_func(self, filename, upload_error=False):
|
||||||
"""
|
"""
|
||||||
This function gets called when a specific file is closed.
|
This function gets called when a specific file is closed.
|
||||||
"""
|
"""
|
||||||
self.progress[filename]['complete'] = True
|
self.progress[filename]["complete"] = True
|
||||||
|
|
||||||
# If the file tells us there was an upload error, let the request know as well
|
# If the file tells us there was an upload error, let the request know as well
|
||||||
if upload_error:
|
if upload_error:
|
||||||
|
|
|
@ -12,6 +12,7 @@ class SendBaseModeWeb:
|
||||||
"""
|
"""
|
||||||
All of the web logic shared between share and website mode (modes where the user sends files)
|
All of the web logic shared between share and website mode (modes where the user sends files)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, web):
|
def __init__(self, common, web):
|
||||||
super(SendBaseModeWeb, self).__init__()
|
super(SendBaseModeWeb, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
|
@ -41,20 +42,24 @@ class SendBaseModeWeb:
|
||||||
"""
|
"""
|
||||||
# If there's just one folder, replace filenames with a list of files inside that folder
|
# If there's just one folder, replace filenames with a list of files inside that folder
|
||||||
if len(filenames) == 1 and os.path.isdir(filenames[0]):
|
if len(filenames) == 1 and os.path.isdir(filenames[0]):
|
||||||
filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])]
|
filenames = [
|
||||||
|
os.path.join(filenames[0], x) for x in os.listdir(filenames[0])
|
||||||
|
]
|
||||||
|
|
||||||
# Re-initialize
|
# Re-initialize
|
||||||
self.files = {} # Dictionary mapping file paths to filenames on disk
|
self.files = {} # Dictionary mapping file paths to filenames on disk
|
||||||
self.root_files = {} # This is only the root files and dirs, as opposed to all of them
|
self.root_files = (
|
||||||
|
{}
|
||||||
|
) # This is only the root files and dirs, as opposed to all of them
|
||||||
self.cleanup_filenames = []
|
self.cleanup_filenames = []
|
||||||
self.cur_history_id = 0
|
self.cur_history_id = 0
|
||||||
self.file_info = {'files': [], 'dirs': []}
|
self.file_info = {"files": [], "dirs": []}
|
||||||
self.gzip_individual_files = {}
|
self.gzip_individual_files = {}
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
# Build the file list
|
# Build the file list
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
basename = os.path.basename(filename.rstrip('/'))
|
basename = os.path.basename(filename.rstrip("/"))
|
||||||
|
|
||||||
# If it's a filename, add it
|
# If it's a filename, add it
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
|
@ -63,42 +68,50 @@ class SendBaseModeWeb:
|
||||||
|
|
||||||
# If it's a directory, add it recursively
|
# If it's a directory, add it recursively
|
||||||
elif os.path.isdir(filename):
|
elif os.path.isdir(filename):
|
||||||
self.root_files[basename + '/'] = filename
|
self.root_files[basename + "/"] = filename
|
||||||
|
|
||||||
for root, _, nested_filenames in os.walk(filename):
|
for root, _, nested_filenames in os.walk(filename):
|
||||||
# Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
|
# Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
|
||||||
# and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
|
# and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
|
||||||
# The normalized_root should be "some_folder/foobar"
|
# The normalized_root should be "some_folder/foobar"
|
||||||
normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/')
|
normalized_root = os.path.join(
|
||||||
|
basename, root[len(filename) :].lstrip("/")
|
||||||
|
).rstrip("/")
|
||||||
|
|
||||||
# Add the dir itself
|
# Add the dir itself
|
||||||
self.files[normalized_root + '/'] = root
|
self.files[normalized_root + "/"] = root
|
||||||
|
|
||||||
# Add the files in this dir
|
# Add the files in this dir
|
||||||
for nested_filename in nested_filenames:
|
for nested_filename in nested_filenames:
|
||||||
self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename)
|
self.files[
|
||||||
|
os.path.join(normalized_root, nested_filename)
|
||||||
|
] = os.path.join(root, nested_filename)
|
||||||
|
|
||||||
self.set_file_info_custom(filenames, processed_size_callback)
|
self.set_file_info_custom(filenames, processed_size_callback)
|
||||||
|
|
||||||
def directory_listing(self, filenames, path='', filesystem_path=None):
|
def directory_listing(self, filenames, path="", filesystem_path=None):
|
||||||
# Tell the GUI about the directory listing
|
# Tell the GUI about the directory listing
|
||||||
history_id = self.cur_history_id
|
history_id = self.cur_history_id
|
||||||
self.cur_history_id += 1
|
self.cur_history_id += 1
|
||||||
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '/{}'.format(path), {
|
self.web.add_request(
|
||||||
'id': history_id,
|
self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
|
||||||
'method': request.method,
|
"/{}".format(path),
|
||||||
'status_code': 200
|
{"id": history_id, "method": request.method, "status_code": 200},
|
||||||
})
|
)
|
||||||
|
|
||||||
breadcrumbs = [('☗', '/')]
|
breadcrumbs = [("☗", "/")]
|
||||||
parts = path.split('/')[:-1]
|
parts = path.split("/")[:-1]
|
||||||
for i in range(len(parts)):
|
for i in range(len(parts)):
|
||||||
breadcrumbs.append(('{}'.format(parts[i]), '/{}/'.format('/'.join(parts[0:i+1]))))
|
breadcrumbs.append(
|
||||||
|
("{}".format(parts[i]), "/{}/".format("/".join(parts[0 : i + 1])))
|
||||||
|
)
|
||||||
breadcrumbs_leaf = breadcrumbs.pop()[0]
|
breadcrumbs_leaf = breadcrumbs.pop()[0]
|
||||||
|
|
||||||
# If filesystem_path is None, this is the root directory listing
|
# If filesystem_path is None, this is the root directory listing
|
||||||
files, dirs = self.build_directory_listing(filenames, filesystem_path)
|
files, dirs = self.build_directory_listing(filenames, filesystem_path)
|
||||||
r = self.directory_listing_template(path, files, dirs, breadcrumbs, breadcrumbs_leaf)
|
r = self.directory_listing_template(
|
||||||
|
path, files, dirs, breadcrumbs, breadcrumbs_leaf
|
||||||
|
)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
def build_directory_listing(self, filenames, filesystem_path):
|
def build_directory_listing(self, filenames, filesystem_path):
|
||||||
|
@ -114,16 +127,11 @@ class SendBaseModeWeb:
|
||||||
is_dir = os.path.isdir(this_filesystem_path)
|
is_dir = os.path.isdir(this_filesystem_path)
|
||||||
|
|
||||||
if is_dir:
|
if is_dir:
|
||||||
dirs.append({
|
dirs.append({"basename": filename})
|
||||||
'basename': filename
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
size = os.path.getsize(this_filesystem_path)
|
size = os.path.getsize(this_filesystem_path)
|
||||||
size_human = self.common.human_readable_filesize(size)
|
size_human = self.common.human_readable_filesize(size)
|
||||||
files.append({
|
files.append({"basename": filename, "size_human": size_human})
|
||||||
'basename': filename,
|
|
||||||
'size_human': size_human
|
|
||||||
})
|
|
||||||
return files, dirs
|
return files, dirs
|
||||||
|
|
||||||
def stream_individual_file(self, filesystem_path):
|
def stream_individual_file(self, filesystem_path):
|
||||||
|
@ -136,7 +144,7 @@ class SendBaseModeWeb:
|
||||||
# gzip compress the individual file, if it hasn't already been compressed
|
# gzip compress the individual file, if it hasn't already been compressed
|
||||||
if use_gzip:
|
if use_gzip:
|
||||||
if filesystem_path not in self.gzip_individual_files:
|
if filesystem_path not in self.gzip_individual_files:
|
||||||
gzip_filename = tempfile.mkstemp('wb+')[1]
|
gzip_filename = tempfile.mkstemp("wb+")[1]
|
||||||
self._gzip_compress(filesystem_path, gzip_filename, 6, None)
|
self._gzip_compress(filesystem_path, gzip_filename, 6, None)
|
||||||
self.gzip_individual_files[filesystem_path] = gzip_filename
|
self.gzip_individual_files[filesystem_path] = gzip_filename
|
||||||
|
|
||||||
|
@ -154,10 +162,11 @@ class SendBaseModeWeb:
|
||||||
# Tell GUI the individual file started
|
# Tell GUI the individual file started
|
||||||
history_id = self.cur_history_id
|
history_id = self.cur_history_id
|
||||||
self.cur_history_id += 1
|
self.cur_history_id += 1
|
||||||
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, {
|
self.web.add_request(
|
||||||
'id': history_id,
|
self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
|
||||||
'filesize': filesize
|
path,
|
||||||
})
|
{"id": history_id, "filesize": filesize},
|
||||||
|
)
|
||||||
|
|
||||||
# Only GET requests are allowed, any other method should fail
|
# Only GET requests are allowed, any other method should fail
|
||||||
if request.method != "GET":
|
if request.method != "GET":
|
||||||
|
@ -166,11 +175,11 @@ class SendBaseModeWeb:
|
||||||
def generate():
|
def generate():
|
||||||
chunk_size = 102400 # 100kb
|
chunk_size = 102400 # 100kb
|
||||||
|
|
||||||
fp = open(file_to_download, 'rb')
|
fp = open(file_to_download, "rb")
|
||||||
done = False
|
done = False
|
||||||
while not done:
|
while not done:
|
||||||
chunk = fp.read(chunk_size)
|
chunk = fp.read(chunk_size)
|
||||||
if chunk == b'':
|
if chunk == b"":
|
||||||
done = True
|
done = True
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -179,59 +188,79 @@ class SendBaseModeWeb:
|
||||||
# Tell GUI the progress
|
# Tell GUI the progress
|
||||||
downloaded_bytes = fp.tell()
|
downloaded_bytes = fp.tell()
|
||||||
percent = (1.0 * downloaded_bytes / filesize) * 100
|
percent = (1.0 * downloaded_bytes / filesize) * 100
|
||||||
if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
if (
|
||||||
|
not self.web.is_gui
|
||||||
|
or self.common.platform == "Linux"
|
||||||
|
or self.common.platform == "BSD"
|
||||||
|
):
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
|
"\r{0:s}, {1:.2f}% ".format(
|
||||||
|
self.common.human_readable_filesize(
|
||||||
|
downloaded_bytes
|
||||||
|
),
|
||||||
|
percent,
|
||||||
|
)
|
||||||
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, {
|
self.web.add_request(
|
||||||
'id': history_id,
|
self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS,
|
||||||
'bytes': downloaded_bytes,
|
path,
|
||||||
'filesize': filesize
|
{
|
||||||
})
|
"id": history_id,
|
||||||
|
"bytes": downloaded_bytes,
|
||||||
|
"filesize": filesize,
|
||||||
|
},
|
||||||
|
)
|
||||||
done = False
|
done = False
|
||||||
except:
|
except:
|
||||||
# Looks like the download was canceled
|
# Looks like the download was canceled
|
||||||
done = True
|
done = True
|
||||||
|
|
||||||
# Tell the GUI the individual file was canceled
|
# Tell the GUI the individual file was canceled
|
||||||
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, {
|
self.web.add_request(
|
||||||
'id': history_id
|
self.web.REQUEST_INDIVIDUAL_FILE_CANCELED,
|
||||||
})
|
path,
|
||||||
|
{"id": history_id},
|
||||||
|
)
|
||||||
|
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
if self.common.platform != 'Darwin':
|
if self.common.platform != "Darwin":
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
basename = os.path.basename(filesystem_path)
|
basename = os.path.basename(filesystem_path)
|
||||||
|
|
||||||
r = Response(generate())
|
r = Response(generate())
|
||||||
if use_gzip:
|
if use_gzip:
|
||||||
r.headers.set('Content-Encoding', 'gzip')
|
r.headers.set("Content-Encoding", "gzip")
|
||||||
r.headers.set('Content-Length', filesize)
|
r.headers.set("Content-Length", filesize)
|
||||||
r.headers.set('Content-Disposition', 'inline', filename=basename)
|
r.headers.set("Content-Disposition", "inline", filename=basename)
|
||||||
r = self.web.add_security_headers(r)
|
r = self.web.add_security_headers(r)
|
||||||
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
r.headers.set('Content-Type', content_type)
|
r.headers.set("Content-Type", content_type)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def should_use_gzip(self):
|
def should_use_gzip(self):
|
||||||
"""
|
"""
|
||||||
Should we use gzip for this browser?
|
Should we use gzip for this browser?
|
||||||
"""
|
"""
|
||||||
return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower())
|
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):
|
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
|
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
|
Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror
|
||||||
"""
|
"""
|
||||||
bytes_processed = 0
|
bytes_processed = 0
|
||||||
blocksize = 1 << 16 # 64kB
|
blocksize = 1 << 16 # 64kB
|
||||||
with open(input_filename, 'rb') as input_file:
|
with open(input_filename, "rb") as input_file:
|
||||||
output_file = gzip.open(output_filename, 'wb', level)
|
output_file = gzip.open(output_filename, "wb", level)
|
||||||
while True:
|
while True:
|
||||||
if processed_size_callback is not None:
|
if processed_size_callback is not None:
|
||||||
processed_size_callback(bytes_processed)
|
processed_size_callback(bytes_processed)
|
||||||
|
@ -269,7 +298,7 @@ class SendBaseModeWeb:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def render_logic(self, path=''):
|
def render_logic(self, path=""):
|
||||||
"""
|
"""
|
||||||
Inherited class will implement this.
|
Inherited class will implement this.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -13,18 +13,22 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
"""
|
"""
|
||||||
All of the web logic for share mode
|
All of the web logic for share mode
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
self.common.log('ShareModeWeb', 'init')
|
self.common.log("ShareModeWeb", "init")
|
||||||
|
|
||||||
# Allow downloading individual files if "Stop sharing after files have been sent" is unchecked
|
# Allow downloading individual files if "Stop sharing after files have been sent" is unchecked
|
||||||
self.download_individual_files = not self.common.settings.get('close_after_first_download')
|
self.download_individual_files = not self.common.settings.get(
|
||||||
|
"close_after_first_download"
|
||||||
|
)
|
||||||
|
|
||||||
def define_routes(self):
|
def define_routes(self):
|
||||||
"""
|
"""
|
||||||
The web app routes for sharing files
|
The web app routes for sharing files
|
||||||
"""
|
"""
|
||||||
@self.web.app.route('/', defaults={'path': ''})
|
|
||||||
@self.web.app.route('/<path:path>')
|
@self.web.app.route("/", defaults={"path": ""})
|
||||||
|
@self.web.app.route("/<path:path>")
|
||||||
def index(path):
|
def index(path):
|
||||||
"""
|
"""
|
||||||
Render the template for the onionshare landing page.
|
Render the template for the onionshare landing page.
|
||||||
|
@ -35,8 +39,10 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
# currently a download
|
# currently a download
|
||||||
deny_download = not self.web.stay_open and self.download_in_progress
|
deny_download = not self.web.stay_open and self.download_in_progress
|
||||||
if deny_download:
|
if deny_download:
|
||||||
r = make_response(render_template('denied.html'),
|
r = make_response(
|
||||||
static_url_path=self.web.static_url_path)
|
render_template("denied.html"),
|
||||||
|
static_url_path=self.web.static_url_path,
|
||||||
|
)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
# If download is allowed to continue, serve download page
|
# If download is allowed to continue, serve download page
|
||||||
|
@ -56,13 +62,16 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
# currently a download
|
# currently a download
|
||||||
deny_download = not self.web.stay_open and self.download_in_progress
|
deny_download = not self.web.stay_open and self.download_in_progress
|
||||||
if deny_download:
|
if deny_download:
|
||||||
r = make_response(render_template('denied.html',
|
r = make_response(
|
||||||
static_url_path=self.web.static_url_path))
|
render_template(
|
||||||
|
"denied.html", static_url_path=self.web.static_url_path
|
||||||
|
)
|
||||||
|
)
|
||||||
return self.web.add_security_headers(r)
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
# Prepare some variables to use inside generate() function below
|
# Prepare some variables to use inside generate() function below
|
||||||
# which is outside of the request context
|
# which is outside of the request context
|
||||||
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
shutdown_func = request.environ.get("werkzeug.server.shutdown")
|
||||||
path = request.path
|
path = request.path
|
||||||
|
|
||||||
# If this is a zipped file, then serve as-is. If it's not zipped, then,
|
# If this is a zipped file, then serve as-is. If it's not zipped, then,
|
||||||
|
@ -79,10 +88,9 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
# Tell GUI the download started
|
# Tell GUI the download started
|
||||||
history_id = self.cur_history_id
|
history_id = self.cur_history_id
|
||||||
self.cur_history_id += 1
|
self.cur_history_id += 1
|
||||||
self.web.add_request(self.web.REQUEST_STARTED, path, {
|
self.web.add_request(
|
||||||
'id': history_id,
|
self.web.REQUEST_STARTED, path, {"id": history_id, "use_gzip": use_gzip}
|
||||||
'use_gzip': use_gzip
|
)
|
||||||
})
|
|
||||||
|
|
||||||
basename = os.path.basename(self.download_filename)
|
basename = os.path.basename(self.download_filename)
|
||||||
|
|
||||||
|
@ -93,19 +101,19 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
|
|
||||||
chunk_size = 102400 # 100kb
|
chunk_size = 102400 # 100kb
|
||||||
|
|
||||||
fp = open(file_to_download, 'rb')
|
fp = open(file_to_download, "rb")
|
||||||
self.web.done = False
|
self.web.done = False
|
||||||
canceled = False
|
canceled = False
|
||||||
while not self.web.done:
|
while not self.web.done:
|
||||||
# The user has canceled the download, so stop serving the file
|
# The user has canceled the download, so stop serving the file
|
||||||
if not self.web.stop_q.empty():
|
if not self.web.stop_q.empty():
|
||||||
self.web.add_request(self.web.REQUEST_CANCELED, path, {
|
self.web.add_request(
|
||||||
'id': history_id
|
self.web.REQUEST_CANCELED, path, {"id": history_id}
|
||||||
})
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
chunk = fp.read(chunk_size)
|
chunk = fp.read(chunk_size)
|
||||||
if chunk == b'':
|
if chunk == b"":
|
||||||
self.web.done = True
|
self.web.done = True
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -116,15 +124,26 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
percent = (1.0 * downloaded_bytes / self.filesize) * 100
|
percent = (1.0 * downloaded_bytes / self.filesize) * 100
|
||||||
|
|
||||||
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
|
# 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':
|
if (
|
||||||
|
not self.web.is_gui
|
||||||
|
or self.common.platform == "Linux"
|
||||||
|
or self.common.platform == "BSD"
|
||||||
|
):
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
|
"\r{0:s}, {1:.2f}% ".format(
|
||||||
|
self.common.human_readable_filesize(
|
||||||
|
downloaded_bytes
|
||||||
|
),
|
||||||
|
percent,
|
||||||
|
)
|
||||||
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
self.web.add_request(self.web.REQUEST_PROGRESS, path, {
|
self.web.add_request(
|
||||||
'id': history_id,
|
self.web.REQUEST_PROGRESS,
|
||||||
'bytes': downloaded_bytes
|
path,
|
||||||
})
|
{"id": history_id, "bytes": downloaded_bytes},
|
||||||
|
)
|
||||||
self.web.done = False
|
self.web.done = False
|
||||||
except:
|
except:
|
||||||
# looks like the download was canceled
|
# looks like the download was canceled
|
||||||
|
@ -132,13 +151,13 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
canceled = True
|
canceled = True
|
||||||
|
|
||||||
# tell the GUI the download has canceled
|
# tell the GUI the download has canceled
|
||||||
self.web.add_request(self.web.REQUEST_CANCELED, path, {
|
self.web.add_request(
|
||||||
'id': history_id
|
self.web.REQUEST_CANCELED, path, {"id": history_id}
|
||||||
})
|
)
|
||||||
|
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
if self.common.platform != 'Darwin':
|
if self.common.platform != "Darwin":
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
# Download is finished
|
# Download is finished
|
||||||
|
@ -151,26 +170,29 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
self.web.running = False
|
self.web.running = False
|
||||||
try:
|
try:
|
||||||
if shutdown_func is None:
|
if shutdown_func is None:
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
raise RuntimeError("Not running with the Werkzeug Server")
|
||||||
shutdown_func()
|
shutdown_func()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
r = Response(generate())
|
r = Response(generate())
|
||||||
if use_gzip:
|
if use_gzip:
|
||||||
r.headers.set('Content-Encoding', 'gzip')
|
r.headers.set("Content-Encoding", "gzip")
|
||||||
r.headers.set('Content-Length', self.filesize)
|
r.headers.set("Content-Length", self.filesize)
|
||||||
r.headers.set('Content-Disposition', 'attachment', filename=basename)
|
r.headers.set("Content-Disposition", "attachment", filename=basename)
|
||||||
r = self.web.add_security_headers(r)
|
r = self.web.add_security_headers(r)
|
||||||
# guess content type
|
# guess content type
|
||||||
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
r.headers.set('Content-Type', content_type)
|
r.headers.set("Content-Type", content_type)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf):
|
def directory_listing_template(
|
||||||
return make_response(render_template(
|
self, path, files, dirs, breadcrumbs, breadcrumbs_leaf
|
||||||
'send.html',
|
):
|
||||||
|
return make_response(
|
||||||
|
render_template(
|
||||||
|
"send.html",
|
||||||
file_info=self.file_info,
|
file_info=self.file_info,
|
||||||
files=files,
|
files=files,
|
||||||
dirs=dirs,
|
dirs=dirs,
|
||||||
|
@ -178,17 +200,21 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
breadcrumbs_leaf=breadcrumbs_leaf,
|
breadcrumbs_leaf=breadcrumbs_leaf,
|
||||||
filename=os.path.basename(self.download_filename),
|
filename=os.path.basename(self.download_filename),
|
||||||
filesize=self.filesize,
|
filesize=self.filesize,
|
||||||
filesize_human=self.common.human_readable_filesize(self.download_filesize),
|
filesize_human=self.common.human_readable_filesize(
|
||||||
|
self.download_filesize
|
||||||
|
),
|
||||||
is_zipped=self.is_zipped,
|
is_zipped=self.is_zipped,
|
||||||
static_url_path=self.web.static_url_path,
|
static_url_path=self.web.static_url_path,
|
||||||
download_individual_files=self.download_individual_files))
|
download_individual_files=self.download_individual_files,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def set_file_info_custom(self, filenames, processed_size_callback):
|
def set_file_info_custom(self, filenames, processed_size_callback):
|
||||||
self.common.log("ShareModeWeb", "set_file_info_custom")
|
self.common.log("ShareModeWeb", "set_file_info_custom")
|
||||||
self.web.cancel_compression = False
|
self.web.cancel_compression = False
|
||||||
self.build_zipfile_list(filenames, processed_size_callback)
|
self.build_zipfile_list(filenames, processed_size_callback)
|
||||||
|
|
||||||
def render_logic(self, path=''):
|
def render_logic(self, path=""):
|
||||||
if path in self.files:
|
if path in self.files:
|
||||||
filesystem_path = self.files[path]
|
filesystem_path = self.files[path]
|
||||||
|
|
||||||
|
@ -198,7 +224,7 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
filenames = []
|
filenames = []
|
||||||
for filename in os.listdir(filesystem_path):
|
for filename in os.listdir(filesystem_path):
|
||||||
if os.path.isdir(os.path.join(filesystem_path, filename)):
|
if os.path.isdir(os.path.join(filesystem_path, filename)):
|
||||||
filenames.append(filename + '/')
|
filenames.append(filename + "/")
|
||||||
else:
|
else:
|
||||||
filenames.append(filename)
|
filenames.append(filename)
|
||||||
filenames.sort()
|
filenames.sort()
|
||||||
|
@ -221,7 +247,7 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
else:
|
else:
|
||||||
# Special case loading /
|
# Special case loading /
|
||||||
|
|
||||||
if path == '':
|
if path == "":
|
||||||
# Root directory listing
|
# Root directory listing
|
||||||
filenames = list(self.root_files)
|
filenames = list(self.root_files)
|
||||||
filenames.sort()
|
filenames.sort()
|
||||||
|
@ -237,28 +263,34 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
self.common.log("ShareModeWeb", "build_zipfile_list")
|
self.common.log("ShareModeWeb", "build_zipfile_list")
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
info = {
|
info = {
|
||||||
'filename': filename,
|
"filename": filename,
|
||||||
'basename': os.path.basename(filename.rstrip('/'))
|
"basename": os.path.basename(filename.rstrip("/")),
|
||||||
}
|
}
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
info['size'] = os.path.getsize(filename)
|
info["size"] = os.path.getsize(filename)
|
||||||
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
info["size_human"] = self.common.human_readable_filesize(info["size"])
|
||||||
self.file_info['files'].append(info)
|
self.file_info["files"].append(info)
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
info['size'] = self.common.dir_size(filename)
|
info["size"] = self.common.dir_size(filename)
|
||||||
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
info["size_human"] = self.common.human_readable_filesize(info["size"])
|
||||||
self.file_info['dirs'].append(info)
|
self.file_info["dirs"].append(info)
|
||||||
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
|
self.file_info["files"] = sorted(
|
||||||
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
|
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
|
# Check if there's only 1 file and no folders
|
||||||
if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0:
|
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_filename = self.file_info["files"][0]["filename"]
|
||||||
self.download_filesize = self.file_info['files'][0]['size']
|
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
|
# 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_filename = tempfile.mkstemp("wb+")[1]
|
||||||
self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback)
|
self._gzip_compress(
|
||||||
|
self.download_filename, self.gzip_filename, 6, processed_size_callback
|
||||||
|
)
|
||||||
self.gzip_filesize = os.path.getsize(self.gzip_filename)
|
self.gzip_filesize = os.path.getsize(self.gzip_filename)
|
||||||
|
|
||||||
# Make sure the gzip file gets cleaned up when onionshare stops
|
# Make sure the gzip file gets cleaned up when onionshare stops
|
||||||
|
@ -268,17 +300,19 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Zip up the files and folders
|
# Zip up the files and folders
|
||||||
self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback)
|
self.zip_writer = ZipWriter(
|
||||||
|
self.common, processed_size_callback=processed_size_callback
|
||||||
|
)
|
||||||
self.download_filename = self.zip_writer.zip_filename
|
self.download_filename = self.zip_writer.zip_filename
|
||||||
for info in self.file_info['files']:
|
for info in self.file_info["files"]:
|
||||||
self.zip_writer.add_file(info['filename'])
|
self.zip_writer.add_file(info["filename"])
|
||||||
# Canceling early?
|
# Canceling early?
|
||||||
if self.web.cancel_compression:
|
if self.web.cancel_compression:
|
||||||
self.zip_writer.close()
|
self.zip_writer.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for info in self.file_info['dirs']:
|
for info in self.file_info["dirs"]:
|
||||||
if not self.zip_writer.add_dir(info['filename']):
|
if not self.zip_writer.add_dir(info["filename"]):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.zip_writer.close()
|
self.zip_writer.close()
|
||||||
|
@ -298,6 +332,7 @@ class ZipWriter(object):
|
||||||
with. If a zip_filename is not passed in, it will use the default onionshare
|
with. If a zip_filename is not passed in, it will use the default onionshare
|
||||||
filename.
|
filename.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
||||||
self.common = common
|
self.common = common
|
||||||
self.cancel_compression = False
|
self.cancel_compression = False
|
||||||
|
@ -305,9 +340,11 @@ class ZipWriter(object):
|
||||||
if zip_filename:
|
if zip_filename:
|
||||||
self.zip_filename = zip_filename
|
self.zip_filename = zip_filename
|
||||||
else:
|
else:
|
||||||
self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6))
|
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.z = zipfile.ZipFile(self.zip_filename, "w", allowZip64=True)
|
||||||
self.processed_size_callback = processed_size_callback
|
self.processed_size_callback = processed_size_callback
|
||||||
if self.processed_size_callback is None:
|
if self.processed_size_callback is None:
|
||||||
self.processed_size_callback = lambda _: None
|
self.processed_size_callback = lambda _: None
|
||||||
|
@ -326,7 +363,7 @@ class ZipWriter(object):
|
||||||
"""
|
"""
|
||||||
Add a directory, and all of its children, to the zip archive.
|
Add a directory, and all of its children, to the zip archive.
|
||||||
"""
|
"""
|
||||||
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
dir_to_strip = os.path.dirname(filename.rstrip("/")) + "/"
|
||||||
for dirpath, dirnames, filenames in os.walk(filename):
|
for dirpath, dirnames, filenames in os.walk(filename):
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
# Canceling early?
|
# Canceling early?
|
||||||
|
|
|
@ -10,7 +10,15 @@ from distutils.version import LooseVersion as Version
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import Flask, request, render_template, abort, make_response, send_file, __version__ as flask_version
|
from flask import (
|
||||||
|
Flask,
|
||||||
|
request,
|
||||||
|
render_template,
|
||||||
|
abort,
|
||||||
|
make_response,
|
||||||
|
send_file,
|
||||||
|
__version__ as flask_version,
|
||||||
|
)
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
|
|
||||||
from .. import strings
|
from .. import strings
|
||||||
|
@ -24,6 +32,7 @@ from .website_mode import WebsiteModeWeb
|
||||||
def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
|
def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flask.cli.show_server_banner = stubbed_show_server_banner
|
flask.cli.show_server_banner = stubbed_show_server_banner
|
||||||
except:
|
except:
|
||||||
|
@ -34,6 +43,7 @@ class Web:
|
||||||
"""
|
"""
|
||||||
The Web object is the OnionShare web server, powered by flask
|
The Web object is the OnionShare web server, powered by flask
|
||||||
"""
|
"""
|
||||||
|
|
||||||
REQUEST_LOAD = 0
|
REQUEST_LOAD = 0
|
||||||
REQUEST_STARTED = 1
|
REQUEST_STARTED = 1
|
||||||
REQUEST_PROGRESS = 2
|
REQUEST_PROGRESS = 2
|
||||||
|
@ -50,14 +60,16 @@ class Web:
|
||||||
REQUEST_OTHER = 13
|
REQUEST_OTHER = 13
|
||||||
REQUEST_INVALID_PASSWORD = 14
|
REQUEST_INVALID_PASSWORD = 14
|
||||||
|
|
||||||
def __init__(self, common, is_gui, mode='share'):
|
def __init__(self, common, is_gui, mode="share"):
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode))
|
self.common.log("Web", "__init__", "is_gui={}, mode={}".format(is_gui, mode))
|
||||||
|
|
||||||
# The flask app
|
# The flask app
|
||||||
self.app = Flask(__name__,
|
self.app = Flask(
|
||||||
static_folder=self.common.get_resource_path('static'),
|
__name__,
|
||||||
template_folder=self.common.get_resource_path('templates'))
|
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)
|
self.app.secret_key = self.common.random_string(8)
|
||||||
self.generate_static_url_path()
|
self.generate_static_url_path()
|
||||||
self.auth = HTTPBasicAuth()
|
self.auth = HTTPBasicAuth()
|
||||||
|
@ -77,7 +89,7 @@ class Web:
|
||||||
|
|
||||||
# Are we using receive mode?
|
# Are we using receive mode?
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
if self.mode == 'receive':
|
if self.mode == "receive":
|
||||||
# Use custom WSGI middleware, to modify environ
|
# Use custom WSGI middleware, to modify environ
|
||||||
self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self)
|
self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self)
|
||||||
# Use a custom Request class to track upload progess
|
# Use a custom Request class to track upload progess
|
||||||
|
@ -87,16 +99,16 @@ class Web:
|
||||||
# by default. To prevent content injection through template variables in
|
# by default. To prevent content injection through template variables in
|
||||||
# earlier versions of Flask, we force autoescaping in the Jinja2 template
|
# earlier versions of Flask, we force autoescaping in the Jinja2 template
|
||||||
# engine if we detect a Flask version with insecure default behavior.
|
# engine if we detect a Flask version with insecure default behavior.
|
||||||
if Version(flask_version) < Version('0.11'):
|
if Version(flask_version) < Version("0.11"):
|
||||||
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
||||||
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
|
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
|
||||||
|
|
||||||
self.security_headers = [
|
self.security_headers = [
|
||||||
('X-Frame-Options', 'DENY'),
|
("X-Frame-Options", "DENY"),
|
||||||
('X-Xss-Protection', '1; mode=block'),
|
("X-Xss-Protection", "1; mode=block"),
|
||||||
('X-Content-Type-Options', 'nosniff'),
|
("X-Content-Type-Options", "nosniff"),
|
||||||
('Referrer-Policy', 'no-referrer'),
|
("Referrer-Policy", "no-referrer"),
|
||||||
('Server', 'OnionShare')
|
("Server", "OnionShare"),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.q = queue.Queue()
|
self.q = queue.Queue()
|
||||||
|
@ -119,19 +131,19 @@ class Web:
|
||||||
self.share_mode = None
|
self.share_mode = None
|
||||||
self.receive_mode = None
|
self.receive_mode = None
|
||||||
self.website_mode = None
|
self.website_mode = None
|
||||||
if self.mode == 'share':
|
if self.mode == "share":
|
||||||
self.share_mode = ShareModeWeb(self.common, self)
|
self.share_mode = ShareModeWeb(self.common, self)
|
||||||
elif self.mode == 'receive':
|
elif self.mode == "receive":
|
||||||
self.receive_mode = ReceiveModeWeb(self.common, self)
|
self.receive_mode = ReceiveModeWeb(self.common, self)
|
||||||
elif self.mode == 'website':
|
elif self.mode == "website":
|
||||||
self.website_mode = WebsiteModeWeb(self.common, self)
|
self.website_mode = WebsiteModeWeb(self.common, self)
|
||||||
|
|
||||||
def get_mode(self):
|
def get_mode(self):
|
||||||
if self.mode == 'share':
|
if self.mode == "share":
|
||||||
return self.share_mode
|
return self.share_mode
|
||||||
elif self.mode == 'receive':
|
elif self.mode == "receive":
|
||||||
return self.receive_mode
|
return self.receive_mode
|
||||||
elif self.mode == 'website':
|
elif self.mode == "website":
|
||||||
return self.website_mode
|
return self.website_mode
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -139,14 +151,20 @@ class Web:
|
||||||
def generate_static_url_path(self):
|
def generate_static_url_path(self):
|
||||||
# The static URL path has a 128-bit random number in it to avoid having name
|
# The static URL path has a 128-bit random number in it to avoid having name
|
||||||
# collisions with files that might be getting shared
|
# collisions with files that might be getting shared
|
||||||
self.static_url_path = '/static_{}'.format(self.common.random_string(16))
|
self.static_url_path = "/static_{}".format(self.common.random_string(16))
|
||||||
self.common.log('Web', 'generate_static_url_path', 'new static_url_path is {}'.format(self.static_url_path))
|
self.common.log(
|
||||||
|
"Web",
|
||||||
|
"generate_static_url_path",
|
||||||
|
"new static_url_path is {}".format(self.static_url_path),
|
||||||
|
)
|
||||||
|
|
||||||
# Update the flask route to handle the new static URL path
|
# Update the flask route to handle the new static URL path
|
||||||
self.app.static_url_path = self.static_url_path
|
self.app.static_url_path = self.static_url_path
|
||||||
self.app.add_url_rule(
|
self.app.add_url_rule(
|
||||||
self.static_url_path + '/<path:filename>',
|
self.static_url_path + "/<path:filename>",
|
||||||
endpoint='static', view_func=self.app.send_static_file)
|
endpoint="static",
|
||||||
|
view_func=self.app.send_static_file,
|
||||||
|
)
|
||||||
|
|
||||||
def define_common_routes(self):
|
def define_common_routes(self):
|
||||||
"""
|
"""
|
||||||
|
@ -155,7 +173,7 @@ class Web:
|
||||||
|
|
||||||
@self.auth.get_password
|
@self.auth.get_password
|
||||||
def get_pw(username):
|
def get_pw(username):
|
||||||
if username == 'onionshare':
|
if username == "onionshare":
|
||||||
return self.password
|
return self.password
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -163,11 +181,12 @@ class Web:
|
||||||
@self.app.before_request
|
@self.app.before_request
|
||||||
def conditional_auth_check():
|
def conditional_auth_check():
|
||||||
# Allow static files without basic authentication
|
# Allow static files without basic authentication
|
||||||
if(request.path.startswith(self.static_url_path + '/')):
|
if request.path.startswith(self.static_url_path + "/"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# If public mode is disabled, require authentication
|
# If public mode is disabled, require authentication
|
||||||
if not self.common.settings.get('public_mode'):
|
if not self.common.settings.get("public_mode"):
|
||||||
|
|
||||||
@self.auth.login_required
|
@self.auth.login_required
|
||||||
def _check_login():
|
def _check_login():
|
||||||
return None
|
return None
|
||||||
|
@ -191,46 +210,63 @@ class Web:
|
||||||
return ""
|
return ""
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
if self.mode != 'website':
|
if self.mode != "website":
|
||||||
|
|
||||||
@self.app.route("/favicon.ico")
|
@self.app.route("/favicon.ico")
|
||||||
def favicon():
|
def favicon():
|
||||||
return send_file('{}/img/favicon.ico'.format(self.common.get_resource_path('static')))
|
return send_file(
|
||||||
|
"{}/img/favicon.ico".format(self.common.get_resource_path("static"))
|
||||||
|
)
|
||||||
|
|
||||||
def error401(self):
|
def error401(self):
|
||||||
auth = request.authorization
|
auth = request.authorization
|
||||||
if auth:
|
if auth:
|
||||||
if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_passwords:
|
if (
|
||||||
print('Invalid password guess: {}'.format(auth['password']))
|
auth["username"] == "onionshare"
|
||||||
self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth['password'])
|
and auth["password"] not in self.invalid_passwords
|
||||||
|
):
|
||||||
|
print("Invalid password guess: {}".format(auth["password"]))
|
||||||
|
self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth["password"])
|
||||||
|
|
||||||
self.invalid_passwords.append(auth['password'])
|
self.invalid_passwords.append(auth["password"])
|
||||||
self.invalid_passwords_count += 1
|
self.invalid_passwords_count += 1
|
||||||
|
|
||||||
if self.invalid_passwords_count == 20:
|
if self.invalid_passwords_count == 20:
|
||||||
self.add_request(Web.REQUEST_RATE_LIMIT)
|
self.add_request(Web.REQUEST_RATE_LIMIT)
|
||||||
self.force_shutdown()
|
self.force_shutdown()
|
||||||
print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.")
|
print(
|
||||||
|
"Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share."
|
||||||
|
)
|
||||||
|
|
||||||
r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401)
|
r = make_response(
|
||||||
|
render_template("401.html", static_url_path=self.static_url_path), 401
|
||||||
|
)
|
||||||
return self.add_security_headers(r)
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def error403(self):
|
def error403(self):
|
||||||
self.add_request(Web.REQUEST_OTHER, request.path)
|
self.add_request(Web.REQUEST_OTHER, request.path)
|
||||||
r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403)
|
r = make_response(
|
||||||
|
render_template("403.html", static_url_path=self.static_url_path), 403
|
||||||
|
)
|
||||||
return self.add_security_headers(r)
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def error404(self, history_id):
|
def error404(self, history_id):
|
||||||
self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), {
|
self.add_request(
|
||||||
'id': history_id,
|
self.REQUEST_INDIVIDUAL_FILE_STARTED,
|
||||||
'status_code': 404
|
"{}".format(request.path),
|
||||||
})
|
{"id": history_id, "status_code": 404},
|
||||||
|
)
|
||||||
|
|
||||||
self.add_request(Web.REQUEST_OTHER, request.path)
|
self.add_request(Web.REQUEST_OTHER, request.path)
|
||||||
r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404)
|
r = make_response(
|
||||||
|
render_template("404.html", static_url_path=self.static_url_path), 404
|
||||||
|
)
|
||||||
return self.add_security_headers(r)
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def error405(self):
|
def error405(self):
|
||||||
r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405)
|
r = make_response(
|
||||||
|
render_template("405.html", static_url_path=self.static_url_path), 405
|
||||||
|
)
|
||||||
return self.add_security_headers(r)
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
def add_security_headers(self, r):
|
def add_security_headers(self, r):
|
||||||
|
@ -240,39 +276,53 @@ class Web:
|
||||||
for header, value in self.security_headers:
|
for header, value in self.security_headers:
|
||||||
r.headers.set(header, value)
|
r.headers.set(header, value)
|
||||||
# Set a CSP header unless in website mode and the user has disabled it
|
# Set a CSP header unless in website mode and the user has disabled it
|
||||||
if not self.common.settings.get('csp_header_disabled') or self.mode != 'website':
|
if (
|
||||||
r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;')
|
not self.common.settings.get("csp_header_disabled")
|
||||||
|
or self.mode != "website"
|
||||||
|
):
|
||||||
|
r.headers.set(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:;",
|
||||||
|
)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def _safe_select_jinja_autoescape(self, filename):
|
def _safe_select_jinja_autoescape(self, filename):
|
||||||
if filename is None:
|
if filename is None:
|
||||||
return True
|
return True
|
||||||
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
return filename.endswith((".html", ".htm", ".xml", ".xhtml"))
|
||||||
|
|
||||||
def add_request(self, request_type, path=None, data=None):
|
def add_request(self, request_type, path=None, data=None):
|
||||||
"""
|
"""
|
||||||
Add a request to the queue, to communicate with the GUI.
|
Add a request to the queue, to communicate with the GUI.
|
||||||
"""
|
"""
|
||||||
self.q.put({
|
self.q.put({"type": request_type, "path": path, "data": data})
|
||||||
'type': request_type,
|
|
||||||
'path': path,
|
|
||||||
'data': data
|
|
||||||
})
|
|
||||||
|
|
||||||
def generate_password(self, persistent_password=None):
|
def generate_password(self, persistent_password=None):
|
||||||
self.common.log('Web', 'generate_password', 'persistent_password={}'.format(persistent_password))
|
self.common.log(
|
||||||
if persistent_password != None and persistent_password != '':
|
"Web",
|
||||||
|
"generate_password",
|
||||||
|
"persistent_password={}".format(persistent_password),
|
||||||
|
)
|
||||||
|
if persistent_password != None and persistent_password != "":
|
||||||
self.password = persistent_password
|
self.password = persistent_password
|
||||||
self.common.log('Web', 'generate_password', 'persistent_password sent, so password is: "{}"'.format(self.password))
|
self.common.log(
|
||||||
|
"Web",
|
||||||
|
"generate_password",
|
||||||
|
'persistent_password sent, so password is: "{}"'.format(self.password),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.password = self.common.build_password()
|
self.password = self.common.build_password()
|
||||||
self.common.log('Web', 'generate_password', 'built random password: "{}"'.format(self.password))
|
self.common.log(
|
||||||
|
"Web",
|
||||||
|
"generate_password",
|
||||||
|
'built random password: "{}"'.format(self.password),
|
||||||
|
)
|
||||||
|
|
||||||
def verbose_mode(self):
|
def verbose_mode(self):
|
||||||
"""
|
"""
|
||||||
Turn on verbose mode, which will log flask errors to a file.
|
Turn on verbose mode, which will log flask errors to a file.
|
||||||
"""
|
"""
|
||||||
flask_log_filename = os.path.join(self.common.build_data_dir(), 'flask.log')
|
flask_log_filename = os.path.join(self.common.build_data_dir(), "flask.log")
|
||||||
log_handler = logging.FileHandler(flask_log_filename)
|
log_handler = logging.FileHandler(flask_log_filename)
|
||||||
log_handler.setLevel(logging.WARNING)
|
log_handler.setLevel(logging.WARNING)
|
||||||
self.app.logger.addHandler(log_handler)
|
self.app.logger.addHandler(log_handler)
|
||||||
|
@ -287,9 +337,9 @@ class Web:
|
||||||
"""
|
"""
|
||||||
# Shutdown the flask service
|
# Shutdown the flask service
|
||||||
try:
|
try:
|
||||||
func = request.environ.get('werkzeug.server.shutdown')
|
func = request.environ.get("werkzeug.server.shutdown")
|
||||||
if func is None:
|
if func is None:
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
raise RuntimeError("Not running with the Werkzeug Server")
|
||||||
func()
|
func()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -299,7 +349,13 @@ class Web:
|
||||||
"""
|
"""
|
||||||
Start the flask web server.
|
Start the flask web server.
|
||||||
"""
|
"""
|
||||||
self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, password={}'.format(port, stay_open, public_mode, password))
|
self.common.log(
|
||||||
|
"Web",
|
||||||
|
"start",
|
||||||
|
"port={}, stay_open={}, public_mode={}, password={}".format(
|
||||||
|
port, stay_open, public_mode, password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
self.stay_open = stay_open
|
self.stay_open = stay_open
|
||||||
|
|
||||||
|
@ -311,10 +367,10 @@ class Web:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
|
# 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'):
|
if os.path.exists("/usr/share/anon-ws-base-files/workstation"):
|
||||||
host = '0.0.0.0'
|
host = "0.0.0.0"
|
||||||
else:
|
else:
|
||||||
host = '127.0.0.1'
|
host = "127.0.0.1"
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
self.app.run(host=host, port=port, threaded=True)
|
self.app.run(host=host, port=port, threaded=True)
|
||||||
|
@ -323,7 +379,7 @@ class Web:
|
||||||
"""
|
"""
|
||||||
Stop the flask web server by loading /shutdown.
|
Stop the flask web server by loading /shutdown.
|
||||||
"""
|
"""
|
||||||
self.common.log('Web', 'stop', 'stopping server')
|
self.common.log("Web", "stop", "stopping server")
|
||||||
|
|
||||||
# Let the mode know that the user stopped the server
|
# Let the mode know that the user stopped the server
|
||||||
self.stop_q.put(True)
|
self.stop_q.put(True)
|
||||||
|
@ -331,8 +387,10 @@ class Web:
|
||||||
# To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
|
# To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
|
||||||
# (We're putting the shutdown_password in the path as well to make routing simpler)
|
# (We're putting the shutdown_password in the path as well to make routing simpler)
|
||||||
if self.running:
|
if self.running:
|
||||||
requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_password),
|
requests.get(
|
||||||
auth=requests.auth.HTTPBasicAuth('onionshare', self.password))
|
"http://127.0.0.1:{}/{}/shutdown".format(port, self.shutdown_password),
|
||||||
|
auth=requests.auth.HTTPBasicAuth("onionshare", self.password),
|
||||||
|
)
|
||||||
|
|
||||||
# Reset any password that was in use
|
# Reset any password that was in use
|
||||||
self.password = None
|
self.password = None
|
||||||
|
|
|
@ -12,6 +12,7 @@ class WebsiteModeWeb(SendBaseModeWeb):
|
||||||
"""
|
"""
|
||||||
All of the web logic for website mode
|
All of the web logic for website mode
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -19,38 +20,45 @@ class WebsiteModeWeb(SendBaseModeWeb):
|
||||||
"""
|
"""
|
||||||
The web app routes for sharing a website
|
The web app routes for sharing a website
|
||||||
"""
|
"""
|
||||||
@self.web.app.route('/', defaults={'path': ''})
|
|
||||||
@self.web.app.route('/<path:path>')
|
@self.web.app.route("/", defaults={"path": ""})
|
||||||
|
@self.web.app.route("/<path:path>")
|
||||||
def path_public(path):
|
def path_public(path):
|
||||||
return path_logic(path)
|
return path_logic(path)
|
||||||
|
|
||||||
def path_logic(path=''):
|
def path_logic(path=""):
|
||||||
"""
|
"""
|
||||||
Render the onionshare website.
|
Render the onionshare website.
|
||||||
"""
|
"""
|
||||||
return self.render_logic(path)
|
return self.render_logic(path)
|
||||||
|
|
||||||
def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf):
|
def directory_listing_template(
|
||||||
return make_response(render_template('listing.html',
|
self, path, files, dirs, breadcrumbs, breadcrumbs_leaf
|
||||||
|
):
|
||||||
|
return make_response(
|
||||||
|
render_template(
|
||||||
|
"listing.html",
|
||||||
path=path,
|
path=path,
|
||||||
files=files,
|
files=files,
|
||||||
dirs=dirs,
|
dirs=dirs,
|
||||||
breadcrumbs=breadcrumbs,
|
breadcrumbs=breadcrumbs,
|
||||||
breadcrumbs_leaf=breadcrumbs_leaf,
|
breadcrumbs_leaf=breadcrumbs_leaf,
|
||||||
static_url_path=self.web.static_url_path))
|
static_url_path=self.web.static_url_path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def set_file_info_custom(self, filenames, processed_size_callback):
|
def set_file_info_custom(self, filenames, processed_size_callback):
|
||||||
self.common.log("WebsiteModeWeb", "set_file_info_custom")
|
self.common.log("WebsiteModeWeb", "set_file_info_custom")
|
||||||
self.web.cancel_compression = True
|
self.web.cancel_compression = True
|
||||||
|
|
||||||
def render_logic(self, path=''):
|
def render_logic(self, path=""):
|
||||||
if path in self.files:
|
if path in self.files:
|
||||||
filesystem_path = self.files[path]
|
filesystem_path = self.files[path]
|
||||||
|
|
||||||
# If it's a directory
|
# If it's a directory
|
||||||
if os.path.isdir(filesystem_path):
|
if os.path.isdir(filesystem_path):
|
||||||
# Is there an index.html?
|
# Is there an index.html?
|
||||||
index_path = os.path.join(path, 'index.html')
|
index_path = os.path.join(path, "index.html")
|
||||||
if index_path in self.files:
|
if index_path in self.files:
|
||||||
# Render it
|
# Render it
|
||||||
return self.stream_individual_file(self.files[index_path])
|
return self.stream_individual_file(self.files[index_path])
|
||||||
|
@ -60,7 +68,7 @@ class WebsiteModeWeb(SendBaseModeWeb):
|
||||||
filenames = []
|
filenames = []
|
||||||
for filename in os.listdir(filesystem_path):
|
for filename in os.listdir(filesystem_path):
|
||||||
if os.path.isdir(os.path.join(filesystem_path, filename)):
|
if os.path.isdir(os.path.join(filesystem_path, filename)):
|
||||||
filenames.append(filename + '/')
|
filenames.append(filename + "/")
|
||||||
else:
|
else:
|
||||||
filenames.append(filename)
|
filenames.append(filename)
|
||||||
filenames.sort()
|
filenames.sort()
|
||||||
|
@ -78,8 +86,8 @@ class WebsiteModeWeb(SendBaseModeWeb):
|
||||||
else:
|
else:
|
||||||
# Special case loading /
|
# Special case loading /
|
||||||
|
|
||||||
if path == '':
|
if path == "":
|
||||||
index_path = 'index.html'
|
index_path = "index.html"
|
||||||
if index_path in self.files:
|
if index_path in self.files:
|
||||||
# Render it
|
# Render it
|
||||||
return self.stream_individual_file(self.files[index_path])
|
return self.stream_individual_file(self.files[index_path])
|
||||||
|
|
|
@ -32,21 +32,25 @@ from onionshare.onionshare import OnionShare
|
||||||
|
|
||||||
from .onionshare_gui import OnionShareGui
|
from .onionshare_gui import OnionShareGui
|
||||||
|
|
||||||
|
|
||||||
class Application(QtWidgets.QApplication):
|
class Application(QtWidgets.QApplication):
|
||||||
"""
|
"""
|
||||||
This is Qt's QApplication class. It has been overridden to support threads
|
This is Qt's QApplication class. It has been overridden to support threads
|
||||||
and the quick keyboard shortcut.
|
and the quick keyboard shortcut.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common):
|
def __init__(self, common):
|
||||||
if common.platform == 'Linux' or common.platform == 'BSD':
|
if common.platform == "Linux" or common.platform == "BSD":
|
||||||
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
|
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
|
||||||
QtWidgets.QApplication.__init__(self, sys.argv)
|
QtWidgets.QApplication.__init__(self, sys.argv)
|
||||||
self.installEventFilter(self)
|
self.installEventFilter(self)
|
||||||
|
|
||||||
def eventFilter(self, obj, event):
|
def eventFilter(self, obj, event):
|
||||||
if (event.type() == QtCore.QEvent.KeyPress and
|
if (
|
||||||
event.key() == QtCore.Qt.Key_Q and
|
event.type() == QtCore.QEvent.KeyPress
|
||||||
event.modifiers() == QtCore.Qt.ControlModifier):
|
and event.key() == QtCore.Qt.Key_Q
|
||||||
|
and event.modifiers() == QtCore.Qt.ControlModifier
|
||||||
|
):
|
||||||
self.quit()
|
self.quit()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -70,11 +74,34 @@ def main():
|
||||||
qtapp = Application(common)
|
qtapp = Application(common)
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48))
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)")
|
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=48)
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
|
)
|
||||||
parser.add_argument('--filenames', metavar='filenames', nargs='+', help="List of files or folders to share")
|
parser.add_argument(
|
||||||
parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
|
"--local-only",
|
||||||
|
action="store_true",
|
||||||
|
dest="local_only",
|
||||||
|
help="Don't use Tor (only for development)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v",
|
||||||
|
"--verbose",
|
||||||
|
action="store_true",
|
||||||
|
dest="verbose",
|
||||||
|
help="Log OnionShare errors to stdout, and web errors to disk",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--filenames",
|
||||||
|
metavar="filenames",
|
||||||
|
nargs="+",
|
||||||
|
help="List of files or folders to share",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
metavar="config",
|
||||||
|
default=False,
|
||||||
|
help="Custom JSON config file location (optional)",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
filenames = args.filenames
|
filenames = args.filenames
|
||||||
|
@ -118,10 +145,12 @@ def main():
|
||||||
def shutdown():
|
def shutdown():
|
||||||
onion.cleanup()
|
onion.cleanup()
|
||||||
app.cleanup()
|
app.cleanup()
|
||||||
|
|
||||||
qtapp.aboutToQuit.connect(shutdown)
|
qtapp.aboutToQuit.connect(shutdown)
|
||||||
|
|
||||||
# All done
|
# All done
|
||||||
sys.exit(qtapp.exec_())
|
sys.exit(qtapp.exec_())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -29,10 +29,12 @@ from ..threads import OnionThread
|
||||||
from ..threads import AutoStartTimer
|
from ..threads import AutoStartTimer
|
||||||
from ..widgets import Alert
|
from ..widgets import Alert
|
||||||
|
|
||||||
|
|
||||||
class Mode(QtWidgets.QWidget):
|
class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
The class that all modes inherit from
|
The class that all modes inherit from
|
||||||
"""
|
"""
|
||||||
|
|
||||||
start_server_finished = QtCore.pyqtSignal()
|
start_server_finished = QtCore.pyqtSignal()
|
||||||
stop_server_finished = QtCore.pyqtSignal()
|
stop_server_finished = QtCore.pyqtSignal()
|
||||||
starting_server_step2 = QtCore.pyqtSignal()
|
starting_server_step2 = QtCore.pyqtSignal()
|
||||||
|
@ -41,7 +43,17 @@ class Mode(QtWidgets.QWidget):
|
||||||
starting_server_early = QtCore.pyqtSignal()
|
starting_server_early = QtCore.pyqtSignal()
|
||||||
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, local_only=False):
|
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
|
||||||
|
@ -65,7 +77,9 @@ class Mode(QtWidgets.QWidget):
|
||||||
self.startup_thread = None
|
self.startup_thread = None
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only)
|
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)
|
||||||
|
@ -103,11 +117,21 @@ class Mode(QtWidgets.QWidget):
|
||||||
minutes = (secs - days * 86400 - hours * 3600) // 60
|
minutes = (secs - days * 86400 - hours * 3600) // 60
|
||||||
seconds = secs - days * 86400 - hours * 3600 - minutes * 60
|
seconds = secs - days * 86400 - hours * 3600 - minutes * 60
|
||||||
if not seconds:
|
if not seconds:
|
||||||
seconds = '0'
|
seconds = "0"
|
||||||
result = ("{0}{1}, ".format(days, strings._('days_first_letter')) if days else "") + \
|
result = (
|
||||||
("{0}{1}, ".format(hours, strings._('hours_first_letter')) if hours else "") + \
|
("{0}{1}, ".format(days, strings._("days_first_letter")) if days else "")
|
||||||
("{0}{1}, ".format(minutes, strings._('minutes_first_letter')) if minutes else "") + \
|
+ (
|
||||||
"{0}{1}".format(seconds, strings._('seconds_first_letter'))
|
"{0}{1}, ".format(hours, strings._("hours_first_letter"))
|
||||||
|
if hours
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
+ (
|
||||||
|
"{0}{1}, ".format(minutes, strings._("minutes_first_letter"))
|
||||||
|
if minutes
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
+ "{0}{1}".format(seconds, strings._("seconds_first_letter"))
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -120,25 +144,45 @@ class Mode(QtWidgets.QWidget):
|
||||||
if self.server_status.autostart_timer_datetime:
|
if self.server_status.autostart_timer_datetime:
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
if self.server_status.local_only:
|
if self.server_status.local_only:
|
||||||
seconds_remaining = now.secsTo(self.server_status.autostart_timer_widget.dateTime())
|
seconds_remaining = now.secsTo(
|
||||||
|
self.server_status.autostart_timer_widget.dateTime()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
seconds_remaining = now.secsTo(self.server_status.autostart_timer_datetime.replace(second=0, microsecond=0))
|
seconds_remaining = now.secsTo(
|
||||||
|
self.server_status.autostart_timer_datetime.replace(
|
||||||
|
second=0, microsecond=0
|
||||||
|
)
|
||||||
|
)
|
||||||
# Update the server button
|
# Update the server button
|
||||||
if seconds_remaining > 0:
|
if seconds_remaining > 0:
|
||||||
self.server_status.server_button.setText(strings._('gui_waiting_to_start').format(self.human_friendly_time(seconds_remaining)))
|
self.server_status.server_button.setText(
|
||||||
|
strings._("gui_waiting_to_start").format(
|
||||||
|
self.human_friendly_time(seconds_remaining)
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.server_status.server_button.setText(strings._('gui_please_wait'))
|
self.server_status.server_button.setText(
|
||||||
|
strings._("gui_please_wait")
|
||||||
|
)
|
||||||
|
|
||||||
# If the auto-stop timer has stopped, stop the server
|
# If the auto-stop timer has stopped, stop the server
|
||||||
if self.server_status.status == ServerStatus.STATUS_STARTED:
|
if self.server_status.status == ServerStatus.STATUS_STARTED:
|
||||||
if self.app.autostop_timer_thread and self.common.settings.get('autostop_timer'):
|
if self.app.autostop_timer_thread and self.common.settings.get(
|
||||||
|
"autostop_timer"
|
||||||
|
):
|
||||||
if self.autostop_timer_datetime_delta > 0:
|
if self.autostop_timer_datetime_delta > 0:
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
seconds_remaining = now.secsTo(self.server_status.autostop_timer_datetime)
|
seconds_remaining = now.secsTo(
|
||||||
|
self.server_status.autostop_timer_datetime
|
||||||
|
)
|
||||||
|
|
||||||
# Update the server button
|
# Update the server button
|
||||||
server_button_text = self.get_stop_server_autostop_timer_text()
|
server_button_text = self.get_stop_server_autostop_timer_text()
|
||||||
self.server_status.server_button.setText(server_button_text.format(self.human_friendly_time(seconds_remaining)))
|
self.server_status.server_button.setText(
|
||||||
|
server_button_text.format(
|
||||||
|
self.human_friendly_time(seconds_remaining)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
if not self.app.autostop_timer_thread.is_alive():
|
if not self.app.autostop_timer_thread.is_alive():
|
||||||
|
@ -168,16 +212,16 @@ class Mode(QtWidgets.QWidget):
|
||||||
Start the onionshare server. This uses multiple threads to start the Tor onion
|
Start the onionshare server. This uses multiple threads to start the Tor onion
|
||||||
server and the web app.
|
server and the web app.
|
||||||
"""
|
"""
|
||||||
self.common.log('Mode', 'start_server')
|
self.common.log("Mode", "start_server")
|
||||||
|
|
||||||
self.start_server_custom()
|
self.start_server_custom()
|
||||||
|
|
||||||
self.set_server_active.emit(True)
|
self.set_server_active.emit(True)
|
||||||
self.app.set_stealth(self.common.settings.get('use_stealth'))
|
self.app.set_stealth(self.common.settings.get("use_stealth"))
|
||||||
|
|
||||||
# Clear the status bar
|
# Clear the status bar
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_status_label.setText('')
|
self.server_status_label.setText("")
|
||||||
|
|
||||||
# Ensure we always get a new random port each time we might launch an OnionThread
|
# Ensure we always get a new random port each time we might launch an OnionThread
|
||||||
self.app.port = None
|
self.app.port = None
|
||||||
|
@ -192,7 +236,7 @@ class Mode(QtWidgets.QWidget):
|
||||||
|
|
||||||
# If scheduling a share, delay starting the real share
|
# If scheduling a share, delay starting the real share
|
||||||
if self.server_status.autostart_timer_datetime:
|
if self.server_status.autostart_timer_datetime:
|
||||||
self.common.log('Mode', 'start_server', 'Starting auto-start timer')
|
self.common.log("Mode", "start_server", "Starting auto-start timer")
|
||||||
self.startup_thread = AutoStartTimer(self)
|
self.startup_thread = AutoStartTimer(self)
|
||||||
# Once the timer has finished, start the real share, with a WebThread
|
# Once the timer has finished, start the real share, with a WebThread
|
||||||
self.startup_thread.success.connect(self.start_scheduled_service)
|
self.startup_thread.success.connect(self.start_scheduled_service)
|
||||||
|
@ -201,7 +245,7 @@ class Mode(QtWidgets.QWidget):
|
||||||
self.startup_thread.start()
|
self.startup_thread.start()
|
||||||
|
|
||||||
def start_onion_thread(self, obtain_onion_early=False):
|
def start_onion_thread(self, obtain_onion_early=False):
|
||||||
self.common.log('Mode', 'start_server', 'Starting an onion thread')
|
self.common.log("Mode", "start_server", "Starting an onion thread")
|
||||||
self.obtain_onion_early = obtain_onion_early
|
self.obtain_onion_early = obtain_onion_early
|
||||||
self.onion_thread = OnionThread(self)
|
self.onion_thread = OnionThread(self)
|
||||||
self.onion_thread.success.connect(self.starting_server_step2.emit)
|
self.onion_thread.success.connect(self.starting_server_step2.emit)
|
||||||
|
@ -213,7 +257,7 @@ class Mode(QtWidgets.QWidget):
|
||||||
# We start a new OnionThread with the saved scheduled key from settings
|
# We start a new OnionThread with the saved scheduled key from settings
|
||||||
self.common.settings.load()
|
self.common.settings.load()
|
||||||
self.obtain_onion_early = obtain_onion_early
|
self.obtain_onion_early = obtain_onion_early
|
||||||
self.common.log('Mode', 'start_server', 'Starting a scheduled onion thread')
|
self.common.log("Mode", "start_server", "Starting a scheduled onion thread")
|
||||||
self.onion_thread = OnionThread(self)
|
self.onion_thread = OnionThread(self)
|
||||||
self.onion_thread.success.connect(self.starting_server_step2.emit)
|
self.onion_thread.success.connect(self.starting_server_step2.emit)
|
||||||
self.onion_thread.error.connect(self.starting_server_error.emit)
|
self.onion_thread.error.connect(self.starting_server_error.emit)
|
||||||
|
@ -237,7 +281,7 @@ class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Step 2 in starting the onionshare server.
|
Step 2 in starting the onionshare server.
|
||||||
"""
|
"""
|
||||||
self.common.log('Mode', 'start_server_step2')
|
self.common.log("Mode", "start_server_step2")
|
||||||
|
|
||||||
self.start_server_step2_custom()
|
self.start_server_step2_custom()
|
||||||
|
|
||||||
|
@ -257,22 +301,28 @@ class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Step 3 in starting the onionshare server.
|
Step 3 in starting the onionshare server.
|
||||||
"""
|
"""
|
||||||
self.common.log('Mode', 'start_server_step3')
|
self.common.log("Mode", "start_server_step3")
|
||||||
|
|
||||||
self.start_server_step3_custom()
|
self.start_server_step3_custom()
|
||||||
|
|
||||||
if self.common.settings.get('autostop_timer'):
|
if self.common.settings.get("autostop_timer"):
|
||||||
# Convert the date value to seconds between now and then
|
# Convert the date value to seconds between now and then
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
self.autostop_timer_datetime_delta = now.secsTo(self.server_status.autostop_timer_datetime)
|
self.autostop_timer_datetime_delta = now.secsTo(
|
||||||
|
self.server_status.autostop_timer_datetime
|
||||||
|
)
|
||||||
# Start the auto-stop timer
|
# Start the auto-stop timer
|
||||||
if self.autostop_timer_datetime_delta > 0:
|
if self.autostop_timer_datetime_delta > 0:
|
||||||
self.app.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer_datetime_delta)
|
self.app.autostop_timer_thread = AutoStopTimer(
|
||||||
|
self.common, self.autostop_timer_datetime_delta
|
||||||
|
)
|
||||||
self.app.autostop_timer_thread.start()
|
self.app.autostop_timer_thread.start()
|
||||||
# The auto-stop timer has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
|
# The auto-stop timer has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
|
||||||
else:
|
else:
|
||||||
self.stop_server()
|
self.stop_server()
|
||||||
self.start_server_error(strings._('gui_server_started_after_autostop_timer'))
|
self.start_server_error(
|
||||||
|
strings._("gui_server_started_after_autostop_timer")
|
||||||
|
)
|
||||||
|
|
||||||
def start_server_step3_custom(self):
|
def start_server_step3_custom(self):
|
||||||
"""
|
"""
|
||||||
|
@ -284,7 +334,7 @@ class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
If there's an error when trying to start the onion service
|
If there's an error when trying to start the onion service
|
||||||
"""
|
"""
|
||||||
self.common.log('Mode', 'start_server_error')
|
self.common.log("Mode", "start_server_error")
|
||||||
|
|
||||||
Alert(self.common, error, QtWidgets.QMessageBox.Warning)
|
Alert(self.common, error, QtWidgets.QMessageBox.Warning)
|
||||||
self.set_server_active.emit(False)
|
self.set_server_active.emit(False)
|
||||||
|
@ -305,16 +355,16 @@ class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
self.cancel_server_custom()
|
self.cancel_server_custom()
|
||||||
if self.startup_thread:
|
if self.startup_thread:
|
||||||
self.common.log('Mode', 'cancel_server: quitting startup thread')
|
self.common.log("Mode", "cancel_server: quitting startup thread")
|
||||||
self.startup_thread.canceled = True
|
self.startup_thread.canceled = True
|
||||||
self.app.onion.scheduled_key = None
|
self.app.onion.scheduled_key = None
|
||||||
self.app.onion.scheduled_auth_cookie = None
|
self.app.onion.scheduled_auth_cookie = None
|
||||||
self.startup_thread.quit()
|
self.startup_thread.quit()
|
||||||
if self.onion_thread:
|
if self.onion_thread:
|
||||||
self.common.log('Mode', 'cancel_server: quitting onion thread')
|
self.common.log("Mode", "cancel_server: quitting onion thread")
|
||||||
self.onion_thread.quit()
|
self.onion_thread.quit()
|
||||||
if self.web_thread:
|
if self.web_thread:
|
||||||
self.common.log('Mode', 'cancel_server: quitting web thread')
|
self.common.log("Mode", "cancel_server: quitting web thread")
|
||||||
self.web_thread.quit()
|
self.web_thread.quit()
|
||||||
self.stop_server()
|
self.stop_server()
|
||||||
|
|
||||||
|
@ -328,7 +378,7 @@ class Mode(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Stop the onionshare server.
|
Stop the onionshare server.
|
||||||
"""
|
"""
|
||||||
self.common.log('Mode', 'stop_server')
|
self.common.log("Mode", "stop_server")
|
||||||
|
|
||||||
if self.server_status.status != ServerStatus.STATUS_STOPPED:
|
if self.server_status.status != ServerStatus.STATUS_STOPPED:
|
||||||
try:
|
try:
|
||||||
|
@ -382,7 +432,9 @@ class Mode(QtWidgets.QWidget):
|
||||||
Handle REQUEST_RATE_LIMIT event.
|
Handle REQUEST_RATE_LIMIT event.
|
||||||
"""
|
"""
|
||||||
self.stop_server()
|
self.stop_server()
|
||||||
Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
|
Alert(
|
||||||
|
self.common, strings._("error_rate_limit"), QtWidgets.QMessageBox.Critical
|
||||||
|
)
|
||||||
|
|
||||||
def handle_request_progress(self, event):
|
def handle_request_progress(self, event):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -24,11 +24,13 @@ from onionshare import strings
|
||||||
|
|
||||||
from ..widgets import Alert, AddFileDialog
|
from ..widgets import Alert, AddFileDialog
|
||||||
|
|
||||||
|
|
||||||
class DropHereLabel(QtWidgets.QLabel):
|
class DropHereLabel(QtWidgets.QLabel):
|
||||||
"""
|
"""
|
||||||
When there are no files or folders in the FileList yet, display the
|
When there are no files or folders in the FileList yet, display the
|
||||||
'drop files here' message and graphic.
|
'drop files here' message and graphic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, parent, image=False):
|
def __init__(self, common, parent, image=False):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
super(DropHereLabel, self).__init__(parent=parent)
|
super(DropHereLabel, self).__init__(parent=parent)
|
||||||
|
@ -39,10 +41,16 @@ class DropHereLabel(QtWidgets.QLabel):
|
||||||
self.setAlignment(QtCore.Qt.AlignCenter)
|
self.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
|
||||||
if image:
|
if image:
|
||||||
self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png'))))
|
self.setPixmap(
|
||||||
|
QtGui.QPixmap.fromImage(
|
||||||
|
QtGui.QImage(
|
||||||
|
self.common.get_resource_path("images/logo_transparent.png")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.setText(strings._('gui_drag_and_drop'))
|
self.setText(strings._("gui_drag_and_drop"))
|
||||||
self.setStyleSheet(self.common.css['share_file_selection_drop_here_label'])
|
self.setStyleSheet(self.common.css["share_file_selection_drop_here_label"])
|
||||||
|
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
|
@ -57,6 +65,7 @@ class DropCountLabel(QtWidgets.QLabel):
|
||||||
While dragging files over the FileList, this counter displays the
|
While dragging files over the FileList, this counter displays the
|
||||||
number of files you're dragging.
|
number of files you're dragging.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, parent):
|
def __init__(self, common, parent):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
super(DropCountLabel, self).__init__(parent=parent)
|
super(DropCountLabel, self).__init__(parent=parent)
|
||||||
|
@ -65,8 +74,8 @@ class DropCountLabel(QtWidgets.QLabel):
|
||||||
|
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setAlignment(QtCore.Qt.AlignCenter)
|
self.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.setText(strings._('gui_drag_and_drop'))
|
self.setText(strings._("gui_drag_and_drop"))
|
||||||
self.setStyleSheet(self.common.css['share_file_selection_drop_count_label'])
|
self.setStyleSheet(self.common.css["share_file_selection_drop_count_label"])
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
|
@ -78,6 +87,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
"""
|
"""
|
||||||
The list of files and folders in the GUI.
|
The list of files and folders in the GUI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
files_dropped = QtCore.pyqtSignal()
|
files_dropped = QtCore.pyqtSignal()
|
||||||
files_updated = QtCore.pyqtSignal()
|
files_updated = QtCore.pyqtSignal()
|
||||||
|
|
||||||
|
@ -139,7 +149,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
if self.count() > 0:
|
if self.count() > 0:
|
||||||
# Add and delete an empty item, to force all items to get redrawn
|
# Add and delete an empty item, to force all items to get redrawn
|
||||||
# This is ugly, but the only way I could figure out how to proceed
|
# This is ugly, but the only way I could figure out how to proceed
|
||||||
item = QtWidgets.QListWidgetItem('fake item')
|
item = QtWidgets.QListWidgetItem("fake item")
|
||||||
self.addItem(item)
|
self.addItem(item)
|
||||||
self.takeItem(self.row(item))
|
self.takeItem(self.row(item))
|
||||||
self.update()
|
self.update()
|
||||||
|
@ -149,21 +159,27 @@ class FileList(QtWidgets.QListWidget):
|
||||||
# and extend based on the overall width minus that amount.
|
# and extend based on the overall width minus that amount.
|
||||||
for index in range(self.count()):
|
for index in range(self.count()):
|
||||||
metrics = QtGui.QFontMetrics(self.item(index).font())
|
metrics = QtGui.QFontMetrics(self.item(index).font())
|
||||||
elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200)
|
elided = metrics.elidedText(
|
||||||
|
self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200
|
||||||
|
)
|
||||||
self.item(index).setText(elided)
|
self.item(index).setText(elided)
|
||||||
|
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
"""
|
"""
|
||||||
dragEnterEvent for dragging files and directories into the widget.
|
dragEnterEvent for dragging files and directories into the widget.
|
||||||
"""
|
"""
|
||||||
if event.mimeData().hasUrls:
|
if event.mimeData().hasUrls:
|
||||||
self.setStyleSheet(self.common.css['share_file_list_drag_enter'])
|
self.setStyleSheet(self.common.css["share_file_list_drag_enter"])
|
||||||
count = len(event.mimeData().urls())
|
count = len(event.mimeData().urls())
|
||||||
self.drop_count.setText('+{}'.format(count))
|
self.drop_count.setText("+{}".format(count))
|
||||||
|
|
||||||
size_hint = self.drop_count.sizeHint()
|
size_hint = self.drop_count.sizeHint()
|
||||||
self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height())
|
self.drop_count.setGeometry(
|
||||||
|
self.width() - size_hint.width() - 10,
|
||||||
|
self.height() - size_hint.height() - 10,
|
||||||
|
size_hint.width(),
|
||||||
|
size_hint.height(),
|
||||||
|
)
|
||||||
self.drop_count.show()
|
self.drop_count.show()
|
||||||
event.accept()
|
event.accept()
|
||||||
else:
|
else:
|
||||||
|
@ -173,7 +189,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
"""
|
"""
|
||||||
dragLeaveEvent for dragging files and directories into the widget.
|
dragLeaveEvent for dragging files and directories into the widget.
|
||||||
"""
|
"""
|
||||||
self.setStyleSheet(self.common.css['share_file_list_drag_leave'])
|
self.setStyleSheet(self.common.css["share_file_list_drag_leave"])
|
||||||
self.drop_count.hide()
|
self.drop_count.hide()
|
||||||
event.accept()
|
event.accept()
|
||||||
self.update()
|
self.update()
|
||||||
|
@ -201,7 +217,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
else:
|
else:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
|
|
||||||
self.setStyleSheet(self.common.css['share_file_list_drag_leave'])
|
self.setStyleSheet(self.common.css["share_file_list_drag_leave"])
|
||||||
self.drop_count.hide()
|
self.drop_count.hide()
|
||||||
|
|
||||||
self.files_dropped.emit()
|
self.files_dropped.emit()
|
||||||
|
@ -238,12 +254,14 @@ class FileList(QtWidgets.QListWidget):
|
||||||
# Item's filename attribute and size labels
|
# Item's filename attribute and size labels
|
||||||
item.filename = filename
|
item.filename = filename
|
||||||
item_size = QtWidgets.QLabel(size_readable)
|
item_size = QtWidgets.QLabel(size_readable)
|
||||||
item_size.setStyleSheet(self.common.css['share_file_list_item_size'])
|
item_size.setStyleSheet(self.common.css["share_file_list_item_size"])
|
||||||
|
|
||||||
item.basename = os.path.basename(filename.rstrip('/'))
|
item.basename = os.path.basename(filename.rstrip("/"))
|
||||||
# Use the basename as the method with which to sort the list
|
# Use the basename as the method with which to sort the list
|
||||||
metrics = QtGui.QFontMetrics(item.font())
|
metrics = QtGui.QFontMetrics(item.font())
|
||||||
elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width())
|
elided = metrics.elidedText(
|
||||||
|
item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()
|
||||||
|
)
|
||||||
item.setData(QtCore.Qt.DisplayRole, elided)
|
item.setData(QtCore.Qt.DisplayRole, elided)
|
||||||
|
|
||||||
# Item's delete button
|
# Item's delete button
|
||||||
|
@ -255,9 +273,13 @@ class FileList(QtWidgets.QListWidget):
|
||||||
item.item_button = QtWidgets.QPushButton()
|
item.item_button = QtWidgets.QPushButton()
|
||||||
item.item_button.setDefault(False)
|
item.item_button.setDefault(False)
|
||||||
item.item_button.setFlat(True)
|
item.item_button.setFlat(True)
|
||||||
item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) )
|
item.item_button.setIcon(
|
||||||
|
QtGui.QIcon(self.common.get_resource_path("images/file_delete.png"))
|
||||||
|
)
|
||||||
item.item_button.clicked.connect(delete_item)
|
item.item_button.clicked.connect(delete_item)
|
||||||
item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
item.item_button.setSizePolicy(
|
||||||
|
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed
|
||||||
|
)
|
||||||
|
|
||||||
# Item info widget, with a white background
|
# Item info widget, with a white background
|
||||||
item_info_layout = QtWidgets.QHBoxLayout()
|
item_info_layout = QtWidgets.QHBoxLayout()
|
||||||
|
@ -265,7 +287,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
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()
|
||||||
item_info.setObjectName('item-info')
|
item_info.setObjectName("item-info")
|
||||||
item_info.setLayout(item_info_layout)
|
item_info.setLayout(item_info_layout)
|
||||||
|
|
||||||
# Create the item's widget and layouts
|
# Create the item's widget and layouts
|
||||||
|
@ -288,6 +310,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
The list of files and folders in the GUI, as well as buttons to add and
|
The list of files and folders in the GUI, as well as buttons to add and
|
||||||
delete the files and folders.
|
delete the files and folders.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, parent):
|
def __init__(self, common, parent):
|
||||||
super(FileSelection, self).__init__()
|
super(FileSelection, self).__init__()
|
||||||
|
|
||||||
|
@ -303,21 +326,21 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
self.file_list.files_updated.connect(self.update)
|
self.file_list.files_updated.connect(self.update)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
if self.common.platform == 'Darwin':
|
if self.common.platform == "Darwin":
|
||||||
# The macOS sandbox makes it so the Mac version needs separate add files
|
# The macOS sandbox makes it so the Mac version needs separate add files
|
||||||
# and folders buttons, in order to use native file selection dialogs
|
# and folders buttons, in order to use native file selection dialogs
|
||||||
self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files'))
|
self.add_files_button = QtWidgets.QPushButton(strings._("gui_add_files"))
|
||||||
self.add_files_button.clicked.connect(self.add_files)
|
self.add_files_button.clicked.connect(self.add_files)
|
||||||
self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder'))
|
self.add_folder_button = QtWidgets.QPushButton(strings._("gui_add_folder"))
|
||||||
self.add_folder_button.clicked.connect(self.add_folder)
|
self.add_folder_button.clicked.connect(self.add_folder)
|
||||||
else:
|
else:
|
||||||
self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
|
self.add_button = QtWidgets.QPushButton(strings._("gui_add"))
|
||||||
self.add_button.clicked.connect(self.add)
|
self.add_button.clicked.connect(self.add)
|
||||||
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete'))
|
self.delete_button = QtWidgets.QPushButton(strings._("gui_delete"))
|
||||||
self.delete_button.clicked.connect(self.delete)
|
self.delete_button.clicked.connect(self.delete)
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
button_layout = QtWidgets.QHBoxLayout()
|
||||||
button_layout.addStretch()
|
button_layout.addStretch()
|
||||||
if self.common.platform == 'Darwin':
|
if self.common.platform == "Darwin":
|
||||||
button_layout.addWidget(self.add_files_button)
|
button_layout.addWidget(self.add_files_button)
|
||||||
button_layout.addWidget(self.add_folder_button)
|
button_layout.addWidget(self.add_folder_button)
|
||||||
else:
|
else:
|
||||||
|
@ -336,14 +359,14 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
"""
|
"""
|
||||||
# All buttons should be hidden if the server is on
|
# All buttons should be hidden if the server is on
|
||||||
if self.server_on:
|
if self.server_on:
|
||||||
if self.common.platform == 'Darwin':
|
if self.common.platform == "Darwin":
|
||||||
self.add_files_button.hide()
|
self.add_files_button.hide()
|
||||||
self.add_folder_button.hide()
|
self.add_folder_button.hide()
|
||||||
else:
|
else:
|
||||||
self.add_button.hide()
|
self.add_button.hide()
|
||||||
self.delete_button.hide()
|
self.delete_button.hide()
|
||||||
else:
|
else:
|
||||||
if self.common.platform == 'Darwin':
|
if self.common.platform == "Darwin":
|
||||||
self.add_files_button.show()
|
self.add_files_button.show()
|
||||||
self.add_folder_button.show()
|
self.add_folder_button.show()
|
||||||
else:
|
else:
|
||||||
|
@ -362,7 +385,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
"""
|
"""
|
||||||
Add button clicked.
|
Add button clicked.
|
||||||
"""
|
"""
|
||||||
file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items'))
|
file_dialog = AddFileDialog(self.common, caption=strings._("gui_choose_items"))
|
||||||
if file_dialog.exec_() == QtWidgets.QDialog.Accepted:
|
if file_dialog.exec_() == QtWidgets.QDialog.Accepted:
|
||||||
for filename in file_dialog.selectedFiles():
|
for filename in file_dialog.selectedFiles():
|
||||||
self.file_list.add_file(filename)
|
self.file_list.add_file(filename)
|
||||||
|
@ -374,7 +397,9 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
"""
|
"""
|
||||||
Add files button clicked.
|
Add files button clicked.
|
||||||
"""
|
"""
|
||||||
files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items'))
|
files = QtWidgets.QFileDialog.getOpenFileNames(
|
||||||
|
self.parent, caption=strings._("gui_choose_items")
|
||||||
|
)
|
||||||
filenames = files[0]
|
filenames = files[0]
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
self.file_list.add_file(filename)
|
self.file_list.add_file(filename)
|
||||||
|
@ -383,9 +408,11 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
"""
|
"""
|
||||||
Add folder button clicked.
|
Add folder button clicked.
|
||||||
"""
|
"""
|
||||||
filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent,
|
filename = QtWidgets.QFileDialog.getExistingDirectory(
|
||||||
caption=strings._('gui_choose_items'),
|
self.parent,
|
||||||
options=QtWidgets.QFileDialog.ShowDirsOnly)
|
caption=strings._("gui_choose_items"),
|
||||||
|
options=QtWidgets.QFileDialog.ShowDirsOnly,
|
||||||
|
)
|
||||||
self.file_list.add_file(filename)
|
self.file_list.add_file(filename)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
|
|
@ -31,6 +31,7 @@ class HistoryItem(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
The base history item
|
The base history item
|
||||||
"""
|
"""
|
||||||
|
|
||||||
STATUS_STARTED = 0
|
STATUS_STARTED = 0
|
||||||
STATUS_FINISHED = 1
|
STATUS_FINISHED = 1
|
||||||
STATUS_CANCELED = 2
|
STATUS_CANCELED = 2
|
||||||
|
@ -49,34 +50,42 @@ class HistoryItem(QtWidgets.QWidget):
|
||||||
When an item finishes, returns a string displaying the start/end datetime range.
|
When an item finishes, returns a string displaying the start/end datetime range.
|
||||||
started is a datetime object.
|
started is a datetime object.
|
||||||
"""
|
"""
|
||||||
return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started)
|
return self._get_label_text(
|
||||||
|
"gui_all_modes_transfer_finished",
|
||||||
|
"gui_all_modes_transfer_finished_range",
|
||||||
|
started,
|
||||||
|
)
|
||||||
|
|
||||||
def get_canceled_label_text(self, started):
|
def get_canceled_label_text(self, started):
|
||||||
"""
|
"""
|
||||||
When an item is canceled, returns a string displaying the start/end datetime range.
|
When an item is canceled, returns a string displaying the start/end datetime range.
|
||||||
started is a datetime object.
|
started is a datetime object.
|
||||||
"""
|
"""
|
||||||
return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started)
|
return self._get_label_text(
|
||||||
|
"gui_all_modes_transfer_canceled",
|
||||||
|
"gui_all_modes_transfer_canceled_range",
|
||||||
|
started,
|
||||||
|
)
|
||||||
|
|
||||||
def _get_label_text(self, string_name, string_range_name, started):
|
def _get_label_text(self, string_name, string_range_name, started):
|
||||||
"""
|
"""
|
||||||
Return a string that contains a date, or date range.
|
Return a string that contains a date, or date range.
|
||||||
"""
|
"""
|
||||||
ended = datetime.now()
|
ended = datetime.now()
|
||||||
if started.year == ended.year and started.month == ended.month and started.day == ended.day:
|
if (
|
||||||
|
started.year == ended.year
|
||||||
|
and started.month == ended.month
|
||||||
|
and started.day == ended.day
|
||||||
|
):
|
||||||
if started.hour == ended.hour and started.minute == ended.minute:
|
if started.hour == ended.hour and started.minute == ended.minute:
|
||||||
text = strings._(string_name).format(
|
text = strings._(string_name).format(started.strftime("%b %d, %I:%M%p"))
|
||||||
started.strftime("%b %d, %I:%M%p")
|
else:
|
||||||
|
text = strings._(string_range_name).format(
|
||||||
|
started.strftime("%b %d, %I:%M%p"), ended.strftime("%I:%M%p")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = strings._(string_range_name).format(
|
text = strings._(string_range_name).format(
|
||||||
started.strftime("%b %d, %I:%M%p"),
|
started.strftime("%b %d, %I:%M%p"), ended.strftime("%b %d, %I:%M%p")
|
||||||
ended.strftime("%I:%M%p")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
text = strings._(string_range_name).format(
|
|
||||||
started.strftime("%b %d, %I:%M%p"),
|
|
||||||
ended.strftime("%b %d, %I:%M%p")
|
|
||||||
)
|
)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
@ -85,6 +94,7 @@ class ShareHistoryItem(HistoryItem):
|
||||||
"""
|
"""
|
||||||
Download history item, for share mode
|
Download history item, for share mode
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, id, total_bytes):
|
def __init__(self, common, id, total_bytes):
|
||||||
super(ShareHistoryItem, self).__init__()
|
super(ShareHistoryItem, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
|
@ -97,7 +107,11 @@ class ShareHistoryItem(HistoryItem):
|
||||||
self.status = HistoryItem.STATUS_STARTED
|
self.status = HistoryItem.STATUS_STARTED
|
||||||
|
|
||||||
# Label
|
# Label
|
||||||
self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p")))
|
self.label = QtWidgets.QLabel(
|
||||||
|
strings._("gui_all_modes_transfer_started").format(
|
||||||
|
self.started_dt.strftime("%b %d, %I:%M%p")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Progress bar
|
# Progress bar
|
||||||
self.progress_bar = QtWidgets.QProgressBar()
|
self.progress_bar = QtWidgets.QProgressBar()
|
||||||
|
@ -107,7 +121,9 @@ class ShareHistoryItem(HistoryItem):
|
||||||
self.progress_bar.setMinimum(0)
|
self.progress_bar.setMinimum(0)
|
||||||
self.progress_bar.setMaximum(total_bytes)
|
self.progress_bar.setMaximum(total_bytes)
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
|
self.progress_bar.setStyleSheet(
|
||||||
|
self.common.css["downloads_uploads_progress_bar"]
|
||||||
|
)
|
||||||
self.progress_bar.total_bytes = total_bytes
|
self.progress_bar.total_bytes = total_bytes
|
||||||
|
|
||||||
# Layout
|
# Layout
|
||||||
|
@ -124,8 +140,9 @@ class ShareHistoryItem(HistoryItem):
|
||||||
|
|
||||||
self.progress_bar.setValue(downloaded_bytes)
|
self.progress_bar.setValue(downloaded_bytes)
|
||||||
if downloaded_bytes == self.progress_bar.total_bytes:
|
if downloaded_bytes == self.progress_bar.total_bytes:
|
||||||
pb_fmt = strings._('gui_all_modes_progress_complete').format(
|
pb_fmt = strings._("gui_all_modes_progress_complete").format(
|
||||||
self.common.format_seconds(time.time() - self.started))
|
self.common.format_seconds(time.time() - self.started)
|
||||||
|
)
|
||||||
|
|
||||||
# Change the label
|
# Change the label
|
||||||
self.label.setText(self.get_finished_label_text(self.started_dt))
|
self.label.setText(self.get_finished_label_text(self.started_dt))
|
||||||
|
@ -137,24 +154,26 @@ class ShareHistoryItem(HistoryItem):
|
||||||
# Wait a couple of seconds for the download rate to stabilize.
|
# Wait a couple of seconds for the download rate to stabilize.
|
||||||
# This prevents a "Windows copy dialog"-esque experience at
|
# This prevents a "Windows copy dialog"-esque experience at
|
||||||
# the beginning of the download.
|
# the beginning of the download.
|
||||||
pb_fmt = strings._('gui_all_modes_progress_starting').format(
|
pb_fmt = strings._("gui_all_modes_progress_starting").format(
|
||||||
self.common.human_readable_filesize(downloaded_bytes))
|
self.common.human_readable_filesize(downloaded_bytes)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
pb_fmt = strings._('gui_all_modes_progress_eta').format(
|
pb_fmt = strings._("gui_all_modes_progress_eta").format(
|
||||||
self.common.human_readable_filesize(downloaded_bytes),
|
self.common.human_readable_filesize(downloaded_bytes),
|
||||||
self.estimated_time_remaining)
|
self.estimated_time_remaining,
|
||||||
|
)
|
||||||
|
|
||||||
self.progress_bar.setFormat(pb_fmt)
|
self.progress_bar.setFormat(pb_fmt)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
self.progress_bar.setFormat(strings._('gui_canceled'))
|
self.progress_bar.setFormat(strings._("gui_canceled"))
|
||||||
self.status = HistoryItem.STATUS_CANCELED
|
self.status = HistoryItem.STATUS_CANCELED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def estimated_time_remaining(self):
|
def estimated_time_remaining(self):
|
||||||
return self.common.estimated_time_remaining(self.downloaded_bytes,
|
return self.common.estimated_time_remaining(
|
||||||
self.total_bytes,
|
self.downloaded_bytes, self.total_bytes, self.started
|
||||||
self.started)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReceiveHistoryItemFile(QtWidgets.QWidget):
|
class ReceiveHistoryItemFile(QtWidgets.QWidget):
|
||||||
|
@ -162,7 +181,9 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget):
|
||||||
super(ReceiveHistoryItemFile, self).__init__()
|
super(ReceiveHistoryItemFile, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename))
|
self.common.log(
|
||||||
|
"ReceiveHistoryItemFile", "__init__", "filename: {}".format(filename)
|
||||||
|
)
|
||||||
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.dir = None
|
self.dir = None
|
||||||
|
@ -174,11 +195,13 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget):
|
||||||
|
|
||||||
# File size label
|
# File size label
|
||||||
self.filesize_label = QtWidgets.QLabel()
|
self.filesize_label = QtWidgets.QLabel()
|
||||||
self.filesize_label.setStyleSheet(self.common.css['receive_file_size'])
|
self.filesize_label.setStyleSheet(self.common.css["receive_file_size"])
|
||||||
self.filesize_label.hide()
|
self.filesize_label.hide()
|
||||||
|
|
||||||
# Folder button
|
# Folder button
|
||||||
folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png')))
|
folder_pixmap = QtGui.QPixmap.fromImage(
|
||||||
|
QtGui.QImage(self.common.get_resource_path("images/open_folder.png"))
|
||||||
|
)
|
||||||
folder_icon = QtGui.QIcon(folder_pixmap)
|
folder_icon = QtGui.QIcon(folder_pixmap)
|
||||||
self.folder_button = QtWidgets.QPushButton()
|
self.folder_button = QtWidgets.QPushButton()
|
||||||
self.folder_button.clicked.connect(self.open_folder)
|
self.folder_button.clicked.connect(self.open_folder)
|
||||||
|
@ -213,29 +236,36 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Open the downloads folder, with the file selected, in a cross-platform manner
|
Open the downloads folder, with the file selected, in a cross-platform manner
|
||||||
"""
|
"""
|
||||||
self.common.log('ReceiveHistoryItemFile', 'open_folder')
|
self.common.log("ReceiveHistoryItemFile", "open_folder")
|
||||||
|
|
||||||
if not self.dir:
|
if not self.dir:
|
||||||
self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder")
|
self.common.log(
|
||||||
|
"ReceiveHistoryItemFile",
|
||||||
|
"open_folder",
|
||||||
|
"dir has not been set yet, can't open folder",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
abs_filename = os.path.join(self.dir, self.filename)
|
abs_filename = os.path.join(self.dir, self.filename)
|
||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
if self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
if self.common.platform == "Linux" or self.common.platform == "BSD":
|
||||||
try:
|
try:
|
||||||
# If nautilus is available, open it
|
# If nautilus is available, open it
|
||||||
subprocess.Popen(['nautilus', abs_filename])
|
subprocess.Popen(["nautilus", abs_filename])
|
||||||
except:
|
except:
|
||||||
Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename))
|
Alert(
|
||||||
|
self.common,
|
||||||
|
strings._("gui_open_folder_error_nautilus").format(abs_filename),
|
||||||
|
)
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
elif self.common.platform == 'Darwin':
|
elif self.common.platform == "Darwin":
|
||||||
subprocess.call(['open', '-R', abs_filename])
|
subprocess.call(["open", "-R", abs_filename])
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
elif self.common.platform == 'Windows':
|
elif self.common.platform == "Windows":
|
||||||
subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)])
|
subprocess.Popen(["explorer", "/select,{}".format(abs_filename)])
|
||||||
|
|
||||||
|
|
||||||
class ReceiveHistoryItem(HistoryItem):
|
class ReceiveHistoryItem(HistoryItem):
|
||||||
|
@ -248,7 +278,11 @@ class ReceiveHistoryItem(HistoryItem):
|
||||||
self.status = HistoryItem.STATUS_STARTED
|
self.status = HistoryItem.STATUS_STARTED
|
||||||
|
|
||||||
# Label
|
# Label
|
||||||
self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p")))
|
self.label = QtWidgets.QLabel(
|
||||||
|
strings._("gui_all_modes_transfer_started").format(
|
||||||
|
self.started.strftime("%b %d, %I:%M%p")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Progress bar
|
# Progress bar
|
||||||
self.progress_bar = QtWidgets.QProgressBar()
|
self.progress_bar = QtWidgets.QProgressBar()
|
||||||
|
@ -257,13 +291,15 @@ class ReceiveHistoryItem(HistoryItem):
|
||||||
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
|
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
|
||||||
self.progress_bar.setMinimum(0)
|
self.progress_bar.setMinimum(0)
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
|
self.progress_bar.setStyleSheet(
|
||||||
|
self.common.css["downloads_uploads_progress_bar"]
|
||||||
|
)
|
||||||
|
|
||||||
# This layout contains file widgets
|
# This layout contains file widgets
|
||||||
self.files_layout = QtWidgets.QVBoxLayout()
|
self.files_layout = QtWidgets.QVBoxLayout()
|
||||||
self.files_layout.setContentsMargins(0, 0, 0, 0)
|
self.files_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
files_widget = QtWidgets.QWidget()
|
files_widget = QtWidgets.QWidget()
|
||||||
files_widget.setStyleSheet(self.common.css['receive_file'])
|
files_widget.setStyleSheet(self.common.css["receive_file"])
|
||||||
files_widget.setLayout(self.files_layout)
|
files_widget.setLayout(self.files_layout)
|
||||||
|
|
||||||
# Layout
|
# Layout
|
||||||
|
@ -282,10 +318,10 @@ class ReceiveHistoryItem(HistoryItem):
|
||||||
Using the progress from Web, update the progress bar and file size labels
|
Using the progress from Web, update the progress bar and file size labels
|
||||||
for each file
|
for each file
|
||||||
"""
|
"""
|
||||||
if data['action'] == 'progress':
|
if data["action"] == "progress":
|
||||||
total_uploaded_bytes = 0
|
total_uploaded_bytes = 0
|
||||||
for filename in data['progress']:
|
for filename in data["progress"]:
|
||||||
total_uploaded_bytes += data['progress'][filename]['uploaded_bytes']
|
total_uploaded_bytes += data["progress"][filename]["uploaded_bytes"]
|
||||||
|
|
||||||
# Update the progress bar
|
# Update the progress bar
|
||||||
self.progress_bar.setMaximum(self.content_length)
|
self.progress_bar.setMaximum(self.content_length)
|
||||||
|
@ -293,35 +329,39 @@ class ReceiveHistoryItem(HistoryItem):
|
||||||
|
|
||||||
elapsed = datetime.now() - self.started
|
elapsed = datetime.now() - self.started
|
||||||
if elapsed.seconds < 10:
|
if elapsed.seconds < 10:
|
||||||
pb_fmt = strings._('gui_all_modes_progress_starting').format(
|
pb_fmt = strings._("gui_all_modes_progress_starting").format(
|
||||||
self.common.human_readable_filesize(total_uploaded_bytes))
|
self.common.human_readable_filesize(total_uploaded_bytes)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
estimated_time_remaining = self.common.estimated_time_remaining(
|
estimated_time_remaining = self.common.estimated_time_remaining(
|
||||||
total_uploaded_bytes,
|
total_uploaded_bytes, self.content_length, self.started.timestamp()
|
||||||
self.content_length,
|
)
|
||||||
self.started.timestamp())
|
pb_fmt = strings._("gui_all_modes_progress_eta").format(
|
||||||
pb_fmt = strings._('gui_all_modes_progress_eta').format(
|
|
||||||
self.common.human_readable_filesize(total_uploaded_bytes),
|
self.common.human_readable_filesize(total_uploaded_bytes),
|
||||||
estimated_time_remaining)
|
estimated_time_remaining,
|
||||||
|
)
|
||||||
|
|
||||||
# Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration"
|
# Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration"
|
||||||
for filename in list(data['progress']):
|
for filename in list(data["progress"]):
|
||||||
# Add a new file if needed
|
# Add a new file if needed
|
||||||
if filename not in self.files:
|
if filename not in self.files:
|
||||||
self.files[filename] = ReceiveHistoryItemFile(self.common, filename)
|
self.files[filename] = ReceiveHistoryItemFile(self.common, filename)
|
||||||
self.files_layout.addWidget(self.files[filename])
|
self.files_layout.addWidget(self.files[filename])
|
||||||
|
|
||||||
# Update the file
|
# Update the file
|
||||||
self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete'])
|
self.files[filename].update(
|
||||||
|
data["progress"][filename]["uploaded_bytes"],
|
||||||
|
data["progress"][filename]["complete"],
|
||||||
|
)
|
||||||
|
|
||||||
elif data['action'] == 'rename':
|
elif data["action"] == "rename":
|
||||||
self.files[data['old_filename']].rename(data['new_filename'])
|
self.files[data["old_filename"]].rename(data["new_filename"])
|
||||||
self.files[data['new_filename']] = self.files.pop(data['old_filename'])
|
self.files[data["new_filename"]] = self.files.pop(data["old_filename"])
|
||||||
|
|
||||||
elif data['action'] == 'set_dir':
|
elif data["action"] == "set_dir":
|
||||||
self.files[data['filename']].set_dir(data['dir'])
|
self.files[data["filename"]].set_dir(data["dir"])
|
||||||
|
|
||||||
elif data['action'] == 'finished':
|
elif data["action"] == "finished":
|
||||||
# Change the status
|
# Change the status
|
||||||
self.status = HistoryItem.STATUS_FINISHED
|
self.status = HistoryItem.STATUS_FINISHED
|
||||||
|
|
||||||
|
@ -331,7 +371,7 @@ class ReceiveHistoryItem(HistoryItem):
|
||||||
# Change the label
|
# Change the label
|
||||||
self.label.setText(self.get_finished_label_text(self.started))
|
self.label.setText(self.get_finished_label_text(self.started))
|
||||||
|
|
||||||
elif data['action'] == 'canceled':
|
elif data["action"] == "canceled":
|
||||||
# Change the status
|
# Change the status
|
||||||
self.status = HistoryItem.STATUS_CANCELED
|
self.status = HistoryItem.STATUS_CANCELED
|
||||||
|
|
||||||
|
@ -346,6 +386,7 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||||
"""
|
"""
|
||||||
Individual file history item, for share mode viewing of individual files
|
Individual file history item, for share mode viewing of individual files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, data, path):
|
def __init__(self, common, data, path):
|
||||||
super(IndividualFileHistoryItem, self).__init__()
|
super(IndividualFileHistoryItem, self).__init__()
|
||||||
self.status = HistoryItem.STATUS_STARTED
|
self.status = HistoryItem.STATUS_STARTED
|
||||||
|
@ -359,11 +400,15 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||||
self.started_dt = datetime.fromtimestamp(self.started)
|
self.started_dt = datetime.fromtimestamp(self.started)
|
||||||
self.status = HistoryItem.STATUS_STARTED
|
self.status = HistoryItem.STATUS_STARTED
|
||||||
|
|
||||||
self.directory_listing = 'directory_listing' in data
|
self.directory_listing = "directory_listing" in data
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p"))
|
self.timestamp_label = QtWidgets.QLabel(
|
||||||
self.timestamp_label.setStyleSheet(self.common.css['history_individual_file_timestamp_label'])
|
self.started_dt.strftime("%b %d, %I:%M%p")
|
||||||
|
)
|
||||||
|
self.timestamp_label.setStyleSheet(
|
||||||
|
self.common.css["history_individual_file_timestamp_label"]
|
||||||
|
)
|
||||||
self.path_label = QtWidgets.QLabel("{}".format(self.path))
|
self.path_label = QtWidgets.QLabel("{}".format(self.path))
|
||||||
self.status_code_label = QtWidgets.QLabel()
|
self.status_code_label = QtWidgets.QLabel()
|
||||||
|
|
||||||
|
@ -373,7 +418,9 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||||
self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||||
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
|
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
|
self.progress_bar.setStyleSheet(
|
||||||
|
self.common.css["downloads_uploads_progress_bar"]
|
||||||
|
)
|
||||||
|
|
||||||
# Text layout
|
# Text layout
|
||||||
labels_layout = QtWidgets.QHBoxLayout()
|
labels_layout = QtWidgets.QHBoxLayout()
|
||||||
|
@ -389,21 +436,25 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
# Is a status code already sent?
|
# Is a status code already sent?
|
||||||
if 'status_code' in data:
|
if "status_code" in data:
|
||||||
self.status_code_label.setText("{}".format(data['status_code']))
|
self.status_code_label.setText("{}".format(data["status_code"]))
|
||||||
if data['status_code'] >= 200 and data['status_code'] < 300:
|
if data["status_code"] >= 200 and data["status_code"] < 300:
|
||||||
self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx'])
|
self.status_code_label.setStyleSheet(
|
||||||
if data['status_code'] >= 400 and data['status_code'] < 500:
|
self.common.css["history_individual_file_status_code_label_2xx"]
|
||||||
self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_4xx'])
|
)
|
||||||
|
if data["status_code"] >= 400 and data["status_code"] < 500:
|
||||||
|
self.status_code_label.setStyleSheet(
|
||||||
|
self.common.css["history_individual_file_status_code_label_4xx"]
|
||||||
|
)
|
||||||
self.status = HistoryItem.STATUS_FINISHED
|
self.status = HistoryItem.STATUS_FINISHED
|
||||||
self.progress_bar.hide()
|
self.progress_bar.hide()
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.total_bytes = data['filesize']
|
self.total_bytes = data["filesize"]
|
||||||
self.progress_bar.setMinimum(0)
|
self.progress_bar.setMinimum(0)
|
||||||
self.progress_bar.setMaximum(data['filesize'])
|
self.progress_bar.setMaximum(data["filesize"])
|
||||||
self.progress_bar.total_bytes = data['filesize']
|
self.progress_bar.total_bytes = data["filesize"]
|
||||||
|
|
||||||
# Start at 0
|
# Start at 0
|
||||||
self.update(0)
|
self.update(0)
|
||||||
|
@ -414,7 +465,9 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||||
self.progress_bar.setValue(downloaded_bytes)
|
self.progress_bar.setValue(downloaded_bytes)
|
||||||
if downloaded_bytes == self.progress_bar.total_bytes:
|
if downloaded_bytes == self.progress_bar.total_bytes:
|
||||||
self.status_code_label.setText("200")
|
self.status_code_label.setText("200")
|
||||||
self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx'])
|
self.status_code_label.setStyleSheet(
|
||||||
|
self.common.css["history_individual_file_status_code_label_2xx"]
|
||||||
|
)
|
||||||
self.progress_bar.hide()
|
self.progress_bar.hide()
|
||||||
self.status = HistoryItem.STATUS_FINISHED
|
self.status = HistoryItem.STATUS_FINISHED
|
||||||
|
|
||||||
|
@ -424,30 +477,33 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||||
# Wait a couple of seconds for the download rate to stabilize.
|
# Wait a couple of seconds for the download rate to stabilize.
|
||||||
# This prevents a "Windows copy dialog"-esque experience at
|
# This prevents a "Windows copy dialog"-esque experience at
|
||||||
# the beginning of the download.
|
# the beginning of the download.
|
||||||
pb_fmt = strings._('gui_all_modes_progress_starting').format(
|
pb_fmt = strings._("gui_all_modes_progress_starting").format(
|
||||||
self.common.human_readable_filesize(downloaded_bytes))
|
self.common.human_readable_filesize(downloaded_bytes)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
pb_fmt = strings._('gui_all_modes_progress_eta').format(
|
pb_fmt = strings._("gui_all_modes_progress_eta").format(
|
||||||
self.common.human_readable_filesize(downloaded_bytes),
|
self.common.human_readable_filesize(downloaded_bytes),
|
||||||
self.estimated_time_remaining)
|
self.estimated_time_remaining,
|
||||||
|
)
|
||||||
|
|
||||||
self.progress_bar.setFormat(pb_fmt)
|
self.progress_bar.setFormat(pb_fmt)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
self.progress_bar.setFormat(strings._('gui_canceled'))
|
self.progress_bar.setFormat(strings._("gui_canceled"))
|
||||||
self.status = HistoryItem.STATUS_CANCELED
|
self.status = HistoryItem.STATUS_CANCELED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def estimated_time_remaining(self):
|
def estimated_time_remaining(self):
|
||||||
return self.common.estimated_time_remaining(self.downloaded_bytes,
|
return self.common.estimated_time_remaining(
|
||||||
self.total_bytes,
|
self.downloaded_bytes, self.total_bytes, self.started
|
||||||
self.started)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HistoryItemList(QtWidgets.QScrollArea):
|
class HistoryItemList(QtWidgets.QScrollArea):
|
||||||
"""
|
"""
|
||||||
List of items
|
List of items
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common):
|
def __init__(self, common):
|
||||||
super(HistoryItemList, self).__init__()
|
super(HistoryItemList, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
|
@ -511,12 +567,14 @@ class HistoryItemList(QtWidgets.QScrollArea):
|
||||||
item.close()
|
item.close()
|
||||||
del self.items[key]
|
del self.items[key]
|
||||||
|
|
||||||
|
|
||||||
class History(QtWidgets.QWidget):
|
class History(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
A history of what's happened so far in this mode. This contains an internal
|
A history of what's happened so far in this mode. This contains an internal
|
||||||
object full of a scrollable list of items.
|
object full of a scrollable list of items.
|
||||||
"""
|
"""
|
||||||
def __init__(self, common, empty_image, empty_text, header_text, mode=''):
|
|
||||||
|
def __init__(self, common, empty_image, empty_text, header_text, mode=""):
|
||||||
super(History, self).__init__()
|
super(History, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -530,17 +588,19 @@ class History(QtWidgets.QWidget):
|
||||||
|
|
||||||
# In progress, completed, and requests labels
|
# In progress, completed, and requests labels
|
||||||
self.in_progress_label = QtWidgets.QLabel()
|
self.in_progress_label = QtWidgets.QLabel()
|
||||||
self.in_progress_label.setStyleSheet(self.common.css['mode_info_label'])
|
self.in_progress_label.setStyleSheet(self.common.css["mode_info_label"])
|
||||||
self.completed_label = QtWidgets.QLabel()
|
self.completed_label = QtWidgets.QLabel()
|
||||||
self.completed_label.setStyleSheet(self.common.css['mode_info_label'])
|
self.completed_label.setStyleSheet(self.common.css["mode_info_label"])
|
||||||
self.requests_label = QtWidgets.QLabel()
|
self.requests_label = QtWidgets.QLabel()
|
||||||
self.requests_label.setStyleSheet(self.common.css['mode_info_label'])
|
self.requests_label.setStyleSheet(self.common.css["mode_info_label"])
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
self.header_label = QtWidgets.QLabel(header_text)
|
self.header_label = QtWidgets.QLabel(header_text)
|
||||||
self.header_label.setStyleSheet(self.common.css['downloads_uploads_label'])
|
self.header_label.setStyleSheet(self.common.css["downloads_uploads_label"])
|
||||||
self.clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history'))
|
self.clear_button = QtWidgets.QPushButton(
|
||||||
self.clear_button.setStyleSheet(self.common.css['downloads_uploads_clear'])
|
strings._("gui_all_modes_clear_history")
|
||||||
|
)
|
||||||
|
self.clear_button.setStyleSheet(self.common.css["downloads_uploads_clear"])
|
||||||
self.clear_button.setFlat(True)
|
self.clear_button.setFlat(True)
|
||||||
self.clear_button.clicked.connect(self.reset)
|
self.clear_button.clicked.connect(self.reset)
|
||||||
header_layout = QtWidgets.QHBoxLayout()
|
header_layout = QtWidgets.QHBoxLayout()
|
||||||
|
@ -557,14 +617,14 @@ class History(QtWidgets.QWidget):
|
||||||
self.empty_image.setPixmap(empty_image)
|
self.empty_image.setPixmap(empty_image)
|
||||||
self.empty_text = QtWidgets.QLabel(empty_text)
|
self.empty_text = QtWidgets.QLabel(empty_text)
|
||||||
self.empty_text.setAlignment(QtCore.Qt.AlignCenter)
|
self.empty_text.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text'])
|
self.empty_text.setStyleSheet(self.common.css["downloads_uploads_empty_text"])
|
||||||
empty_layout = QtWidgets.QVBoxLayout()
|
empty_layout = QtWidgets.QVBoxLayout()
|
||||||
empty_layout.addStretch()
|
empty_layout.addStretch()
|
||||||
empty_layout.addWidget(self.empty_image)
|
empty_layout.addWidget(self.empty_image)
|
||||||
empty_layout.addWidget(self.empty_text)
|
empty_layout.addWidget(self.empty_text)
|
||||||
empty_layout.addStretch()
|
empty_layout.addStretch()
|
||||||
self.empty = QtWidgets.QWidget()
|
self.empty = QtWidgets.QWidget()
|
||||||
self.empty.setStyleSheet(self.common.css['downloads_uploads_empty'])
|
self.empty.setStyleSheet(self.common.css["downloads_uploads_empty"])
|
||||||
self.empty.setLayout(empty_layout)
|
self.empty.setLayout(empty_layout)
|
||||||
|
|
||||||
# When there are items
|
# When there are items
|
||||||
|
@ -589,7 +649,7 @@ class History(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Add a new item.
|
Add a new item.
|
||||||
"""
|
"""
|
||||||
self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item))
|
self.common.log("History", "add", "id: {}, item: {}".format(id, item))
|
||||||
|
|
||||||
# Hide empty, show not empty
|
# Hide empty, show not empty
|
||||||
self.empty.hide()
|
self.empty.hide()
|
||||||
|
@ -636,35 +696,47 @@ class History(QtWidgets.QWidget):
|
||||||
Update the 'completed' widget.
|
Update the 'completed' widget.
|
||||||
"""
|
"""
|
||||||
if self.completed_count == 0:
|
if self.completed_count == 0:
|
||||||
image = self.common.get_resource_path('images/history_completed_none.png')
|
image = self.common.get_resource_path("images/history_completed_none.png")
|
||||||
else:
|
else:
|
||||||
image = self.common.get_resource_path('images/history_completed.png')
|
image = self.common.get_resource_path("images/history_completed.png")
|
||||||
self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count))
|
self.completed_label.setText(
|
||||||
self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count))
|
'<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):
|
def update_in_progress(self):
|
||||||
"""
|
"""
|
||||||
Update the 'in progress' widget.
|
Update the 'in progress' widget.
|
||||||
"""
|
"""
|
||||||
if self.in_progress_count == 0:
|
if self.in_progress_count == 0:
|
||||||
image = self.common.get_resource_path('images/history_in_progress_none.png')
|
image = self.common.get_resource_path("images/history_in_progress_none.png")
|
||||||
else:
|
else:
|
||||||
image = self.common.get_resource_path('images/history_in_progress.png')
|
image = self.common.get_resource_path("images/history_in_progress.png")
|
||||||
|
|
||||||
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
|
self.in_progress_label.setText(
|
||||||
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
|
'<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count)
|
||||||
|
)
|
||||||
|
self.in_progress_label.setToolTip(
|
||||||
|
strings._("history_in_progress_tooltip").format(self.in_progress_count)
|
||||||
|
)
|
||||||
|
|
||||||
def update_requests(self):
|
def update_requests(self):
|
||||||
"""
|
"""
|
||||||
Update the 'web requests' widget.
|
Update the 'web requests' widget.
|
||||||
"""
|
"""
|
||||||
if self.requests_count == 0:
|
if self.requests_count == 0:
|
||||||
image = self.common.get_resource_path('images/history_requests_none.png')
|
image = self.common.get_resource_path("images/history_requests_none.png")
|
||||||
else:
|
else:
|
||||||
image = self.common.get_resource_path('images/history_requests.png')
|
image = self.common.get_resource_path("images/history_requests.png")
|
||||||
|
|
||||||
self.requests_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.requests_count))
|
self.requests_label.setText(
|
||||||
self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.requests_count))
|
'<img src="{0:s}" /> {1:d}'.format(image, self.requests_count)
|
||||||
|
)
|
||||||
|
self.requests_label.setToolTip(
|
||||||
|
strings._("history_requests_tooltip").format(self.requests_count)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ToggleHistory(QtWidgets.QPushButton):
|
class ToggleHistory(QtWidgets.QPushButton):
|
||||||
|
@ -672,6 +744,7 @@ class ToggleHistory(QtWidgets.QPushButton):
|
||||||
Widget for toggling showing or hiding the history, as well as keeping track
|
Widget for toggling showing or hiding the history, as well as keeping track
|
||||||
of the indicator counter if it's hidden
|
of the indicator counter if it's hidden
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, current_mode, history_widget, icon, selected_icon):
|
def __init__(self, common, current_mode, history_widget, icon, selected_icon):
|
||||||
super(ToggleHistory, self).__init__()
|
super(ToggleHistory, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
|
@ -691,7 +764,9 @@ class ToggleHistory(QtWidgets.QPushButton):
|
||||||
# Keep track of indicator
|
# Keep track of indicator
|
||||||
self.indicator_count = 0
|
self.indicator_count = 0
|
||||||
self.indicator_label = QtWidgets.QLabel(parent=self)
|
self.indicator_label = QtWidgets.QLabel(parent=self)
|
||||||
self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator'])
|
self.indicator_label.setStyleSheet(
|
||||||
|
self.common.css["download_uploads_indicator"]
|
||||||
|
)
|
||||||
self.update_indicator()
|
self.update_indicator()
|
||||||
|
|
||||||
def update_indicator(self, increment=False):
|
def update_indicator(self, increment=False):
|
||||||
|
@ -708,14 +783,16 @@ class ToggleHistory(QtWidgets.QPushButton):
|
||||||
self.indicator_label.hide()
|
self.indicator_label.hide()
|
||||||
else:
|
else:
|
||||||
size = self.indicator_label.sizeHint()
|
size = self.indicator_label.sizeHint()
|
||||||
self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height())
|
self.indicator_label.setGeometry(
|
||||||
|
35 - size.width(), 0, size.width(), size.height()
|
||||||
|
)
|
||||||
self.indicator_label.show()
|
self.indicator_label.show()
|
||||||
|
|
||||||
def toggle_clicked(self):
|
def toggle_clicked(self):
|
||||||
"""
|
"""
|
||||||
Toggle showing and hiding the history widget
|
Toggle showing and hiding the history widget
|
||||||
"""
|
"""
|
||||||
self.common.log('ToggleHistory', 'toggle_clicked')
|
self.common.log("ToggleHistory", "toggle_clicked")
|
||||||
|
|
||||||
if self.history_widget.isVisible():
|
if self.history_widget.isVisible():
|
||||||
self.history_widget.hide()
|
self.history_widget.hide()
|
||||||
|
|
|
@ -25,19 +25,21 @@ from onionshare.web import Web
|
||||||
from ..history import History, ToggleHistory, ReceiveHistoryItem
|
from ..history import History, ToggleHistory, ReceiveHistoryItem
|
||||||
from .. import Mode
|
from .. import Mode
|
||||||
|
|
||||||
|
|
||||||
class ReceiveMode(Mode):
|
class ReceiveMode(Mode):
|
||||||
"""
|
"""
|
||||||
Parts of the main window UI for receiving files.
|
Parts of the main window UI for receiving files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
Custom initialization for ReceiveMode.
|
Custom initialization for ReceiveMode.
|
||||||
"""
|
"""
|
||||||
# Create the Web object
|
# Create the Web object
|
||||||
self.web = Web(self.common, True, 'receive')
|
self.web = Web(self.common, True, "receive")
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status.set_mode('receive')
|
self.server_status.set_mode("receive")
|
||||||
self.server_status.server_started_finished.connect(self.update_primary_action)
|
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_stopped.connect(self.update_primary_action)
|
||||||
self.server_status.server_canceled.connect(self.update_primary_action)
|
self.server_status.server_canceled.connect(self.update_primary_action)
|
||||||
|
@ -49,21 +51,31 @@ class ReceiveMode(Mode):
|
||||||
# Upload history
|
# Upload history
|
||||||
self.history = History(
|
self.history = History(
|
||||||
self.common,
|
self.common,
|
||||||
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))),
|
QtGui.QPixmap.fromImage(
|
||||||
strings._('gui_receive_mode_no_files'),
|
QtGui.QImage(
|
||||||
strings._('gui_all_modes_history')
|
self.common.get_resource_path("images/receive_icon_transparent.png")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
strings._("gui_receive_mode_no_files"),
|
||||||
|
strings._("gui_all_modes_history"),
|
||||||
)
|
)
|
||||||
self.history.hide()
|
self.history.hide()
|
||||||
|
|
||||||
# Toggle history
|
# Toggle history
|
||||||
self.toggle_history = ToggleHistory(
|
self.toggle_history = ToggleHistory(
|
||||||
self.common, self, self.history,
|
self.common,
|
||||||
QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')),
|
self,
|
||||||
QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png'))
|
self.history,
|
||||||
|
QtGui.QIcon(
|
||||||
|
self.common.get_resource_path("images/receive_icon_toggle.png")
|
||||||
|
),
|
||||||
|
QtGui.QIcon(
|
||||||
|
self.common.get_resource_path("images/receive_icon_toggle_selected.png")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Receive mode warning
|
# Receive mode warning
|
||||||
receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning'))
|
receive_warning = QtWidgets.QLabel(strings._("gui_receive_mode_warning"))
|
||||||
receive_warning.setMinimumHeight(80)
|
receive_warning.setMinimumHeight(80)
|
||||||
receive_warning.setWordWrap(True)
|
receive_warning.setWordWrap(True)
|
||||||
|
|
||||||
|
@ -90,20 +102,25 @@ class ReceiveMode(Mode):
|
||||||
"""
|
"""
|
||||||
Return the string to put on the stop server button, if there's an auto-stop timer
|
Return the string to put on the stop server button, if there's an auto-stop timer
|
||||||
"""
|
"""
|
||||||
return strings._('gui_receive_stop_server_autostop_timer')
|
return strings._("gui_receive_stop_server_autostop_timer")
|
||||||
|
|
||||||
def autostop_timer_finished_should_stop_server(self):
|
def autostop_timer_finished_should_stop_server(self):
|
||||||
"""
|
"""
|
||||||
The auto-stop timer expired, should we stop the server? Returns a bool
|
The auto-stop timer expired, should we stop the server? Returns a bool
|
||||||
"""
|
"""
|
||||||
# If there were no attempts to upload files, or all uploads are done, we can stop
|
# If there were no attempts to upload files, or all uploads are done, we can stop
|
||||||
if self.web.receive_mode.cur_history_id == 0 or not self.web.receive_mode.uploads_in_progress:
|
if (
|
||||||
|
self.web.receive_mode.cur_history_id == 0
|
||||||
|
or not self.web.receive_mode.uploads_in_progress
|
||||||
|
):
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.server_status_label.setText(strings._('close_on_autostop_timer'))
|
self.server_status_label.setText(strings._("close_on_autostop_timer"))
|
||||||
return True
|
return True
|
||||||
# An upload is probably still running - hold off on stopping the share, but block new shares.
|
# An upload is probably still running - hold off on stopping the share, but block new shares.
|
||||||
else:
|
else:
|
||||||
self.server_status_label.setText(strings._('gui_receive_mode_autostop_timer_waiting'))
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_receive_mode_autostop_timer_waiting")
|
||||||
|
)
|
||||||
self.web.receive_mode.can_upload = False
|
self.web.receive_mode.can_upload = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -136,56 +153,68 @@ class ReceiveMode(Mode):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_LOAD event.
|
Handle REQUEST_LOAD event.
|
||||||
"""
|
"""
|
||||||
self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("systray_page_loaded_title"),
|
||||||
|
strings._("systray_page_loaded_message"),
|
||||||
|
)
|
||||||
|
|
||||||
def handle_request_started(self, event):
|
def handle_request_started(self, event):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_STARTED event.
|
Handle REQUEST_STARTED event.
|
||||||
"""
|
"""
|
||||||
item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"])
|
item = ReceiveHistoryItem(
|
||||||
|
self.common, event["data"]["id"], event["data"]["content_length"]
|
||||||
|
)
|
||||||
self.history.add(event["data"]["id"], item)
|
self.history.add(event["data"]["id"], item)
|
||||||
self.toggle_history.update_indicator(True)
|
self.toggle_history.update_indicator(True)
|
||||||
self.history.in_progress_count += 1
|
self.history.in_progress_count += 1
|
||||||
self.history.update_in_progress()
|
self.history.update_in_progress()
|
||||||
|
|
||||||
self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("systray_receive_started_title"),
|
||||||
|
strings._("systray_receive_started_message"),
|
||||||
|
)
|
||||||
|
|
||||||
def handle_request_progress(self, event):
|
def handle_request_progress(self, event):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_PROGRESS event.
|
Handle REQUEST_PROGRESS event.
|
||||||
"""
|
"""
|
||||||
self.history.update(event["data"]["id"], {
|
self.history.update(
|
||||||
'action': 'progress',
|
event["data"]["id"],
|
||||||
'progress': event["data"]["progress"]
|
{"action": "progress", "progress": event["data"]["progress"]},
|
||||||
})
|
)
|
||||||
|
|
||||||
def handle_request_upload_file_renamed(self, event):
|
def handle_request_upload_file_renamed(self, event):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_UPLOAD_FILE_RENAMED event.
|
Handle REQUEST_UPLOAD_FILE_RENAMED event.
|
||||||
"""
|
"""
|
||||||
self.history.update(event["data"]["id"], {
|
self.history.update(
|
||||||
'action': 'rename',
|
event["data"]["id"],
|
||||||
'old_filename': event["data"]["old_filename"],
|
{
|
||||||
'new_filename': event["data"]["new_filename"]
|
"action": "rename",
|
||||||
})
|
"old_filename": event["data"]["old_filename"],
|
||||||
|
"new_filename": event["data"]["new_filename"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def handle_request_upload_set_dir(self, event):
|
def handle_request_upload_set_dir(self, event):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_UPLOAD_SET_DIR event.
|
Handle REQUEST_UPLOAD_SET_DIR event.
|
||||||
"""
|
"""
|
||||||
self.history.update(event["data"]["id"], {
|
self.history.update(
|
||||||
'action': 'set_dir',
|
event["data"]["id"],
|
||||||
'filename': event["data"]["filename"],
|
{
|
||||||
'dir': event["data"]["dir"]
|
"action": "set_dir",
|
||||||
})
|
"filename": event["data"]["filename"],
|
||||||
|
"dir": event["data"]["dir"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def handle_request_upload_finished(self, event):
|
def handle_request_upload_finished(self, event):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_UPLOAD_FINISHED event.
|
Handle REQUEST_UPLOAD_FINISHED event.
|
||||||
"""
|
"""
|
||||||
self.history.update(event["data"]["id"], {
|
self.history.update(event["data"]["id"], {"action": "finished"})
|
||||||
'action': 'finished'
|
|
||||||
})
|
|
||||||
self.history.completed_count += 1
|
self.history.completed_count += 1
|
||||||
self.history.in_progress_count -= 1
|
self.history.in_progress_count -= 1
|
||||||
self.history.update_completed()
|
self.history.update_completed()
|
||||||
|
@ -195,9 +224,7 @@ class ReceiveMode(Mode):
|
||||||
"""
|
"""
|
||||||
Handle REQUEST_UPLOAD_CANCELED event.
|
Handle REQUEST_UPLOAD_CANCELED event.
|
||||||
"""
|
"""
|
||||||
self.history.update(event["data"]["id"], {
|
self.history.update(event["data"]["id"], {"action": "canceled"})
|
||||||
'action': 'canceled'
|
|
||||||
})
|
|
||||||
self.history.in_progress_count -= 1
|
self.history.in_progress_count -= 1
|
||||||
self.history.update_in_progress()
|
self.history.update_in_progress()
|
||||||
|
|
||||||
|
@ -216,4 +243,4 @@ class ReceiveMode(Mode):
|
||||||
self.toggle_history.update_indicator()
|
self.toggle_history.update_indicator()
|
||||||
|
|
||||||
def update_primary_action(self):
|
def update_primary_action(self):
|
||||||
self.common.log('ReceiveMode', 'update_primary_action')
|
self.common.log("ReceiveMode", "update_primary_action")
|
||||||
|
|
|
@ -36,6 +36,7 @@ class ShareMode(Mode):
|
||||||
"""
|
"""
|
||||||
Parts of the main window UI for sharing files.
|
Parts of the main window UI for sharing files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
"""
|
"""
|
||||||
Custom initialization for ReceiveMode.
|
Custom initialization for ReceiveMode.
|
||||||
|
@ -44,7 +45,7 @@ class ShareMode(Mode):
|
||||||
self.compress_thread = None
|
self.compress_thread = None
|
||||||
|
|
||||||
# Create the Web object
|
# Create the Web object
|
||||||
self.web = Web(self.common, True, 'share')
|
self.web = Web(self.common, True, "share")
|
||||||
|
|
||||||
# File selection
|
# File selection
|
||||||
self.file_selection = FileSelection(self.common, self)
|
self.file_selection = FileSelection(self.common, self)
|
||||||
|
@ -53,7 +54,7 @@ class ShareMode(Mode):
|
||||||
self.file_selection.file_list.add_file(filename)
|
self.file_selection.file_list.add_file(filename)
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status.set_mode('share', self.file_selection)
|
self.server_status.set_mode("share", self.file_selection)
|
||||||
self.server_status.server_started.connect(self.file_selection.server_started)
|
self.server_status.server_started.connect(self.file_selection.server_started)
|
||||||
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
|
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
|
||||||
self.server_status.server_stopped.connect(self.update_primary_action)
|
self.server_status.server_stopped.connect(self.update_primary_action)
|
||||||
|
@ -68,15 +69,19 @@ class ShareMode(Mode):
|
||||||
# Filesize warning
|
# Filesize warning
|
||||||
self.filesize_warning = QtWidgets.QLabel()
|
self.filesize_warning = QtWidgets.QLabel()
|
||||||
self.filesize_warning.setWordWrap(True)
|
self.filesize_warning.setWordWrap(True)
|
||||||
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()
|
||||||
|
|
||||||
# Download history
|
# Download history
|
||||||
self.history = History(
|
self.history = History(
|
||||||
self.common,
|
self.common,
|
||||||
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))),
|
QtGui.QPixmap.fromImage(
|
||||||
strings._('gui_share_mode_no_files'),
|
QtGui.QImage(
|
||||||
strings._('gui_all_modes_history')
|
self.common.get_resource_path("images/share_icon_transparent.png")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
strings._("gui_share_mode_no_files"),
|
||||||
|
strings._("gui_all_modes_history"),
|
||||||
)
|
)
|
||||||
self.history.hide()
|
self.history.hide()
|
||||||
|
|
||||||
|
@ -86,9 +91,13 @@ class ShareMode(Mode):
|
||||||
|
|
||||||
# Toggle history
|
# Toggle history
|
||||||
self.toggle_history = ToggleHistory(
|
self.toggle_history = ToggleHistory(
|
||||||
self.common, self, self.history,
|
self.common,
|
||||||
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')),
|
self,
|
||||||
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png'))
|
self.history,
|
||||||
|
QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")),
|
||||||
|
QtGui.QIcon(
|
||||||
|
self.common.get_resource_path("images/share_icon_toggle_selected.png")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Top bar
|
# Top bar
|
||||||
|
@ -125,7 +134,7 @@ class ShareMode(Mode):
|
||||||
"""
|
"""
|
||||||
Return the string to put on the stop server button, if there's an auto-stop timer
|
Return the string to put on the stop server button, if there's an auto-stop timer
|
||||||
"""
|
"""
|
||||||
return strings._('gui_share_stop_server_autostop_timer')
|
return strings._("gui_share_stop_server_autostop_timer")
|
||||||
|
|
||||||
def autostop_timer_finished_should_stop_server(self):
|
def autostop_timer_finished_should_stop_server(self):
|
||||||
"""
|
"""
|
||||||
|
@ -134,11 +143,13 @@ class ShareMode(Mode):
|
||||||
# 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.share_mode.cur_history_id == 0 or self.web.done:
|
if self.web.share_mode.cur_history_id == 0 or self.web.done:
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.server_status_label.setText(strings._('close_on_autostop_timer'))
|
self.server_status_label.setText(strings._("close_on_autostop_timer"))
|
||||||
return True
|
return True
|
||||||
# A download is probably still running - hold off on stopping the share
|
# A download is probably still running - hold off on stopping the share
|
||||||
else:
|
else:
|
||||||
self.server_status_label.setText(strings._('gui_share_mode_autostop_timer_waiting'))
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_share_mode_autostop_timer_waiting")
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def start_server_custom(self):
|
def start_server_custom(self):
|
||||||
|
@ -162,7 +173,9 @@ class ShareMode(Mode):
|
||||||
for index in range(self.file_selection.file_list.count()):
|
for index in range(self.file_selection.file_list.count()):
|
||||||
self.filenames.append(self.file_selection.file_list.item(index).filename)
|
self.filenames.append(self.file_selection.file_list.item(index).filename)
|
||||||
|
|
||||||
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
|
||||||
|
@ -216,7 +229,7 @@ class ShareMode(Mode):
|
||||||
Stop the compression thread on cancel
|
Stop the compression thread on cancel
|
||||||
"""
|
"""
|
||||||
if self.compress_thread:
|
if self.compress_thread:
|
||||||
self.common.log('ShareMode', 'cancel_server: quitting compress thread')
|
self.common.log("ShareMode", "cancel_server: quitting compress thread")
|
||||||
self.compress_thread.quit()
|
self.compress_thread.quit()
|
||||||
|
|
||||||
def handle_tor_broke_custom(self):
|
def handle_tor_broke_custom(self):
|
||||||
|
@ -240,7 +253,10 @@ class ShareMode(Mode):
|
||||||
self.history.in_progress_count += 1
|
self.history.in_progress_count += 1
|
||||||
self.history.update_in_progress()
|
self.history.update_in_progress()
|
||||||
|
|
||||||
self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("systray_share_started_title"),
|
||||||
|
strings._("systray_share_started_message"),
|
||||||
|
)
|
||||||
|
|
||||||
def handle_request_progress(self, event):
|
def handle_request_progress(self, event):
|
||||||
"""
|
"""
|
||||||
|
@ -250,7 +266,10 @@ class ShareMode(Mode):
|
||||||
|
|
||||||
# Is the download complete?
|
# Is the download complete?
|
||||||
if event["data"]["bytes"] == self.web.share_mode.filesize:
|
if event["data"]["bytes"] == self.web.share_mode.filesize:
|
||||||
self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("systray_share_completed_title"),
|
||||||
|
strings._("systray_share_completed_message"),
|
||||||
|
)
|
||||||
|
|
||||||
# Update completed and in progress labels
|
# Update completed and in progress labels
|
||||||
self.history.completed_count += 1
|
self.history.completed_count += 1
|
||||||
|
@ -259,10 +278,10 @@ class ShareMode(Mode):
|
||||||
self.history.update_in_progress()
|
self.history.update_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"):
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_status_label.setText(strings._('closing_automatically'))
|
self.server_status_label.setText(strings._("closing_automatically"))
|
||||||
else:
|
else:
|
||||||
if self.server_status.status == self.server_status.STATUS_STOPPED:
|
if self.server_status.status == self.server_status.STATUS_STOPPED:
|
||||||
self.history.cancel(event["data"]["id"])
|
self.history.cancel(event["data"]["id"])
|
||||||
|
@ -278,7 +297,10 @@ class ShareMode(Mode):
|
||||||
# Update in progress count
|
# Update in progress count
|
||||||
self.history.in_progress_count -= 1
|
self.history.in_progress_count -= 1
|
||||||
self.history.update_in_progress()
|
self.history.update_in_progress()
|
||||||
self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("systray_share_canceled_title"),
|
||||||
|
strings._("systray_share_canceled_message"),
|
||||||
|
)
|
||||||
|
|
||||||
def on_reload_settings(self):
|
def on_reload_settings(self):
|
||||||
"""
|
"""
|
||||||
|
@ -290,7 +312,7 @@ class ShareMode(Mode):
|
||||||
self.info_label.show()
|
self.info_label.show()
|
||||||
|
|
||||||
def update_primary_action(self):
|
def update_primary_action(self):
|
||||||
self.common.log('ShareMode', 'update_primary_action')
|
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()
|
||||||
|
@ -306,9 +328,15 @@ class ShareMode(Mode):
|
||||||
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
|
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
|
||||||
|
|
||||||
if file_count > 1:
|
if file_count > 1:
|
||||||
self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable))
|
self.info_label.setText(
|
||||||
|
strings._("gui_file_info").format(file_count, total_size_readable)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable))
|
self.info_label.setText(
|
||||||
|
strings._("gui_file_info_single").format(
|
||||||
|
file_count, total_size_readable
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.primary_action.hide()
|
self.primary_action.hide()
|
||||||
|
@ -343,8 +371,8 @@ class ZipProgressBar(QtWidgets.QProgressBar):
|
||||||
self.setMaximumHeight(20)
|
self.setMaximumHeight(20)
|
||||||
self.setMinimumWidth(200)
|
self.setMinimumWidth(200)
|
||||||
self.setValue(0)
|
self.setValue(0)
|
||||||
self.setFormat(strings._('zip_progress_bar_format'))
|
self.setFormat(strings._("zip_progress_bar_format"))
|
||||||
self.setStyleSheet(self.common.css['share_zip_progess_bar'])
|
self.setStyleSheet(self.common.css["share_zip_progess_bar"])
|
||||||
|
|
||||||
self._total_files_size = total_files_size
|
self._total_files_size = total_files_size
|
||||||
self._processed_size = 0
|
self._processed_size = 0
|
||||||
|
|
|
@ -24,13 +24,14 @@ class CompressThread(QtCore.QThread):
|
||||||
"""
|
"""
|
||||||
Compresses files to be shared
|
Compresses files to be shared
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = QtCore.pyqtSignal()
|
success = QtCore.pyqtSignal()
|
||||||
error = QtCore.pyqtSignal(str)
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, mode):
|
def __init__(self, mode):
|
||||||
super(CompressThread, self).__init__()
|
super(CompressThread, self).__init__()
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.mode.common.log('CompressThread', '__init__')
|
self.mode.common.log("CompressThread", "__init__")
|
||||||
|
|
||||||
# prepare files to share
|
# prepare files to share
|
||||||
def set_processed_size(self, x):
|
def set_processed_size(self, x):
|
||||||
|
@ -38,17 +39,21 @@ class CompressThread(QtCore.QThread):
|
||||||
self.mode._zip_progress_bar.update_processed_size_signal.emit(x)
|
self.mode._zip_progress_bar.update_processed_size_signal.emit(x)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.mode.common.log('CompressThread', 'run')
|
self.mode.common.log("CompressThread", "run")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size)
|
self.mode.web.share_mode.set_file_info(
|
||||||
|
self.mode.filenames, processed_size_callback=self.set_processed_size
|
||||||
|
)
|
||||||
self.success.emit()
|
self.success.emit()
|
||||||
self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames
|
self.mode.app.cleanup_filenames += (
|
||||||
|
self.mode.web.share_mode.cleanup_filenames
|
||||||
|
)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.error.emit(e.strerror)
|
self.error.emit(e.strerror)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
self.mode.common.log('CompressThread', 'cancel')
|
self.mode.common.log("CompressThread", "cancel")
|
||||||
|
|
||||||
# Let the Web and ZipWriter objects know that we're canceling compression early
|
# Let the Web and ZipWriter objects know that we're canceling compression early
|
||||||
self.mode.web.cancel_compression = True
|
self.mode.web.cancel_compression = True
|
||||||
|
|
|
@ -33,10 +33,12 @@ from .. import Mode
|
||||||
from ..history import History, ToggleHistory
|
from ..history import History, ToggleHistory
|
||||||
from ...widgets import Alert
|
from ...widgets import Alert
|
||||||
|
|
||||||
|
|
||||||
class WebsiteMode(Mode):
|
class WebsiteMode(Mode):
|
||||||
"""
|
"""
|
||||||
Parts of the main window UI for sharing files.
|
Parts of the main window UI for sharing files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = QtCore.pyqtSignal()
|
success = QtCore.pyqtSignal()
|
||||||
error = QtCore.pyqtSignal(str)
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ class WebsiteMode(Mode):
|
||||||
Custom initialization for ReceiveMode.
|
Custom initialization for ReceiveMode.
|
||||||
"""
|
"""
|
||||||
# Create the Web object
|
# Create the Web object
|
||||||
self.web = Web(self.common, True, 'website')
|
self.web = Web(self.common, True, "website")
|
||||||
|
|
||||||
# File selection
|
# File selection
|
||||||
self.file_selection = FileSelection(self.common, self)
|
self.file_selection = FileSelection(self.common, self)
|
||||||
|
@ -54,7 +56,7 @@ class WebsiteMode(Mode):
|
||||||
self.file_selection.file_list.add_file(filename)
|
self.file_selection.file_list.add_file(filename)
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status.set_mode('website', self.file_selection)
|
self.server_status.set_mode("website", self.file_selection)
|
||||||
self.server_status.server_started.connect(self.file_selection.server_started)
|
self.server_status.server_started.connect(self.file_selection.server_started)
|
||||||
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
|
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
|
||||||
self.server_status.server_stopped.connect(self.update_primary_action)
|
self.server_status.server_stopped.connect(self.update_primary_action)
|
||||||
|
@ -69,16 +71,20 @@ class WebsiteMode(Mode):
|
||||||
# Filesize warning
|
# Filesize warning
|
||||||
self.filesize_warning = QtWidgets.QLabel()
|
self.filesize_warning = QtWidgets.QLabel()
|
||||||
self.filesize_warning.setWordWrap(True)
|
self.filesize_warning.setWordWrap(True)
|
||||||
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()
|
||||||
|
|
||||||
# Download history
|
# Download history
|
||||||
self.history = History(
|
self.history = History(
|
||||||
self.common,
|
self.common,
|
||||||
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))),
|
QtGui.QPixmap.fromImage(
|
||||||
strings._('gui_website_mode_no_files'),
|
QtGui.QImage(
|
||||||
strings._('gui_all_modes_history'),
|
self.common.get_resource_path("images/share_icon_transparent.png")
|
||||||
'website'
|
)
|
||||||
|
),
|
||||||
|
strings._("gui_website_mode_no_files"),
|
||||||
|
strings._("gui_all_modes_history"),
|
||||||
|
"website",
|
||||||
)
|
)
|
||||||
self.history.in_progress_label.hide()
|
self.history.in_progress_label.hide()
|
||||||
self.history.completed_label.hide()
|
self.history.completed_label.hide()
|
||||||
|
@ -90,9 +96,13 @@ class WebsiteMode(Mode):
|
||||||
|
|
||||||
# Toggle history
|
# Toggle history
|
||||||
self.toggle_history = ToggleHistory(
|
self.toggle_history = ToggleHistory(
|
||||||
self.common, self, self.history,
|
self.common,
|
||||||
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')),
|
self,
|
||||||
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png'))
|
self.history,
|
||||||
|
QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")),
|
||||||
|
QtGui.QIcon(
|
||||||
|
self.common.get_resource_path("images/share_icon_toggle_selected.png")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Top bar
|
# Top bar
|
||||||
|
@ -126,7 +136,7 @@ class WebsiteMode(Mode):
|
||||||
"""
|
"""
|
||||||
Return the string to put on the stop server button, if there's an auto-stop timer
|
Return the string to put on the stop server button, if there's an auto-stop timer
|
||||||
"""
|
"""
|
||||||
return strings._('gui_share_stop_server_autostop_timer')
|
return strings._("gui_share_stop_server_autostop_timer")
|
||||||
|
|
||||||
def autostop_timer_finished_should_stop_server(self):
|
def autostop_timer_finished_should_stop_server(self):
|
||||||
"""
|
"""
|
||||||
|
@ -134,10 +144,9 @@ class WebsiteMode(Mode):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.server_status_label.setText(strings._('close_on_autostop_timer'))
|
self.server_status_label.setText(strings._("close_on_autostop_timer"))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def start_server_custom(self):
|
def start_server_custom(self):
|
||||||
"""
|
"""
|
||||||
Starting the server.
|
Starting the server.
|
||||||
|
@ -161,7 +170,6 @@ class WebsiteMode(Mode):
|
||||||
self.starting_server_step3.emit()
|
self.starting_server_step3.emit()
|
||||||
self.start_server_finished.emit()
|
self.start_server_finished.emit()
|
||||||
|
|
||||||
|
|
||||||
def start_server_step3_custom(self):
|
def start_server_step3_custom(self):
|
||||||
"""
|
"""
|
||||||
Step 3 in starting the server. Display large filesize
|
Step 3 in starting the server. Display large filesize
|
||||||
|
@ -191,8 +199,7 @@ class WebsiteMode(Mode):
|
||||||
"""
|
"""
|
||||||
Log that the server has been cancelled
|
Log that the server has been cancelled
|
||||||
"""
|
"""
|
||||||
self.common.log('WebsiteMode', 'cancel_server')
|
self.common.log("WebsiteMode", "cancel_server")
|
||||||
|
|
||||||
|
|
||||||
def handle_tor_broke_custom(self):
|
def handle_tor_broke_custom(self):
|
||||||
"""
|
"""
|
||||||
|
@ -210,7 +217,7 @@ class WebsiteMode(Mode):
|
||||||
self.info_label.show()
|
self.info_label.show()
|
||||||
|
|
||||||
def update_primary_action(self):
|
def update_primary_action(self):
|
||||||
self.common.log('WebsiteMode', 'update_primary_action')
|
self.common.log("WebsiteMode", "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()
|
||||||
|
@ -226,9 +233,15 @@ class WebsiteMode(Mode):
|
||||||
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
|
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
|
||||||
|
|
||||||
if file_count > 1:
|
if file_count > 1:
|
||||||
self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable))
|
self.info_label.setText(
|
||||||
|
strings._("gui_file_info").format(file_count, total_size_readable)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable))
|
self.info_label.setText(
|
||||||
|
strings._("gui_file_info_single").format(
|
||||||
|
file_count, total_size_readable
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.primary_action.hide()
|
self.primary_action.hide()
|
||||||
|
|
|
@ -33,20 +33,24 @@ from .widgets import Alert
|
||||||
from .update_checker import UpdateThread
|
from .update_checker import UpdateThread
|
||||||
from .server_status import ServerStatus
|
from .server_status import ServerStatus
|
||||||
|
|
||||||
|
|
||||||
class OnionShareGui(QtWidgets.QMainWindow):
|
class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
OnionShareGui is the main window for the GUI that contains all of the
|
OnionShareGui is the main window for the GUI that contains all of the
|
||||||
GUI elements.
|
GUI elements.
|
||||||
"""
|
"""
|
||||||
MODE_SHARE = 'share'
|
|
||||||
MODE_RECEIVE = 'receive'
|
|
||||||
MODE_WEBSITE = 'website'
|
|
||||||
|
|
||||||
def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False):
|
MODE_SHARE = "share"
|
||||||
|
MODE_RECEIVE = "receive"
|
||||||
|
MODE_WEBSITE = "website"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, common, onion, qtapp, app, filenames, config=False, local_only=False
|
||||||
|
):
|
||||||
super(OnionShareGui, self).__init__()
|
super(OnionShareGui, self).__init__()
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log('OnionShareGui', '__init__')
|
self.common.log("OnionShareGui", "__init__")
|
||||||
self.setMinimumWidth(820)
|
self.setMinimumWidth(820)
|
||||||
self.setMinimumHeight(660)
|
self.setMinimumHeight(660)
|
||||||
|
|
||||||
|
@ -57,8 +61,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
self.mode = self.MODE_SHARE
|
self.mode = self.MODE_SHARE
|
||||||
|
|
||||||
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"))
|
||||||
|
)
|
||||||
|
|
||||||
# Load settings, if a custom config was passed in
|
# Load settings, if a custom config was passed in
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -71,40 +77,52 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
# System tray
|
# System tray
|
||||||
menu = QtWidgets.QMenu()
|
menu = QtWidgets.QMenu()
|
||||||
self.settings_action = menu.addAction(strings._('gui_settings_window_title'))
|
self.settings_action = menu.addAction(strings._("gui_settings_window_title"))
|
||||||
self.settings_action.triggered.connect(self.open_settings)
|
self.settings_action.triggered.connect(self.open_settings)
|
||||||
self.help_action = menu.addAction(strings._('gui_settings_button_help'))
|
self.help_action = menu.addAction(strings._("gui_settings_button_help"))
|
||||||
self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self))
|
self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self))
|
||||||
exit_action = menu.addAction(strings._('systray_menu_exit'))
|
exit_action = menu.addAction(strings._("systray_menu_exit"))
|
||||||
exit_action.triggered.connect(self.close)
|
exit_action.triggered.connect(self.close)
|
||||||
|
|
||||||
self.system_tray = QtWidgets.QSystemTrayIcon(self)
|
self.system_tray = QtWidgets.QSystemTrayIcon(self)
|
||||||
# The convention is Mac systray icons are always grayscale
|
# The convention is Mac systray icons are always grayscale
|
||||||
if self.common.platform == 'Darwin':
|
if self.common.platform == "Darwin":
|
||||||
self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png')))
|
self.system_tray.setIcon(
|
||||||
|
QtGui.QIcon(self.common.get_resource_path("images/logo_grayscale.png"))
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
self.system_tray.setIcon(
|
||||||
|
QtGui.QIcon(self.common.get_resource_path("images/logo.png"))
|
||||||
|
)
|
||||||
self.system_tray.setContextMenu(menu)
|
self.system_tray.setContextMenu(menu)
|
||||||
self.system_tray.show()
|
self.system_tray.show()
|
||||||
|
|
||||||
# Mode switcher, to switch between share files and receive files
|
# Mode switcher, to switch between share files and receive files
|
||||||
self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button'));
|
self.share_mode_button = QtWidgets.QPushButton(
|
||||||
|
strings._("gui_mode_share_button")
|
||||||
|
)
|
||||||
self.share_mode_button.setFixedHeight(50)
|
self.share_mode_button.setFixedHeight(50)
|
||||||
self.share_mode_button.clicked.connect(self.share_mode_clicked)
|
self.share_mode_button.clicked.connect(self.share_mode_clicked)
|
||||||
self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button'));
|
self.receive_mode_button = QtWidgets.QPushButton(
|
||||||
|
strings._("gui_mode_receive_button")
|
||||||
|
)
|
||||||
self.receive_mode_button.setFixedHeight(50)
|
self.receive_mode_button.setFixedHeight(50)
|
||||||
self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
|
self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
|
||||||
self.website_mode_button = QtWidgets.QPushButton(strings._('gui_mode_website_button'));
|
self.website_mode_button = QtWidgets.QPushButton(
|
||||||
|
strings._("gui_mode_website_button")
|
||||||
|
)
|
||||||
self.website_mode_button.setFixedHeight(50)
|
self.website_mode_button.setFixedHeight(50)
|
||||||
self.website_mode_button.clicked.connect(self.website_mode_clicked)
|
self.website_mode_button.clicked.connect(self.website_mode_clicked)
|
||||||
self.settings_button = QtWidgets.QPushButton()
|
self.settings_button = QtWidgets.QPushButton()
|
||||||
self.settings_button.setDefault(False)
|
self.settings_button.setDefault(False)
|
||||||
self.settings_button.setFixedWidth(40)
|
self.settings_button.setFixedWidth(40)
|
||||||
self.settings_button.setFixedHeight(50)
|
self.settings_button.setFixedHeight(50)
|
||||||
self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) )
|
self.settings_button.setIcon(
|
||||||
|
QtGui.QIcon(self.common.get_resource_path("images/settings.png"))
|
||||||
|
)
|
||||||
self.settings_button.clicked.connect(self.open_settings)
|
self.settings_button.clicked.connect(self.open_settings)
|
||||||
self.settings_button.setStyleSheet(self.common.css['settings_button'])
|
self.settings_button.setStyleSheet(self.common.css["settings_button"])
|
||||||
mode_switcher_layout = QtWidgets.QHBoxLayout();
|
mode_switcher_layout = QtWidgets.QHBoxLayout()
|
||||||
mode_switcher_layout.setSpacing(0)
|
mode_switcher_layout.setSpacing(0)
|
||||||
mode_switcher_layout.addWidget(self.share_mode_button)
|
mode_switcher_layout.addWidget(self.share_mode_button)
|
||||||
mode_switcher_layout.addWidget(self.receive_mode_button)
|
mode_switcher_layout.addWidget(self.receive_mode_button)
|
||||||
|
@ -112,13 +130,21 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
mode_switcher_layout.addWidget(self.settings_button)
|
mode_switcher_layout.addWidget(self.settings_button)
|
||||||
|
|
||||||
# Server status indicator on the status bar
|
# Server status indicator on the status bar
|
||||||
self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png'))
|
self.server_status_image_stopped = QtGui.QImage(
|
||||||
self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png'))
|
self.common.get_resource_path("images/server_stopped.png")
|
||||||
self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png'))
|
)
|
||||||
|
self.server_status_image_working = QtGui.QImage(
|
||||||
|
self.common.get_resource_path("images/server_working.png")
|
||||||
|
)
|
||||||
|
self.server_status_image_started = QtGui.QImage(
|
||||||
|
self.common.get_resource_path("images/server_started.png")
|
||||||
|
)
|
||||||
self.server_status_image_label = QtWidgets.QLabel()
|
self.server_status_image_label = QtWidgets.QLabel()
|
||||||
self.server_status_image_label.setFixedWidth(20)
|
self.server_status_image_label.setFixedWidth(20)
|
||||||
self.server_status_label = QtWidgets.QLabel('')
|
self.server_status_label = QtWidgets.QLabel("")
|
||||||
self.server_status_label.setStyleSheet(self.common.css['server_status_indicator_label'])
|
self.server_status_label.setStyleSheet(
|
||||||
|
self.common.css["server_status_indicator_label"]
|
||||||
|
)
|
||||||
server_status_indicator_layout = QtWidgets.QHBoxLayout()
|
server_status_indicator_layout = QtWidgets.QHBoxLayout()
|
||||||
server_status_indicator_layout.addWidget(self.server_status_image_label)
|
server_status_indicator_layout.addWidget(self.server_status_image_label)
|
||||||
server_status_indicator_layout.addWidget(self.server_status_label)
|
server_status_indicator_layout.addWidget(self.server_status_label)
|
||||||
|
@ -128,17 +154,34 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
# Status bar
|
# Status bar
|
||||||
self.status_bar = QtWidgets.QStatusBar()
|
self.status_bar = QtWidgets.QStatusBar()
|
||||||
self.status_bar.setSizeGripEnabled(False)
|
self.status_bar.setSizeGripEnabled(False)
|
||||||
self.status_bar.setStyleSheet(self.common.css['status_bar'])
|
self.status_bar.setStyleSheet(self.common.css["status_bar"])
|
||||||
self.status_bar.addPermanentWidget(self.server_status_indicator)
|
self.status_bar.addPermanentWidget(self.server_status_indicator)
|
||||||
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.local_only)
|
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.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
self.update_server_status_indicator
|
||||||
self.share_mode.start_server_finished.connect(self.update_server_status_indicator)
|
)
|
||||||
self.share_mode.stop_server_finished.connect(self.update_server_status_indicator)
|
self.share_mode.server_status.server_stopped.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
|
self.share_mode.start_server_finished.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
|
self.share_mode.stop_server_finished.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
self.share_mode.stop_server_finished.connect(self.stop_server_finished)
|
self.share_mode.stop_server_finished.connect(self.stop_server_finished)
|
||||||
self.share_mode.start_server_finished.connect(self.clear_message)
|
self.share_mode.start_server_finished.connect(self.clear_message)
|
||||||
self.share_mode.server_status.button_clicked.connect(self.clear_message)
|
self.share_mode.server_status.button_clicked.connect(self.clear_message)
|
||||||
|
@ -147,31 +190,68 @@ 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, None, self.local_only)
|
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.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
self.update_server_status_indicator
|
||||||
self.receive_mode.start_server_finished.connect(self.update_server_status_indicator)
|
)
|
||||||
self.receive_mode.stop_server_finished.connect(self.update_server_status_indicator)
|
self.receive_mode.server_status.server_stopped.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
|
self.receive_mode.start_server_finished.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
|
self.receive_mode.stop_server_finished.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
self.receive_mode.stop_server_finished.connect(self.stop_server_finished)
|
self.receive_mode.stop_server_finished.connect(self.stop_server_finished)
|
||||||
self.receive_mode.start_server_finished.connect(self.clear_message)
|
self.receive_mode.start_server_finished.connect(self.clear_message)
|
||||||
self.receive_mode.server_status.button_clicked.connect(self.clear_message)
|
self.receive_mode.server_status.button_clicked.connect(self.clear_message)
|
||||||
self.receive_mode.server_status.url_copied.connect(self.copy_url)
|
self.receive_mode.server_status.url_copied.connect(self.copy_url)
|
||||||
self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
self.receive_mode.server_status.hidservauth_copied.connect(
|
||||||
|
self.copy_hidservauth
|
||||||
|
)
|
||||||
self.receive_mode.set_server_active.connect(self.set_server_active)
|
self.receive_mode.set_server_active.connect(self.set_server_active)
|
||||||
|
|
||||||
# Website mode
|
# Website mode
|
||||||
self.website_mode = WebsiteMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames)
|
self.website_mode = WebsiteMode(
|
||||||
|
self.common,
|
||||||
|
qtapp,
|
||||||
|
app,
|
||||||
|
self.status_bar,
|
||||||
|
self.server_status_label,
|
||||||
|
self.system_tray,
|
||||||
|
filenames,
|
||||||
|
)
|
||||||
self.website_mode.init()
|
self.website_mode.init()
|
||||||
self.website_mode.server_status.server_started.connect(self.update_server_status_indicator)
|
self.website_mode.server_status.server_started.connect(
|
||||||
self.website_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
|
self.update_server_status_indicator
|
||||||
self.website_mode.start_server_finished.connect(self.update_server_status_indicator)
|
)
|
||||||
self.website_mode.stop_server_finished.connect(self.update_server_status_indicator)
|
self.website_mode.server_status.server_stopped.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
|
self.website_mode.start_server_finished.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
|
self.website_mode.stop_server_finished.connect(
|
||||||
|
self.update_server_status_indicator
|
||||||
|
)
|
||||||
self.website_mode.stop_server_finished.connect(self.stop_server_finished)
|
self.website_mode.stop_server_finished.connect(self.stop_server_finished)
|
||||||
self.website_mode.start_server_finished.connect(self.clear_message)
|
self.website_mode.start_server_finished.connect(self.clear_message)
|
||||||
self.website_mode.server_status.button_clicked.connect(self.clear_message)
|
self.website_mode.server_status.button_clicked.connect(self.clear_message)
|
||||||
self.website_mode.server_status.url_copied.connect(self.copy_url)
|
self.website_mode.server_status.url_copied.connect(self.copy_url)
|
||||||
self.website_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
self.website_mode.server_status.hidservauth_copied.connect(
|
||||||
|
self.copy_hidservauth
|
||||||
|
)
|
||||||
self.website_mode.set_server_active.connect(self.set_server_active)
|
self.website_mode.set_server_active.connect(self.set_server_active)
|
||||||
|
|
||||||
self.update_mode_switcher()
|
self.update_mode_switcher()
|
||||||
|
@ -218,25 +298,43 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
# Based on the current mode, switch the mode switcher button styles,
|
# Based on the current mode, switch the mode switcher button styles,
|
||||||
# and show and hide widgets to switch modes
|
# and show and hide widgets to switch modes
|
||||||
if self.mode == self.MODE_SHARE:
|
if self.mode == self.MODE_SHARE:
|
||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
self.share_mode_button.setStyleSheet(
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.common.css["mode_switcher_selected_style"]
|
||||||
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
)
|
||||||
|
self.receive_mode_button.setStyleSheet(
|
||||||
|
self.common.css["mode_switcher_unselected_style"]
|
||||||
|
)
|
||||||
|
self.website_mode_button.setStyleSheet(
|
||||||
|
self.common.css["mode_switcher_unselected_style"]
|
||||||
|
)
|
||||||
|
|
||||||
self.receive_mode.hide()
|
self.receive_mode.hide()
|
||||||
self.share_mode.show()
|
self.share_mode.show()
|
||||||
self.website_mode.hide()
|
self.website_mode.hide()
|
||||||
elif self.mode == self.MODE_WEBSITE:
|
elif self.mode == self.MODE_WEBSITE:
|
||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.share_mode_button.setStyleSheet(
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.common.css["mode_switcher_unselected_style"]
|
||||||
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
)
|
||||||
|
self.receive_mode_button.setStyleSheet(
|
||||||
|
self.common.css["mode_switcher_unselected_style"]
|
||||||
|
)
|
||||||
|
self.website_mode_button.setStyleSheet(
|
||||||
|
self.common.css["mode_switcher_selected_style"]
|
||||||
|
)
|
||||||
|
|
||||||
self.receive_mode.hide()
|
self.receive_mode.hide()
|
||||||
self.share_mode.hide()
|
self.share_mode.hide()
|
||||||
self.website_mode.show()
|
self.website_mode.show()
|
||||||
else:
|
else:
|
||||||
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
self.share_mode_button.setStyleSheet(
|
||||||
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
|
self.common.css["mode_switcher_unselected_style"]
|
||||||
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
|
)
|
||||||
|
self.receive_mode_button.setStyleSheet(
|
||||||
|
self.common.css["mode_switcher_selected_style"]
|
||||||
|
)
|
||||||
|
self.website_mode_button.setStyleSheet(
|
||||||
|
self.common.css["mode_switcher_unselected_style"]
|
||||||
|
)
|
||||||
|
|
||||||
self.share_mode.hide()
|
self.share_mode.hide()
|
||||||
self.receive_mode.show()
|
self.receive_mode.show()
|
||||||
|
@ -246,19 +344,19 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
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")
|
||||||
self.mode = self.MODE_SHARE
|
self.mode = self.MODE_SHARE
|
||||||
self.update_mode_switcher()
|
self.update_mode_switcher()
|
||||||
|
|
||||||
def receive_mode_clicked(self):
|
def receive_mode_clicked(self):
|
||||||
if self.mode != self.MODE_RECEIVE:
|
if self.mode != self.MODE_RECEIVE:
|
||||||
self.common.log('OnionShareGui', 'receive_mode_clicked')
|
self.common.log("OnionShareGui", "receive_mode_clicked")
|
||||||
self.mode = self.MODE_RECEIVE
|
self.mode = self.MODE_RECEIVE
|
||||||
self.update_mode_switcher()
|
self.update_mode_switcher()
|
||||||
|
|
||||||
def website_mode_clicked(self):
|
def website_mode_clicked(self):
|
||||||
if self.mode != self.MODE_WEBSITE:
|
if self.mode != self.MODE_WEBSITE:
|
||||||
self.common.log('OnionShareGui', 'website_mode_clicked')
|
self.common.log("OnionShareGui", "website_mode_clicked")
|
||||||
self.mode = self.MODE_WEBSITE
|
self.mode = self.MODE_WEBSITE
|
||||||
self.update_mode_switcher()
|
self.update_mode_switcher()
|
||||||
|
|
||||||
|
@ -267,42 +365,82 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
if self.mode == self.MODE_SHARE:
|
if self.mode == self.MODE_SHARE:
|
||||||
# Share mode
|
# Share mode
|
||||||
if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_stopped'))
|
QtGui.QPixmap.fromImage(self.server_status_image_stopped)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_stopped")
|
||||||
|
)
|
||||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
|
self.server_status_image_label.setPixmap(
|
||||||
|
QtGui.QPixmap.fromImage(self.server_status_image_working)
|
||||||
|
)
|
||||||
if self.share_mode.server_status.autostart_timer_datetime:
|
if self.share_mode.server_status.autostart_timer_datetime:
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_scheduled'))
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_scheduled")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_working'))
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_working")
|
||||||
|
)
|
||||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
|
QtGui.QPixmap.fromImage(self.server_status_image_started)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_started")
|
||||||
|
)
|
||||||
elif self.mode == self.MODE_WEBSITE:
|
elif self.mode == self.MODE_WEBSITE:
|
||||||
# Website mode
|
# Website mode
|
||||||
if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_stopped'))
|
QtGui.QPixmap.fromImage(self.server_status_image_stopped)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_stopped")
|
||||||
|
)
|
||||||
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_working'))
|
QtGui.QPixmap.fromImage(self.server_status_image_working)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_working")
|
||||||
|
)
|
||||||
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
|
QtGui.QPixmap.fromImage(self.server_status_image_started)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_started")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Receive mode
|
# Receive mode
|
||||||
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped'))
|
QtGui.QPixmap.fromImage(self.server_status_image_stopped)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_receive_stopped")
|
||||||
|
)
|
||||||
elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
|
self.server_status_image_label.setPixmap(
|
||||||
|
QtGui.QPixmap.fromImage(self.server_status_image_working)
|
||||||
|
)
|
||||||
if self.receive_mode.server_status.autostart_timer_datetime:
|
if self.receive_mode.server_status.autostart_timer_datetime:
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_receive_scheduled'))
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_receive_scheduled")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_receive_working'))
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_receive_working")
|
||||||
|
)
|
||||||
elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||||
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
|
self.server_status_image_label.setPixmap(
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_receive_started'))
|
QtGui.QPixmap.fromImage(self.server_status_image_started)
|
||||||
|
)
|
||||||
|
self.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_receive_started")
|
||||||
|
)
|
||||||
|
|
||||||
def stop_server_finished(self):
|
def stop_server_finished(self):
|
||||||
# When the server stopped, cleanup the ephemeral onion service
|
# When the server stopped, cleanup the ephemeral onion service
|
||||||
|
@ -313,12 +451,22 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
If the user cancels before Tor finishes connecting, ask if they want to
|
If the user cancels before Tor finishes connecting, ask if they want to
|
||||||
quit, or open settings.
|
quit, or open settings.
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShareGui', '_tor_connection_canceled')
|
self.common.log("OnionShareGui", "_tor_connection_canceled")
|
||||||
|
|
||||||
def ask():
|
def ask():
|
||||||
a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
|
a = Alert(
|
||||||
settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings'))
|
self.common,
|
||||||
quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit'))
|
strings._("gui_tor_connection_ask"),
|
||||||
|
QtWidgets.QMessageBox.Question,
|
||||||
|
buttons=QtWidgets.QMessageBox.NoButton,
|
||||||
|
autostart=False,
|
||||||
|
)
|
||||||
|
settings_button = QtWidgets.QPushButton(
|
||||||
|
strings._("gui_tor_connection_ask_open_settings")
|
||||||
|
)
|
||||||
|
quit_button = QtWidgets.QPushButton(
|
||||||
|
strings._("gui_tor_connection_ask_quit")
|
||||||
|
)
|
||||||
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
|
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
|
||||||
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
|
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
|
||||||
a.setDefaultButton(settings_button)
|
a.setDefaultButton(settings_button)
|
||||||
|
@ -326,12 +474,18 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
if a.clickedButton() == settings_button:
|
if a.clickedButton() == settings_button:
|
||||||
# Open settings
|
# Open settings
|
||||||
self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked')
|
self.common.log(
|
||||||
|
"OnionShareGui",
|
||||||
|
"_tor_connection_canceled",
|
||||||
|
"Settings button clicked",
|
||||||
|
)
|
||||||
self.open_settings()
|
self.open_settings()
|
||||||
|
|
||||||
if a.clickedButton() == quit_button:
|
if a.clickedButton() == quit_button:
|
||||||
# Quit
|
# Quit
|
||||||
self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked')
|
self.common.log(
|
||||||
|
"OnionShareGui", "_tor_connection_canceled", "Quit button clicked"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait 1ms for the event loop to finish, then quit
|
# Wait 1ms for the event loop to finish, then quit
|
||||||
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
||||||
|
@ -343,7 +497,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
The TorConnectionDialog wants to open the Settings dialog
|
The TorConnectionDialog wants to open the Settings dialog
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShareGui', '_tor_connection_open_settings')
|
self.common.log("OnionShareGui", "_tor_connection_open_settings")
|
||||||
|
|
||||||
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
|
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
|
||||||
QtCore.QTimer.singleShot(1, self.open_settings)
|
QtCore.QTimer.singleShot(1, self.open_settings)
|
||||||
|
@ -352,10 +506,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
Open the SettingsDialog.
|
Open the SettingsDialog.
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShareGui', 'open_settings')
|
self.common.log("OnionShareGui", "open_settings")
|
||||||
|
|
||||||
def reload_settings():
|
def reload_settings():
|
||||||
self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
|
self.common.log(
|
||||||
|
"OnionShareGui", "open_settings", "settings have changed, reloading"
|
||||||
|
)
|
||||||
self.common.settings.load()
|
self.common.settings.load()
|
||||||
|
|
||||||
# We might've stopped the main requests timer if a Tor connection failed.
|
# We might've stopped the main requests timer if a Tor connection failed.
|
||||||
|
@ -371,12 +527,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
|
|
||||||
# If we switched off the auto-stop timer setting, ensure the widget is hidden.
|
# If we switched off the auto-stop timer setting, ensure the widget is hidden.
|
||||||
if not self.common.settings.get('autostop_timer'):
|
if not self.common.settings.get("autostop_timer"):
|
||||||
self.share_mode.server_status.autostop_timer_container.hide()
|
self.share_mode.server_status.autostop_timer_container.hide()
|
||||||
self.receive_mode.server_status.autostop_timer_container.hide()
|
self.receive_mode.server_status.autostop_timer_container.hide()
|
||||||
self.website_mode.server_status.autostop_timer_container.hide()
|
self.website_mode.server_status.autostop_timer_container.hide()
|
||||||
# If we switched off the auto-start timer setting, ensure the widget is hidden.
|
# If we switched off the auto-start timer setting, ensure the widget is hidden.
|
||||||
if not self.common.settings.get('autostart_timer'):
|
if not self.common.settings.get("autostart_timer"):
|
||||||
self.share_mode.server_status.autostart_timer_datetime = None
|
self.share_mode.server_status.autostart_timer_datetime = None
|
||||||
self.receive_mode.server_status.autostart_timer_datetime = None
|
self.receive_mode.server_status.autostart_timer_datetime = None
|
||||||
self.website_mode.server_status.autostart_timer_datetime = None
|
self.website_mode.server_status.autostart_timer_datetime = None
|
||||||
|
@ -384,7 +540,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.receive_mode.server_status.autostart_timer_container.hide()
|
self.receive_mode.server_status.autostart_timer_container.hide()
|
||||||
self.website_mode.server_status.autostart_timer_container.hide()
|
self.website_mode.server_status.autostart_timer_container.hide()
|
||||||
|
|
||||||
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
|
d = SettingsDialog(
|
||||||
|
self.common, self.onion, self.qtapp, self.config, self.local_only
|
||||||
|
)
|
||||||
d.settings_saved.connect(reload_settings)
|
d.settings_saved.connect(reload_settings)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
@ -397,10 +555,16 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
Check for updates in a new thread, if enabled.
|
Check for updates in a new thread, if enabled.
|
||||||
"""
|
"""
|
||||||
if self.common.platform == 'Windows' or self.common.platform == 'Darwin':
|
if self.common.platform == "Windows" or self.common.platform == "Darwin":
|
||||||
if self.common.settings.get('use_autoupdate'):
|
if self.common.settings.get("use_autoupdate"):
|
||||||
|
|
||||||
def update_available(update_url, installed_version, latest_version):
|
def update_available(update_url, installed_version, latest_version):
|
||||||
Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version))
|
Alert(
|
||||||
|
self.common,
|
||||||
|
strings._("update_available").format(
|
||||||
|
update_url, installed_version, latest_version
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
self.update_thread = UpdateThread(self.common, self.onion, self.config)
|
self.update_thread = UpdateThread(self.common, self.onion, self.config)
|
||||||
self.update_thread.update_available.connect(update_available)
|
self.update_thread.update_available.connect(update_available)
|
||||||
|
@ -417,8 +581,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
# Have we lost connection to Tor somehow?
|
# Have we lost connection to Tor somehow?
|
||||||
if not self.onion.is_authenticated():
|
if not self.onion.is_authenticated():
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
self.status_bar.showMessage(strings._('gui_tor_connection_lost'))
|
self.status_bar.showMessage(strings._("gui_tor_connection_lost"))
|
||||||
self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("gui_tor_connection_lost"),
|
||||||
|
strings._("gui_tor_connection_error_settings"),
|
||||||
|
)
|
||||||
|
|
||||||
self.share_mode.handle_tor_broke()
|
self.share_mode.handle_tor_broke()
|
||||||
self.receive_mode.handle_tor_broke()
|
self.receive_mode.handle_tor_broke()
|
||||||
|
@ -480,14 +647,31 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
mode.handle_request_individual_file_canceled(event)
|
mode.handle_request_individual_file_canceled(event)
|
||||||
|
|
||||||
if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE:
|
if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE:
|
||||||
Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"]))
|
Alert(
|
||||||
|
self.common,
|
||||||
|
strings._("error_cannot_create_data_dir").format(
|
||||||
|
event["data"]["receive_mode_dir"]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if event["type"] == Web.REQUEST_OTHER:
|
if event["type"] == Web.REQUEST_OTHER:
|
||||||
if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_password):
|
if event["path"] != "/favicon.ico" and event[
|
||||||
self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"]))
|
"path"
|
||||||
|
] != "/{}/shutdown".format(mode.web.shutdown_password):
|
||||||
|
self.status_bar.showMessage(
|
||||||
|
"{0:s}: {1:s}".format(
|
||||||
|
strings._("other_page_loaded"), event["path"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if event["type"] == Web.REQUEST_INVALID_PASSWORD:
|
if event["type"] == Web.REQUEST_INVALID_PASSWORD:
|
||||||
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_passwords_count, strings._('incorrect_password'), event["data"]))
|
self.status_bar.showMessage(
|
||||||
|
"[#{0:d}] {1:s}: {2:s}".format(
|
||||||
|
mode.web.invalid_passwords_count,
|
||||||
|
strings._("incorrect_password"),
|
||||||
|
event["data"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
mode.timer_callback()
|
mode.timer_callback()
|
||||||
|
|
||||||
|
@ -495,15 +679,20 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
When the URL gets copied to the clipboard, display this in the status bar.
|
When the URL gets copied to the clipboard, display this in the status bar.
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShareGui', 'copy_url')
|
self.common.log("OnionShareGui", "copy_url")
|
||||||
self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("gui_copied_url_title"), strings._("gui_copied_url")
|
||||||
|
)
|
||||||
|
|
||||||
def copy_hidservauth(self):
|
def copy_hidservauth(self):
|
||||||
"""
|
"""
|
||||||
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
|
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
|
||||||
"""
|
"""
|
||||||
self.common.log('OnionShareGui', 'copy_hidservauth')
|
self.common.log("OnionShareGui", "copy_hidservauth")
|
||||||
self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth'))
|
self.system_tray.showMessage(
|
||||||
|
strings._("gui_copied_hidservauth_title"),
|
||||||
|
strings._("gui_copied_hidservauth"),
|
||||||
|
)
|
||||||
|
|
||||||
def clear_message(self):
|
def clear_message(self):
|
||||||
"""
|
"""
|
||||||
|
@ -539,7 +728,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.settings_action.setEnabled(not active)
|
self.settings_action.setEnabled(not active)
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
self.common.log('OnionShareGui', 'closeEvent')
|
self.common.log("OnionShareGui", "closeEvent")
|
||||||
self.system_tray.hide()
|
self.system_tray.hide()
|
||||||
try:
|
try:
|
||||||
if self.mode == OnionShareGui.MODE_SHARE:
|
if self.mode == OnionShareGui.MODE_SHARE:
|
||||||
|
@ -549,16 +738,21 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
else:
|
else:
|
||||||
server_status = self.receive_mode.server_status
|
server_status = self.receive_mode.server_status
|
||||||
if server_status.status != server_status.STATUS_STOPPED:
|
if server_status.status != server_status.STATUS_STOPPED:
|
||||||
self.common.log('OnionShareGui', 'closeEvent, opening warning dialog')
|
self.common.log("OnionShareGui", "closeEvent, opening warning dialog")
|
||||||
dialog = QtWidgets.QMessageBox()
|
dialog = QtWidgets.QMessageBox()
|
||||||
dialog.setWindowTitle(strings._('gui_quit_title'))
|
dialog.setWindowTitle(strings._("gui_quit_title"))
|
||||||
if self.mode == OnionShareGui.MODE_SHARE:
|
if self.mode == OnionShareGui.MODE_SHARE:
|
||||||
dialog.setText(strings._('gui_share_quit_warning'))
|
dialog.setText(strings._("gui_share_quit_warning"))
|
||||||
else:
|
else:
|
||||||
dialog.setText(strings._('gui_receive_quit_warning'))
|
dialog.setText(strings._("gui_receive_quit_warning"))
|
||||||
dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||||
quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole)
|
quit_button = dialog.addButton(
|
||||||
dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole)
|
strings._("gui_quit_warning_quit"), QtWidgets.QMessageBox.YesRole
|
||||||
|
)
|
||||||
|
dont_quit_button = dialog.addButton(
|
||||||
|
strings._("gui_quit_warning_dont_quit"),
|
||||||
|
QtWidgets.QMessageBox.NoRole,
|
||||||
|
)
|
||||||
dialog.setDefaultButton(dont_quit_button)
|
dialog.setDefaultButton(dont_quit_button)
|
||||||
reply = dialog.exec_()
|
reply = dialog.exec_()
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,12 @@ from onionshare import strings
|
||||||
|
|
||||||
from .widgets import Alert
|
from .widgets import Alert
|
||||||
|
|
||||||
|
|
||||||
class ServerStatus(QtWidgets.QWidget):
|
class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
The server status chunk of the GUI.
|
The server status chunk of the GUI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
server_started = QtCore.pyqtSignal()
|
server_started = QtCore.pyqtSignal()
|
||||||
server_started_finished = QtCore.pyqtSignal()
|
server_started_finished = QtCore.pyqtSignal()
|
||||||
server_stopped = QtCore.pyqtSignal()
|
server_stopped = QtCore.pyqtSignal()
|
||||||
|
@ -37,9 +39,9 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
url_copied = QtCore.pyqtSignal()
|
url_copied = QtCore.pyqtSignal()
|
||||||
hidservauth_copied = QtCore.pyqtSignal()
|
hidservauth_copied = QtCore.pyqtSignal()
|
||||||
|
|
||||||
MODE_SHARE = 'share'
|
MODE_SHARE = "share"
|
||||||
MODE_RECEIVE = 'receive'
|
MODE_RECEIVE = "receive"
|
||||||
MODE_WEBSITE = 'website'
|
MODE_WEBSITE = "website"
|
||||||
|
|
||||||
STATUS_STOPPED = 0
|
STATUS_STOPPED = 0
|
||||||
STATUS_WORKING = 1
|
STATUS_WORKING = 1
|
||||||
|
@ -63,19 +65,31 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.resizeEvent(None)
|
self.resizeEvent(None)
|
||||||
|
|
||||||
# Auto-start timer layout
|
# Auto-start timer layout
|
||||||
self.autostart_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostart_timer'))
|
self.autostart_timer_label = QtWidgets.QLabel(
|
||||||
|
strings._("gui_settings_autostart_timer")
|
||||||
|
)
|
||||||
self.autostart_timer_widget = QtWidgets.QDateTimeEdit()
|
self.autostart_timer_widget = QtWidgets.QDateTimeEdit()
|
||||||
self.autostart_timer_widget.setDisplayFormat("hh:mm A MMM d, yy")
|
self.autostart_timer_widget.setDisplayFormat("hh:mm A MMM d, yy")
|
||||||
if self.local_only:
|
if self.local_only:
|
||||||
# For testing
|
# For testing
|
||||||
self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15))
|
self.autostart_timer_widget.setDateTime(
|
||||||
self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime())
|
QtCore.QDateTime.currentDateTime().addSecs(15)
|
||||||
|
)
|
||||||
|
self.autostart_timer_widget.setMinimumDateTime(
|
||||||
|
QtCore.QDateTime.currentDateTime()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Set proposed timer to be 5 minutes into the future
|
# Set proposed timer to be 5 minutes into the future
|
||||||
self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
self.autostart_timer_widget.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 60s 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.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
|
self.autostart_timer_widget.setMinimumDateTime(
|
||||||
self.autostart_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
|
QtCore.QDateTime.currentDateTime().addSecs(60)
|
||||||
|
)
|
||||||
|
self.autostart_timer_widget.setCurrentSection(
|
||||||
|
QtWidgets.QDateTimeEdit.MinuteSection
|
||||||
|
)
|
||||||
autostart_timer_layout = QtWidgets.QHBoxLayout()
|
autostart_timer_layout = QtWidgets.QHBoxLayout()
|
||||||
autostart_timer_layout.addWidget(self.autostart_timer_label)
|
autostart_timer_layout.addWidget(self.autostart_timer_label)
|
||||||
autostart_timer_layout.addWidget(self.autostart_timer_widget)
|
autostart_timer_layout.addWidget(self.autostart_timer_widget)
|
||||||
|
@ -88,19 +102,31 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.autostart_timer_container.hide()
|
self.autostart_timer_container.hide()
|
||||||
|
|
||||||
# Auto-stop timer layout
|
# Auto-stop timer layout
|
||||||
self.autostop_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostop_timer'))
|
self.autostop_timer_label = QtWidgets.QLabel(
|
||||||
|
strings._("gui_settings_autostop_timer")
|
||||||
|
)
|
||||||
self.autostop_timer_widget = QtWidgets.QDateTimeEdit()
|
self.autostop_timer_widget = QtWidgets.QDateTimeEdit()
|
||||||
self.autostop_timer_widget.setDisplayFormat("hh:mm A MMM d, yy")
|
self.autostop_timer_widget.setDisplayFormat("hh:mm A MMM d, yy")
|
||||||
if self.local_only:
|
if self.local_only:
|
||||||
# For testing
|
# For testing
|
||||||
self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15))
|
self.autostop_timer_widget.setDateTime(
|
||||||
self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime())
|
QtCore.QDateTime.currentDateTime().addSecs(15)
|
||||||
|
)
|
||||||
|
self.autostop_timer_widget.setMinimumDateTime(
|
||||||
|
QtCore.QDateTime.currentDateTime()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Set proposed timer to be 5 minutes into the future
|
# Set proposed timer to be 5 minutes into the future
|
||||||
self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
self.autostop_timer_widget.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 60s 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.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
|
self.autostop_timer_widget.setMinimumDateTime(
|
||||||
self.autostop_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
|
QtCore.QDateTime.currentDateTime().addSecs(60)
|
||||||
|
)
|
||||||
|
self.autostop_timer_widget.setCurrentSection(
|
||||||
|
QtWidgets.QDateTimeEdit.MinuteSection
|
||||||
|
)
|
||||||
autostop_timer_layout = QtWidgets.QHBoxLayout()
|
autostop_timer_layout = QtWidgets.QHBoxLayout()
|
||||||
autostop_timer_layout.addWidget(self.autostop_timer_label)
|
autostop_timer_layout.addWidget(self.autostop_timer_label)
|
||||||
autostop_timer_layout.addWidget(self.autostop_timer_widget)
|
autostop_timer_layout.addWidget(self.autostop_timer_widget)
|
||||||
|
@ -125,16 +151,20 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.url.setFont(url_font)
|
self.url.setFont(url_font)
|
||||||
self.url.setWordWrap(True)
|
self.url.setWordWrap(True)
|
||||||
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'))
|
self.copy_url_button = QtWidgets.QPushButton(strings._("gui_copy_url"))
|
||||||
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.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'))
|
self.copy_hidservauth_button = QtWidgets.QPushButton(
|
||||||
|
strings._("gui_copy_hidservauth")
|
||||||
|
)
|
||||||
self.copy_hidservauth_button.setFlat(True)
|
self.copy_hidservauth_button.setFlat(True)
|
||||||
self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons'])
|
self.copy_hidservauth_button.setStyleSheet(
|
||||||
|
self.common.css["server_status_url_buttons"]
|
||||||
|
)
|
||||||
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
|
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
|
||||||
url_buttons_layout = QtWidgets.QHBoxLayout()
|
url_buttons_layout = QtWidgets.QHBoxLayout()
|
||||||
url_buttons_layout.addWidget(self.copy_url_button)
|
url_buttons_layout.addWidget(self.copy_url_button)
|
||||||
|
@ -160,7 +190,9 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
self.mode = share_mode
|
self.mode = share_mode
|
||||||
|
|
||||||
if (self.mode == ServerStatus.MODE_SHARE) or (self.mode == ServerStatus.MODE_WEBSITE):
|
if (self.mode == ServerStatus.MODE_SHARE) or (
|
||||||
|
self.mode == ServerStatus.MODE_WEBSITE
|
||||||
|
):
|
||||||
self.file_selection = file_selection
|
self.file_selection = file_selection
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
@ -186,17 +218,25 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Reset the auto-start timer in the UI after stopping a share
|
Reset the auto-start timer in the UI after stopping a share
|
||||||
"""
|
"""
|
||||||
self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
self.autostart_timer_widget.setDateTime(
|
||||||
|
QtCore.QDateTime.currentDateTime().addSecs(300)
|
||||||
|
)
|
||||||
if not self.local_only:
|
if not self.local_only:
|
||||||
self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
|
self.autostart_timer_widget.setMinimumDateTime(
|
||||||
|
QtCore.QDateTime.currentDateTime().addSecs(60)
|
||||||
|
)
|
||||||
|
|
||||||
def autostop_timer_reset(self):
|
def autostop_timer_reset(self):
|
||||||
"""
|
"""
|
||||||
Reset the auto-stop timer in the UI after stopping a share
|
Reset the auto-stop timer in the UI after stopping a share
|
||||||
"""
|
"""
|
||||||
self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
|
self.autostop_timer_widget.setDateTime(
|
||||||
|
QtCore.QDateTime.currentDateTime().addSecs(300)
|
||||||
|
)
|
||||||
if not self.local_only:
|
if not self.local_only:
|
||||||
self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
|
self.autostop_timer_widget.setMinimumDateTime(
|
||||||
|
QtCore.QDateTime.currentDateTime().addSecs(60)
|
||||||
|
)
|
||||||
|
|
||||||
def show_url(self):
|
def show_url(self):
|
||||||
"""
|
"""
|
||||||
|
@ -204,26 +244,38 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
self.url_description.show()
|
self.url_description.show()
|
||||||
|
|
||||||
info_image = self.common.get_resource_path('images/info.png')
|
info_image = self.common.get_resource_path("images/info.png")
|
||||||
|
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if self.mode == ServerStatus.MODE_SHARE:
|
||||||
self.url_description.setText(strings._('gui_share_url_description').format(info_image))
|
self.url_description.setText(
|
||||||
|
strings._("gui_share_url_description").format(info_image)
|
||||||
|
)
|
||||||
elif self.mode == ServerStatus.MODE_WEBSITE:
|
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||||
self.url_description.setText(strings._('gui_website_url_description').format(info_image))
|
self.url_description.setText(
|
||||||
|
strings._("gui_website_url_description").format(info_image)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
|
self.url_description.setText(
|
||||||
|
strings._("gui_receive_url_description").format(info_image)
|
||||||
|
)
|
||||||
|
|
||||||
# Show a Tool Tip explaining the lifecycle of this URL
|
# Show a Tool Tip explaining the lifecycle of this URL
|
||||||
if self.common.settings.get('save_private_key'):
|
if self.common.settings.get("save_private_key"):
|
||||||
if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'):
|
if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get(
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent'))
|
"close_after_first_download"
|
||||||
|
):
|
||||||
|
self.url_description.setToolTip(
|
||||||
|
strings._("gui_url_label_onetime_and_persistent")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_persistent'))
|
self.url_description.setToolTip(strings._("gui_url_label_persistent"))
|
||||||
else:
|
else:
|
||||||
if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'):
|
if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get(
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_onetime'))
|
"close_after_first_download"
|
||||||
|
):
|
||||||
|
self.url_description.setToolTip(strings._("gui_url_label_onetime"))
|
||||||
else:
|
else:
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_stay_open'))
|
self.url_description.setToolTip(strings._("gui_url_label_stay_open"))
|
||||||
|
|
||||||
self.url.setText(self.get_url())
|
self.url.setText(self.get_url())
|
||||||
self.url.show()
|
self.url.show()
|
||||||
|
@ -245,15 +297,15 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.common.settings.load()
|
self.common.settings.load()
|
||||||
self.show_url()
|
self.show_url()
|
||||||
|
|
||||||
if self.common.settings.get('save_private_key'):
|
if self.common.settings.get("save_private_key"):
|
||||||
if not self.common.settings.get('password'):
|
if not self.common.settings.get("password"):
|
||||||
self.common.settings.set('password', self.web.password)
|
self.common.settings.set("password", self.web.password)
|
||||||
self.common.settings.save()
|
self.common.settings.save()
|
||||||
|
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get("autostart_timer"):
|
||||||
self.autostart_timer_container.hide()
|
self.autostart_timer_container.hide()
|
||||||
|
|
||||||
if self.common.settings.get('autostop_timer'):
|
if self.common.settings.get("autostop_timer"):
|
||||||
self.autostop_timer_container.hide()
|
self.autostop_timer_container.hide()
|
||||||
else:
|
else:
|
||||||
self.url_description.hide()
|
self.url_description.hide()
|
||||||
|
@ -262,59 +314,91 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.copy_hidservauth_button.hide()
|
self.copy_hidservauth_button.hide()
|
||||||
|
|
||||||
# Button
|
# Button
|
||||||
if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0:
|
if (
|
||||||
|
self.mode == ServerStatus.MODE_SHARE
|
||||||
|
and self.file_selection.get_num_files() == 0
|
||||||
|
):
|
||||||
self.server_button.hide()
|
self.server_button.hide()
|
||||||
elif self.mode == ServerStatus.MODE_WEBSITE and self.file_selection.get_num_files() == 0:
|
elif (
|
||||||
|
self.mode == ServerStatus.MODE_WEBSITE
|
||||||
|
and self.file_selection.get_num_files() == 0
|
||||||
|
):
|
||||||
self.server_button.hide()
|
self.server_button.hide()
|
||||||
else:
|
else:
|
||||||
self.server_button.show()
|
self.server_button.show()
|
||||||
|
|
||||||
if self.status == self.STATUS_STOPPED:
|
if self.status == self.STATUS_STOPPED:
|
||||||
self.server_button.setStyleSheet(self.common.css['server_status_button_stopped'])
|
self.server_button.setStyleSheet(
|
||||||
|
self.common.css["server_status_button_stopped"]
|
||||||
|
)
|
||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if self.mode == ServerStatus.MODE_SHARE:
|
||||||
self.server_button.setText(strings._('gui_share_start_server'))
|
self.server_button.setText(strings._("gui_share_start_server"))
|
||||||
elif self.mode == ServerStatus.MODE_WEBSITE:
|
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||||
self.server_button.setText(strings._('gui_share_start_server'))
|
self.server_button.setText(strings._("gui_share_start_server"))
|
||||||
else:
|
else:
|
||||||
self.server_button.setText(strings._('gui_receive_start_server'))
|
self.server_button.setText(strings._("gui_receive_start_server"))
|
||||||
self.server_button.setToolTip('')
|
self.server_button.setToolTip("")
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get("autostart_timer"):
|
||||||
self.autostart_timer_container.show()
|
self.autostart_timer_container.show()
|
||||||
if self.common.settings.get('autostop_timer'):
|
if self.common.settings.get("autostop_timer"):
|
||||||
self.autostop_timer_container.show()
|
self.autostop_timer_container.show()
|
||||||
elif self.status == self.STATUS_STARTED:
|
elif self.status == self.STATUS_STARTED:
|
||||||
self.server_button.setStyleSheet(self.common.css['server_status_button_started'])
|
self.server_button.setStyleSheet(
|
||||||
|
self.common.css["server_status_button_started"]
|
||||||
|
)
|
||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
if self.mode == ServerStatus.MODE_SHARE:
|
if self.mode == ServerStatus.MODE_SHARE:
|
||||||
self.server_button.setText(strings._('gui_share_stop_server'))
|
self.server_button.setText(strings._("gui_share_stop_server"))
|
||||||
elif self.mode == ServerStatus.MODE_WEBSITE:
|
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||||
self.server_button.setText(strings._('gui_share_stop_server'))
|
self.server_button.setText(strings._("gui_share_stop_server"))
|
||||||
else:
|
else:
|
||||||
self.server_button.setText(strings._('gui_receive_stop_server'))
|
self.server_button.setText(strings._("gui_receive_stop_server"))
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get("autostart_timer"):
|
||||||
self.autostart_timer_container.hide()
|
self.autostart_timer_container.hide()
|
||||||
if self.common.settings.get('autostop_timer'):
|
if self.common.settings.get("autostop_timer"):
|
||||||
self.autostop_timer_container.hide()
|
self.autostop_timer_container.hide()
|
||||||
self.server_button.setToolTip(strings._('gui_stop_server_autostop_timer_tooltip').format(self.autostop_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy")))
|
self.server_button.setToolTip(
|
||||||
|
strings._("gui_stop_server_autostop_timer_tooltip").format(
|
||||||
|
self.autostop_timer_widget.dateTime().toString(
|
||||||
|
"h:mm AP, MMMM dd, yyyy"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
elif self.status == self.STATUS_WORKING:
|
elif self.status == self.STATUS_WORKING:
|
||||||
self.server_button.setStyleSheet(self.common.css['server_status_button_working'])
|
self.server_button.setStyleSheet(
|
||||||
|
self.common.css["server_status_button_working"]
|
||||||
|
)
|
||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
if self.autostart_timer_datetime:
|
if self.autostart_timer_datetime:
|
||||||
self.autostart_timer_container.hide()
|
self.autostart_timer_container.hide()
|
||||||
self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy")))
|
self.server_button.setToolTip(
|
||||||
|
strings._("gui_start_server_autostart_timer_tooltip").format(
|
||||||
|
self.autostart_timer_widget.dateTime().toString(
|
||||||
|
"h:mm AP, MMMM dd, yyyy"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.server_button.setText(strings._('gui_please_wait'))
|
self.server_button.setText(strings._("gui_please_wait"))
|
||||||
if self.common.settings.get('autostop_timer'):
|
if self.common.settings.get("autostop_timer"):
|
||||||
self.autostop_timer_container.hide()
|
self.autostop_timer_container.hide()
|
||||||
else:
|
else:
|
||||||
self.server_button.setStyleSheet(self.common.css['server_status_button_working'])
|
self.server_button.setStyleSheet(
|
||||||
|
self.common.css["server_status_button_working"]
|
||||||
|
)
|
||||||
self.server_button.setEnabled(False)
|
self.server_button.setEnabled(False)
|
||||||
self.server_button.setText(strings._('gui_please_wait'))
|
self.server_button.setText(strings._("gui_please_wait"))
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get("autostart_timer"):
|
||||||
self.autostart_timer_container.hide()
|
self.autostart_timer_container.hide()
|
||||||
self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy")))
|
self.server_button.setToolTip(
|
||||||
if self.common.settings.get('autostop_timer'):
|
strings._("gui_start_server_autostart_timer_tooltip").format(
|
||||||
|
self.autostart_timer_widget.dateTime().toString(
|
||||||
|
"h:mm AP, MMMM dd, yyyy"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self.common.settings.get("autostop_timer"):
|
||||||
self.autostop_timer_container.hide()
|
self.autostop_timer_container.hide()
|
||||||
|
|
||||||
def server_button_clicked(self):
|
def server_button_clicked(self):
|
||||||
|
@ -323,28 +407,60 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
if self.status == self.STATUS_STOPPED:
|
if self.status == self.STATUS_STOPPED:
|
||||||
can_start = True
|
can_start = True
|
||||||
if self.common.settings.get('autostart_timer'):
|
if self.common.settings.get("autostart_timer"):
|
||||||
if self.local_only:
|
if self.local_only:
|
||||||
self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime()
|
self.autostart_timer_datetime = (
|
||||||
|
self.autostart_timer_widget.dateTime().toPyDateTime()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0)
|
self.autostart_timer_datetime = (
|
||||||
|
self.autostart_timer_widget.dateTime()
|
||||||
|
.toPyDateTime()
|
||||||
|
.replace(second=0, microsecond=0)
|
||||||
|
)
|
||||||
# If the timer has actually passed already before the user hit Start, refuse to start the server.
|
# If the timer has actually passed already before the user hit Start, refuse to start the server.
|
||||||
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostart_timer_datetime:
|
if (
|
||||||
|
QtCore.QDateTime.currentDateTime().toPyDateTime()
|
||||||
|
> self.autostart_timer_datetime
|
||||||
|
):
|
||||||
can_start = False
|
can_start = False
|
||||||
Alert(self.common, strings._('gui_server_autostart_timer_expired'), QtWidgets.QMessageBox.Warning)
|
Alert(
|
||||||
if self.common.settings.get('autostop_timer'):
|
self.common,
|
||||||
|
strings._("gui_server_autostart_timer_expired"),
|
||||||
|
QtWidgets.QMessageBox.Warning,
|
||||||
|
)
|
||||||
|
if self.common.settings.get("autostop_timer"):
|
||||||
if self.local_only:
|
if self.local_only:
|
||||||
self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime()
|
self.autostop_timer_datetime = (
|
||||||
|
self.autostop_timer_widget.dateTime().toPyDateTime()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Get the timer chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
|
# Get the timer chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
|
||||||
self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0)
|
self.autostop_timer_datetime = (
|
||||||
|
self.autostop_timer_widget.dateTime()
|
||||||
|
.toPyDateTime()
|
||||||
|
.replace(second=0, microsecond=0)
|
||||||
|
)
|
||||||
# If the timer has actually passed already before the user hit Start, refuse to start the server.
|
# If the timer has actually passed already before the user hit Start, refuse to start the server.
|
||||||
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostop_timer_datetime:
|
if (
|
||||||
|
QtCore.QDateTime.currentDateTime().toPyDateTime()
|
||||||
|
> self.autostop_timer_datetime
|
||||||
|
):
|
||||||
can_start = False
|
can_start = False
|
||||||
Alert(self.common, strings._('gui_server_autostop_timer_expired'), QtWidgets.QMessageBox.Warning)
|
Alert(
|
||||||
if self.common.settings.get('autostart_timer'):
|
self.common,
|
||||||
|
strings._("gui_server_autostop_timer_expired"),
|
||||||
|
QtWidgets.QMessageBox.Warning,
|
||||||
|
)
|
||||||
|
if self.common.settings.get("autostart_timer"):
|
||||||
if self.autostop_timer_datetime <= self.autostart_timer_datetime:
|
if self.autostop_timer_datetime <= self.autostart_timer_datetime:
|
||||||
Alert(self.common, strings._('gui_autostop_timer_cant_be_earlier_than_autostart_timer'), QtWidgets.QMessageBox.Warning)
|
Alert(
|
||||||
|
self.common,
|
||||||
|
strings._(
|
||||||
|
"gui_autostop_timer_cant_be_earlier_than_autostart_timer"
|
||||||
|
),
|
||||||
|
QtWidgets.QMessageBox.Warning,
|
||||||
|
)
|
||||||
can_start = False
|
can_start = False
|
||||||
if can_start:
|
if can_start:
|
||||||
self.start_server()
|
self.start_server()
|
||||||
|
@ -385,7 +501,9 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Cancel the server.
|
Cancel the server.
|
||||||
"""
|
"""
|
||||||
self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
|
self.common.log(
|
||||||
|
"ServerStatus", "cancel_server", "Canceling the server mid-startup"
|
||||||
|
)
|
||||||
self.status = self.STATUS_WORKING
|
self.status = self.STATUS_WORKING
|
||||||
self.autostart_timer_reset()
|
self.autostart_timer_reset()
|
||||||
self.autostop_timer_reset()
|
self.autostop_timer_reset()
|
||||||
|
@ -421,8 +539,10 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Returns the OnionShare URL.
|
Returns the OnionShare URL.
|
||||||
"""
|
"""
|
||||||
if self.common.settings.get('public_mode'):
|
if self.common.settings.get("public_mode"):
|
||||||
url = 'http://{0:s}'.format(self.app.onion_host)
|
url = "http://{0:s}".format(self.app.onion_host)
|
||||||
else:
|
else:
|
||||||
url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.password, self.app.onion_host)
|
url = "http://onionshare:{0:s}@{1:s}".format(
|
||||||
|
self.web.password, self.app.onion_host
|
||||||
|
)
|
||||||
return url
|
return url
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,7 @@ class OnionThread(QtCore.QThread):
|
||||||
"""
|
"""
|
||||||
Starts the onion service, and waits for it to finish
|
Starts the onion service, and waits for it to finish
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = QtCore.pyqtSignal()
|
success = QtCore.pyqtSignal()
|
||||||
success_early = QtCore.pyqtSignal()
|
success_early = QtCore.pyqtSignal()
|
||||||
error = QtCore.pyqtSignal(str)
|
error = QtCore.pyqtSignal(str)
|
||||||
|
@ -34,28 +35,34 @@ class OnionThread(QtCore.QThread):
|
||||||
def __init__(self, mode):
|
def __init__(self, mode):
|
||||||
super(OnionThread, self).__init__()
|
super(OnionThread, self).__init__()
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.mode.common.log('OnionThread', '__init__')
|
self.mode.common.log("OnionThread", "__init__")
|
||||||
|
|
||||||
# allow this thread to be terminated
|
# allow this thread to be terminated
|
||||||
self.setTerminationEnabled()
|
self.setTerminationEnabled()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.mode.common.log('OnionThread', 'run')
|
self.mode.common.log("OnionThread", "run")
|
||||||
|
|
||||||
# Make a new static URL path for each new share
|
# Make a new static URL path for each new share
|
||||||
self.mode.web.generate_static_url_path()
|
self.mode.web.generate_static_url_path()
|
||||||
|
|
||||||
# Choose port and password early, because we need them to exist in advance for scheduled shares
|
# Choose port and password early, because we need them to exist in advance for scheduled shares
|
||||||
self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
|
self.mode.app.stay_open = not self.mode.common.settings.get(
|
||||||
|
"close_after_first_download"
|
||||||
|
)
|
||||||
if not self.mode.app.port:
|
if not self.mode.app.port:
|
||||||
self.mode.app.choose_port()
|
self.mode.app.choose_port()
|
||||||
if not self.mode.common.settings.get('public_mode'):
|
if not self.mode.common.settings.get("public_mode"):
|
||||||
if not self.mode.web.password:
|
if not self.mode.web.password:
|
||||||
self.mode.web.generate_password(self.mode.common.settings.get('password'))
|
self.mode.web.generate_password(
|
||||||
|
self.mode.common.settings.get("password")
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.mode.obtain_onion_early:
|
if self.mode.obtain_onion_early:
|
||||||
self.mode.app.start_onion_service(await_publication=False, save_scheduled_key=True)
|
self.mode.app.start_onion_service(
|
||||||
|
await_publication=False, save_scheduled_key=True
|
||||||
|
)
|
||||||
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
self.success_early.emit()
|
self.success_early.emit()
|
||||||
|
@ -70,7 +77,19 @@ class OnionThread(QtCore.QThread):
|
||||||
self.mode.web_thread.start()
|
self.mode.web_thread.start()
|
||||||
self.success.emit()
|
self.success.emit()
|
||||||
|
|
||||||
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
|
except (
|
||||||
|
TorTooOld,
|
||||||
|
TorErrorInvalidSetting,
|
||||||
|
TorErrorAutomatic,
|
||||||
|
TorErrorSocketPort,
|
||||||
|
TorErrorSocketFile,
|
||||||
|
TorErrorMissingPassword,
|
||||||
|
TorErrorUnreadableCookieFile,
|
||||||
|
TorErrorAuthError,
|
||||||
|
TorErrorProtocolError,
|
||||||
|
BundledTorTimeout,
|
||||||
|
OSError,
|
||||||
|
) as e:
|
||||||
self.error.emit(e.args[0])
|
self.error.emit(e.args[0])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -79,17 +98,23 @@ class WebThread(QtCore.QThread):
|
||||||
"""
|
"""
|
||||||
Starts the web service
|
Starts the web service
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = QtCore.pyqtSignal()
|
success = QtCore.pyqtSignal()
|
||||||
error = QtCore.pyqtSignal(str)
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, mode):
|
def __init__(self, mode):
|
||||||
super(WebThread, self).__init__()
|
super(WebThread, self).__init__()
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.mode.common.log('WebThread', '__init__')
|
self.mode.common.log("WebThread", "__init__")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.mode.common.log('WebThread', 'run')
|
self.mode.common.log("WebThread", "run")
|
||||||
self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.password)
|
self.mode.web.start(
|
||||||
|
self.mode.app.port,
|
||||||
|
self.mode.app.stay_open,
|
||||||
|
self.mode.common.settings.get("public_mode"),
|
||||||
|
self.mode.web.password,
|
||||||
|
)
|
||||||
self.success.emit()
|
self.success.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,30 +122,40 @@ class AutoStartTimer(QtCore.QThread):
|
||||||
"""
|
"""
|
||||||
Waits for a prescribed time before allowing a share to start
|
Waits for a prescribed time before allowing a share to start
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = QtCore.pyqtSignal()
|
success = QtCore.pyqtSignal()
|
||||||
error = QtCore.pyqtSignal(str)
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, mode, canceled=False):
|
def __init__(self, mode, canceled=False):
|
||||||
super(AutoStartTimer, self).__init__()
|
super(AutoStartTimer, self).__init__()
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.canceled = canceled
|
self.canceled = canceled
|
||||||
self.mode.common.log('AutoStartTimer', '__init__')
|
self.mode.common.log("AutoStartTimer", "__init__")
|
||||||
|
|
||||||
# allow this thread to be terminated
|
# allow this thread to be terminated
|
||||||
self.setTerminationEnabled()
|
self.setTerminationEnabled()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime)
|
autostart_timer_datetime_delta = now.secsTo(
|
||||||
|
self.mode.server_status.autostart_timer_datetime
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
# Sleep until scheduled time
|
# Sleep until scheduled time
|
||||||
while autostart_timer_datetime_delta > 0 and self.canceled == False:
|
while autostart_timer_datetime_delta > 0 and self.canceled == False:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime)
|
autostart_timer_datetime_delta = now.secsTo(
|
||||||
|
self.mode.server_status.autostart_timer_datetime
|
||||||
|
)
|
||||||
# Timer has now finished
|
# Timer has now finished
|
||||||
if self.canceled == False:
|
if self.canceled == False:
|
||||||
self.mode.server_status.server_button.setText(strings._('gui_please_wait'))
|
self.mode.server_status.server_button.setText(
|
||||||
self.mode.server_status_label.setText(strings._('gui_status_indicator_share_working'))
|
strings._("gui_please_wait")
|
||||||
|
)
|
||||||
|
self.mode.server_status_label.setText(
|
||||||
|
strings._("gui_status_indicator_share_working")
|
||||||
|
)
|
||||||
self.success.emit()
|
self.success.emit()
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.error.emit(e.args[0])
|
self.error.emit(e.args[0])
|
||||||
|
|
|
@ -24,10 +24,12 @@ from onionshare.onion import *
|
||||||
|
|
||||||
from .widgets import Alert
|
from .widgets import Alert
|
||||||
|
|
||||||
|
|
||||||
class TorConnectionDialog(QtWidgets.QProgressDialog):
|
class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
"""
|
"""
|
||||||
Connecting to Tor dialog.
|
Connecting to Tor dialog.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
open_settings = QtCore.pyqtSignal()
|
open_settings = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, common, qtapp, onion, custom_settings=False):
|
def __init__(self, common, qtapp, onion, custom_settings=False):
|
||||||
|
@ -40,18 +42,20 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
else:
|
else:
|
||||||
self.settings = self.common.settings
|
self.settings = self.common.settings
|
||||||
|
|
||||||
self.common.log('TorConnectionDialog', '__init__')
|
self.common.log("TorConnectionDialog", "__init__")
|
||||||
|
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
|
|
||||||
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.setModal(True)
|
self.setModal(True)
|
||||||
self.setFixedSize(400, 150)
|
self.setFixedSize(400, 150)
|
||||||
|
|
||||||
# Label
|
# Label
|
||||||
self.setLabelText(strings._('connecting_to_tor'))
|
self.setLabelText(strings._("connecting_to_tor"))
|
||||||
|
|
||||||
# Progress bar ticks from 0 to 100
|
# Progress bar ticks from 0 to 100
|
||||||
self.setRange(0, 100)
|
self.setRange(0, 100)
|
||||||
|
@ -59,10 +63,10 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
self.setMinimumDuration(100)
|
self.setMinimumDuration(100)
|
||||||
|
|
||||||
# Start displaying the status at 0
|
# Start displaying the status at 0
|
||||||
self._tor_status_update(0, '')
|
self._tor_status_update(0, "")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.common.log('TorConnectionDialog', 'start')
|
self.common.log("TorConnectionDialog", "start")
|
||||||
|
|
||||||
t = TorConnectionThread(self.common, self.settings, self, self.onion)
|
t = TorConnectionThread(self.common, self.settings, self, self.onion)
|
||||||
t.tor_status_update.connect(self._tor_status_update)
|
t.tor_status_update.connect(self._tor_status_update)
|
||||||
|
@ -81,17 +85,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
|
|
||||||
def _tor_status_update(self, progress, summary):
|
def _tor_status_update(self, progress, summary):
|
||||||
self.setValue(int(progress))
|
self.setValue(int(progress))
|
||||||
self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary))
|
self.setLabelText(
|
||||||
|
"<strong>{}</strong><br>{}".format(strings._("connecting_to_tor"), summary)
|
||||||
|
)
|
||||||
|
|
||||||
def _connected_to_tor(self):
|
def _connected_to_tor(self):
|
||||||
self.common.log('TorConnectionDialog', '_connected_to_tor')
|
self.common.log("TorConnectionDialog", "_connected_to_tor")
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
# Close the dialog after connecting
|
# Close the dialog after connecting
|
||||||
self.setValue(self.maximum())
|
self.setValue(self.maximum())
|
||||||
|
|
||||||
def _canceled_connecting_to_tor(self):
|
def _canceled_connecting_to_tor(self):
|
||||||
self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor')
|
self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor")
|
||||||
self.active = False
|
self.active = False
|
||||||
self.onion.cleanup()
|
self.onion.cleanup()
|
||||||
|
|
||||||
|
@ -99,12 +105,16 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
QtCore.QTimer.singleShot(1, self.cancel)
|
QtCore.QTimer.singleShot(1, self.cancel)
|
||||||
|
|
||||||
def _error_connecting_to_tor(self, msg):
|
def _error_connecting_to_tor(self, msg):
|
||||||
self.common.log('TorConnectionDialog', '_error_connecting_to_tor')
|
self.common.log("TorConnectionDialog", "_error_connecting_to_tor")
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
def alert_and_open_settings():
|
def alert_and_open_settings():
|
||||||
# Display the exception in an alert box
|
# Display the exception in an alert box
|
||||||
Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning)
|
Alert(
|
||||||
|
self.common,
|
||||||
|
"{}\n\n{}".format(msg, strings._("gui_tor_connection_error_settings")),
|
||||||
|
QtWidgets.QMessageBox.Warning,
|
||||||
|
)
|
||||||
|
|
||||||
# Open settings
|
# Open settings
|
||||||
self.open_settings.emit()
|
self.open_settings.emit()
|
||||||
|
@ -114,6 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
# Cancel connecting to Tor
|
# Cancel connecting to Tor
|
||||||
QtCore.QTimer.singleShot(1, self.cancel)
|
QtCore.QTimer.singleShot(1, self.cancel)
|
||||||
|
|
||||||
|
|
||||||
class TorConnectionThread(QtCore.QThread):
|
class TorConnectionThread(QtCore.QThread):
|
||||||
tor_status_update = QtCore.pyqtSignal(str, str)
|
tor_status_update = QtCore.pyqtSignal(str, str)
|
||||||
connected_to_tor = QtCore.pyqtSignal()
|
connected_to_tor = QtCore.pyqtSignal()
|
||||||
|
@ -125,7 +136,7 @@ class TorConnectionThread(QtCore.QThread):
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('TorConnectionThread', '__init__')
|
self.common.log("TorConnectionThread", "__init__")
|
||||||
|
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
|
||||||
|
@ -133,7 +144,7 @@ class TorConnectionThread(QtCore.QThread):
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.common.log('TorConnectionThread', 'run')
|
self.common.log("TorConnectionThread", "run")
|
||||||
|
|
||||||
# Connect to the Onion
|
# Connect to the Onion
|
||||||
try:
|
try:
|
||||||
|
@ -144,11 +155,15 @@ class TorConnectionThread(QtCore.QThread):
|
||||||
self.canceled_connecting_to_tor.emit()
|
self.canceled_connecting_to_tor.emit()
|
||||||
|
|
||||||
except BundledTorCanceled as e:
|
except BundledTorCanceled as e:
|
||||||
self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled')
|
self.common.log(
|
||||||
|
"TorConnectionThread", "run", "caught exception: BundledTorCanceled"
|
||||||
|
)
|
||||||
self.canceled_connecting_to_tor.emit()
|
self.canceled_connecting_to_tor.emit()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0]))
|
self.common.log(
|
||||||
|
"TorConnectionThread", "run", "caught exception: {}".format(e.args[0])
|
||||||
|
)
|
||||||
self.error_connecting_to_tor.emit(str(e.args[0]))
|
self.error_connecting_to_tor.emit(str(e.args[0]))
|
||||||
|
|
||||||
def _tor_status_update(self, progress, summary):
|
def _tor_status_update(self, progress, summary):
|
||||||
|
|
|
@ -27,21 +27,26 @@ from onionshare.onion import Onion
|
||||||
|
|
||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
|
|
||||||
|
|
||||||
class UpdateCheckerCheckError(Exception):
|
class UpdateCheckerCheckError(Exception):
|
||||||
"""
|
"""
|
||||||
Error checking for updates because of some Tor connection issue, or because
|
Error checking for updates because of some Tor connection issue, or because
|
||||||
the OnionShare website is down.
|
the OnionShare website is down.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UpdateCheckerInvalidLatestVersion(Exception):
|
class UpdateCheckerInvalidLatestVersion(Exception):
|
||||||
"""
|
"""
|
||||||
Successfully downloaded the latest version, but it doesn't appear to be a
|
Successfully downloaded the latest version, but it doesn't appear to be a
|
||||||
valid version string.
|
valid version string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, latest_version):
|
def __init__(self, latest_version):
|
||||||
self.latest_version = latest_version
|
self.latest_version = latest_version
|
||||||
|
|
||||||
|
|
||||||
class UpdateChecker(QtCore.QObject):
|
class UpdateChecker(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest
|
Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest
|
||||||
|
@ -50,6 +55,7 @@ class UpdateChecker(QtCore.QObject):
|
||||||
|
|
||||||
Only check at most once per day, unless force is True.
|
Only check at most once per day, unless force is True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
update_available = QtCore.pyqtSignal(str, str, str)
|
update_available = QtCore.pyqtSignal(str, str, str)
|
||||||
update_not_available = QtCore.pyqtSignal()
|
update_not_available = QtCore.pyqtSignal()
|
||||||
update_error = QtCore.pyqtSignal()
|
update_error = QtCore.pyqtSignal()
|
||||||
|
@ -60,12 +66,12 @@ class UpdateChecker(QtCore.QObject):
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('UpdateChecker', '__init__')
|
self.common.log("UpdateChecker", "__init__")
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def check(self, force=False, config=False):
|
def check(self, force=False, config=False):
|
||||||
self.common.log('UpdateChecker', 'check', 'force={}'.format(force))
|
self.common.log("UpdateChecker", "check", "force={}".format(force))
|
||||||
# Load the settings
|
# Load the settings
|
||||||
settings = Settings(self.common, config)
|
settings = Settings(self.common, config)
|
||||||
settings.load()
|
settings.load()
|
||||||
|
@ -77,7 +83,7 @@ class UpdateChecker(QtCore.QObject):
|
||||||
check_for_updates = False
|
check_for_updates = False
|
||||||
|
|
||||||
# See if it's been 1 day since the last check
|
# See if it's been 1 day since the last check
|
||||||
autoupdate_timestamp = settings.get('autoupdate_timestamp')
|
autoupdate_timestamp = settings.get("autoupdate_timestamp")
|
||||||
if autoupdate_timestamp:
|
if autoupdate_timestamp:
|
||||||
last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp)
|
last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp)
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
@ -90,24 +96,32 @@ class UpdateChecker(QtCore.QObject):
|
||||||
|
|
||||||
# Check for updates
|
# Check for updates
|
||||||
if check_for_updates:
|
if check_for_updates:
|
||||||
self.common.log('UpdateChecker', 'check', 'checking for updates')
|
self.common.log("UpdateChecker", "check", "checking for updates")
|
||||||
# Download the latest-version file over Tor
|
# Download the latest-version file over Tor
|
||||||
try:
|
try:
|
||||||
# User agent string includes OnionShare version and platform
|
# User agent string includes OnionShare version and platform
|
||||||
user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform)
|
user_agent = "OnionShare {}, {}".format(
|
||||||
|
self.common.version, self.common.platform
|
||||||
|
)
|
||||||
|
|
||||||
# If the update is forced, add '?force=1' to the URL, to more
|
# If the update is forced, add '?force=1' to the URL, to more
|
||||||
# accurately measure daily users
|
# accurately measure daily users
|
||||||
path = '/latest-version.txt'
|
path = "/latest-version.txt"
|
||||||
if force:
|
if force:
|
||||||
path += '?force=1'
|
path += "?force=1"
|
||||||
|
|
||||||
if Version(self.onion.tor_version) >= Version('0.3.2.9'):
|
if Version(self.onion.tor_version) >= Version("0.3.2.9"):
|
||||||
onion_domain = 'lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion'
|
onion_domain = (
|
||||||
|
"lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
onion_domain = 'elx57ue5uyfplgva.onion'
|
onion_domain = "elx57ue5uyfplgva.onion"
|
||||||
|
|
||||||
self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path))
|
self.common.log(
|
||||||
|
"UpdateChecker",
|
||||||
|
"check",
|
||||||
|
"loading http://{}{}".format(onion_domain, path),
|
||||||
|
)
|
||||||
|
|
||||||
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
||||||
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
||||||
|
@ -116,19 +130,27 @@ class UpdateChecker(QtCore.QObject):
|
||||||
s.settimeout(15) # 15 second timeout
|
s.settimeout(15) # 15 second timeout
|
||||||
s.connect((onion_domain, 80))
|
s.connect((onion_domain, 80))
|
||||||
|
|
||||||
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
http_request = "GET {} HTTP/1.0\r\n".format(path)
|
||||||
http_request += 'Host: {}\r\n'.format(onion_domain)
|
http_request += "Host: {}\r\n".format(onion_domain)
|
||||||
http_request += 'User-Agent: {}\r\n'.format(user_agent)
|
http_request += "User-Agent: {}\r\n".format(user_agent)
|
||||||
http_request += '\r\n'
|
http_request += "\r\n"
|
||||||
s.sendall(http_request.encode('utf-8'))
|
s.sendall(http_request.encode("utf-8"))
|
||||||
|
|
||||||
http_response = s.recv(1024)
|
http_response = s.recv(1024)
|
||||||
latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8')
|
latest_version = (
|
||||||
|
http_response[http_response.find(b"\r\n\r\n") :]
|
||||||
|
.strip()
|
||||||
|
.decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version))
|
self.common.log(
|
||||||
|
"UpdateChecker",
|
||||||
|
"check",
|
||||||
|
"latest OnionShare version: {}".format(latest_version),
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.common.log('UpdateChecker', 'check', '{}'.format(e))
|
self.common.log("UpdateChecker", "check", "{}".format(e))
|
||||||
self.update_error.emit()
|
self.update_error.emit()
|
||||||
raise UpdateCheckerCheckError
|
raise UpdateCheckerCheckError
|
||||||
|
|
||||||
|
@ -140,22 +162,32 @@ class UpdateChecker(QtCore.QObject):
|
||||||
raise UpdateCheckerInvalidLatestVersion(latest_version)
|
raise UpdateCheckerInvalidLatestVersion(latest_version)
|
||||||
|
|
||||||
# Update the last checked timestamp (dropping the seconds and milliseconds)
|
# Update the last checked timestamp (dropping the seconds and milliseconds)
|
||||||
timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp()
|
timestamp = (
|
||||||
|
datetime.datetime.now()
|
||||||
|
.replace(microsecond=0)
|
||||||
|
.replace(second=0)
|
||||||
|
.timestamp()
|
||||||
|
)
|
||||||
# Re-load the settings first before saving, just in case they've changed since we started our thread
|
# Re-load the settings first before saving, just in case they've changed since we started our thread
|
||||||
settings.load()
|
settings.load()
|
||||||
settings.set('autoupdate_timestamp', timestamp)
|
settings.set("autoupdate_timestamp", timestamp)
|
||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
# Do we need to update?
|
# Do we need to update?
|
||||||
update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version)
|
update_url = "https://github.com/micahflee/onionshare/releases/tag/v{}".format(
|
||||||
|
latest_version
|
||||||
|
)
|
||||||
installed_version = self.common.version
|
installed_version = self.common.version
|
||||||
if installed_version < latest_version:
|
if installed_version < latest_version:
|
||||||
self.update_available.emit(update_url, installed_version, latest_version)
|
self.update_available.emit(
|
||||||
|
update_url, installed_version, latest_version
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# No updates are available
|
# No updates are available
|
||||||
self.update_not_available.emit()
|
self.update_not_available.emit()
|
||||||
|
|
||||||
|
|
||||||
class UpdateThread(QtCore.QThread):
|
class UpdateThread(QtCore.QThread):
|
||||||
update_available = QtCore.pyqtSignal(str, str, str)
|
update_available = QtCore.pyqtSignal(str, str, str)
|
||||||
update_not_available = QtCore.pyqtSignal()
|
update_not_available = QtCore.pyqtSignal()
|
||||||
|
@ -167,13 +199,13 @@ class UpdateThread(QtCore.QThread):
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('UpdateThread', '__init__')
|
self.common.log("UpdateThread", "__init__")
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.config = config
|
self.config = config
|
||||||
self.force = force
|
self.force = force
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.common.log('UpdateThread', 'run')
|
self.common.log("UpdateThread", "run")
|
||||||
|
|
||||||
u = UpdateChecker(self.common, self.onion, self.config)
|
u = UpdateChecker(self.common, self.onion, self.config)
|
||||||
u.update_available.connect(self._update_available)
|
u.update_available.connect(self._update_available)
|
||||||
|
@ -185,25 +217,25 @@ class UpdateThread(QtCore.QThread):
|
||||||
u.check(config=self.config, force=self.force)
|
u.check(config=self.config, force=self.force)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If update check fails, silently ignore
|
# If update check fails, silently ignore
|
||||||
self.common.log('UpdateThread', 'run', '{}'.format(e))
|
self.common.log("UpdateThread", "run", "{}".format(e))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _update_available(self, update_url, installed_version, latest_version):
|
def _update_available(self, update_url, installed_version, latest_version):
|
||||||
self.common.log('UpdateThread', '_update_available')
|
self.common.log("UpdateThread", "_update_available")
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_available.emit(update_url, installed_version, latest_version)
|
self.update_available.emit(update_url, installed_version, latest_version)
|
||||||
|
|
||||||
def _update_not_available(self):
|
def _update_not_available(self):
|
||||||
self.common.log('UpdateThread', '_update_not_available')
|
self.common.log("UpdateThread", "_update_not_available")
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_not_available.emit()
|
self.update_not_available.emit()
|
||||||
|
|
||||||
def _update_error(self):
|
def _update_error(self):
|
||||||
self.common.log('UpdateThread', '_update_error')
|
self.common.log("UpdateThread", "_update_error")
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_error.emit()
|
self.update_error.emit()
|
||||||
|
|
||||||
def _update_invalid_version(self, latest_version):
|
def _update_invalid_version(self, latest_version):
|
||||||
self.common.log('UpdateThread', '_update_invalid_version')
|
self.common.log("UpdateThread", "_update_invalid_version")
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_invalid_version.emit(latest_version)
|
self.update_invalid_version.emit(latest_version)
|
||||||
|
|
|
@ -19,19 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
|
||||||
class Alert(QtWidgets.QMessageBox):
|
class Alert(QtWidgets.QMessageBox):
|
||||||
"""
|
"""
|
||||||
An alert box dialog.
|
An alert box dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
common,
|
||||||
|
message,
|
||||||
|
icon=QtWidgets.QMessageBox.NoIcon,
|
||||||
|
buttons=QtWidgets.QMessageBox.Ok,
|
||||||
|
autostart=True,
|
||||||
|
):
|
||||||
super(Alert, self).__init__(None)
|
super(Alert, self).__init__(None)
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log('Alert', '__init__')
|
self.common.log("Alert", "__init__")
|
||||||
|
|
||||||
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.setText(message)
|
self.setText(message)
|
||||||
self.setIcon(icon)
|
self.setIcon(icon)
|
||||||
self.setStandardButtons(buttons)
|
self.setStandardButtons(buttons)
|
||||||
|
@ -49,11 +60,12 @@ class AddFileDialog(QtWidgets.QFileDialog):
|
||||||
This is because the macOS sandbox requires native dialogs, and this is a Qt5
|
This is because the macOS sandbox requires native dialogs, and this is a Qt5
|
||||||
dialog.
|
dialog.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, common, *args, **kwargs):
|
def __init__(self, common, *args, **kwargs):
|
||||||
QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
|
QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log('AddFileDialog', '__init__')
|
self.common.log("AddFileDialog", "__init__")
|
||||||
|
|
||||||
self.setOption(self.DontUseNativeDialog, True)
|
self.setOption(self.DontUseNativeDialog, True)
|
||||||
self.setOption(self.ReadOnly, True)
|
self.setOption(self.ReadOnly, True)
|
||||||
|
@ -65,5 +77,5 @@ class AddFileDialog(QtWidgets.QFileDialog):
|
||||||
list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.common.log('AddFileDialog', 'accept')
|
self.common.log("AddFileDialog", "accept")
|
||||||
QtWidgets.QDialog.accept(self)
|
QtWidgets.QDialog.accept(self)
|
||||||
|
|
106
setup.py
106
setup.py
|
@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import os, sys, platform, tempfile
|
import os, sys, platform, tempfile
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
|
|
||||||
def file_list(path):
|
def file_list(path):
|
||||||
files = []
|
files = []
|
||||||
for filename in os.listdir(path):
|
for filename in os.listdir(path):
|
||||||
|
@ -29,7 +30,8 @@ def file_list(path):
|
||||||
files.append(os.path.join(path, filename))
|
files.append(os.path.join(path, filename))
|
||||||
return files
|
return files
|
||||||
|
|
||||||
version = open('share/version.txt').read().strip()
|
|
||||||
|
version = open("share/version.txt").read().strip()
|
||||||
description = (
|
description = (
|
||||||
"""OnionShare lets you securely and anonymously send and receive files. It """
|
"""OnionShare lets you securely and anonymously send and receive files. It """
|
||||||
"""works by starting a web server, making it accessible as a Tor onion """
|
"""works by starting a web server, making it accessible as a Tor onion """
|
||||||
|
@ -37,7 +39,10 @@ description = (
|
||||||
"""files from you, or upload files to you. It does _not_ require setting up """
|
"""files from you, or upload files to you. It does _not_ require setting up """
|
||||||
"""a separate server or using a third party file-sharing service."""
|
"""a separate server or using a third party file-sharing service."""
|
||||||
)
|
)
|
||||||
long_description = description + "\n\n" + (
|
long_description = (
|
||||||
|
description
|
||||||
|
+ "\n\n"
|
||||||
|
+ (
|
||||||
"""If you want to send files to someone, OnionShare hosts them on your own """
|
"""If you want to send files to someone, OnionShare hosts them on your own """
|
||||||
"""computer and uses a Tor onion service to make them temporarily accessible """
|
"""computer and uses a Tor onion service to make them temporarily accessible """
|
||||||
"""over the internet. The receiving user just needs to open the web address """
|
"""over the internet. The receiving user just needs to open the web address """
|
||||||
|
@ -47,11 +52,12 @@ long_description = description + "\n\n" + (
|
||||||
"""Other users can upload files to you from by loading the web address in """
|
"""Other users can upload files to you from by loading the web address in """
|
||||||
"""Tor Browser."""
|
"""Tor Browser."""
|
||||||
)
|
)
|
||||||
author = 'Micah Lee'
|
)
|
||||||
author_email = 'micah@micahflee.com'
|
author = "Micah Lee"
|
||||||
url = 'https://github.com/micahflee/onionshare'
|
author_email = "micah@micahflee.com"
|
||||||
license = 'GPL v3'
|
url = "https://github.com/micahflee/onionshare"
|
||||||
keywords = 'onion, share, onionshare, tor, anonymous, web server'
|
license = "GPL v3"
|
||||||
|
keywords = "onion, share, onionshare, tor, anonymous, web server"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Framework :: Flask",
|
"Framework :: Flask",
|
||||||
|
@ -60,38 +66,72 @@ classifiers = [
|
||||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||||
"Intended Audience :: End Users/Desktop",
|
"Intended Audience :: End Users/Desktop",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Environment :: Web Environment"
|
"Environment :: Web Environment",
|
||||||
]
|
]
|
||||||
data_files = [
|
data_files = [
|
||||||
(os.path.join(sys.prefix, 'share/applications'), ['install/org.onionshare.OnionShare.desktop']),
|
(
|
||||||
(os.path.join(sys.prefix, 'share/icons/hicolor/scalable/apps'), ['install/org.onionshare.OnionShare.svg']),
|
os.path.join(sys.prefix, "share/applications"),
|
||||||
(os.path.join(sys.prefix, 'share/metainfo'), ['install/org.onionshare.OnionShare.appdata.xml']),
|
["install/org.onionshare.OnionShare.desktop"],
|
||||||
(os.path.join(sys.prefix, 'share/onionshare'), file_list('share')),
|
),
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')),
|
(
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')),
|
os.path.join(sys.prefix, "share/icons/hicolor/scalable/apps"),
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')),
|
["install/org.onionshare.OnionShare.svg"],
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/static/css'), file_list('share/static/css')),
|
),
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')),
|
(
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js'))
|
os.path.join(sys.prefix, "share/metainfo"),
|
||||||
|
["install/org.onionshare.OnionShare.appdata.xml"],
|
||||||
|
),
|
||||||
|
(os.path.join(sys.prefix, "share/onionshare"), file_list("share")),
|
||||||
|
(os.path.join(sys.prefix, "share/onionshare/images"), file_list("share/images")),
|
||||||
|
(os.path.join(sys.prefix, "share/onionshare/locale"), file_list("share/locale")),
|
||||||
|
(
|
||||||
|
os.path.join(sys.prefix, "share/onionshare/templates"),
|
||||||
|
file_list("share/templates"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
os.path.join(sys.prefix, "share/onionshare/static/css"),
|
||||||
|
file_list("share/static/css"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
os.path.join(sys.prefix, "share/onionshare/static/img"),
|
||||||
|
file_list("share/static/img"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
os.path.join(sys.prefix, "share/onionshare/static/js"),
|
||||||
|
file_list("share/static/js"),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if not platform.system().endswith('BSD') and platform.system() != 'DragonFly':
|
if not platform.system().endswith("BSD") and platform.system() != "DragonFly":
|
||||||
data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py']))
|
data_files.append(
|
||||||
|
(
|
||||||
|
"/usr/share/nautilus-python/extensions/",
|
||||||
|
["install/scripts/onionshare-nautilus.py"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='onionshare', version=version,
|
name="onionshare",
|
||||||
description=description, long_description=long_description,
|
version=version,
|
||||||
author=author, author_email=author_email, maintainer=author, maintainer_email=author_email,
|
description=description,
|
||||||
url=url, license=license, keywords=keywords, classifiers=classifiers,
|
long_description=long_description,
|
||||||
|
author=author,
|
||||||
|
author_email=author_email,
|
||||||
|
maintainer=author,
|
||||||
|
maintainer_email=author_email,
|
||||||
|
url=url,
|
||||||
|
license=license,
|
||||||
|
keywords=keywords,
|
||||||
|
classifiers=classifiers,
|
||||||
packages=[
|
packages=[
|
||||||
'onionshare',
|
"onionshare",
|
||||||
'onionshare.web',
|
"onionshare.web",
|
||||||
'onionshare_gui',
|
"onionshare_gui",
|
||||||
'onionshare_gui.mode',
|
"onionshare_gui.mode",
|
||||||
'onionshare_gui.mode.share_mode',
|
"onionshare_gui.mode.share_mode",
|
||||||
'onionshare_gui.mode.receive_mode',
|
"onionshare_gui.mode.receive_mode",
|
||||||
'onionshare_gui.mode.website_mode'
|
"onionshare_gui.mode.website_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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,17 +20,17 @@ from onionshare_gui.mode.website_mode import WebsiteMode
|
||||||
class GuiBaseTest(object):
|
class GuiBaseTest(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_up(test_settings):
|
def set_up(test_settings):
|
||||||
'''Create GUI with given settings'''
|
"""Create GUI with given settings"""
|
||||||
# Create our test file
|
# Create our test file
|
||||||
testfile = open('/tmp/test.txt', 'w')
|
testfile = open("/tmp/test.txt", "w")
|
||||||
testfile.write('onionshare')
|
testfile.write("onionshare")
|
||||||
testfile.close()
|
testfile.close()
|
||||||
|
|
||||||
# Create a test dir and files
|
# Create a test dir and files
|
||||||
if not os.path.exists('/tmp/testdir'):
|
if not os.path.exists("/tmp/testdir"):
|
||||||
testdir = os.mkdir('/tmp/testdir')
|
testdir = os.mkdir("/tmp/testdir")
|
||||||
testfile = open('/tmp/testdir/test', 'w')
|
testfile = open("/tmp/testdir/test", "w")
|
||||||
testfile.write('onionshare')
|
testfile.write("onionshare")
|
||||||
testfile.close()
|
testfile.close()
|
||||||
|
|
||||||
common = Common()
|
common = Common()
|
||||||
|
@ -39,7 +39,7 @@ class GuiBaseTest(object):
|
||||||
strings.load_strings(common)
|
strings.load_strings(common)
|
||||||
|
|
||||||
# Get all of the settings in test_settings
|
# Get all of the settings in test_settings
|
||||||
test_settings['data_dir'] = '/tmp/OnionShare'
|
test_settings["data_dir"] = "/tmp/OnionShare"
|
||||||
for key, val in common.settings.default_settings.items():
|
for key, val in common.settings.default_settings.items():
|
||||||
if key not in test_settings:
|
if key not in test_settings:
|
||||||
test_settings[key] = val
|
test_settings[key] = val
|
||||||
|
@ -51,53 +51,55 @@ class GuiBaseTest(object):
|
||||||
app = OnionShare(common, testonion, True, 0)
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
web = Web(common, False, True)
|
web = Web(common, False, True)
|
||||||
open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
|
open("/tmp/settings.json", "w").write(json.dumps(test_settings))
|
||||||
|
|
||||||
gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', True)
|
gui = OnionShareGui(
|
||||||
|
common,
|
||||||
|
testonion,
|
||||||
|
qtapp,
|
||||||
|
app,
|
||||||
|
["/tmp/test.txt", "/tmp/testdir"],
|
||||||
|
"/tmp/settings.json",
|
||||||
|
True,
|
||||||
|
)
|
||||||
return gui
|
return gui
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tear_down():
|
def tear_down():
|
||||||
'''Clean up after tests'''
|
"""Clean up after tests"""
|
||||||
try:
|
try:
|
||||||
os.remove('/tmp/test.txt')
|
os.remove("/tmp/test.txt")
|
||||||
os.remove('/tmp/settings.json')
|
os.remove("/tmp/settings.json")
|
||||||
os.remove('/tmp/large_file')
|
os.remove("/tmp/large_file")
|
||||||
os.remove('/tmp/download.zip')
|
os.remove("/tmp/download.zip")
|
||||||
os.remove('/tmp/webpage')
|
os.remove("/tmp/webpage")
|
||||||
shutil.rmtree('/tmp/testdir')
|
shutil.rmtree("/tmp/testdir")
|
||||||
shutil.rmtree('/tmp/OnionShare')
|
shutil.rmtree("/tmp/OnionShare")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def gui_loaded(self):
|
def gui_loaded(self):
|
||||||
'''Test that the GUI actually is shown'''
|
"""Test that the GUI actually is shown"""
|
||||||
self.assertTrue(self.gui.show)
|
self.assertTrue(self.gui.show)
|
||||||
|
|
||||||
|
|
||||||
def windowTitle_seen(self):
|
def windowTitle_seen(self):
|
||||||
'''Test that the window title is OnionShare'''
|
"""Test that the window title is OnionShare"""
|
||||||
self.assertEqual(self.gui.windowTitle(), 'OnionShare')
|
self.assertEqual(self.gui.windowTitle(), "OnionShare")
|
||||||
|
|
||||||
|
|
||||||
def settings_button_is_visible(self):
|
def settings_button_is_visible(self):
|
||||||
'''Test that the settings button is visible'''
|
"""Test that the settings button is visible"""
|
||||||
self.assertTrue(self.gui.settings_button.isVisible())
|
self.assertTrue(self.gui.settings_button.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def settings_button_is_hidden(self):
|
def settings_button_is_hidden(self):
|
||||||
'''Test that the settings button is hidden when the server starts'''
|
"""Test that the settings button is hidden when the server starts"""
|
||||||
self.assertFalse(self.gui.settings_button.isVisible())
|
self.assertFalse(self.gui.settings_button.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def server_status_bar_is_visible(self):
|
def server_status_bar_is_visible(self):
|
||||||
'''Test that the status bar is visible'''
|
"""Test that the status bar is visible"""
|
||||||
self.assertTrue(self.gui.status_bar.isVisible())
|
self.assertTrue(self.gui.status_bar.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def click_mode(self, mode):
|
def click_mode(self, mode):
|
||||||
'''Test that we can switch Mode by clicking the button'''
|
"""Test that we can switch Mode by clicking the button"""
|
||||||
if type(mode) == ReceiveMode:
|
if type(mode) == ReceiveMode:
|
||||||
QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton)
|
||||||
self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE)
|
self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE)
|
||||||
|
@ -108,16 +110,14 @@ class GuiBaseTest(object):
|
||||||
QtTest.QTest.mouseClick(self.gui.website_mode_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(self.gui.website_mode_button, QtCore.Qt.LeftButton)
|
||||||
self.assertTrue(self.gui.mode, self.gui.MODE_WEBSITE)
|
self.assertTrue(self.gui.mode, self.gui.MODE_WEBSITE)
|
||||||
|
|
||||||
|
|
||||||
def click_toggle_history(self, mode):
|
def click_toggle_history(self, mode):
|
||||||
'''Test that we can toggle Download or Upload history by clicking the toggle button'''
|
"""Test that we can toggle Download or Upload history by clicking the toggle button"""
|
||||||
currently_visible = mode.history.isVisible()
|
currently_visible = mode.history.isVisible()
|
||||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
self.assertEqual(mode.history.isVisible(), not currently_visible)
|
self.assertEqual(mode.history.isVisible(), not currently_visible)
|
||||||
|
|
||||||
|
|
||||||
def history_indicator(self, mode, public_mode, indicator_count="1"):
|
def history_indicator(self, mode, public_mode, indicator_count="1"):
|
||||||
'''Test that we can make sure the history is toggled off, do an action, and the indiciator works'''
|
"""Test that we can make sure the history is toggled off, do an action, and the indiciator works"""
|
||||||
# Make sure history is toggled off
|
# Make sure history is toggled off
|
||||||
if mode.history.isVisible():
|
if mode.history.isVisible():
|
||||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
|
@ -128,12 +128,16 @@ class GuiBaseTest(object):
|
||||||
|
|
||||||
if type(mode) == ReceiveMode:
|
if type(mode) == ReceiveMode:
|
||||||
# Upload a file
|
# Upload a file
|
||||||
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
files = {"file[]": open("/tmp/test.txt", "rb")}
|
||||||
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/upload".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.post(url, files=files)
|
r = requests.post(url, files=files)
|
||||||
else:
|
else:
|
||||||
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password))
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
files=files,
|
||||||
|
auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password),
|
||||||
|
)
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
if type(mode) == ShareMode:
|
if type(mode) == ShareMode:
|
||||||
|
@ -142,7 +146,10 @@ class GuiBaseTest(object):
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password))
|
r = requests.get(
|
||||||
|
url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password),
|
||||||
|
)
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
# Indicator should be visible, have a value of "1"
|
# Indicator should be visible, have a value of "1"
|
||||||
|
@ -153,19 +160,16 @@ class GuiBaseTest(object):
|
||||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def history_is_not_visible(self, mode):
|
def history_is_not_visible(self, mode):
|
||||||
'''Test that the History section is not visible'''
|
"""Test that the History section is not visible"""
|
||||||
self.assertFalse(mode.history.isVisible())
|
self.assertFalse(mode.history.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def history_is_visible(self, mode):
|
def history_is_visible(self, mode):
|
||||||
'''Test that the History section is visible'''
|
"""Test that the History section is visible"""
|
||||||
self.assertTrue(mode.history.isVisible())
|
self.assertTrue(mode.history.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def server_working_on_start_button_pressed(self, mode):
|
def server_working_on_start_button_pressed(self, mode):
|
||||||
'''Test we can start the service'''
|
"""Test we can start the service"""
|
||||||
# Should be in SERVER_WORKING state
|
# Should be in SERVER_WORKING state
|
||||||
QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
self.assertEqual(mode.server_status.status, 1)
|
self.assertEqual(mode.server_status.status, 1)
|
||||||
|
@ -175,115 +179,143 @@ class GuiBaseTest(object):
|
||||||
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||||
|
|
||||||
def server_status_indicator_says_starting(self, mode):
|
def server_status_indicator_says_starting(self, mode):
|
||||||
'''Test that the Server Status indicator shows we are Starting'''
|
"""Test that the Server Status indicator shows we are Starting"""
|
||||||
self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working'))
|
self.assertEqual(
|
||||||
|
mode.server_status_label.text(),
|
||||||
|
strings._("gui_status_indicator_share_working"),
|
||||||
|
)
|
||||||
|
|
||||||
def server_status_indicator_says_scheduled(self, mode):
|
def server_status_indicator_says_scheduled(self, mode):
|
||||||
'''Test that the Server Status indicator shows we are Scheduled'''
|
"""Test that the Server Status indicator shows we are Scheduled"""
|
||||||
self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_scheduled'))
|
self.assertEqual(
|
||||||
|
mode.server_status_label.text(),
|
||||||
|
strings._("gui_status_indicator_share_scheduled"),
|
||||||
|
)
|
||||||
|
|
||||||
def server_is_started(self, mode, startup_time=2000):
|
def server_is_started(self, mode, startup_time=2000):
|
||||||
'''Test that the server has started'''
|
"""Test that the server has started"""
|
||||||
QtTest.QTest.qWait(startup_time)
|
QtTest.QTest.qWait(startup_time)
|
||||||
# Should now be in SERVER_STARTED state
|
# Should now be in SERVER_STARTED state
|
||||||
self.assertEqual(mode.server_status.status, 2)
|
self.assertEqual(mode.server_status.status, 2)
|
||||||
|
|
||||||
|
|
||||||
def web_server_is_running(self):
|
def web_server_is_running(self):
|
||||||
'''Test that the web server has started'''
|
"""Test that the web server has started"""
|
||||||
try:
|
try:
|
||||||
r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port))
|
r = requests.get("http://127.0.0.1:{}/".format(self.gui.app.port))
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
self.assertTrue(False)
|
self.assertTrue(False)
|
||||||
|
|
||||||
|
|
||||||
def have_a_password(self, mode, public_mode):
|
def have_a_password(self, mode, public_mode):
|
||||||
'''Test that we have a valid password'''
|
"""Test that we have a valid password"""
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
self.assertRegex(mode.server_status.web.password, r'(\w+)-(\w+)')
|
self.assertRegex(mode.server_status.web.password, r"(\w+)-(\w+)")
|
||||||
else:
|
else:
|
||||||
self.assertIsNone(mode.server_status.web.password, r'(\w+)-(\w+)')
|
self.assertIsNone(mode.server_status.web.password, r"(\w+)-(\w+)")
|
||||||
|
|
||||||
def add_button_visible(self, mode):
|
def add_button_visible(self, mode):
|
||||||
'''Test that the add button should be visible'''
|
"""Test that the add button should be visible"""
|
||||||
self.assertTrue(mode.server_status.file_selection.add_button.isVisible())
|
self.assertTrue(mode.server_status.file_selection.add_button.isVisible())
|
||||||
|
|
||||||
def url_description_shown(self, mode):
|
def url_description_shown(self, mode):
|
||||||
'''Test that the URL label is showing'''
|
"""Test that the URL label is showing"""
|
||||||
self.assertTrue(mode.server_status.url_description.isVisible())
|
self.assertTrue(mode.server_status.url_description.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def have_copy_url_button(self, mode, public_mode):
|
def have_copy_url_button(self, mode, public_mode):
|
||||||
'''Test that the Copy URL button is shown and that the clipboard is correct'''
|
"""Test that the Copy URL button is shown and that the clipboard is correct"""
|
||||||
self.assertTrue(mode.server_status.copy_url_button.isVisible())
|
self.assertTrue(mode.server_status.copy_url_button.isVisible())
|
||||||
|
|
||||||
QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
mode.server_status.copy_url_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
clipboard = self.gui.qtapp.clipboard()
|
clipboard = self.gui.qtapp.clipboard()
|
||||||
if public_mode:
|
if public_mode:
|
||||||
self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port))
|
self.assertEqual(
|
||||||
|
clipboard.text(), "http://127.0.0.1:{}".format(self.gui.app.port)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(clipboard.text(), 'http://onionshare:{}@127.0.0.1:{}'.format(mode.server_status.web.password, self.gui.app.port))
|
self.assertEqual(
|
||||||
|
clipboard.text(),
|
||||||
|
"http://onionshare:{}@127.0.0.1:{}".format(
|
||||||
|
mode.server_status.web.password, self.gui.app.port
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def server_status_indicator_says_started(self, mode):
|
def server_status_indicator_says_started(self, mode):
|
||||||
'''Test that the Server Status indicator shows we are started'''
|
"""Test that the Server Status indicator shows we are started"""
|
||||||
if type(mode) == ReceiveMode:
|
if type(mode) == ReceiveMode:
|
||||||
self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started'))
|
self.assertEqual(
|
||||||
|
mode.server_status_label.text(),
|
||||||
|
strings._("gui_status_indicator_receive_started"),
|
||||||
|
)
|
||||||
if type(mode) == ShareMode:
|
if type(mode) == ShareMode:
|
||||||
self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_started'))
|
self.assertEqual(
|
||||||
|
mode.server_status_label.text(),
|
||||||
|
strings._("gui_status_indicator_share_started"),
|
||||||
|
)
|
||||||
|
|
||||||
def web_page(self, mode, string, public_mode):
|
def web_page(self, mode, string, public_mode):
|
||||||
'''Test that the web page contains a string'''
|
"""Test that the web page contains a string"""
|
||||||
|
|
||||||
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password))
|
r = requests.get(
|
||||||
|
url, auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password)
|
||||||
|
)
|
||||||
|
|
||||||
self.assertTrue(string in r.text)
|
self.assertTrue(string in r.text)
|
||||||
|
|
||||||
|
|
||||||
def history_widgets_present(self, mode):
|
def history_widgets_present(self, mode):
|
||||||
'''Test that the relevant widgets are present in the history view after activity has taken place'''
|
"""Test that the relevant widgets are present in the history view after activity has taken place"""
|
||||||
self.assertFalse(mode.history.empty.isVisible())
|
self.assertFalse(mode.history.empty.isVisible())
|
||||||
self.assertTrue(mode.history.not_empty.isVisible())
|
self.assertTrue(mode.history.not_empty.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def counter_incremented(self, mode, count):
|
def counter_incremented(self, mode, count):
|
||||||
'''Test that the counter has incremented'''
|
"""Test that the counter has incremented"""
|
||||||
self.assertEqual(mode.history.completed_count, count)
|
self.assertEqual(mode.history.completed_count, count)
|
||||||
|
|
||||||
|
|
||||||
def server_is_stopped(self, mode, stay_open):
|
def server_is_stopped(self, mode, stay_open):
|
||||||
'''Test that the server stops when we click Stop'''
|
"""Test that the server stops when we click Stop"""
|
||||||
if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open) or (type(mode) == WebsiteMode):
|
if (
|
||||||
QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
type(mode) == ReceiveMode
|
||||||
|
or (type(mode) == ShareMode and stay_open)
|
||||||
|
or (type(mode) == WebsiteMode)
|
||||||
|
):
|
||||||
|
QtTest.QTest.mouseClick(
|
||||||
|
mode.server_status.server_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
self.assertEqual(mode.server_status.status, 0)
|
self.assertEqual(mode.server_status.status, 0)
|
||||||
|
|
||||||
|
|
||||||
def web_server_is_stopped(self):
|
def web_server_is_stopped(self):
|
||||||
'''Test that the web server also stopped'''
|
"""Test that the web server also stopped"""
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port))
|
r = requests.get("http://127.0.0.1:{}/".format(self.gui.app.port))
|
||||||
self.assertTrue(False)
|
self.assertTrue(False)
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
|
||||||
def server_status_indicator_says_closed(self, mode, stay_open):
|
def server_status_indicator_says_closed(self, mode, stay_open):
|
||||||
'''Test that the Server Status indicator shows we closed'''
|
"""Test that the Server Status indicator shows we closed"""
|
||||||
if type(mode) == ReceiveMode:
|
if type(mode) == ReceiveMode:
|
||||||
self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped'))
|
self.assertEqual(
|
||||||
|
self.gui.receive_mode.server_status_label.text(),
|
||||||
|
strings._("gui_status_indicator_receive_stopped"),
|
||||||
|
)
|
||||||
if type(mode) == ShareMode:
|
if type(mode) == ShareMode:
|
||||||
if stay_open:
|
if stay_open:
|
||||||
self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped'))
|
self.assertEqual(
|
||||||
|
self.gui.share_mode.server_status_label.text(),
|
||||||
|
strings._("gui_status_indicator_share_stopped"),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically'))
|
self.assertEqual(
|
||||||
|
self.gui.share_mode.server_status_label.text(),
|
||||||
|
strings._("closing_automatically"),
|
||||||
|
)
|
||||||
|
|
||||||
def clear_all_history_items(self, mode, count):
|
def clear_all_history_items(self, mode, count):
|
||||||
if count == 0:
|
if count == 0:
|
||||||
|
@ -292,41 +324,40 @@ class GuiBaseTest(object):
|
||||||
|
|
||||||
# Auto-stop timer tests
|
# Auto-stop timer tests
|
||||||
def set_timeout(self, mode, timeout):
|
def set_timeout(self, mode, timeout):
|
||||||
'''Test that the timeout can be set'''
|
"""Test that the timeout can be set"""
|
||||||
timer = QtCore.QDateTime.currentDateTime().addSecs(timeout)
|
timer = QtCore.QDateTime.currentDateTime().addSecs(timeout)
|
||||||
mode.server_status.autostop_timer_widget.setDateTime(timer)
|
mode.server_status.autostop_timer_widget.setDateTime(timer)
|
||||||
self.assertTrue(mode.server_status.autostop_timer_widget.dateTime(), timer)
|
self.assertTrue(mode.server_status.autostop_timer_widget.dateTime(), timer)
|
||||||
|
|
||||||
def autostop_timer_widget_hidden(self, mode):
|
def autostop_timer_widget_hidden(self, mode):
|
||||||
'''Test that the auto-stop timer widget is hidden when share has started'''
|
"""Test that the auto-stop timer widget is hidden when share has started"""
|
||||||
self.assertFalse(mode.server_status.autostop_timer_container.isVisible())
|
self.assertFalse(mode.server_status.autostop_timer_container.isVisible())
|
||||||
|
|
||||||
|
|
||||||
def server_timed_out(self, mode, wait):
|
def server_timed_out(self, mode, wait):
|
||||||
'''Test that the server has timed out after the timer ran out'''
|
"""Test that the server has timed out after the timer ran out"""
|
||||||
QtTest.QTest.qWait(wait)
|
QtTest.QTest.qWait(wait)
|
||||||
# We should have timed out now
|
# We should have timed out now
|
||||||
self.assertEqual(mode.server_status.status, 0)
|
self.assertEqual(mode.server_status.status, 0)
|
||||||
|
|
||||||
# Auto-start timer tests
|
# Auto-start timer tests
|
||||||
def set_autostart_timer(self, mode, timer):
|
def set_autostart_timer(self, mode, timer):
|
||||||
'''Test that the timer can be set'''
|
"""Test that the timer can be set"""
|
||||||
schedule = QtCore.QDateTime.currentDateTime().addSecs(timer)
|
schedule = QtCore.QDateTime.currentDateTime().addSecs(timer)
|
||||||
mode.server_status.autostart_timer_widget.setDateTime(schedule)
|
mode.server_status.autostart_timer_widget.setDateTime(schedule)
|
||||||
self.assertTrue(mode.server_status.autostart_timer_widget.dateTime(), schedule)
|
self.assertTrue(mode.server_status.autostart_timer_widget.dateTime(), schedule)
|
||||||
|
|
||||||
def autostart_timer_widget_hidden(self, mode):
|
def autostart_timer_widget_hidden(self, mode):
|
||||||
'''Test that the auto-start timer widget is hidden when share has started'''
|
"""Test that the auto-start timer widget is hidden when share has started"""
|
||||||
self.assertFalse(mode.server_status.autostart_timer_container.isVisible())
|
self.assertFalse(mode.server_status.autostart_timer_container.isVisible())
|
||||||
|
|
||||||
def scheduled_service_started(self, mode, wait):
|
def scheduled_service_started(self, mode, wait):
|
||||||
'''Test that the server has timed out after the timer ran out'''
|
"""Test that the server has timed out after the timer ran out"""
|
||||||
QtTest.QTest.qWait(wait)
|
QtTest.QTest.qWait(wait)
|
||||||
# We should have started now
|
# We should have started now
|
||||||
self.assertEqual(mode.server_status.status, 2)
|
self.assertEqual(mode.server_status.status, 2)
|
||||||
|
|
||||||
def cancel_the_share(self, mode):
|
def cancel_the_share(self, mode):
|
||||||
'''Test that we can cancel a share before it's started up '''
|
"""Test that we can cancel a share before it's started up """
|
||||||
self.server_working_on_start_button_pressed(mode)
|
self.server_working_on_start_button_pressed(mode)
|
||||||
self.server_status_indicator_says_scheduled(mode)
|
self.server_status_indicator_says_scheduled(mode)
|
||||||
self.add_delete_buttons_hidden()
|
self.add_delete_buttons_hidden()
|
||||||
|
@ -334,7 +365,9 @@ class GuiBaseTest(object):
|
||||||
self.set_autostart_timer(mode, 10)
|
self.set_autostart_timer(mode, 10)
|
||||||
QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseRelease(
|
||||||
|
mode.server_status.server_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
self.assertEqual(mode.server_status.status, 0)
|
self.assertEqual(mode.server_status.status, 0)
|
||||||
self.server_is_stopped(mode, False)
|
self.server_is_stopped(mode, False)
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
|
|
|
@ -4,25 +4,44 @@ from datetime import datetime, timedelta
|
||||||
from PyQt5 import QtCore, QtTest
|
from PyQt5 import QtCore, QtTest
|
||||||
from .GuiBaseTest import GuiBaseTest
|
from .GuiBaseTest import GuiBaseTest
|
||||||
|
|
||||||
|
|
||||||
class GuiReceiveTest(GuiBaseTest):
|
class GuiReceiveTest(GuiBaseTest):
|
||||||
def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False):
|
def upload_file(
|
||||||
'''Test that we can upload the file'''
|
self,
|
||||||
|
public_mode,
|
||||||
|
file_to_upload,
|
||||||
|
expected_basename,
|
||||||
|
identical_files_at_once=False,
|
||||||
|
):
|
||||||
|
"""Test that we can upload the file"""
|
||||||
|
|
||||||
# Wait 2 seconds to make sure the filename, based on timestamp, isn't accidentally reused
|
# Wait 2 seconds to make sure the filename, based on timestamp, isn't accidentally reused
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
files = {'file[]': open(file_to_upload, 'rb')}
|
files = {"file[]": open(file_to_upload, "rb")}
|
||||||
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/upload".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.post(url, files=files)
|
r = requests.post(url, files=files)
|
||||||
if identical_files_at_once:
|
if identical_files_at_once:
|
||||||
# Send a duplicate upload to test for collisions
|
# Send a duplicate upload to test for collisions
|
||||||
r = requests.post(url, files=files)
|
r = requests.post(url, files=files)
|
||||||
else:
|
else:
|
||||||
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password))
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
files=files,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.receive_mode.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
if identical_files_at_once:
|
if identical_files_at_once:
|
||||||
# Send a duplicate upload to test for collisions
|
# Send a duplicate upload to test for collisions
|
||||||
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password))
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
files=files,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.receive_mode.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
|
@ -35,7 +54,9 @@ class GuiReceiveTest(GuiBaseTest):
|
||||||
time_dir = now.strftime("%H.%M.%S-1")
|
time_dir = now.strftime("%H.%M.%S-1")
|
||||||
else:
|
else:
|
||||||
time_dir = now.strftime("%H.%M.%S")
|
time_dir = now.strftime("%H.%M.%S")
|
||||||
receive_mode_dir = os.path.join(self.gui.common.settings.get('data_dir'), date_dir, time_dir)
|
receive_mode_dir = os.path.join(
|
||||||
|
self.gui.common.settings.get("data_dir"), date_dir, time_dir
|
||||||
|
)
|
||||||
expected_filename = os.path.join(receive_mode_dir, expected_basename)
|
expected_filename = os.path.join(receive_mode_dir, expected_basename)
|
||||||
if os.path.exists(expected_filename):
|
if os.path.exists(expected_filename):
|
||||||
exists = True
|
exists = True
|
||||||
|
@ -45,31 +66,37 @@ class GuiReceiveTest(GuiBaseTest):
|
||||||
self.assertTrue(exists)
|
self.assertTrue(exists)
|
||||||
|
|
||||||
def upload_file_should_fail(self, public_mode):
|
def upload_file_should_fail(self, public_mode):
|
||||||
'''Test that we can't upload the file when permissions are wrong, and expected content is shown'''
|
"""Test that we can't upload the file when permissions are wrong, and expected content is shown"""
|
||||||
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
files = {"file[]": open("/tmp/test.txt", "rb")}
|
||||||
url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/upload".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.post(url, files=files)
|
r = requests.post(url, files=files)
|
||||||
else:
|
else:
|
||||||
r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password))
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
files=files,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.receive_mode.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
||||||
self.assertTrue('Error uploading, please inform the OnionShare user' in r.text)
|
self.assertTrue("Error uploading, please inform the OnionShare user" in r.text)
|
||||||
|
|
||||||
def upload_dir_permissions(self, mode=0o755):
|
def upload_dir_permissions(self, mode=0o755):
|
||||||
'''Manipulate the permissions on the upload dir in between tests'''
|
"""Manipulate the permissions on the upload dir in between tests"""
|
||||||
os.chmod('/tmp/OnionShare', mode)
|
os.chmod("/tmp/OnionShare", mode)
|
||||||
|
|
||||||
def try_without_auth_in_non_public_mode(self):
|
def try_without_auth_in_non_public_mode(self):
|
||||||
r = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port))
|
r = requests.post("http://127.0.0.1:{}/upload".format(self.gui.app.port))
|
||||||
self.assertEqual(r.status_code, 401)
|
self.assertEqual(r.status_code, 401)
|
||||||
r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port))
|
r = requests.get("http://127.0.0.1:{}/close".format(self.gui.app.port))
|
||||||
self.assertEqual(r.status_code, 401)
|
self.assertEqual(r.status_code, 401)
|
||||||
|
|
||||||
# 'Grouped' tests follow from here
|
# 'Grouped' tests follow from here
|
||||||
|
|
||||||
def run_all_receive_mode_setup_tests(self, public_mode):
|
def run_all_receive_mode_setup_tests(self, public_mode):
|
||||||
'''Set up a share in Receive mode and start it'''
|
"""Set up a share in Receive mode and start it"""
|
||||||
self.click_mode(self.gui.receive_mode)
|
self.click_mode(self.gui.receive_mode)
|
||||||
self.history_is_not_visible(self.gui.receive_mode)
|
self.history_is_not_visible(self.gui.receive_mode)
|
||||||
self.click_toggle_history(self.gui.receive_mode)
|
self.click_toggle_history(self.gui.receive_mode)
|
||||||
|
@ -83,24 +110,28 @@ class GuiReceiveTest(GuiBaseTest):
|
||||||
self.url_description_shown(self.gui.receive_mode)
|
self.url_description_shown(self.gui.receive_mode)
|
||||||
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.receive_mode)
|
self.server_status_indicator_says_started(self.gui.receive_mode)
|
||||||
self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode)
|
self.web_page(
|
||||||
|
self.gui.receive_mode,
|
||||||
|
"Select the files you want to send, then click",
|
||||||
|
public_mode,
|
||||||
|
)
|
||||||
|
|
||||||
def run_all_receive_mode_tests(self, public_mode):
|
def run_all_receive_mode_tests(self, public_mode):
|
||||||
'''Upload files in receive mode and stop the share'''
|
"""Upload files in receive mode and stop the share"""
|
||||||
self.run_all_receive_mode_setup_tests(public_mode)
|
self.run_all_receive_mode_setup_tests(public_mode)
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
self.try_without_auth_in_non_public_mode()
|
self.try_without_auth_in_non_public_mode()
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
|
self.upload_file(public_mode, "/tmp/test.txt", "test.txt")
|
||||||
self.history_widgets_present(self.gui.receive_mode)
|
self.history_widgets_present(self.gui.receive_mode)
|
||||||
self.counter_incremented(self.gui.receive_mode, 1)
|
self.counter_incremented(self.gui.receive_mode, 1)
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
|
self.upload_file(public_mode, "/tmp/test.txt", "test.txt")
|
||||||
self.counter_incremented(self.gui.receive_mode, 2)
|
self.counter_incremented(self.gui.receive_mode, 2)
|
||||||
self.upload_file(public_mode, '/tmp/testdir/test', 'test')
|
self.upload_file(public_mode, "/tmp/testdir/test", "test")
|
||||||
self.counter_incremented(self.gui.receive_mode, 3)
|
self.counter_incremented(self.gui.receive_mode, 3)
|
||||||
self.upload_file(public_mode, '/tmp/testdir/test', 'test')
|
self.upload_file(public_mode, "/tmp/testdir/test", "test")
|
||||||
self.counter_incremented(self.gui.receive_mode, 4)
|
self.counter_incremented(self.gui.receive_mode, 4)
|
||||||
# Test uploading the same file twice at the same time, and make sure no collisions
|
# Test uploading the same file twice at the same time, and make sure no collisions
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt', True)
|
self.upload_file(public_mode, "/tmp/test.txt", "test.txt", True)
|
||||||
self.counter_incremented(self.gui.receive_mode, 6)
|
self.counter_incremented(self.gui.receive_mode, 6)
|
||||||
self.history_indicator(self.gui.receive_mode, public_mode, "2")
|
self.history_indicator(self.gui.receive_mode, public_mode, "2")
|
||||||
self.server_is_stopped(self.gui.receive_mode, False)
|
self.server_is_stopped(self.gui.receive_mode, False)
|
||||||
|
@ -111,7 +142,7 @@ class GuiReceiveTest(GuiBaseTest):
|
||||||
self.history_indicator(self.gui.receive_mode, public_mode, "2")
|
self.history_indicator(self.gui.receive_mode, public_mode, "2")
|
||||||
|
|
||||||
def run_all_receive_mode_unwritable_dir_tests(self, public_mode):
|
def run_all_receive_mode_unwritable_dir_tests(self, public_mode):
|
||||||
'''Attempt to upload (unwritable) files in receive mode and stop the share'''
|
"""Attempt to upload (unwritable) files in receive mode and stop the share"""
|
||||||
self.run_all_receive_mode_setup_tests(public_mode)
|
self.run_all_receive_mode_setup_tests(public_mode)
|
||||||
self.upload_dir_permissions(0o400)
|
self.upload_dir_permissions(0o400)
|
||||||
self.upload_file_should_fail(public_mode)
|
self.upload_file_should_fail(public_mode)
|
||||||
|
@ -131,8 +162,8 @@ class GuiReceiveTest(GuiBaseTest):
|
||||||
def run_all_clear_all_button_tests(self, public_mode):
|
def run_all_clear_all_button_tests(self, public_mode):
|
||||||
"""Test the Clear All history button"""
|
"""Test the Clear All history button"""
|
||||||
self.run_all_receive_mode_setup_tests(public_mode)
|
self.run_all_receive_mode_setup_tests(public_mode)
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
|
self.upload_file(public_mode, "/tmp/test.txt", "test.txt")
|
||||||
self.history_widgets_present(self.gui.receive_mode)
|
self.history_widgets_present(self.gui.receive_mode)
|
||||||
self.clear_all_history_items(self.gui.receive_mode, 0)
|
self.clear_all_history_items(self.gui.receive_mode, 0)
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
|
self.upload_file(public_mode, "/tmp/test.txt", "test.txt")
|
||||||
self.clear_all_history_items(self.gui.receive_mode, 2)
|
self.clear_all_history_items(self.gui.receive_mode, 2)
|
||||||
|
|
|
@ -6,89 +6,137 @@ import tempfile
|
||||||
from PyQt5 import QtCore, QtTest
|
from PyQt5 import QtCore, QtTest
|
||||||
from .GuiBaseTest import GuiBaseTest
|
from .GuiBaseTest import GuiBaseTest
|
||||||
|
|
||||||
|
|
||||||
class GuiShareTest(GuiBaseTest):
|
class GuiShareTest(GuiBaseTest):
|
||||||
# Persistence tests
|
# Persistence tests
|
||||||
def have_same_password(self, password):
|
def have_same_password(self, password):
|
||||||
'''Test that we have the same password'''
|
"""Test that we have the same password"""
|
||||||
self.assertEqual(self.gui.share_mode.server_status.web.password, password)
|
self.assertEqual(self.gui.share_mode.server_status.web.password, password)
|
||||||
|
|
||||||
# Share-specific tests
|
# Share-specific tests
|
||||||
|
|
||||||
def file_selection_widget_has_files(self, num=2):
|
def file_selection_widget_has_files(self, num=2):
|
||||||
'''Test that the number of items in the list is as expected'''
|
"""Test that the number of items in the list is as expected"""
|
||||||
self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num)
|
self.assertEqual(
|
||||||
|
self.gui.share_mode.server_status.file_selection.get_num_files(), num
|
||||||
|
)
|
||||||
|
|
||||||
def deleting_all_files_hides_delete_button(self):
|
def deleting_all_files_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'''
|
"""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))
|
rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center())
|
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
|
# Delete button should be visible
|
||||||
self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
self.assertTrue(
|
||||||
|
self.gui.share_mode.server_status.file_selection.delete_button.isVisible()
|
||||||
|
)
|
||||||
# Click delete, delete button should still be visible since we have one more file
|
# Click delete, delete button should still be visible since we have one more file
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.file_selection.delete_button,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
)
|
||||||
|
|
||||||
rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0))
|
rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center())
|
self.gui.share_mode.server_status.file_selection.file_list.item(0)
|
||||||
self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
)
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.file_selection.file_list.viewport(),
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=rect.center(),
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
self.gui.share_mode.server_status.file_selection.delete_button.isVisible()
|
||||||
|
)
|
||||||
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.file_selection.delete_button,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
)
|
||||||
|
|
||||||
# No more files, the delete button should be hidden
|
# No more files, the delete button should be hidden
|
||||||
self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
self.assertFalse(
|
||||||
|
self.gui.share_mode.server_status.file_selection.delete_button.isVisible()
|
||||||
|
)
|
||||||
|
|
||||||
def add_a_file_and_delete_using_its_delete_widget(self):
|
def add_a_file_and_delete_using_its_delete_widget(self):
|
||||||
'''Test that we can also delete a file by clicking on its [X] widget'''
|
"""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')
|
self.gui.share_mode.server_status.file_selection.file_list.add_file(
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton)
|
"/etc/hosts"
|
||||||
|
)
|
||||||
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.file_selection.file_list.item(
|
||||||
|
0
|
||||||
|
).item_button,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
)
|
||||||
self.file_selection_widget_has_files(0)
|
self.file_selection_widget_has_files(0)
|
||||||
|
|
||||||
|
|
||||||
def file_selection_widget_read_files(self):
|
def file_selection_widget_read_files(self):
|
||||||
'''Re-add some files to the list so we can share'''
|
"""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(
|
||||||
self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt')
|
"/etc/hosts"
|
||||||
|
)
|
||||||
|
self.gui.share_mode.server_status.file_selection.file_list.add_file(
|
||||||
|
"/tmp/test.txt"
|
||||||
|
)
|
||||||
self.file_selection_widget_has_files(2)
|
self.file_selection_widget_has_files(2)
|
||||||
|
|
||||||
|
|
||||||
def add_large_file(self):
|
def add_large_file(self):
|
||||||
'''Add a large file to the share'''
|
"""Add a large file to the share"""
|
||||||
size = 1024 * 1024 * 155
|
size = 1024 * 1024 * 155
|
||||||
with open('/tmp/large_file', 'wb') as fout:
|
with open("/tmp/large_file", "wb") as fout:
|
||||||
fout.write(os.urandom(size))
|
fout.write(os.urandom(size))
|
||||||
self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file')
|
self.gui.share_mode.server_status.file_selection.file_list.add_file(
|
||||||
|
"/tmp/large_file"
|
||||||
|
)
|
||||||
|
|
||||||
def add_delete_buttons_hidden(self):
|
def add_delete_buttons_hidden(self):
|
||||||
'''Test that the add and delete buttons are hidden when the server starts'''
|
"""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.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible())
|
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 download_share(self, public_mode):
|
def download_share(self, public_mode):
|
||||||
'''Test that we can download the share'''
|
"""Test that we can download the share"""
|
||||||
url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/download".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password))
|
r = requests.get(
|
||||||
|
url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.share_mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
tmp_file = tempfile.NamedTemporaryFile()
|
tmp_file = tempfile.NamedTemporaryFile()
|
||||||
with open(tmp_file.name, 'wb') as f:
|
with open(tmp_file.name, "wb") as f:
|
||||||
f.write(r.content)
|
f.write(r.content)
|
||||||
|
|
||||||
zip = zipfile.ZipFile(tmp_file.name)
|
zip = zipfile.ZipFile(tmp_file.name)
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
|
self.assertEqual("onionshare", zip.read("test.txt").decode("utf-8"))
|
||||||
|
|
||||||
def individual_file_is_viewable_or_not(self, public_mode, stay_open):
|
def individual_file_is_viewable_or_not(self, public_mode, stay_open):
|
||||||
'''Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)'''
|
"""Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)"""
|
||||||
url = "http://127.0.0.1:{}".format(self.gui.app.port)
|
url = "http://127.0.0.1:{}".format(self.gui.app.port)
|
||||||
download_file_url = "http://127.0.0.1:{}/test.txt".format(self.gui.app.port)
|
download_file_url = "http://127.0.0.1:{}/test.txt".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password))
|
r = requests.get(
|
||||||
|
url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.share_mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if stay_open:
|
if stay_open:
|
||||||
self.assertTrue('a href="test.txt"' in r.text)
|
self.assertTrue('a href="test.txt"' in r.text)
|
||||||
|
@ -96,32 +144,44 @@ class GuiShareTest(GuiBaseTest):
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(download_file_url)
|
r = requests.get(download_file_url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password))
|
r = requests.get(
|
||||||
|
download_file_url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.share_mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
tmp_file = tempfile.NamedTemporaryFile()
|
tmp_file = tempfile.NamedTemporaryFile()
|
||||||
with open(tmp_file.name, 'wb') as f:
|
with open(tmp_file.name, "wb") as f:
|
||||||
f.write(r.content)
|
f.write(r.content)
|
||||||
|
|
||||||
with open(tmp_file.name, 'r') as f:
|
with open(tmp_file.name, "r") as f:
|
||||||
self.assertEqual('onionshare', f.read())
|
self.assertEqual("onionshare", f.read())
|
||||||
else:
|
else:
|
||||||
self.assertFalse('a href="/test.txt"' in r.text)
|
self.assertFalse('a href="/test.txt"' in r.text)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(download_file_url)
|
r = requests.get(download_file_url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password))
|
r = requests.get(
|
||||||
|
download_file_url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.share_mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
self.download_share(public_mode)
|
self.download_share(public_mode)
|
||||||
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
|
|
||||||
def hit_401(self, public_mode):
|
def hit_401(self, public_mode):
|
||||||
'''Test that the server stops after too many 401s, or doesn't when in public_mode'''
|
"""Test that the server stops after too many 401s, or doesn't when in public_mode"""
|
||||||
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
||||||
|
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
password_guess = self.gui.common.build_password()
|
password_guess = self.gui.common.build_password()
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password_guess))
|
r = requests.get(
|
||||||
|
url, auth=requests.auth.HTTPBasicAuth("onionshare", password_guess)
|
||||||
|
)
|
||||||
|
|
||||||
# A nasty hack to avoid the Alert dialog that blocks the rest of the test
|
# A nasty hack to avoid the Alert dialog that blocks the rest of the test
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
|
@ -134,7 +194,6 @@ class GuiShareTest(GuiBaseTest):
|
||||||
else:
|
else:
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
|
|
||||||
|
|
||||||
# 'Grouped' tests follow from here
|
# 'Grouped' tests follow from here
|
||||||
|
|
||||||
def run_all_share_mode_setup_tests(self):
|
def run_all_share_mode_setup_tests(self):
|
||||||
|
@ -148,7 +207,6 @@ class GuiShareTest(GuiBaseTest):
|
||||||
self.add_a_file_and_delete_using_its_delete_widget()
|
self.add_a_file_and_delete_using_its_delete_widget()
|
||||||
self.file_selection_widget_read_files()
|
self.file_selection_widget_read_files()
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_started_tests(self, public_mode, startup_time=2000):
|
def run_all_share_mode_started_tests(self, public_mode, startup_time=2000):
|
||||||
"""Tests in share mode after starting a share"""
|
"""Tests in share mode after starting a share"""
|
||||||
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
||||||
|
@ -162,10 +220,9 @@ class GuiShareTest(GuiBaseTest):
|
||||||
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.share_mode)
|
self.server_status_indicator_says_started(self.gui.share_mode)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_download_tests(self, public_mode, stay_open):
|
def run_all_share_mode_download_tests(self, public_mode, stay_open):
|
||||||
"""Tests in share mode after downloading a share"""
|
"""Tests in share mode after downloading a share"""
|
||||||
self.web_page(self.gui.share_mode, 'Total size', public_mode)
|
self.web_page(self.gui.share_mode, "Total size", public_mode)
|
||||||
self.download_share(public_mode)
|
self.download_share(public_mode)
|
||||||
self.history_widgets_present(self.gui.share_mode)
|
self.history_widgets_present(self.gui.share_mode)
|
||||||
self.server_is_stopped(self.gui.share_mode, stay_open)
|
self.server_is_stopped(self.gui.share_mode, stay_open)
|
||||||
|
@ -179,7 +236,7 @@ class GuiShareTest(GuiBaseTest):
|
||||||
|
|
||||||
def run_all_share_mode_individual_file_download_tests(self, public_mode, stay_open):
|
def run_all_share_mode_individual_file_download_tests(self, public_mode, stay_open):
|
||||||
"""Tests in share mode after downloading a share"""
|
"""Tests in share mode after downloading a share"""
|
||||||
self.web_page(self.gui.share_mode, 'Total size', public_mode)
|
self.web_page(self.gui.share_mode, "Total size", public_mode)
|
||||||
self.individual_file_is_viewable_or_not(public_mode, stay_open)
|
self.individual_file_is_viewable_or_not(public_mode, stay_open)
|
||||||
self.history_widgets_present(self.gui.share_mode)
|
self.history_widgets_present(self.gui.share_mode)
|
||||||
self.server_is_stopped(self.gui.share_mode, stay_open)
|
self.server_is_stopped(self.gui.share_mode, stay_open)
|
||||||
|
@ -222,7 +279,6 @@ class GuiShareTest(GuiBaseTest):
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
self.server_status_indicator_says_closed(self.gui.share_mode, stay_open)
|
self.server_status_indicator_says_closed(self.gui.share_mode, stay_open)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
||||||
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
@ -231,7 +287,6 @@ class GuiShareTest(GuiBaseTest):
|
||||||
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
||||||
self.have_same_password(password)
|
self.have_same_password(password)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_timer_tests(self, public_mode):
|
def run_all_share_mode_timer_tests(self, public_mode):
|
||||||
"""Auto-stop timer tests in share mode"""
|
"""Auto-stop timer tests in share mode"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
@ -258,12 +313,16 @@ class GuiShareTest(GuiBaseTest):
|
||||||
self.set_autostart_timer(self.gui.share_mode, 15)
|
self.set_autostart_timer(self.gui.share_mode, 15)
|
||||||
self.set_timeout(self.gui.share_mode, 5)
|
self.set_timeout(self.gui.share_mode, 5)
|
||||||
QtCore.QTimer.singleShot(4000, self.accept_dialog)
|
QtCore.QTimer.singleShot(4000, self.accept_dialog)
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
self.server_is_stopped(self.gui.share_mode, False)
|
self.server_is_stopped(self.gui.share_mode, False)
|
||||||
|
|
||||||
def run_all_share_mode_unreadable_file_tests(self):
|
def run_all_share_mode_unreadable_file_tests(self):
|
||||||
'''Attempt to share an unreadable file'''
|
"""Attempt to share an unreadable file"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
||||||
self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/nonexistent.txt')
|
self.gui.share_mode.server_status.file_selection.file_list.add_file(
|
||||||
|
"/tmp/nonexistent.txt"
|
||||||
|
)
|
||||||
self.file_selection_widget_has_files(2)
|
self.file_selection_widget_has_files(2)
|
||||||
|
|
|
@ -13,13 +13,16 @@ from onionshare.web import Web
|
||||||
from onionshare_gui import Application, OnionShare, OnionShareGui
|
from onionshare_gui import Application, OnionShare, OnionShareGui
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class GuiWebsiteTest(GuiShareTest):
|
class GuiWebsiteTest(GuiShareTest):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_up(test_settings):
|
def set_up(test_settings):
|
||||||
'''Create GUI with given settings'''
|
"""Create GUI with given settings"""
|
||||||
# Create our test file
|
# Create our test file
|
||||||
testfile = open('/tmp/index.html', 'w')
|
testfile = open("/tmp/index.html", "w")
|
||||||
testfile.write('<html><body><p>This is a test website hosted by OnionShare</p></body></html>')
|
testfile.write(
|
||||||
|
"<html><body><p>This is a test website hosted by OnionShare</p></body></html>"
|
||||||
|
)
|
||||||
testfile.close()
|
testfile.close()
|
||||||
|
|
||||||
common = Common()
|
common = Common()
|
||||||
|
@ -28,7 +31,7 @@ class GuiWebsiteTest(GuiShareTest):
|
||||||
strings.load_strings(common)
|
strings.load_strings(common)
|
||||||
|
|
||||||
# Get all of the settings in test_settings
|
# Get all of the settings in test_settings
|
||||||
test_settings['data_dir'] = '/tmp/OnionShare'
|
test_settings["data_dir"] = "/tmp/OnionShare"
|
||||||
for key, val in common.settings.default_settings.items():
|
for key, val in common.settings.default_settings.items():
|
||||||
if key not in test_settings:
|
if key not in test_settings:
|
||||||
test_settings[key] = val
|
test_settings[key] = val
|
||||||
|
@ -40,44 +43,62 @@ class GuiWebsiteTest(GuiShareTest):
|
||||||
app = OnionShare(common, testonion, True, 0)
|
app = OnionShare(common, testonion, True, 0)
|
||||||
|
|
||||||
web = Web(common, False, True)
|
web = Web(common, False, True)
|
||||||
open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
|
open("/tmp/settings.json", "w").write(json.dumps(test_settings))
|
||||||
|
|
||||||
gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/index.html'], '/tmp/settings.json', True)
|
gui = OnionShareGui(
|
||||||
|
common,
|
||||||
|
testonion,
|
||||||
|
qtapp,
|
||||||
|
app,
|
||||||
|
["/tmp/index.html"],
|
||||||
|
"/tmp/settings.json",
|
||||||
|
True,
|
||||||
|
)
|
||||||
return gui
|
return gui
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tear_down():
|
def tear_down():
|
||||||
'''Clean up after tests'''
|
"""Clean up after tests"""
|
||||||
try:
|
try:
|
||||||
os.remove('/tmp/index.html')
|
os.remove("/tmp/index.html")
|
||||||
os.remove('/tmp/settings.json')
|
os.remove("/tmp/settings.json")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def view_website(self, public_mode):
|
def view_website(self, public_mode):
|
||||||
'''Test that we can download the share'''
|
"""Test that we can download the share"""
|
||||||
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password))
|
r = requests.get(
|
||||||
|
url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.website_mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
self.assertTrue('This is a test website hosted by OnionShare' in r.text)
|
self.assertTrue("This is a test website hosted by OnionShare" in r.text)
|
||||||
|
|
||||||
def check_csp_header(self, public_mode, csp_header_disabled):
|
def check_csp_header(self, public_mode, csp_header_disabled):
|
||||||
'''Test that the CSP header is present when enabled or vice versa'''
|
"""Test that the CSP header is present when enabled or vice versa"""
|
||||||
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
url = "http://127.0.0.1:{}/".format(self.gui.app.port)
|
||||||
if public_mode:
|
if public_mode:
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
else:
|
else:
|
||||||
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password))
|
r = requests.get(
|
||||||
|
url,
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
"onionshare", self.gui.website_mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
QtTest.QTest.qWait(2000)
|
QtTest.QTest.qWait(2000)
|
||||||
if csp_header_disabled:
|
if csp_header_disabled:
|
||||||
self.assertFalse('Content-Security-Policy' in r.headers)
|
self.assertFalse("Content-Security-Policy" in r.headers)
|
||||||
else:
|
else:
|
||||||
self.assertTrue('Content-Security-Policy' in r.headers)
|
self.assertTrue("Content-Security-Policy" in r.headers)
|
||||||
|
|
||||||
def run_all_website_mode_setup_tests(self):
|
def run_all_website_mode_setup_tests(self):
|
||||||
"""Tests in website mode prior to starting a share"""
|
"""Tests in website mode prior to starting a share"""
|
||||||
|
@ -100,16 +121,16 @@ class GuiWebsiteTest(GuiShareTest):
|
||||||
self.have_copy_url_button(self.gui.website_mode, public_mode)
|
self.have_copy_url_button(self.gui.website_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.website_mode)
|
self.server_status_indicator_says_started(self.gui.website_mode)
|
||||||
|
|
||||||
|
|
||||||
def run_all_website_mode_download_tests(self, public_mode):
|
def run_all_website_mode_download_tests(self, public_mode):
|
||||||
"""Tests in website mode after viewing the site"""
|
"""Tests in website mode after viewing the site"""
|
||||||
self.run_all_website_mode_setup_tests()
|
self.run_all_website_mode_setup_tests()
|
||||||
self.run_all_website_mode_started_tests(public_mode, startup_time=2000)
|
self.run_all_website_mode_started_tests(public_mode, startup_time=2000)
|
||||||
self.view_website(public_mode)
|
self.view_website(public_mode)
|
||||||
self.check_csp_header(public_mode, self.gui.common.settings.get('csp_header_disabled'))
|
self.check_csp_header(
|
||||||
|
public_mode, self.gui.common.settings.get("csp_header_disabled")
|
||||||
|
)
|
||||||
self.history_widgets_present(self.gui.website_mode)
|
self.history_widgets_present(self.gui.website_mode)
|
||||||
self.server_is_stopped(self.gui.website_mode, False)
|
self.server_is_stopped(self.gui.website_mode, False)
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
self.server_status_indicator_says_closed(self.gui.website_mode, False)
|
self.server_status_indicator_says_closed(self.gui.website_mode, False)
|
||||||
self.add_button_visible(self.gui.website_mode)
|
self.add_button_visible(self.gui.website_mode)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ class OnionStub(object):
|
||||||
class SettingsGuiBaseTest(object):
|
class SettingsGuiBaseTest(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_up():
|
def set_up():
|
||||||
'''Create the GUI'''
|
"""Create the GUI"""
|
||||||
|
|
||||||
# Default settings for the settings GUI tests
|
# Default settings for the settings GUI tests
|
||||||
test_settings = {
|
test_settings = {
|
||||||
|
@ -32,8 +32,8 @@ class SettingsGuiBaseTest(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create our test file
|
# Create our test file
|
||||||
testfile = open('/tmp/test.txt', 'w')
|
testfile = open("/tmp/test.txt", "w")
|
||||||
testfile.write('onionshare')
|
testfile.write("onionshare")
|
||||||
testfile.close()
|
testfile.close()
|
||||||
|
|
||||||
common = Common()
|
common = Common()
|
||||||
|
@ -51,22 +51,22 @@ class SettingsGuiBaseTest(object):
|
||||||
if key not in test_settings:
|
if key not in test_settings:
|
||||||
test_settings[key] = val
|
test_settings[key] = val
|
||||||
|
|
||||||
open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
|
open("/tmp/settings.json", "w").write(json.dumps(test_settings))
|
||||||
|
|
||||||
gui = SettingsDialog(common, testonion, qtapp, '/tmp/settings.json', True)
|
gui = SettingsDialog(common, testonion, qtapp, "/tmp/settings.json", True)
|
||||||
return gui
|
return gui
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tear_down():
|
def tear_down():
|
||||||
'''Clean up after tests'''
|
"""Clean up after tests"""
|
||||||
os.remove('/tmp/settings.json')
|
os.remove("/tmp/settings.json")
|
||||||
|
|
||||||
def run_settings_gui_tests(self):
|
def run_settings_gui_tests(self):
|
||||||
self.gui.show()
|
self.gui.show()
|
||||||
|
|
||||||
# Window is shown
|
# Window is shown
|
||||||
self.assertTrue(self.gui.isVisible())
|
self.assertTrue(self.gui.isVisible())
|
||||||
self.assertEqual(self.gui.windowTitle(), strings._('gui_settings_window_title'))
|
self.assertEqual(self.gui.windowTitle(), strings._("gui_settings_window_title"))
|
||||||
|
|
||||||
# Check for updates button is hidden
|
# Check for updates button is hidden
|
||||||
self.assertFalse(self.gui.check_for_updates_button.isVisible())
|
self.assertFalse(self.gui.check_for_updates_button.isVisible())
|
||||||
|
@ -74,13 +74,21 @@ class SettingsGuiBaseTest(object):
|
||||||
# public mode is off
|
# public mode is off
|
||||||
self.assertFalse(self.gui.public_mode_checkbox.isChecked())
|
self.assertFalse(self.gui.public_mode_checkbox.isChecked())
|
||||||
# enable public mode
|
# enable public mode
|
||||||
QtTest.QTest.mouseClick(self.gui.public_mode_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.public_mode_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.public_mode_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.public_mode_checkbox.height() / 2),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.public_mode_checkbox.isChecked())
|
self.assertTrue(self.gui.public_mode_checkbox.isChecked())
|
||||||
|
|
||||||
# autostop timer is off
|
# autostop timer is off
|
||||||
self.assertFalse(self.gui.autostop_timer_checkbox.isChecked())
|
self.assertFalse(self.gui.autostop_timer_checkbox.isChecked())
|
||||||
# enable autostop timer
|
# enable autostop timer
|
||||||
QtTest.QTest.mouseClick(self.gui.autostop_timer_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.autostop_timer_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.autostop_timer_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.autostop_timer_checkbox.height() / 2),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.autostop_timer_checkbox.isChecked())
|
self.assertTrue(self.gui.autostop_timer_checkbox.isChecked())
|
||||||
|
|
||||||
# legacy mode checkbox and related widgets
|
# legacy mode checkbox and related widgets
|
||||||
|
@ -96,32 +104,70 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertFalse(self.gui.hidservauth_copy_button.isVisible())
|
self.assertFalse(self.gui.hidservauth_copy_button.isVisible())
|
||||||
|
|
||||||
# enable legacy mode
|
# enable legacy mode
|
||||||
QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.use_legacy_v2_onions_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.use_legacy_v2_onions_checkbox.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked())
|
self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked())
|
||||||
self.assertTrue(self.gui.save_private_key_checkbox.isVisible())
|
self.assertTrue(self.gui.save_private_key_checkbox.isVisible())
|
||||||
self.assertTrue(self.gui.use_stealth_widget.isVisible())
|
self.assertTrue(self.gui.use_stealth_widget.isVisible())
|
||||||
|
|
||||||
# enable persistent mode
|
# enable persistent mode
|
||||||
QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.save_private_key_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.save_private_key_checkbox.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.save_private_key_checkbox.isChecked())
|
self.assertTrue(self.gui.save_private_key_checkbox.isChecked())
|
||||||
# enable stealth mode
|
# enable stealth mode
|
||||||
QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.stealth_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.stealth_checkbox.height() / 2),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.stealth_checkbox.isChecked())
|
self.assertTrue(self.gui.stealth_checkbox.isChecked())
|
||||||
# now that stealth is enabled, we can't turn off legacy mode
|
# now that stealth is enabled, we can't turn off legacy mode
|
||||||
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled())
|
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled())
|
||||||
# disable stealth, persistence
|
# disable stealth, persistence
|
||||||
QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2))
|
self.gui.save_private_key_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.save_private_key_checkbox.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.stealth_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.stealth_checkbox.height() / 2),
|
||||||
|
)
|
||||||
# legacy mode checkbox is enabled again
|
# legacy mode checkbox is enabled again
|
||||||
self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled())
|
self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled())
|
||||||
# uncheck legacy mode
|
# uncheck legacy mode
|
||||||
QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.use_legacy_v2_onions_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.use_legacy_v2_onions_checkbox.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
# legacy options hidden again
|
# legacy options hidden again
|
||||||
self.assertTrue(self.gui.save_private_key_widget.isVisible())
|
self.assertTrue(self.gui.save_private_key_widget.isVisible())
|
||||||
self.assertFalse(self.gui.use_stealth_widget.isVisible())
|
self.assertFalse(self.gui.use_stealth_widget.isVisible())
|
||||||
|
|
||||||
# re-enable legacy mode
|
# re-enable legacy mode
|
||||||
QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.use_legacy_v2_onions_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.use_legacy_v2_onions_checkbox.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# legacy mode setting is hidden
|
# legacy mode setting is hidden
|
||||||
|
@ -131,8 +177,16 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertTrue(self.gui.use_stealth_widget.isVisible())
|
self.assertTrue(self.gui.use_stealth_widget.isVisible())
|
||||||
|
|
||||||
# enable them all again so that we can see the setting stick in settings.json
|
# enable them all again so that we can see the setting stick in settings.json
|
||||||
QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2))
|
self.gui.save_private_key_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.save_private_key_checkbox.height() / 2),
|
||||||
|
)
|
||||||
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.stealth_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.stealth_checkbox.height() / 2),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# None of the onion settings should appear
|
# None of the onion settings should appear
|
||||||
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible())
|
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible())
|
||||||
|
@ -144,12 +198,17 @@ class SettingsGuiBaseTest(object):
|
||||||
|
|
||||||
# stay open toggled off, on
|
# stay open toggled off, on
|
||||||
self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked())
|
self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked())
|
||||||
QtTest.QTest.mouseClick(self.gui.close_after_first_download_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.close_after_first_download_checkbox.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.close_after_first_download_checkbox,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.close_after_first_download_checkbox.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked())
|
self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked())
|
||||||
|
|
||||||
# receive mode
|
# receive mode
|
||||||
self.gui.data_dir_lineedit.setText('/tmp/OnionShareSettingsTest')
|
self.gui.data_dir_lineedit.setText("/tmp/OnionShareSettingsTest")
|
||||||
|
|
||||||
|
|
||||||
# bundled mode is enabled
|
# bundled mode is enabled
|
||||||
self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled())
|
self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled())
|
||||||
|
@ -161,7 +220,11 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked())
|
self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked())
|
||||||
|
|
||||||
# switch to obfs4
|
# switch to obfs4
|
||||||
QtTest.QTest.mouseClick(self.gui.tor_bridges_use_obfs4_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_obfs4_radio.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.tor_bridges_use_obfs4_radio,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.tor_bridges_use_obfs4_radio.height() / 2),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked())
|
self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked())
|
||||||
|
|
||||||
# custom bridges are hidden
|
# custom bridges are hidden
|
||||||
|
@ -175,7 +238,11 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked())
|
self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked())
|
||||||
|
|
||||||
# enable automatic mode
|
# enable automatic mode
|
||||||
QtTest.QTest.mouseClick(self.gui.connection_type_automatic_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_automatic_radio.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.connection_type_automatic_radio,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.connection_type_automatic_radio.height() / 2),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.connection_type_automatic_radio.isChecked())
|
self.assertTrue(self.gui.connection_type_automatic_radio.isChecked())
|
||||||
# bundled is off
|
# bundled is off
|
||||||
self.assertFalse(self.gui.connection_type_bundled_radio.isChecked())
|
self.assertFalse(self.gui.connection_type_bundled_radio.isChecked())
|
||||||
|
@ -187,7 +254,13 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertFalse(self.gui.authenticate_password_radio.isVisible())
|
self.assertFalse(self.gui.authenticate_password_radio.isVisible())
|
||||||
|
|
||||||
# enable control port mode
|
# enable control port mode
|
||||||
QtTest.QTest.mouseClick(self.gui.connection_type_control_port_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_control_port_radio.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.connection_type_control_port_radio,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.connection_type_control_port_radio.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.connection_type_control_port_radio.isChecked())
|
self.assertTrue(self.gui.connection_type_control_port_radio.isChecked())
|
||||||
# automatic is off
|
# automatic is off
|
||||||
self.assertFalse(self.gui.connection_type_automatic_radio.isChecked())
|
self.assertFalse(self.gui.connection_type_automatic_radio.isChecked())
|
||||||
|
@ -196,7 +269,13 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertTrue(self.gui.authenticate_password_radio.isVisible())
|
self.assertTrue(self.gui.authenticate_password_radio.isVisible())
|
||||||
|
|
||||||
# enable socket mode
|
# enable socket mode
|
||||||
QtTest.QTest.mouseClick(self.gui.connection_type_socket_file_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_socket_file_radio.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.connection_type_socket_file_radio,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(
|
||||||
|
2, self.gui.connection_type_socket_file_radio.height() / 2
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked())
|
self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked())
|
||||||
# control port is off
|
# control port is off
|
||||||
self.assertFalse(self.gui.connection_type_control_port_radio.isChecked())
|
self.assertFalse(self.gui.connection_type_control_port_radio.isChecked())
|
||||||
|
@ -205,20 +284,30 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertTrue(self.gui.authenticate_password_radio.isVisible())
|
self.assertTrue(self.gui.authenticate_password_radio.isVisible())
|
||||||
|
|
||||||
# re-enable bundled mode
|
# re-enable bundled mode
|
||||||
QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.connection_type_bundled_radio,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.connection_type_bundled_radio.height() / 2),
|
||||||
|
)
|
||||||
# go back to custom bridges
|
# go back to custom bridges
|
||||||
QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2))
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.tor_bridges_use_custom_radio,
|
||||||
|
QtCore.Qt.LeftButton,
|
||||||
|
pos=QtCore.QPoint(2, self.gui.tor_bridges_use_custom_radio.height() / 2),
|
||||||
|
)
|
||||||
self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked())
|
self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked())
|
||||||
self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible())
|
self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible())
|
||||||
self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked())
|
self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked())
|
||||||
self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3')
|
self.gui.tor_bridges_use_custom_textbox.setPlainText(
|
||||||
|
"94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3"
|
||||||
|
)
|
||||||
|
|
||||||
# Test that the Settings Dialog can save the settings and close itself
|
# Test that the Settings Dialog can save the settings and close itself
|
||||||
QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton)
|
||||||
self.assertFalse(self.gui.isVisible())
|
self.assertFalse(self.gui.isVisible())
|
||||||
|
|
||||||
# Test our settings are reflected in the settings json
|
# Test our settings are reflected in the settings json
|
||||||
with open('/tmp/settings.json') as f:
|
with open("/tmp/settings.json") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
self.assertTrue(data["public_mode"])
|
self.assertTrue(data["public_mode"])
|
||||||
|
@ -238,4 +327,7 @@ class SettingsGuiBaseTest(object):
|
||||||
self.assertFalse(data["close_after_first_download"])
|
self.assertFalse(data["close_after_first_download"])
|
||||||
self.assertEqual(data["connection_type"], "bundled")
|
self.assertEqual(data["connection_type"], "bundled")
|
||||||
self.assertFalse(data["tor_bridges_use_obfs4"])
|
self.assertFalse(data["tor_bridges_use_obfs4"])
|
||||||
self.assertEqual(data["tor_bridges_use_custom_bridges"], "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n")
|
self.assertEqual(
|
||||||
|
data["tor_bridges_use_custom_bridges"],
|
||||||
|
"Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n",
|
||||||
|
)
|
||||||
|
|
|
@ -16,20 +16,21 @@ from onionshare_gui.mode.receive_mode import ReceiveMode
|
||||||
|
|
||||||
from .GuiBaseTest import GuiBaseTest
|
from .GuiBaseTest import GuiBaseTest
|
||||||
|
|
||||||
|
|
||||||
class TorGuiBaseTest(GuiBaseTest):
|
class TorGuiBaseTest(GuiBaseTest):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_up(test_settings):
|
def set_up(test_settings):
|
||||||
'''Create GUI with given settings'''
|
"""Create GUI with given settings"""
|
||||||
# Create our test file
|
# Create our test file
|
||||||
testfile = open('/tmp/test.txt', 'w')
|
testfile = open("/tmp/test.txt", "w")
|
||||||
testfile.write('onionshare')
|
testfile.write("onionshare")
|
||||||
testfile.close()
|
testfile.close()
|
||||||
|
|
||||||
# Create a test dir and files
|
# Create a test dir and files
|
||||||
if not os.path.exists('/tmp/testdir'):
|
if not os.path.exists("/tmp/testdir"):
|
||||||
testdir = os.mkdir('/tmp/testdir')
|
testdir = os.mkdir("/tmp/testdir")
|
||||||
testfile = open('/tmp/testdir/test.txt', 'w')
|
testfile = open("/tmp/testdir/test.txt", "w")
|
||||||
testfile.write('onionshare')
|
testfile.write("onionshare")
|
||||||
testfile.close()
|
testfile.close()
|
||||||
|
|
||||||
common = Common()
|
common = Common()
|
||||||
|
@ -38,8 +39,8 @@ class TorGuiBaseTest(GuiBaseTest):
|
||||||
strings.load_strings(common)
|
strings.load_strings(common)
|
||||||
|
|
||||||
# Get all of the settings in test_settings
|
# Get all of the settings in test_settings
|
||||||
test_settings['connection_type'] = 'automatic'
|
test_settings["connection_type"] = "automatic"
|
||||||
test_settings['data_dir'] = '/tmp/OnionShare'
|
test_settings["data_dir"] = "/tmp/OnionShare"
|
||||||
for key, val in common.settings.default_settings.items():
|
for key, val in common.settings.default_settings.items():
|
||||||
if key not in test_settings:
|
if key not in test_settings:
|
||||||
test_settings[key] = val
|
test_settings[key] = val
|
||||||
|
@ -51,13 +52,21 @@ class TorGuiBaseTest(GuiBaseTest):
|
||||||
app = OnionShare(common, testonion, False, 0)
|
app = OnionShare(common, testonion, False, 0)
|
||||||
|
|
||||||
web = Web(common, False, False)
|
web = Web(common, False, False)
|
||||||
open('/tmp/settings.json', 'w').write(json.dumps(test_settings))
|
open("/tmp/settings.json", "w").write(json.dumps(test_settings))
|
||||||
|
|
||||||
gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', False)
|
gui = OnionShareGui(
|
||||||
|
common,
|
||||||
|
testonion,
|
||||||
|
qtapp,
|
||||||
|
app,
|
||||||
|
["/tmp/test.txt", "/tmp/testdir"],
|
||||||
|
"/tmp/settings.json",
|
||||||
|
False,
|
||||||
|
)
|
||||||
return gui
|
return gui
|
||||||
|
|
||||||
def history_indicator(self, mode, public_mode):
|
def history_indicator(self, mode, public_mode):
|
||||||
'''Test that we can make sure the history is toggled off, do an action, and the indiciator works'''
|
"""Test that we can make sure the history is toggled off, do an action, and the indiciator works"""
|
||||||
# Make sure history is toggled off
|
# Make sure history is toggled off
|
||||||
if mode.history.isVisible():
|
if mode.history.isVisible():
|
||||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||||
|
@ -70,15 +79,17 @@ class TorGuiBaseTest(GuiBaseTest):
|
||||||
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
session.proxies = {}
|
session.proxies = {}
|
||||||
session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
|
session.proxies["http"] = "socks5h://{}:{}".format(socks_address, socks_port)
|
||||||
|
|
||||||
if type(mode) == ReceiveMode:
|
if type(mode) == ReceiveMode:
|
||||||
# Upload a file
|
# Upload a file
|
||||||
files = {'file[]': open('/tmp/test.txt', 'rb')}
|
files = {"file[]": open("/tmp/test.txt", "rb")}
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.password)
|
path = "http://{}/{}/upload".format(
|
||||||
|
self.gui.app.onion_host, mode.web.password
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
path = 'http://{}/upload'.format(self.gui.app.onion_host)
|
path = "http://{}/upload".format(self.gui.app.onion_host)
|
||||||
response = session.post(path, files=files)
|
response = session.post(path, files=files)
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
|
|
||||||
|
@ -87,7 +98,9 @@ class TorGuiBaseTest(GuiBaseTest):
|
||||||
if public_mode:
|
if public_mode:
|
||||||
path = "http://{}/download".format(self.gui.app.onion_host)
|
path = "http://{}/download".format(self.gui.app.onion_host)
|
||||||
else:
|
else:
|
||||||
path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.password)
|
path = "http://{}/{}/download".format(
|
||||||
|
self.gui.app.onion_host, mode.web.password
|
||||||
|
)
|
||||||
response = session.get(path)
|
response = session.get(path)
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
|
|
||||||
|
@ -100,61 +113,72 @@ class TorGuiBaseTest(GuiBaseTest):
|
||||||
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||||
|
|
||||||
def have_an_onion_service(self):
|
def have_an_onion_service(self):
|
||||||
'''Test that we have a valid Onion URL'''
|
"""Test that we have a valid Onion URL"""
|
||||||
self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion')
|
self.assertRegex(self.gui.app.onion_host, r"[a-z2-7].onion")
|
||||||
|
|
||||||
def web_page(self, mode, string, public_mode):
|
def web_page(self, mode, string, public_mode):
|
||||||
'''Test that the web page contains a string'''
|
"""Test that the web page contains a string"""
|
||||||
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
||||||
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
||||||
s = socks.socksocket()
|
s = socks.socksocket()
|
||||||
s.settimeout(60)
|
s.settimeout(60)
|
||||||
s.connect((self.gui.app.onion_host, 80))
|
s.connect((self.gui.app.onion_host, 80))
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
path = '/{}'.format(mode.server_status.web.password)
|
path = "/{}".format(mode.server_status.web.password)
|
||||||
else:
|
else:
|
||||||
path = '/'
|
path = "/"
|
||||||
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
http_request = "GET {} HTTP/1.0\r\n".format(path)
|
||||||
http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host)
|
http_request += "Host: {}\r\n".format(self.gui.app.onion_host)
|
||||||
http_request += '\r\n'
|
http_request += "\r\n"
|
||||||
s.sendall(http_request.encode('utf-8'))
|
s.sendall(http_request.encode("utf-8"))
|
||||||
with open('/tmp/webpage', 'wb') as file_to_write:
|
with open("/tmp/webpage", "wb") as file_to_write:
|
||||||
while True:
|
while True:
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
file_to_write.write(data)
|
file_to_write.write(data)
|
||||||
file_to_write.close()
|
file_to_write.close()
|
||||||
f = open('/tmp/webpage')
|
f = open("/tmp/webpage")
|
||||||
self.assertTrue(string in f.read())
|
self.assertTrue(string in f.read())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def have_copy_url_button(self, mode, public_mode):
|
def have_copy_url_button(self, mode, public_mode):
|
||||||
'''Test that the Copy URL button is shown and that the clipboard is correct'''
|
"""Test that the Copy URL button is shown and that the clipboard is correct"""
|
||||||
self.assertTrue(mode.server_status.copy_url_button.isVisible())
|
self.assertTrue(mode.server_status.copy_url_button.isVisible())
|
||||||
|
|
||||||
QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
mode.server_status.copy_url_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
clipboard = self.gui.qtapp.clipboard()
|
clipboard = self.gui.qtapp.clipboard()
|
||||||
if public_mode:
|
if public_mode:
|
||||||
self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host))
|
self.assertEqual(
|
||||||
|
clipboard.text(), "http://{}".format(self.gui.app.onion_host)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.password))
|
self.assertEqual(
|
||||||
|
clipboard.text(),
|
||||||
|
"http://{}/{}".format(
|
||||||
|
self.gui.app.onion_host, mode.server_status.web.password
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Stealth tests
|
# Stealth tests
|
||||||
def copy_have_hidserv_auth_button(self, mode):
|
def copy_have_hidserv_auth_button(self, mode):
|
||||||
'''Test that the Copy HidservAuth button is shown'''
|
"""Test that the Copy HidservAuth button is shown"""
|
||||||
self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible())
|
self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible())
|
||||||
|
|
||||||
def hidserv_auth_string(self):
|
def hidserv_auth_string(self):
|
||||||
'''Test the validity of the HidservAuth string'''
|
"""Test the validity of the HidservAuth string"""
|
||||||
self.assertRegex(self.gui.app.auth_string, r'HidServAuth {} [a-zA-Z1-9]'.format(self.gui.app.onion_host))
|
self.assertRegex(
|
||||||
|
self.gui.app.auth_string,
|
||||||
|
r"HidServAuth {} [a-zA-Z1-9]".format(self.gui.app.onion_host),
|
||||||
|
)
|
||||||
|
|
||||||
# Miscellaneous tests
|
# Miscellaneous tests
|
||||||
def tor_killed_statusbar_message_shown(self, mode):
|
def tor_killed_statusbar_message_shown(self, mode):
|
||||||
'''Test that the status bar message shows Tor was disconnected'''
|
"""Test that the status bar message shows Tor was disconnected"""
|
||||||
self.gui.app.onion.c = None
|
self.gui.app.onion.c = None
|
||||||
QtTest.QTest.qWait(1000)
|
QtTest.QTest.qWait(1000)
|
||||||
self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost'))
|
self.assertTrue(
|
||||||
|
mode.status_bar.currentMessage(), strings._("gui_tor_connection_lost")
|
||||||
|
)
|
||||||
|
|
|
@ -3,28 +3,29 @@ import requests
|
||||||
from PyQt5 import QtTest
|
from PyQt5 import QtTest
|
||||||
from .TorGuiBaseTest import TorGuiBaseTest
|
from .TorGuiBaseTest import TorGuiBaseTest
|
||||||
|
|
||||||
class TorGuiReceiveTest(TorGuiBaseTest):
|
|
||||||
|
|
||||||
|
class TorGuiReceiveTest(TorGuiBaseTest):
|
||||||
def upload_file(self, public_mode, file_to_upload, expected_file):
|
def upload_file(self, public_mode, file_to_upload, expected_file):
|
||||||
'''Test that we can upload the file'''
|
"""Test that we can upload the file"""
|
||||||
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
session.proxies = {}
|
session.proxies = {}
|
||||||
session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
|
session.proxies["http"] = "socks5h://{}:{}".format(socks_address, socks_port)
|
||||||
files = {'file[]': open(file_to_upload, 'rb')}
|
files = {"file[]": open(file_to_upload, "rb")}
|
||||||
if not public_mode:
|
if not public_mode:
|
||||||
path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.password)
|
path = "http://{}/{}/upload".format(
|
||||||
|
self.gui.app.onion_host, self.gui.receive_mode.web.password
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
path = 'http://{}/upload'.format(self.gui.app.onion_host)
|
path = "http://{}/upload".format(self.gui.app.onion_host)
|
||||||
response = session.post(path, files=files)
|
response = session.post(path, files=files)
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
self.assertTrue(os.path.isfile(expected_file))
|
self.assertTrue(os.path.isfile(expected_file))
|
||||||
|
|
||||||
|
|
||||||
# 'Grouped' tests follow from here
|
# 'Grouped' tests follow from here
|
||||||
|
|
||||||
def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown):
|
def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown):
|
||||||
'''Run a full suite of tests in Receive mode'''
|
"""Run a full suite of tests in Receive mode"""
|
||||||
self.click_mode(self.gui.receive_mode)
|
self.click_mode(self.gui.receive_mode)
|
||||||
self.history_is_not_visible(self.gui.receive_mode)
|
self.history_is_not_visible(self.gui.receive_mode)
|
||||||
self.click_toggle_history(self.gui.receive_mode)
|
self.click_toggle_history(self.gui.receive_mode)
|
||||||
|
@ -39,15 +40,19 @@ class TorGuiReceiveTest(TorGuiBaseTest):
|
||||||
self.url_description_shown(self.gui.receive_mode)
|
self.url_description_shown(self.gui.receive_mode)
|
||||||
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
self.have_copy_url_button(self.gui.receive_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.receive_mode)
|
self.server_status_indicator_says_started(self.gui.receive_mode)
|
||||||
self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode)
|
self.web_page(
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test.txt')
|
self.gui.receive_mode,
|
||||||
|
"Select the files you want to send, then click",
|
||||||
|
public_mode,
|
||||||
|
)
|
||||||
|
self.upload_file(public_mode, "/tmp/test.txt", "/tmp/OnionShare/test.txt")
|
||||||
self.history_widgets_present(self.gui.receive_mode)
|
self.history_widgets_present(self.gui.receive_mode)
|
||||||
self.counter_incremented(self.gui.receive_mode, 1)
|
self.counter_incremented(self.gui.receive_mode, 1)
|
||||||
self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test-2.txt')
|
self.upload_file(public_mode, "/tmp/test.txt", "/tmp/OnionShare/test-2.txt")
|
||||||
self.counter_incremented(self.gui.receive_mode, 2)
|
self.counter_incremented(self.gui.receive_mode, 2)
|
||||||
self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test')
|
self.upload_file(public_mode, "/tmp/testdir/test", "/tmp/OnionShare/test")
|
||||||
self.counter_incremented(self.gui.receive_mode, 3)
|
self.counter_incremented(self.gui.receive_mode, 3)
|
||||||
self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test-2')
|
self.upload_file(public_mode, "/tmp/testdir/test", "/tmp/OnionShare/test-2")
|
||||||
self.counter_incremented(self.gui.receive_mode, 4)
|
self.counter_incremented(self.gui.receive_mode, 4)
|
||||||
self.history_indicator(self.gui.receive_mode, public_mode)
|
self.history_indicator(self.gui.receive_mode, public_mode)
|
||||||
self.server_is_stopped(self.gui.receive_mode, False)
|
self.server_is_stopped(self.gui.receive_mode, False)
|
||||||
|
|
|
@ -4,48 +4,50 @@ from PyQt5 import QtTest
|
||||||
from .TorGuiBaseTest import TorGuiBaseTest
|
from .TorGuiBaseTest import TorGuiBaseTest
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
||||||
def download_share(self, public_mode):
|
def download_share(self, public_mode):
|
||||||
'''Test downloading a share'''
|
"""Test downloading a share"""
|
||||||
# Set up connecting to the onion
|
# Set up connecting to the onion
|
||||||
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
(socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port()
|
||||||
session = requests.session()
|
session = requests.session()
|
||||||
session.proxies = {}
|
session.proxies = {}
|
||||||
session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port)
|
session.proxies["http"] = "socks5h://{}:{}".format(socks_address, socks_port)
|
||||||
|
|
||||||
# Download files
|
# Download files
|
||||||
if public_mode:
|
if public_mode:
|
||||||
path = "http://{}/download".format(self.gui.app.onion_host)
|
path = "http://{}/download".format(self.gui.app.onion_host)
|
||||||
else:
|
else:
|
||||||
path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.password)
|
path = "http://{}/{}/download".format(
|
||||||
|
self.gui.app.onion_host, self.gui.share_mode.web.password
|
||||||
|
)
|
||||||
response = session.get(path, stream=True)
|
response = session.get(path, stream=True)
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open('/tmp/download.zip', 'wb') as file_to_write:
|
with open("/tmp/download.zip", "wb") as file_to_write:
|
||||||
for chunk in response.iter_content(chunk_size=128):
|
for chunk in response.iter_content(chunk_size=128):
|
||||||
file_to_write.write(chunk)
|
file_to_write.write(chunk)
|
||||||
file_to_write.close()
|
file_to_write.close()
|
||||||
zip = zipfile.ZipFile('/tmp/download.zip')
|
zip = zipfile.ZipFile("/tmp/download.zip")
|
||||||
QtTest.QTest.qWait(4000)
|
QtTest.QTest.qWait(4000)
|
||||||
self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8'))
|
self.assertEqual("onionshare", zip.read("test.txt").decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
# Persistence tests
|
# Persistence tests
|
||||||
def have_same_onion(self, onion):
|
def have_same_onion(self, onion):
|
||||||
'''Test that we have the same onion'''
|
"""Test that we have the same onion"""
|
||||||
self.assertEqual(self.gui.app.onion_host, onion)
|
self.assertEqual(self.gui.app.onion_host, onion)
|
||||||
|
|
||||||
# legacy v2 onion test
|
# legacy v2 onion test
|
||||||
def have_v2_onion(self):
|
def have_v2_onion(self):
|
||||||
'''Test that the onion is a v2 style onion'''
|
"""Test that the onion is a v2 style onion"""
|
||||||
self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion')
|
self.assertRegex(self.gui.app.onion_host, r"[a-z2-7].onion")
|
||||||
self.assertEqual(len(self.gui.app.onion_host), 22)
|
self.assertEqual(len(self.gui.app.onion_host), 22)
|
||||||
|
|
||||||
# 'Grouped' tests follow from here
|
# 'Grouped' tests follow from here
|
||||||
|
|
||||||
def run_all_share_mode_started_tests(self, public_mode):
|
def run_all_share_mode_started_tests(self, public_mode):
|
||||||
'''Tests in share mode after starting a share'''
|
"""Tests in share mode after starting a share"""
|
||||||
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
||||||
self.server_status_indicator_says_starting(self.gui.share_mode)
|
self.server_status_indicator_says_starting(self.gui.share_mode)
|
||||||
self.add_delete_buttons_hidden()
|
self.add_delete_buttons_hidden()
|
||||||
|
@ -58,10 +60,9 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
||||||
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
self.have_copy_url_button(self.gui.share_mode, public_mode)
|
||||||
self.server_status_indicator_says_started(self.gui.share_mode)
|
self.server_status_indicator_says_started(self.gui.share_mode)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_download_tests(self, public_mode, stay_open):
|
def run_all_share_mode_download_tests(self, public_mode, stay_open):
|
||||||
"""Tests in share mode after downloading a share"""
|
"""Tests in share mode after downloading a share"""
|
||||||
self.web_page(self.gui.share_mode, 'Total size', public_mode)
|
self.web_page(self.gui.share_mode, "Total size", public_mode)
|
||||||
self.download_share(public_mode)
|
self.download_share(public_mode)
|
||||||
self.history_widgets_present(self.gui.share_mode)
|
self.history_widgets_present(self.gui.share_mode)
|
||||||
self.server_is_stopped(self.gui.share_mode, stay_open)
|
self.server_is_stopped(self.gui.share_mode, stay_open)
|
||||||
|
@ -72,7 +73,6 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
||||||
self.server_is_started(self.gui.share_mode, startup_time=45000)
|
self.server_is_started(self.gui.share_mode, startup_time=45000)
|
||||||
self.history_indicator(self.gui.share_mode, public_mode)
|
self.history_indicator(self.gui.share_mode, public_mode)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
||||||
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
@ -83,7 +83,6 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest):
|
||||||
self.have_same_onion(onion)
|
self.have_same_onion(onion)
|
||||||
self.have_same_password(password)
|
self.have_same_password(password)
|
||||||
|
|
||||||
|
|
||||||
def run_all_share_mode_timer_tests(self, public_mode):
|
def run_all_share_mode_timer_tests(self, public_mode):
|
||||||
"""Auto-stop timer tests in share mode"""
|
"""Auto-stop timer tests in share mode"""
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Force tests to look for resources in the source code tree
|
# Force tests to look for resources in the source code tree
|
||||||
sys.onionshare_dev_mode = True
|
sys.onionshare_dev_mode = True
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import pytest
|
||||||
|
|
||||||
from onionshare import common, web, settings, strings
|
from onionshare import common, web, settings, strings
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addoption(
|
parser.addoption(
|
||||||
"--rungui", action="store_true", default=False, help="run GUI tests"
|
"--rungui", action="store_true", default=False, help="run GUI tests"
|
||||||
|
@ -27,7 +29,7 @@ def pytest_collection_modifyitems(config, items):
|
||||||
if "tor" in item.keywords:
|
if "tor" in item.keywords:
|
||||||
item.add_marker(skip_tor)
|
item.add_marker(skip_tor)
|
||||||
|
|
||||||
if not config.getoption('--rungui'):
|
if not config.getoption("--rungui"):
|
||||||
# --rungui given in cli: do not skip GUI tests
|
# --rungui given in cli: do not skip GUI tests
|
||||||
skip_gui = pytest.mark.skip(reason="need --rungui option to run")
|
skip_gui = pytest.mark.skip(reason="need --rungui option to run")
|
||||||
for item in items:
|
for item in items:
|
||||||
|
@ -43,8 +45,8 @@ def temp_dir_1024():
|
||||||
|
|
||||||
tmp_dir = tempfile.mkdtemp()
|
tmp_dir = tempfile.mkdtemp()
|
||||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||||
with open(tmp_file, 'wb') as f:
|
with open(tmp_file, "wb") as f:
|
||||||
f.write(b'*' * 1024)
|
f.write(b"*" * 1024)
|
||||||
return tmp_dir
|
return tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,8 +60,8 @@ def temp_dir_1024_delete():
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||||
with open(tmp_file, 'wb') as f:
|
with open(tmp_file, "wb") as f:
|
||||||
f.write(b'*' * 1024)
|
f.write(b"*" * 1024)
|
||||||
yield tmp_dir
|
yield tmp_dir
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,7 +70,7 @@ def temp_file_1024():
|
||||||
""" Create a temporary file of a particular size (1024 bytes). """
|
""" Create a temporary file of a particular size (1024 bytes). """
|
||||||
|
|
||||||
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)
|
||||||
return tmp_file.name
|
return tmp_file.name
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,18 +83,18 @@ def temp_file_1024_delete():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile() as tmp_file:
|
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||||
tmp_file.write(b'*' * 1024)
|
tmp_file.write(b"*" * 1024)
|
||||||
tmp_file.flush()
|
tmp_file.flush()
|
||||||
yield tmp_file.name
|
yield tmp_file.name
|
||||||
|
|
||||||
|
|
||||||
# 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.share_mode.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",
|
||||||
)
|
)
|
||||||
yield zw
|
yield zw
|
||||||
zw.close()
|
zw.close()
|
||||||
|
@ -100,7 +102,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.share_mode.ZipWriter(common.Common())
|
zw = web.share_mode.ZipWriter(common.Common())
|
||||||
yield zw
|
yield zw
|
||||||
|
@ -111,76 +113,77 @@ def default_zw():
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def locale_en(monkeypatch):
|
def locale_en(monkeypatch):
|
||||||
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8'))
|
monkeypatch.setattr("locale.getdefaultlocale", lambda: ("en_US", "UTF-8"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def locale_fr(monkeypatch):
|
def locale_fr(monkeypatch):
|
||||||
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8'))
|
monkeypatch.setattr("locale.getdefaultlocale", lambda: ("fr_FR", "UTF-8"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def locale_invalid(monkeypatch):
|
def locale_invalid(monkeypatch):
|
||||||
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8'))
|
monkeypatch.setattr("locale.getdefaultlocale", lambda: ("xx_XX", "UTF-8"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def locale_ru(monkeypatch):
|
def locale_ru(monkeypatch):
|
||||||
monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8'))
|
monkeypatch.setattr("locale.getdefaultlocale", lambda: ("ru_RU", "UTF-8"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def platform_darwin(monkeypatch):
|
def platform_darwin(monkeypatch):
|
||||||
monkeypatch.setattr('platform.system', lambda: 'Darwin')
|
monkeypatch.setattr("platform.system", lambda: "Darwin")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture # (scope="session")
|
@pytest.fixture # (scope="session")
|
||||||
def platform_linux(monkeypatch):
|
def platform_linux(monkeypatch):
|
||||||
monkeypatch.setattr('platform.system', lambda: 'Linux')
|
monkeypatch.setattr("platform.system", lambda: "Linux")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def platform_windows(monkeypatch):
|
def platform_windows(monkeypatch):
|
||||||
monkeypatch.setattr('platform.system', lambda: 'Windows')
|
monkeypatch.setattr("platform.system", lambda: "Windows")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sys_argv_sys_prefix(monkeypatch):
|
def sys_argv_sys_prefix(monkeypatch):
|
||||||
monkeypatch.setattr('sys.argv', [sys.prefix])
|
monkeypatch.setattr("sys.argv", [sys.prefix])
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sys_frozen(monkeypatch):
|
def sys_frozen(monkeypatch):
|
||||||
monkeypatch.setattr('sys.frozen', True, raising=False)
|
monkeypatch.setattr("sys.frozen", True, raising=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sys_meipass(monkeypatch):
|
def sys_meipass(monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr("sys._MEIPASS", os.path.expanduser("~"), raising=False)
|
||||||
'sys._MEIPASS', os.path.expanduser('~'), raising=False)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture # (scope="session")
|
@pytest.fixture # (scope="session")
|
||||||
def sys_onionshare_dev_mode(monkeypatch):
|
def sys_onionshare_dev_mode(monkeypatch):
|
||||||
monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False)
|
monkeypatch.setattr("sys.onionshare_dev_mode", True, raising=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def time_time_100(monkeypatch):
|
def time_time_100(monkeypatch):
|
||||||
monkeypatch.setattr('time.time', lambda: 100)
|
monkeypatch.setattr("time.time", lambda: 100)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def time_strftime(monkeypatch):
|
def time_strftime(monkeypatch):
|
||||||
monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00')
|
monkeypatch.setattr("time.strftime", lambda _: "Jun 06 2013 11:05:00")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def common_obj():
|
def common_obj():
|
||||||
return common.Common()
|
return common.Common()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
||||||
_common = common.Common()
|
_common = common.Common()
|
||||||
_common.version = 'DUMMY_VERSION_1.2.3'
|
_common.version = "DUMMY_VERSION_1.2.3"
|
||||||
strings.load_strings(_common)
|
strings.load_strings(_common)
|
||||||
return settings.Settings(_common)
|
return settings.Settings(_common)
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False, "public_mode": True}
|
||||||
"close_after_first_download": False,
|
|
||||||
"public_mode": True
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,11 +16,12 @@ class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(True, True)
|
self.run_all_share_mode_tests(True, True)
|
||||||
self.hit_401(True)
|
self.hit_401(True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class Local401RateLimitTest(unittest.TestCase, GuiShareTest):
|
class Local401RateLimitTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False}
|
||||||
"close_after_first_download": False
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,11 +16,12 @@ class Local401RateLimitTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, True)
|
self.run_all_share_mode_tests(False, True)
|
||||||
self.hit_401(False)
|
self.hit_401(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -5,12 +5,11 @@ from PyQt5 import QtCore, QtTest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest):
|
class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False}
|
||||||
"close_after_first_download": False
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,7 +17,7 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, True)
|
self.run_all_share_mode_tests(False, True)
|
||||||
|
@ -30,5 +29,6 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest
|
||||||
self.server_is_started(self.gui.share_mode, 0)
|
self.server_is_started(self.gui.share_mode, 0)
|
||||||
self.web_server_is_running()
|
self.web_server_is_running()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,11 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiReceiveTest import GuiReceiveTest
|
from .GuiReceiveTest import GuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class LocalReceiveModeClearAllButtonTest(unittest.TestCase, GuiReceiveTest):
|
class LocalReceiveModeClearAllButtonTest(unittest.TestCase, GuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {}
|
||||||
}
|
|
||||||
cls.gui = GuiReceiveTest.set_up(test_settings)
|
cls.gui = GuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -16,10 +16,11 @@ class LocalReceiveModeClearAllButtonTest(unittest.TestCase, GuiReceiveTest):
|
||||||
GuiReceiveTest.tear_down()
|
GuiReceiveTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_clear_all_button_tests(False)
|
self.run_all_clear_all_button_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiReceiveTest import GuiReceiveTest
|
from .GuiReceiveTest import GuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest):
|
class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": False, "autostop_timer": True}
|
||||||
"public_mode": False,
|
|
||||||
"autostop_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiReceiveTest.set_up(test_settings)
|
cls.gui = GuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +16,11 @@ class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest):
|
||||||
GuiReceiveTest.tear_down()
|
GuiReceiveTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_timer_tests(False)
|
self.run_all_receive_mode_timer_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiReceiveTest import GuiReceiveTest
|
from .GuiReceiveTest import GuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"receive_allow_receiver_shutdown": True}
|
||||||
"receive_allow_receiver_shutdown": True
|
|
||||||
}
|
|
||||||
cls.gui = GuiReceiveTest.set_up(test_settings)
|
cls.gui = GuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
||||||
GuiReceiveTest.tear_down()
|
GuiReceiveTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_unwritable_dir_tests(False)
|
self.run_all_receive_mode_unwritable_dir_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiReceiveTest import GuiReceiveTest
|
from .GuiReceiveTest import GuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": True, "receive_allow_receiver_shutdown": True}
|
||||||
"public_mode": True,
|
|
||||||
"receive_allow_receiver_shutdown": True
|
|
||||||
}
|
|
||||||
cls.gui = GuiReceiveTest.set_up(test_settings)
|
cls.gui = GuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +16,11 @@ class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
||||||
GuiReceiveTest.tear_down()
|
GuiReceiveTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_unwritable_dir_tests(True)
|
self.run_all_receive_mode_unwritable_dir_tests(True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiReceiveTest import GuiReceiveTest
|
from .GuiReceiveTest import GuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest):
|
class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": True, "receive_allow_receiver_shutdown": True}
|
||||||
"public_mode": True,
|
|
||||||
"receive_allow_receiver_shutdown": True
|
|
||||||
}
|
|
||||||
cls.gui = GuiReceiveTest.set_up(test_settings)
|
cls.gui = GuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +16,11 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest):
|
||||||
GuiReceiveTest.tear_down()
|
GuiReceiveTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_tests(True)
|
self.run_all_receive_mode_tests(True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiReceiveTest import GuiReceiveTest
|
from .GuiReceiveTest import GuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest):
|
class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"receive_allow_receiver_shutdown": True}
|
||||||
"receive_allow_receiver_shutdown": True
|
|
||||||
}
|
|
||||||
cls.gui = GuiReceiveTest.set_up(test_settings)
|
cls.gui = GuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest):
|
||||||
GuiReceiveTest.tear_down()
|
GuiReceiveTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_tests(False)
|
self.run_all_receive_mode_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -16,7 +16,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
|
||||||
SettingsGuiBaseTest.tear_down()
|
SettingsGuiBaseTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui_legacy_tor(self):
|
def test_gui_legacy_tor(self):
|
||||||
self.gui.onion = OnionStub(True, False)
|
self.gui.onion = OnionStub(True, False)
|
||||||
self.gui.reload_settings()
|
self.gui.reload_settings()
|
||||||
|
|
|
@ -16,7 +16,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
|
||||||
SettingsGuiBaseTest.tear_down()
|
SettingsGuiBaseTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui_no_tor(self):
|
def test_gui_no_tor(self):
|
||||||
self.gui.onion = OnionStub(False, False)
|
self.gui.onion = OnionStub(False, False)
|
||||||
self.gui.reload_settings()
|
self.gui.reload_settings()
|
||||||
|
|
|
@ -16,7 +16,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
|
||||||
SettingsGuiBaseTest.tear_down()
|
SettingsGuiBaseTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui_v3_tor(self):
|
def test_gui_v3_tor(self):
|
||||||
self.gui.onion = OnionStub(True, True)
|
self.gui.onion = OnionStub(True, True)
|
||||||
self.gui.reload_settings()
|
self.gui.reload_settings()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
@ -19,10 +20,11 @@ class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_autostop_autostart_mismatch_tests(False)
|
self.run_all_share_mode_autostop_autostart_mismatch_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": False, "autostart_timer": True}
|
||||||
"public_mode": False,
|
|
||||||
"autostart_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +16,11 @@ class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_autostart_timer_tests(False)
|
self.run_all_share_mode_autostart_timer_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -5,13 +5,11 @@ from PyQt5 import QtCore, QtTest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": False, "autostart_timer": True}
|
||||||
"public_mode": False,
|
|
||||||
"autostart_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -19,7 +17,7 @@ class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
@ -27,8 +25,11 @@ class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
||||||
self.set_autostart_timer(self.gui.share_mode, 2)
|
self.set_autostart_timer(self.gui.share_mode, 2)
|
||||||
QtTest.QTest.qWait(3000)
|
QtTest.QTest.qWait(3000)
|
||||||
QtCore.QTimer.singleShot(4000, self.accept_dialog)
|
QtCore.QTimer.singleShot(4000, self.accept_dialog)
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"autostart_timer": True}
|
||||||
"autostart_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,11 +16,12 @@ class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
self.cancel_the_share(self.gui.share_mode)
|
self.cancel_the_share(self.gui.share_mode)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeClearAllButtonTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeClearAllButtonTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False}
|
||||||
"close_after_first_download": False,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalShareModeClearAllButtonTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_clear_all_button_tests(False, True)
|
self.run_all_clear_all_button_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": True}
|
||||||
"public_mode": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(True, False)
|
self.run_all_share_mode_tests(True, False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False}
|
||||||
"close_after_first_download": False,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, True)
|
self.run_all_share_mode_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,11 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {}
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -16,10 +16,11 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, False)
|
self.run_all_share_mode_tests(False, False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeIndividualFileViewStayOpenTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeIndividualFileViewStayOpenTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False}
|
||||||
"close_after_first_download": False,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalShareModeIndividualFileViewStayOpenTest(unittest.TestCase, GuiShareTe
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_individual_file_tests(False, True)
|
self.run_all_share_mode_individual_file_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeIndividualFileViewTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeIndividualFileViewTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": True}
|
||||||
"close_after_first_download": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalShareModeIndividualFileViewTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_individual_file_tests(False, False)
|
self.run_all_share_mode_individual_file_tests(False, False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,11 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {}
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -16,10 +16,11 @@ class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_large_file_tests(False, True)
|
self.run_all_large_file_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
@ -20,10 +21,11 @@ class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_persistent_tests(False, True)
|
self.run_all_share_mode_persistent_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": False, "autostop_timer": True}
|
||||||
"public_mode": False,
|
|
||||||
"autostop_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +16,11 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_timer_tests(False)
|
self.run_all_share_mode_timer_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -5,13 +5,11 @@ from PyQt5 import QtCore, QtTest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": False, "autostop_timer": True}
|
||||||
"public_mode": False,
|
|
||||||
"autostop_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -19,7 +17,7 @@ class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
@ -27,8 +25,11 @@ class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest):
|
||||||
self.set_timeout(self.gui.share_mode, 2)
|
self.set_timeout(self.gui.share_mode, 2)
|
||||||
QtTest.QTest.qWait(3000)
|
QtTest.QTest.qWait(3000)
|
||||||
QtCore.QTimer.singleShot(4000, self.accept_dialog)
|
QtCore.QTimer.singleShot(4000, self.accept_dialog)
|
||||||
QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton)
|
QtTest.QTest.mouseClick(
|
||||||
|
self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton
|
||||||
|
)
|
||||||
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,11 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiShareTest import GuiShareTest
|
from .GuiShareTest import GuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest):
|
class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {}
|
||||||
}
|
|
||||||
cls.gui = GuiShareTest.set_up(test_settings)
|
cls.gui = GuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -16,10 +16,11 @@ class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest):
|
||||||
GuiShareTest.tear_down()
|
GuiShareTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_unreadable_file_tests()
|
self.run_all_share_mode_unreadable_file_tests()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiWebsiteTest import GuiWebsiteTest
|
from .GuiWebsiteTest import GuiWebsiteTest
|
||||||
|
|
||||||
|
|
||||||
class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest):
|
class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"csp_header_disabled": False}
|
||||||
"csp_header_disabled": False,
|
|
||||||
}
|
|
||||||
cls.gui = GuiWebsiteTest.set_up(test_settings)
|
cls.gui = GuiWebsiteTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest):
|
||||||
GuiWebsiteTest.tear_down()
|
GuiWebsiteTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
# self.run_all_common_setup_tests()
|
# self.run_all_common_setup_tests()
|
||||||
self.run_all_website_mode_download_tests(False)
|
self.run_all_website_mode_download_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .GuiWebsiteTest import GuiWebsiteTest
|
from .GuiWebsiteTest import GuiWebsiteTest
|
||||||
|
|
||||||
|
|
||||||
class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest):
|
class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"csp_header_disabled": True}
|
||||||
"csp_header_disabled": True
|
|
||||||
}
|
|
||||||
cls.gui = GuiWebsiteTest.set_up(test_settings)
|
cls.gui = GuiWebsiteTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +16,11 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest):
|
||||||
GuiWebsiteTest.tear_down()
|
GuiWebsiteTest.tear_down()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
# self.run_all_common_setup_tests()
|
# self.run_all_common_setup_tests()
|
||||||
self.run_all_website_mode_download_tests(False)
|
self.run_all_website_mode_download_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -8,9 +8,7 @@ from .TorGuiShareTest import TorGuiShareTest
|
||||||
class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": True}
|
||||||
"close_after_first_download": True
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -19,7 +17,7 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, False)
|
self.run_all_share_mode_tests(False, False)
|
||||||
|
@ -27,5 +25,6 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest):
|
||||||
self.server_is_stopped(self.gui.share_mode, False)
|
self.server_is_stopped(self.gui.share_mode, False)
|
||||||
self.web_server_is_stopped()
|
self.web_server_is_stopped()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiReceiveTest import TorGuiReceiveTest
|
from .TorGuiReceiveTest import TorGuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": True, "receive_allow_receiver_shutdown": True}
|
||||||
"public_mode": True,
|
|
||||||
"receive_allow_receiver_shutdown": True
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiReceiveTest.set_up(test_settings)
|
cls.gui = TorGuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -19,10 +17,11 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_tests(True, True)
|
self.run_all_receive_mode_tests(True, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiReceiveTest import TorGuiReceiveTest
|
from .TorGuiReceiveTest import TorGuiReceiveTest
|
||||||
|
|
||||||
|
|
||||||
class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"receive_allow_receiver_shutdown": True}
|
||||||
"receive_allow_receiver_shutdown": True
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiReceiveTest.set_up(test_settings)
|
cls.gui = TorGuiReceiveTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +17,11 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_receive_mode_tests(False, True)
|
self.run_all_receive_mode_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"autostart_timer": True}
|
||||||
"autostart_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,11 +17,12 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
self.cancel_the_share(self.gui.share_mode)
|
self.cancel_the_share(self.gui.share_mode)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": True}
|
||||||
"public_mode": True,
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +17,11 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(True, False)
|
self.run_all_share_mode_tests(True, False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"close_after_first_download": False}
|
||||||
"close_after_first_download": False,
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,10 +17,11 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, True)
|
self.run_all_share_mode_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,11 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {}
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -17,10 +17,11 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, False)
|
self.run_all_share_mode_tests(False, False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
@ -22,10 +23,11 @@ class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_persistent_tests(False, True)
|
self.run_all_share_mode_persistent_tests(False, True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"use_legacy_v2_onions": True, "use_stealth": True}
|
||||||
"use_legacy_v2_onions": True,
|
|
||||||
"use_stealth": True,
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -19,7 +17,7 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
@ -27,5 +25,6 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest):
|
||||||
self.hidserv_auth_string()
|
self.hidserv_auth_string()
|
||||||
self.copy_have_hidserv_auth_button(self.gui.share_mode)
|
self.copy_have_hidserv_auth_button(self.gui.share_mode)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,13 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"public_mode": False, "autostop_timer": True}
|
||||||
"public_mode": False,
|
|
||||||
"autostop_timer": True,
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -19,10 +17,11 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_timer_tests(False)
|
self.run_all_share_mode_timer_tests(False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4,16 +4,16 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {}
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_setup_tests()
|
self.run_all_share_mode_setup_tests()
|
||||||
|
|
|
@ -4,12 +4,11 @@ import unittest
|
||||||
|
|
||||||
from .TorGuiShareTest import TorGuiShareTest
|
from .TorGuiShareTest import TorGuiShareTest
|
||||||
|
|
||||||
|
|
||||||
class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest):
|
class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
test_settings = {
|
test_settings = {"use_legacy_v2_onions": True}
|
||||||
"use_legacy_v2_onions": True,
|
|
||||||
}
|
|
||||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,11 +17,12 @@ class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest):
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@pytest.mark.tor
|
@pytest.mark.tor
|
||||||
@pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest")
|
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||||
def test_gui(self):
|
def test_gui(self):
|
||||||
self.run_all_common_setup_tests()
|
self.run_all_common_setup_tests()
|
||||||
self.run_all_share_mode_tests(False, False)
|
self.run_all_share_mode_tests(False, False)
|
||||||
self.have_v2_onion()
|
self.have_v2_onion()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -20,7 +20,7 @@ import tempfile
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
class MockSubprocess():
|
class MockSubprocess:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.last_call = None
|
self.last_call = None
|
||||||
|
|
||||||
|
|
|
@ -27,14 +27,14 @@ from onionshare.common import Common
|
||||||
|
|
||||||
class MyOnion:
|
class MyOnion:
|
||||||
def __init__(self, stealth=False):
|
def __init__(self, stealth=False):
|
||||||
self.auth_string = 'TestHidServAuth'
|
self.auth_string = "TestHidServAuth"
|
||||||
self.private_key = ''
|
self.private_key = ""
|
||||||
self.stealth = stealth
|
self.stealth = stealth
|
||||||
self.scheduled_key = None
|
self.scheduled_key = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start_onion_service(self, await_publication=True, save_scheduled_key=False):
|
def start_onion_service(self, await_publication=True, save_scheduled_key=False):
|
||||||
return 'test_service_id.onion'
|
return "test_service_id.onion"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -65,18 +65,17 @@ class TestOnionShare:
|
||||||
onionshare_obj.set_stealth(False)
|
onionshare_obj.set_stealth(False)
|
||||||
onionshare_obj.start_onion_service()
|
onionshare_obj.start_onion_service()
|
||||||
assert 17600 <= onionshare_obj.port <= 17650
|
assert 17600 <= onionshare_obj.port <= 17650
|
||||||
assert onionshare_obj.onion_host == 'test_service_id.onion'
|
assert onionshare_obj.onion_host == "test_service_id.onion"
|
||||||
|
|
||||||
def test_start_onion_service_stealth(self, onionshare_obj):
|
def test_start_onion_service_stealth(self, onionshare_obj):
|
||||||
onionshare_obj.set_stealth(True)
|
onionshare_obj.set_stealth(True)
|
||||||
onionshare_obj.start_onion_service()
|
onionshare_obj.start_onion_service()
|
||||||
assert onionshare_obj.auth_string == 'TestHidServAuth'
|
assert onionshare_obj.auth_string == "TestHidServAuth"
|
||||||
|
|
||||||
def test_start_onion_service_local_only(self, onionshare_obj):
|
def test_start_onion_service_local_only(self, onionshare_obj):
|
||||||
onionshare_obj.local_only = True
|
onionshare_obj.local_only = True
|
||||||
onionshare_obj.start_onion_service()
|
onionshare_obj.start_onion_service()
|
||||||
assert onionshare_obj.onion_host == '127.0.0.1:{}'.format(
|
assert onionshare_obj.onion_host == "127.0.0.1:{}".format(onionshare_obj.port)
|
||||||
onionshare_obj.port)
|
|
||||||
|
|
||||||
def test_cleanup(self, onionshare_obj, temp_dir_1024, temp_file_1024):
|
def test_cleanup(self, onionshare_obj, temp_dir_1024, temp_file_1024):
|
||||||
onionshare_obj.cleanup_filenames = [temp_dir_1024, temp_file_1024]
|
onionshare_obj.cleanup_filenames = [temp_dir_1024, temp_file_1024]
|
||||||
|
|
|
@ -29,37 +29,40 @@ import zipfile
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
LOG_MSG_REGEX = re.compile(r"""
|
LOG_MSG_REGEX = re.compile(
|
||||||
|
r"""
|
||||||
^\[Jun\ 06\ 2013\ 11:05:00\]
|
^\[Jun\ 06\ 2013\ 11:05:00\]
|
||||||
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
|
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
|
||||||
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
|
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""",
|
||||||
PASSWORD_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$')
|
re.VERBOSE,
|
||||||
|
)
|
||||||
|
PASSWORD_REGEX = re.compile(r"^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$")
|
||||||
|
|
||||||
|
|
||||||
# TODO: Improve the Common tests to test it all as a single class
|
# TODO: Improve the Common tests to test it all as a single class
|
||||||
|
|
||||||
|
|
||||||
class TestBuildPassword:
|
class TestBuildPassword:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize(
|
||||||
|
"test_input,expected",
|
||||||
|
(
|
||||||
# VALID, two lowercase words, separated by a hyphen
|
# VALID, two lowercase words, separated by a hyphen
|
||||||
('syrup-enzyme', True),
|
("syrup-enzyme", True),
|
||||||
('caution-friday', True),
|
("caution-friday", True),
|
||||||
|
|
||||||
# VALID, two lowercase words, with one hyphenated compound word
|
# VALID, two lowercase words, with one hyphenated compound word
|
||||||
('drop-down-thimble', True),
|
("drop-down-thimble", True),
|
||||||
('unmixed-yo-yo', True),
|
("unmixed-yo-yo", True),
|
||||||
|
|
||||||
# VALID, two lowercase hyphenated compound words, separated by hyphen
|
# VALID, two lowercase hyphenated compound words, separated by hyphen
|
||||||
('yo-yo-drop-down', True),
|
("yo-yo-drop-down", True),
|
||||||
('felt-tip-t-shirt', True),
|
("felt-tip-t-shirt", True),
|
||||||
('hello-world', True),
|
("hello-world", True),
|
||||||
|
|
||||||
# INVALID
|
# INVALID
|
||||||
('Upper-Case', False),
|
("Upper-Case", False),
|
||||||
('digits-123', False),
|
("digits-123", False),
|
||||||
('too-many-hyphens-', False),
|
("too-many-hyphens-", False),
|
||||||
('symbols-!@#$%', False)
|
("symbols-!@#$%", False),
|
||||||
))
|
),
|
||||||
|
)
|
||||||
def test_build_password_regex(self, test_input, expected):
|
def test_build_password_regex(self, test_input, expected):
|
||||||
""" Test that `PASSWORD_REGEX` accounts for the following patterns
|
""" Test that `PASSWORD_REGEX` accounts for the following patterns
|
||||||
|
|
||||||
|
@ -92,79 +95,87 @@ class TestDirSize:
|
||||||
|
|
||||||
|
|
||||||
class TestEstimatedTimeRemaining:
|
class TestEstimatedTimeRemaining:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize(
|
||||||
((2, 676, 12), '8h14m16s'),
|
"test_input,expected",
|
||||||
((14, 1049, 30), '1h26m15s'),
|
(
|
||||||
((21, 450, 1), '33m42s'),
|
((2, 676, 12), "8h14m16s"),
|
||||||
((31, 1115, 80), '11m39s'),
|
((14, 1049, 30), "1h26m15s"),
|
||||||
((336, 989, 32), '2m12s'),
|
((21, 450, 1), "33m42s"),
|
||||||
((603, 949, 38), '36s'),
|
((31, 1115, 80), "11m39s"),
|
||||||
((971, 1009, 83), '1s')
|
((336, 989, 32), "2m12s"),
|
||||||
))
|
((603, 949, 38), "36s"),
|
||||||
|
((971, 1009, 83), "1s"),
|
||||||
|
),
|
||||||
|
)
|
||||||
def test_estimated_time_remaining(
|
def test_estimated_time_remaining(
|
||||||
self, common_obj, test_input, expected, time_time_100):
|
self, common_obj, test_input, expected, time_time_100
|
||||||
|
):
|
||||||
assert common_obj.estimated_time_remaining(*test_input) == expected
|
assert common_obj.estimated_time_remaining(*test_input) == expected
|
||||||
|
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize(
|
||||||
|
"test_input",
|
||||||
|
(
|
||||||
(10, 20, 100), # if `time_elapsed == 0`
|
(10, 20, 100), # if `time_elapsed == 0`
|
||||||
(0, 37, 99) # if `download_rate == 0`
|
(0, 37, 99), # if `download_rate == 0`
|
||||||
))
|
),
|
||||||
|
)
|
||||||
def test_raises_zero_division_error(self, common_obj, test_input, time_time_100):
|
def test_raises_zero_division_error(self, common_obj, test_input, time_time_100):
|
||||||
with pytest.raises(ZeroDivisionError):
|
with pytest.raises(ZeroDivisionError):
|
||||||
common_obj.estimated_time_remaining(*test_input)
|
common_obj.estimated_time_remaining(*test_input)
|
||||||
|
|
||||||
|
|
||||||
class TestFormatSeconds:
|
class TestFormatSeconds:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize(
|
||||||
(0, '0s'),
|
"test_input,expected",
|
||||||
(26, '26s'),
|
(
|
||||||
(60, '1m'),
|
(0, "0s"),
|
||||||
(947.35, '15m47s'),
|
(26, "26s"),
|
||||||
(1847, '30m47s'),
|
(60, "1m"),
|
||||||
(2193.94, '36m34s'),
|
(947.35, "15m47s"),
|
||||||
(3600, '1h'),
|
(1847, "30m47s"),
|
||||||
(13426.83, '3h43m47s'),
|
(2193.94, "36m34s"),
|
||||||
(16293, '4h31m33s'),
|
(3600, "1h"),
|
||||||
(18392.14, '5h6m32s'),
|
(13426.83, "3h43m47s"),
|
||||||
(86400, '1d'),
|
(16293, "4h31m33s"),
|
||||||
(129674, '1d12h1m14s'),
|
(18392.14, "5h6m32s"),
|
||||||
(56404.12, '15h40m4s')
|
(86400, "1d"),
|
||||||
))
|
(129674, "1d12h1m14s"),
|
||||||
|
(56404.12, "15h40m4s"),
|
||||||
|
),
|
||||||
|
)
|
||||||
def test_format_seconds(self, common_obj, test_input, expected):
|
def test_format_seconds(self, common_obj, test_input, expected):
|
||||||
assert common_obj.format_seconds(test_input) == expected
|
assert common_obj.format_seconds(test_input) == expected
|
||||||
|
|
||||||
# TODO: test negative numbers?
|
# TODO: test negative numbers?
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize("test_input", ("string", lambda: None, [], {}, set()))
|
||||||
'string', lambda: None, [], {}, set()
|
|
||||||
))
|
|
||||||
def test_invalid_input_types(self, common_obj, test_input):
|
def test_invalid_input_types(self, common_obj, test_input):
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
common_obj.format_seconds(test_input)
|
common_obj.format_seconds(test_input)
|
||||||
|
|
||||||
|
|
||||||
class TestGetAvailablePort:
|
class TestGetAvailablePort:
|
||||||
@pytest.mark.parametrize('port_min,port_max', (
|
@pytest.mark.parametrize(
|
||||||
(random.randint(1024, 1500),
|
"port_min,port_max",
|
||||||
random.randint(1800, 2048)) for _ in range(50)
|
((random.randint(1024, 1500), random.randint(1800, 2048)) for _ in range(50)),
|
||||||
))
|
)
|
||||||
def test_returns_an_open_port(self, common_obj, port_min, port_max):
|
def test_returns_an_open_port(self, common_obj, port_min, port_max):
|
||||||
""" get_available_port() should return an open port within the range """
|
""" get_available_port() should return an open port within the range """
|
||||||
|
|
||||||
port = common_obj.get_available_port(port_min, port_max)
|
port = common_obj.get_available_port(port_min, port_max)
|
||||||
assert port_min <= port <= port_max
|
assert port_min <= port <= port_max
|
||||||
with socket.socket() as tmpsock:
|
with socket.socket() as tmpsock:
|
||||||
tmpsock.bind(('127.0.0.1', port))
|
tmpsock.bind(("127.0.0.1", port))
|
||||||
|
|
||||||
|
|
||||||
class TestGetPlatform:
|
class TestGetPlatform:
|
||||||
def test_darwin(self, platform_darwin, common_obj):
|
def test_darwin(self, platform_darwin, common_obj):
|
||||||
assert common_obj.platform == 'Darwin'
|
assert common_obj.platform == "Darwin"
|
||||||
|
|
||||||
def test_linux(self, platform_linux, common_obj):
|
def test_linux(self, platform_linux, common_obj):
|
||||||
assert common_obj.platform == 'Linux'
|
assert common_obj.platform == "Linux"
|
||||||
|
|
||||||
def test_windows(self, platform_windows, common_obj):
|
def test_windows(self, platform_windows, common_obj):
|
||||||
assert common_obj.platform == 'Windows'
|
assert common_obj.platform == "Windows"
|
||||||
|
|
||||||
|
|
||||||
# TODO: double-check these tests
|
# TODO: double-check these tests
|
||||||
|
@ -173,94 +184,114 @@ class TestGetResourcePath:
|
||||||
prefix = os.path.join(
|
prefix = os.path.join(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
os.path.abspath(
|
os.path.abspath(inspect.getfile(inspect.currentframe()))
|
||||||
inspect.getfile(
|
)
|
||||||
inspect.currentframe())))), 'share')
|
),
|
||||||
assert (
|
"share",
|
||||||
common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
)
|
||||||
os.path.join(prefix, 'test_filename'))
|
assert common_obj.get_resource_path(
|
||||||
|
os.path.join(prefix, "test_filename")
|
||||||
|
) == os.path.join(prefix, "test_filename")
|
||||||
|
|
||||||
def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix):
|
def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix):
|
||||||
prefix = os.path.join(sys.prefix, 'share/onionshare')
|
prefix = os.path.join(sys.prefix, "share/onionshare")
|
||||||
assert (
|
assert common_obj.get_resource_path(
|
||||||
common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
os.path.join(prefix, "test_filename")
|
||||||
os.path.join(prefix, 'test_filename'))
|
) == os.path.join(prefix, "test_filename")
|
||||||
|
|
||||||
def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass):
|
def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass):
|
||||||
prefix = os.path.join(sys._MEIPASS, 'share')
|
prefix = os.path.join(sys._MEIPASS, "share")
|
||||||
assert (
|
assert common_obj.get_resource_path(
|
||||||
common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
os.path.join(prefix, "test_filename")
|
||||||
os.path.join(prefix, 'test_filename'))
|
) == os.path.join(prefix, "test_filename")
|
||||||
|
|
||||||
|
|
||||||
class TestGetTorPaths:
|
class TestGetTorPaths:
|
||||||
# @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ?
|
# @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ?
|
||||||
def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass):
|
def test_get_tor_paths_darwin(
|
||||||
|
self, platform_darwin, common_obj, sys_frozen, sys_meipass
|
||||||
|
):
|
||||||
base_path = os.path.dirname(
|
base_path = os.path.dirname(
|
||||||
os.path.dirname(
|
os.path.dirname(os.path.dirname(common_obj.get_resource_path("")))
|
||||||
os.path.dirname(
|
)
|
||||||
common_obj.get_resource_path(''))))
|
tor_path = os.path.join(base_path, "Resources", "Tor", "tor")
|
||||||
tor_path = os.path.join(
|
tor_geo_ip_file_path = os.path.join(base_path, "Resources", "Tor", "geoip")
|
||||||
base_path, 'Resources', 'Tor', 'tor')
|
tor_geo_ipv6_file_path = os.path.join(base_path, "Resources", "Tor", "geoip6")
|
||||||
tor_geo_ip_file_path = os.path.join(
|
obfs4proxy_file_path = os.path.join(base_path, "Resources", "Tor", "obfs4proxy")
|
||||||
base_path, 'Resources', 'Tor', 'geoip')
|
assert common_obj.get_tor_paths() == (
|
||||||
tor_geo_ipv6_file_path = os.path.join(
|
tor_path,
|
||||||
base_path, 'Resources', 'Tor', 'geoip6')
|
tor_geo_ip_file_path,
|
||||||
obfs4proxy_file_path = os.path.join(
|
tor_geo_ipv6_file_path,
|
||||||
base_path, 'Resources', 'Tor', 'obfs4proxy')
|
obfs4proxy_file_path,
|
||||||
assert (common_obj.get_tor_paths() ==
|
)
|
||||||
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
|
|
||||||
|
|
||||||
# @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ?
|
# @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ?
|
||||||
def test_get_tor_paths_linux(self, platform_linux, common_obj):
|
def test_get_tor_paths_linux(self, platform_linux, common_obj):
|
||||||
assert (common_obj.get_tor_paths() ==
|
assert common_obj.get_tor_paths() == (
|
||||||
('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy'))
|
"/usr/bin/tor",
|
||||||
|
"/usr/share/tor/geoip",
|
||||||
|
"/usr/share/tor/geoip6",
|
||||||
|
"/usr/bin/obfs4proxy",
|
||||||
|
)
|
||||||
|
|
||||||
# @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ?
|
# @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ?
|
||||||
def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen):
|
def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen):
|
||||||
base_path = os.path.join(
|
base_path = os.path.join(
|
||||||
os.path.dirname(
|
os.path.dirname(os.path.dirname(common_obj.get_resource_path(""))), "tor"
|
||||||
os.path.dirname(
|
)
|
||||||
common_obj.get_resource_path(''))), 'tor')
|
tor_path = os.path.join(os.path.join(base_path, "Tor"), "tor.exe")
|
||||||
tor_path = os.path.join(
|
|
||||||
os.path.join(base_path, 'Tor'), 'tor.exe')
|
|
||||||
obfs4proxy_file_path = os.path.join(
|
obfs4proxy_file_path = os.path.join(
|
||||||
os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
|
os.path.join(base_path, "Tor"), "obfs4proxy.exe"
|
||||||
|
)
|
||||||
tor_geo_ip_file_path = os.path.join(
|
tor_geo_ip_file_path = os.path.join(
|
||||||
os.path.join(
|
os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip"
|
||||||
os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
|
)
|
||||||
tor_geo_ipv6_file_path = os.path.join(
|
tor_geo_ipv6_file_path = os.path.join(
|
||||||
os.path.join(
|
os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip6"
|
||||||
os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
|
)
|
||||||
assert (common_obj.get_tor_paths() ==
|
assert common_obj.get_tor_paths() == (
|
||||||
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
|
tor_path,
|
||||||
|
tor_geo_ip_file_path,
|
||||||
|
tor_geo_ipv6_file_path,
|
||||||
|
obfs4proxy_file_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestHumanReadableFilesize:
|
class TestHumanReadableFilesize:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize(
|
||||||
(1024 ** 0, '1.0 B'),
|
"test_input,expected",
|
||||||
(1024 ** 1, '1.0 KiB'),
|
(
|
||||||
(1024 ** 2, '1.0 MiB'),
|
(1024 ** 0, "1.0 B"),
|
||||||
(1024 ** 3, '1.0 GiB'),
|
(1024 ** 1, "1.0 KiB"),
|
||||||
(1024 ** 4, '1.0 TiB'),
|
(1024 ** 2, "1.0 MiB"),
|
||||||
(1024 ** 5, '1.0 PiB'),
|
(1024 ** 3, "1.0 GiB"),
|
||||||
(1024 ** 6, '1.0 EiB'),
|
(1024 ** 4, "1.0 TiB"),
|
||||||
(1024 ** 7, '1.0 ZiB'),
|
(1024 ** 5, "1.0 PiB"),
|
||||||
(1024 ** 8, '1.0 YiB')
|
(1024 ** 6, "1.0 EiB"),
|
||||||
))
|
(1024 ** 7, "1.0 ZiB"),
|
||||||
|
(1024 ** 8, "1.0 YiB"),
|
||||||
|
),
|
||||||
|
)
|
||||||
def test_human_readable_filesize(self, common_obj, test_input, expected):
|
def test_human_readable_filesize(self, common_obj, test_input, expected):
|
||||||
assert common_obj.human_readable_filesize(test_input) == expected
|
assert common_obj.human_readable_filesize(test_input) == expected
|
||||||
|
|
||||||
|
|
||||||
class TestLog:
|
class TestLog:
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize(
|
||||||
('[Jun 06 2013 11:05:00]'
|
"test_input",
|
||||||
' TestModule.<function TestLog.test_output.<locals>.dummy_func'
|
(
|
||||||
' at 0xdeadbeef>'),
|
(
|
||||||
('[Jun 06 2013 11:05:00]'
|
"[Jun 06 2013 11:05:00]"
|
||||||
' TestModule.<function TestLog.test_output.<locals>.dummy_func'
|
" TestModule.<function TestLog.test_output.<locals>.dummy_func"
|
||||||
' at 0xdeadbeef>: TEST_MSG')
|
" at 0xdeadbeef>"
|
||||||
))
|
),
|
||||||
|
(
|
||||||
|
"[Jun 06 2013 11:05:00]"
|
||||||
|
" TestModule.<function TestLog.test_output.<locals>.dummy_func"
|
||||||
|
" at 0xdeadbeef>: TEST_MSG"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
def test_log_msg_regex(self, test_input):
|
def test_log_msg_regex(self, test_input):
|
||||||
assert bool(LOG_MSG_REGEX.match(test_input))
|
assert bool(LOG_MSG_REGEX.match(test_input))
|
||||||
|
|
||||||
|
@ -272,10 +303,10 @@ class TestLog:
|
||||||
|
|
||||||
# From: https://stackoverflow.com/questions/1218933
|
# From: https://stackoverflow.com/questions/1218933
|
||||||
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
|
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
|
||||||
common_obj.log('TestModule', dummy_func)
|
common_obj.log("TestModule", dummy_func)
|
||||||
common_obj.log('TestModule', dummy_func, 'TEST_MSG')
|
common_obj.log("TestModule", dummy_func, "TEST_MSG")
|
||||||
output = buf.getvalue()
|
output = buf.getvalue()
|
||||||
|
|
||||||
line_one, line_two, _ = output.split('\n')
|
line_one, line_two, _ = output.split("\n")
|
||||||
assert LOG_MSG_REGEX.match(line_one)
|
assert LOG_MSG_REGEX.match(line_one)
|
||||||
assert LOG_MSG_REGEX.match(line_two)
|
assert LOG_MSG_REGEX.match(line_two)
|
||||||
|
|
|
@ -28,86 +28,86 @@ from onionshare import common, settings, strings
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def os_path_expanduser(monkeypatch):
|
def os_path_expanduser(monkeypatch):
|
||||||
monkeypatch.setattr('os.path.expanduser', lambda path: path)
|
monkeypatch.setattr("os.path.expanduser", lambda path: path)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
||||||
_common = common.Common()
|
_common = common.Common()
|
||||||
_common.version = 'DUMMY_VERSION_1.2.3'
|
_common.version = "DUMMY_VERSION_1.2.3"
|
||||||
return settings.Settings(_common)
|
return settings.Settings(_common)
|
||||||
|
|
||||||
|
|
||||||
class TestSettings:
|
class TestSettings:
|
||||||
def test_init(self, settings_obj):
|
def test_init(self, settings_obj):
|
||||||
expected_settings = {
|
expected_settings = {
|
||||||
'version': 'DUMMY_VERSION_1.2.3',
|
"version": "DUMMY_VERSION_1.2.3",
|
||||||
'connection_type': 'bundled',
|
"connection_type": "bundled",
|
||||||
'control_port_address': '127.0.0.1',
|
"control_port_address": "127.0.0.1",
|
||||||
'control_port_port': 9051,
|
"control_port_port": 9051,
|
||||||
'socks_address': '127.0.0.1',
|
"socks_address": "127.0.0.1",
|
||||||
'socks_port': 9050,
|
"socks_port": 9050,
|
||||||
'socket_file_path': '/var/run/tor/control',
|
"socket_file_path": "/var/run/tor/control",
|
||||||
'auth_type': 'no_auth',
|
"auth_type": "no_auth",
|
||||||
'auth_password': '',
|
"auth_password": "",
|
||||||
'close_after_first_download': True,
|
"close_after_first_download": True,
|
||||||
'autostop_timer': False,
|
"autostop_timer": False,
|
||||||
'autostart_timer': False,
|
"autostart_timer": False,
|
||||||
'use_stealth': False,
|
"use_stealth": False,
|
||||||
'use_autoupdate': True,
|
"use_autoupdate": True,
|
||||||
'autoupdate_timestamp': None,
|
"autoupdate_timestamp": None,
|
||||||
'no_bridges': True,
|
"no_bridges": True,
|
||||||
'tor_bridges_use_obfs4': False,
|
"tor_bridges_use_obfs4": False,
|
||||||
'tor_bridges_use_meek_lite_azure': False,
|
"tor_bridges_use_meek_lite_azure": False,
|
||||||
'tor_bridges_use_custom_bridges': '',
|
"tor_bridges_use_custom_bridges": "",
|
||||||
'use_legacy_v2_onions': False,
|
"use_legacy_v2_onions": False,
|
||||||
'save_private_key': False,
|
"save_private_key": False,
|
||||||
'private_key': '',
|
"private_key": "",
|
||||||
'password': '',
|
"password": "",
|
||||||
'hidservauth_string': '',
|
"hidservauth_string": "",
|
||||||
'data_dir': os.path.expanduser('~/OnionShare'),
|
"data_dir": os.path.expanduser("~/OnionShare"),
|
||||||
'public_mode': False,
|
"public_mode": False,
|
||||||
'csp_header_disabled': False
|
"csp_header_disabled": False,
|
||||||
}
|
}
|
||||||
for key in settings_obj._settings:
|
for key in settings_obj._settings:
|
||||||
# Skip locale, it will not always default to the same thing
|
# Skip locale, it will not always default to the same thing
|
||||||
if key != 'locale':
|
if key != "locale":
|
||||||
assert settings_obj._settings[key] == settings_obj.default_settings[key]
|
assert settings_obj._settings[key] == settings_obj.default_settings[key]
|
||||||
assert settings_obj._settings[key] == expected_settings[key]
|
assert settings_obj._settings[key] == expected_settings[key]
|
||||||
|
|
||||||
def test_fill_in_defaults(self, settings_obj):
|
def test_fill_in_defaults(self, settings_obj):
|
||||||
del settings_obj._settings['version']
|
del settings_obj._settings["version"]
|
||||||
settings_obj.fill_in_defaults()
|
settings_obj.fill_in_defaults()
|
||||||
assert settings_obj._settings['version'] == 'DUMMY_VERSION_1.2.3'
|
assert settings_obj._settings["version"] == "DUMMY_VERSION_1.2.3"
|
||||||
|
|
||||||
def test_load(self, settings_obj):
|
def test_load(self, settings_obj):
|
||||||
custom_settings = {
|
custom_settings = {
|
||||||
'version': 'CUSTOM_VERSION',
|
"version": "CUSTOM_VERSION",
|
||||||
'socks_port': 9999,
|
"socks_port": 9999,
|
||||||
'use_stealth': True
|
"use_stealth": True,
|
||||||
}
|
}
|
||||||
tmp_file, tmp_file_path = tempfile.mkstemp()
|
tmp_file, tmp_file_path = tempfile.mkstemp()
|
||||||
with open(tmp_file, 'w') as f:
|
with open(tmp_file, "w") as f:
|
||||||
json.dump(custom_settings, f)
|
json.dump(custom_settings, f)
|
||||||
settings_obj.filename = tmp_file_path
|
settings_obj.filename = tmp_file_path
|
||||||
settings_obj.load()
|
settings_obj.load()
|
||||||
|
|
||||||
assert settings_obj._settings['version'] == 'CUSTOM_VERSION'
|
assert settings_obj._settings["version"] == "CUSTOM_VERSION"
|
||||||
assert settings_obj._settings['socks_port'] == 9999
|
assert settings_obj._settings["socks_port"] == 9999
|
||||||
assert settings_obj._settings['use_stealth'] is True
|
assert settings_obj._settings["use_stealth"] is True
|
||||||
|
|
||||||
os.remove(tmp_file_path)
|
os.remove(tmp_file_path)
|
||||||
assert os.path.exists(tmp_file_path) is False
|
assert os.path.exists(tmp_file_path) is False
|
||||||
|
|
||||||
def test_save(self, monkeypatch, settings_obj):
|
def test_save(self, monkeypatch, settings_obj):
|
||||||
monkeypatch.setattr(strings, '_', lambda _: '')
|
monkeypatch.setattr(strings, "_", lambda _: "")
|
||||||
|
|
||||||
settings_filename = 'default_settings.json'
|
settings_filename = "default_settings.json"
|
||||||
tmp_dir = tempfile.gettempdir()
|
tmp_dir = tempfile.gettempdir()
|
||||||
settings_path = os.path.join(tmp_dir, settings_filename)
|
settings_path = os.path.join(tmp_dir, settings_filename)
|
||||||
settings_obj.filename = settings_path
|
settings_obj.filename = settings_path
|
||||||
settings_obj.save()
|
settings_obj.save()
|
||||||
with open(settings_path, 'r') as f:
|
with open(settings_path, "r") as f:
|
||||||
settings = json.load(f)
|
settings = json.load(f)
|
||||||
|
|
||||||
assert settings_obj._settings == settings
|
assert settings_obj._settings == settings
|
||||||
|
@ -116,69 +116,64 @@ class TestSettings:
|
||||||
assert os.path.exists(settings_path) is False
|
assert os.path.exists(settings_path) is False
|
||||||
|
|
||||||
def test_get(self, settings_obj):
|
def test_get(self, settings_obj):
|
||||||
assert settings_obj.get('version') == 'DUMMY_VERSION_1.2.3'
|
assert settings_obj.get("version") == "DUMMY_VERSION_1.2.3"
|
||||||
assert settings_obj.get('connection_type') == 'bundled'
|
assert settings_obj.get("connection_type") == "bundled"
|
||||||
assert settings_obj.get('control_port_address') == '127.0.0.1'
|
assert settings_obj.get("control_port_address") == "127.0.0.1"
|
||||||
assert settings_obj.get('control_port_port') == 9051
|
assert settings_obj.get("control_port_port") == 9051
|
||||||
assert settings_obj.get('socks_address') == '127.0.0.1'
|
assert settings_obj.get("socks_address") == "127.0.0.1"
|
||||||
assert settings_obj.get('socks_port') == 9050
|
assert settings_obj.get("socks_port") == 9050
|
||||||
assert settings_obj.get('socket_file_path') == '/var/run/tor/control'
|
assert settings_obj.get("socket_file_path") == "/var/run/tor/control"
|
||||||
assert settings_obj.get('auth_type') == 'no_auth'
|
assert settings_obj.get("auth_type") == "no_auth"
|
||||||
assert settings_obj.get('auth_password') == ''
|
assert settings_obj.get("auth_password") == ""
|
||||||
assert settings_obj.get('close_after_first_download') is True
|
assert settings_obj.get("close_after_first_download") is True
|
||||||
assert settings_obj.get('use_stealth') is False
|
assert settings_obj.get("use_stealth") is False
|
||||||
assert settings_obj.get('use_autoupdate') is True
|
assert settings_obj.get("use_autoupdate") is True
|
||||||
assert settings_obj.get('autoupdate_timestamp') is None
|
assert settings_obj.get("autoupdate_timestamp") is None
|
||||||
assert settings_obj.get('autoupdate_timestamp') is None
|
assert settings_obj.get("autoupdate_timestamp") is None
|
||||||
assert settings_obj.get('no_bridges') is True
|
assert settings_obj.get("no_bridges") is True
|
||||||
assert settings_obj.get('tor_bridges_use_obfs4') is False
|
assert settings_obj.get("tor_bridges_use_obfs4") is False
|
||||||
assert settings_obj.get('tor_bridges_use_meek_lite_azure') is False
|
assert settings_obj.get("tor_bridges_use_meek_lite_azure") is False
|
||||||
assert settings_obj.get('tor_bridges_use_custom_bridges') == ''
|
assert settings_obj.get("tor_bridges_use_custom_bridges") == ""
|
||||||
|
|
||||||
|
|
||||||
def test_set_version(self, settings_obj):
|
def test_set_version(self, settings_obj):
|
||||||
settings_obj.set('version', 'CUSTOM_VERSION')
|
settings_obj.set("version", "CUSTOM_VERSION")
|
||||||
assert settings_obj._settings['version'] == 'CUSTOM_VERSION'
|
assert settings_obj._settings["version"] == "CUSTOM_VERSION"
|
||||||
|
|
||||||
def test_set_control_port_port(self, settings_obj):
|
def test_set_control_port_port(self, settings_obj):
|
||||||
settings_obj.set('control_port_port', 999)
|
settings_obj.set("control_port_port", 999)
|
||||||
assert settings_obj._settings['control_port_port'] == 999
|
assert settings_obj._settings["control_port_port"] == 999
|
||||||
|
|
||||||
settings_obj.set('control_port_port', 'NON_INTEGER')
|
settings_obj.set("control_port_port", "NON_INTEGER")
|
||||||
assert settings_obj._settings['control_port_port'] == 9051
|
assert settings_obj._settings["control_port_port"] == 9051
|
||||||
|
|
||||||
def test_set_socks_port(self, settings_obj):
|
def test_set_socks_port(self, settings_obj):
|
||||||
settings_obj.set('socks_port', 888)
|
settings_obj.set("socks_port", 888)
|
||||||
assert settings_obj._settings['socks_port'] == 888
|
assert settings_obj._settings["socks_port"] == 888
|
||||||
|
|
||||||
settings_obj.set('socks_port', 'NON_INTEGER')
|
settings_obj.set("socks_port", "NON_INTEGER")
|
||||||
assert settings_obj._settings['socks_port'] == 9050
|
assert settings_obj._settings["socks_port"] == 9050
|
||||||
|
|
||||||
def test_filename_darwin(
|
def test_filename_darwin(self, monkeypatch, os_path_expanduser, platform_darwin):
|
||||||
self,
|
|
||||||
monkeypatch,
|
|
||||||
os_path_expanduser,
|
|
||||||
platform_darwin):
|
|
||||||
obj = settings.Settings(common.Common())
|
obj = settings.Settings(common.Common())
|
||||||
assert (obj.filename ==
|
assert (
|
||||||
'~/Library/Application Support/OnionShare/onionshare.json')
|
obj.filename == "~/Library/Application Support/OnionShare/onionshare.json"
|
||||||
|
)
|
||||||
|
|
||||||
def test_filename_linux(
|
def test_filename_linux(self, monkeypatch, os_path_expanduser, platform_linux):
|
||||||
self,
|
|
||||||
monkeypatch,
|
|
||||||
os_path_expanduser,
|
|
||||||
platform_linux):
|
|
||||||
obj = settings.Settings(common.Common())
|
obj = settings.Settings(common.Common())
|
||||||
assert obj.filename == '~/.config/onionshare/onionshare.json'
|
assert obj.filename == "~/.config/onionshare/onionshare.json"
|
||||||
|
|
||||||
def test_filename_windows(
|
def test_filename_windows(self, monkeypatch, platform_windows):
|
||||||
self,
|
monkeypatch.setenv("APPDATA", "C:")
|
||||||
monkeypatch,
|
|
||||||
platform_windows):
|
|
||||||
monkeypatch.setenv('APPDATA', 'C:')
|
|
||||||
obj = settings.Settings(common.Common())
|
obj = settings.Settings(common.Common())
|
||||||
assert obj.filename.replace('/', '\\') == 'C:\\OnionShare\\onionshare.json'
|
assert obj.filename.replace("/", "\\") == "C:\\OnionShare\\onionshare.json"
|
||||||
|
|
||||||
def test_set_custom_bridge(self, settings_obj):
|
def test_set_custom_bridge(self, settings_obj):
|
||||||
settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E')
|
settings_obj.set(
|
||||||
assert settings_obj._settings['tor_bridges_use_custom_bridges'] == 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E'
|
"tor_bridges_use_custom_bridges",
|
||||||
|
"Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E",
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
settings_obj._settings["tor_bridges_use_custom_bridges"]
|
||||||
|
== "Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E"
|
||||||
|
)
|
||||||
|
|
|
@ -32,31 +32,34 @@ from onionshare.settings import Settings
|
||||||
# return path
|
# return path
|
||||||
# common.get_resource_path = get_resource_path
|
# common.get_resource_path = get_resource_path
|
||||||
|
|
||||||
|
|
||||||
def test_underscore_is_function():
|
def test_underscore_is_function():
|
||||||
assert callable(strings._) and isinstance(strings._, types.FunctionType)
|
assert callable(strings._) and isinstance(strings._, types.FunctionType)
|
||||||
|
|
||||||
|
|
||||||
class TestLoadStrings:
|
class TestLoadStrings:
|
||||||
def test_load_strings_defaults_to_english(
|
def test_load_strings_defaults_to_english(
|
||||||
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 """
|
||||||
common_obj.settings = Settings(common_obj)
|
common_obj.settings = Settings(common_obj)
|
||||||
strings.load_strings(common_obj)
|
strings.load_strings(common_obj)
|
||||||
assert strings._('preparing_files') == "Compressing files."
|
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 """
|
||||||
common_obj.settings = Settings(common_obj)
|
common_obj.settings = Settings(common_obj)
|
||||||
common_obj.settings.set('locale', 'fr')
|
common_obj.settings.set("locale", "fr")
|
||||||
strings.load_strings(common_obj)
|
strings.load_strings(common_obj)
|
||||||
assert strings._('preparing_files') == "Compression des fichiers."
|
assert strings._("preparing_files") == "Compression des fichiers."
|
||||||
|
|
||||||
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
|
||||||
|
):
|
||||||
""" load_strings() raises a KeyError for an invalid locale """
|
""" load_strings() raises a KeyError for an invalid locale """
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
common_obj.settings = Settings(common_obj)
|
common_obj.settings = Settings(common_obj)
|
||||||
common_obj.settings.set('locale', 'XX')
|
common_obj.settings.set("locale", "XX")
|
||||||
strings.load_strings(common_obj)
|
strings.load_strings(common_obj)
|
||||||
|
|
|
@ -37,8 +37,8 @@ from onionshare import strings
|
||||||
from onionshare.web import Web
|
from onionshare.web import Web
|
||||||
from onionshare.settings import Settings
|
from onionshare.settings import Settings
|
||||||
|
|
||||||
DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$')
|
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, mode, num_files=0):
|
def web_obj(common_obj, mode, num_files=0):
|
||||||
|
@ -53,12 +53,12 @@ def web_obj(common_obj, mode, num_files=0):
|
||||||
web.app.testing = True
|
web.app.testing = True
|
||||||
|
|
||||||
# Share mode
|
# Share mode
|
||||||
if mode == 'share':
|
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.share_mode.set_file_info(files)
|
web.share_mode.set_file_info(files)
|
||||||
# Receive mode
|
# Receive mode
|
||||||
|
@ -70,122 +70,130 @@ def web_obj(common_obj, 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, 'share', 3)
|
web = web_obj(common_obj, "share", 3)
|
||||||
assert web.mode is 'share'
|
assert web.mode is "share"
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Load / without auth
|
# Load / without auth
|
||||||
res = c.get('/')
|
res = c.get("/")
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 401
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Load / with invalid auth
|
# Load / with invalid auth
|
||||||
res = c.get('/', headers=self._make_auth_headers('invalid'))
|
res = c.get("/", headers=self._make_auth_headers("invalid"))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 401
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Load / with valid auth
|
# Load / with valid auth
|
||||||
res = c.get('/', headers=self._make_auth_headers(web.password))
|
res = c.get("/", headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
# Download
|
# Download
|
||||||
res = c.get('/download', headers=self._make_auth_headers(web.password))
|
res = c.get("/download", headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
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, 'share', 3)
|
web = web_obj(common_obj, "share", 3)
|
||||||
web.stay_open = False
|
web.stay_open = False
|
||||||
|
|
||||||
assert web.running == True
|
assert web.running == True
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Download the first time
|
# Download the first time
|
||||||
res = c.get('/download', headers=self._make_auth_headers(web.password))
|
res = c.get("/download", headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert res.mimetype == 'application/zip'
|
assert res.mimetype == "application/zip"
|
||||||
|
|
||||||
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(
|
||||||
web = web_obj(common_obj, 'share', 3)
|
self, common_obj, temp_file_1024
|
||||||
|
):
|
||||||
|
web = web_obj(common_obj, "share", 3)
|
||||||
web.stay_open = True
|
web.stay_open = True
|
||||||
|
|
||||||
assert web.running == True
|
assert web.running == True
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Download the first time
|
# Download the first time
|
||||||
res = c.get('/download', headers=self._make_auth_headers(web.password))
|
res = c.get("/download", headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert res.mimetype == 'application/zip'
|
assert res.mimetype == "application/zip"
|
||||||
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, 'receive')
|
web = web_obj(common_obj, "receive")
|
||||||
assert web.mode is 'receive'
|
assert web.mode is "receive"
|
||||||
|
|
||||||
with web.app.test_client() as c:
|
with web.app.test_client() as c:
|
||||||
# Load / without auth
|
# Load / without auth
|
||||||
res = c.get('/')
|
res = c.get("/")
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 401
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Load / with invalid auth
|
# Load / with invalid auth
|
||||||
res = c.get('/', headers=self._make_auth_headers('invalid'))
|
res = c.get("/", headers=self._make_auth_headers("invalid"))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 401
|
assert res.status_code == 401
|
||||||
|
|
||||||
# Load / with valid auth
|
# Load / with valid auth
|
||||||
res = c.get('/', headers=self._make_auth_headers(web.password))
|
res = c.get("/", headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
def test_public_mode_on(self, common_obj):
|
def test_public_mode_on(self, common_obj):
|
||||||
web = web_obj(common_obj, 'receive')
|
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:
|
||||||
# Loading / should work without auth
|
# Loading / should work without auth
|
||||||
res = c.get('/')
|
res = c.get("/")
|
||||||
data1 = res.get_data()
|
data1 = res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
def test_public_mode_off(self, common_obj):
|
def test_public_mode_off(self, common_obj):
|
||||||
web = web_obj(common_obj, 'receive')
|
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:
|
||||||
# Load / without auth
|
# Load / without auth
|
||||||
res = c.get('/')
|
res = c.get("/")
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 401
|
assert res.status_code == 401
|
||||||
|
|
||||||
# But static resources should work without auth
|
# But static resources should work without auth
|
||||||
res = c.get('{}/css/style.css'.format(web.static_url_path))
|
res = c.get("{}/css/style.css".format(web.static_url_path))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
# Load / with valid auth
|
# Load / with valid auth
|
||||||
res = c.get('/', headers=self._make_auth_headers(web.password))
|
res = c.get("/", headers=self._make_auth_headers(web.password))
|
||||||
res.get_data()
|
res.get_data()
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
def _make_auth_headers(self, password):
|
def _make_auth_headers(self, password):
|
||||||
auth = base64.b64encode(b'onionshare:'+password.encode()).decode()
|
auth = base64.b64encode(b"onionshare:" + password.encode()).decode()
|
||||||
h = Headers()
|
h = Headers()
|
||||||
h.add('Authorization', 'Basic ' + auth)
|
h.add("Authorization", "Basic " + auth)
|
||||||
return h
|
return h
|
||||||
|
|
||||||
|
|
||||||
class TestZipWriterDefault:
|
class TestZipWriterDefault:
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize(
|
||||||
'onionshare_{}.zip'.format(''.join(
|
"test_input",
|
||||||
random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6)
|
(
|
||||||
)) for _ in range(50)
|
"onionshare_{}.zip".format(
|
||||||
))
|
"".join(
|
||||||
|
random.choice("abcdefghijklmnopqrstuvwxyz234567") for _ in range(6)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for _ in range(50)
|
||||||
|
),
|
||||||
|
)
|
||||||
def test_default_zw_filename_regex(self, test_input):
|
def test_default_zw_filename_regex(self, test_input):
|
||||||
assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input))
|
assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input))
|
||||||
|
|
||||||
|
@ -200,15 +208,14 @@ class TestZipWriterDefault:
|
||||||
assert default_zw.z._allowZip64 is True
|
assert default_zw.z._allowZip64 is True
|
||||||
|
|
||||||
def test_zipfile_mode(self, default_zw):
|
def test_zipfile_mode(self, default_zw):
|
||||||
assert default_zw.z.mode == 'w'
|
assert default_zw.z.mode == "w"
|
||||||
|
|
||||||
def test_callback(self, default_zw):
|
def test_callback(self, default_zw):
|
||||||
assert default_zw.processed_size_callback(None) is None
|
assert default_zw.processed_size_callback(None) is None
|
||||||
|
|
||||||
def test_add_file(self, default_zw, temp_file_1024_delete):
|
def test_add_file(self, default_zw, temp_file_1024_delete):
|
||||||
default_zw.add_file(temp_file_1024_delete)
|
default_zw.add_file(temp_file_1024_delete)
|
||||||
zipfile_info = default_zw.z.getinfo(
|
zipfile_info = default_zw.z.getinfo(os.path.basename(temp_file_1024_delete))
|
||||||
os.path.basename(temp_file_1024_delete))
|
|
||||||
|
|
||||||
assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
|
assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
|
||||||
assert zipfile_info.file_size == 1024
|
assert zipfile_info.file_size == 1024
|
||||||
|
@ -220,12 +227,15 @@ class TestZipWriterDefault:
|
||||||
|
|
||||||
|
|
||||||
class TestZipWriterCustom:
|
class TestZipWriterCustom:
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize(
|
||||||
|
"test_input",
|
||||||
|
(
|
||||||
Common.random_string(
|
Common.random_string(
|
||||||
random.randint(2, 50),
|
random.randint(2, 50), random.choice((None, random.randint(2, 50)))
|
||||||
random.choice((None, random.randint(2, 50)))
|
)
|
||||||
) for _ in range(50)
|
for _ in range(50)
|
||||||
))
|
),
|
||||||
|
)
|
||||||
def test_random_string_regex(self, test_input):
|
def test_random_string_regex(self, test_input):
|
||||||
assert bool(RANDOM_STR_REGEX.match(test_input))
|
assert bool(RANDOM_STR_REGEX.match(test_input))
|
||||||
|
|
||||||
|
@ -233,4 +243,4 @@ class TestZipWriterCustom:
|
||||||
assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename))
|
assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename))
|
||||||
|
|
||||||
def test_custom_callback(self, custom_zw):
|
def test_custom_callback(self, custom_zw):
|
||||||
assert custom_zw.processed_size_callback(None) == 'custom_callback'
|
assert custom_zw.processed_size_callback(None) == "custom_callback"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue