Completely refactor common to make a Common class, and pass that class down into all parts of the program

This commit is contained in:
Micah Lee 2018-03-08 10:18:31 -08:00
parent 49e352d131
commit 50409167d4
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
17 changed files with 458 additions and 444 deletions

View File

@ -20,7 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, sys, time, argparse, threading import os, sys, time, argparse, threading
from . import strings, common from . import strings
from .common import Common
from .web import Web from .web import Web
from .onion import * from .onion import *
from .onionshare import OnionShare from .onionshare import OnionShare
@ -31,11 +32,13 @@ def main(cwd=None):
The main() function implements all of the logic that the command-line version of The main() function implements all of the logic that the command-line version of
onionshare uses. onionshare uses.
""" """
common = Common()
strings.load_strings(common) strings.load_strings(common)
print(strings._('version_string').format(common.get_version())) print(strings._('version_string').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.get_platform() == 'Darwin': if common.platform == 'Darwin':
if cwd: if cwd:
os.chdir(cwd) os.chdir(cwd)
@ -69,8 +72,7 @@ def main(cwd=None):
sys.exit() sys.exit()
# Debug mode? # Debug mode?
if debug: common.debug = debug
common.set_debug(debug)
# Validate filenames # Validate filenames
if not receive: if not receive:
@ -86,7 +88,7 @@ def main(cwd=None):
sys.exit() sys.exit()
# Load settings # Load settings
settings = Settings(config) settings = Settings(common, config)
settings.load() settings.load()
# In receive mode, validate downloads dir # In receive mode, validate downloads dir
@ -105,10 +107,10 @@ def main(cwd=None):
sys.exit() sys.exit()
# Create the Web object # Create the Web object
web = Web(debug, stay_open, False, receive) web = Web(common, stay_open, False, receive)
# Start the Onion object # Start the Onion object
onion = Onion() onion = Onion(common)
try: try:
onion.connect(settings=False, config=config) onion.connect(settings=False, config=config)
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
@ -119,7 +121,7 @@ def main(cwd=None):
# Start the onionshare app # Start the onionshare app
try: try:
app = OnionShare(onion, local_only, stay_open, shutdown_timeout) app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout)
app.set_stealth(stealth) app.set_stealth(stealth)
app.start_onion_service() app.start_onion_service()
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -29,212 +29,197 @@ import tempfile
import threading import threading
import time import time
debug = False class Common(object):
def log(module, func, msg=None):
""" """
If debug mode is on, log error messages to stdout The Common object is shared amongst all parts of OnionShare.
""" """
global debug def __init__(self, debug=False):
if debug: self.debug = debug
timestamp = time.strftime("%b %d %Y %X")
final_msg = "[{}] {}.{}".format(timestamp, module, func) # The platform OnionShare is running on
if msg: self.platform = platform.system()
final_msg = '{}: {}'.format(final_msg, msg) if self.platform.endswith('BSD'):
print(final_msg) self.platform = 'BSD'
# The current version of OnionShare
with open(self.get_resource_path('version.txt')) as f:
self.version = f.read().strip()
def set_debug(new_debug): def log(self, module, func, msg=None):
global debug """
debug = new_debug If debug mode is on, log error messages to stdout
"""
if self.debug:
timestamp = time.strftime("%b %d %Y %X")
final_msg = "[{}] {}.{}".format(timestamp, module, func)
if msg:
final_msg = '{}: {}'.format(final_msg, msg)
print(final_msg)
def get_platform(): def get_resource_path(self, filename):
""" """
Returns the platform OnionShare is running on. Returns the absolute path of a resource, regardless of whether OnionShare is installed
""" systemwide, and whether regardless of platform
plat = platform.system() """
if plat.endswith('BSD'): # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
plat = 'BSD' if self.platform == 'Windows':
return plat filename = filename.replace('/', '\\')
if getattr(sys, 'onionshare_dev_mode', False):
# 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')
if not os.path.exists(prefix):
# 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')
def get_resource_path(filename): elif self.platform == 'BSD' or self.platform == 'Linux':
""" # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
Returns the absolute path of a resource, regardless of whether OnionShare is installed prefix = os.path.join(sys.prefix, 'share/onionshare')
systemwide, and whether regardless of platform
"""
p = get_platform()
# On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes elif getattr(sys, 'frozen', False):
if p == 'Windows': # Check if app is "frozen"
filename = filename.replace('/', '\\') # https://pythonhosted.org/PyInstaller/#run-time-information
if self.platform == 'Darwin':
prefix = os.path.join(sys._MEIPASS, 'share')
elif self.platform == 'Windows':
prefix = os.path.join(os.path.dirname(sys.executable), 'share')
if getattr(sys, 'onionshare_dev_mode', False): return os.path.join(prefix, filename)
# 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')
if not os.path.exists(prefix):
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
elif p == 'BSD' or p == 'Linux': def get_tor_paths(self):
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode if self.platform == 'Linux':
prefix = os.path.join(sys.prefix, 'share/onionshare') tor_path = '/usr/bin/tor'
tor_geo_ip_file_path = '/usr/share/tor/geoip'
tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
obfs4proxy_file_path = '/usr/bin/obfs4proxy'
elif self.platform == 'Windows':
base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor')
tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
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_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
elif self.platform == 'Darwin':
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(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'
elif getattr(sys, 'frozen', False): return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
# Check if app is "frozen"
# https://pythonhosted.org/PyInstaller/#run-time-information
if p == 'Darwin':
prefix = os.path.join(sys._MEIPASS, 'share')
elif p == 'Windows':
prefix = os.path.join(os.path.dirname(sys.executable), 'share')
return os.path.join(prefix, filename) def build_slug(self):
"""
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:
wordlist = f.read().split()
r = random.SystemRandom()
return '-'.join(r.choice(wordlist) for _ in range(2))
def get_tor_paths(): @staticmethod
p = get_platform() def random_string(num_bytes, output_len=None):
if p == 'Linux': """
tor_path = '/usr/bin/tor' Returns a random string with a specified number of bytes.
tor_geo_ip_file_path = '/usr/share/tor/geoip' """
tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' b = os.urandom(num_bytes)
obfs4proxy_file_path = '/usr/bin/obfs4proxy' h = hashlib.sha256(b).digest()[:16]
elif p == 'Windows': s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8')
base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor') if not output_len:
tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe') return s
obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') return s[:output_len]
tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
elif p == 'Darwin':
base_path = os.path.dirname(os.path.dirname(os.path.dirname(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 p == 'BSD':
tor_path = '/usr/local/bin/tor'
tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
obfs4proxy_file_path = '/usr/local/bin/obfs4proxy'
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) @staticmethod
def human_readable_filesize(b):
"""
def get_version(): Returns filesize in a human readable format.
""" """
Returns the version of OnionShare that is running. thresh = 1024.0
""" if b < thresh:
with open(get_resource_path('version.txt')) as f: return '{:.1f} B'.format(b)
version = f.read().strip() units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
return version u = 0
def random_string(num_bytes, output_len=None):
"""
Returns a random string with a specified number of bytes.
"""
b = os.urandom(num_bytes)
h = hashlib.sha256(b).digest()[:16]
s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8')
if not output_len:
return s
return s[:output_len]
def build_slug():
"""
Returns a random string made from two words from the wordlist, such as "deter-trig".
"""
with open(get_resource_path('wordlist.txt')) as f:
wordlist = f.read().split()
r = random.SystemRandom()
return '-'.join(r.choice(wordlist) for _ in range(2))
def human_readable_filesize(b):
"""
Returns filesize in a human readable format.
"""
thresh = 1024.0
if b < thresh:
return '{:.1f} B'.format(b)
units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
u = 0
b /= thresh
while b >= thresh:
b /= thresh b /= thresh
u += 1 while b >= thresh:
return '{:.1f} {}'.format(b, units[u]) b /= thresh
u += 1
return '{:.1f} {}'.format(b, units[u])
@staticmethod
def format_seconds(seconds):
"""Return a human-readable string of the format 1d2h3m4s"""
days, seconds = divmod(seconds, 86400)
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)
def format_seconds(seconds): human_readable = []
"""Return a human-readable string of the format 1d2h3m4s""" if days:
days, seconds = divmod(seconds, 86400) human_readable.append("{:.0f}d".format(days))
hours, seconds = divmod(seconds, 3600) if hours:
minutes, seconds = divmod(seconds, 60) human_readable.append("{:.0f}h".format(hours))
if minutes:
human_readable.append("{:.0f}m".format(minutes))
if seconds or not human_readable:
human_readable.append("{:.0f}s".format(seconds))
return ''.join(human_readable)
human_readable = [] @staticmethod
if days: def estimated_time_remaining(bytes_downloaded, total_bytes, started):
human_readable.append("{:.0f}d".format(days)) now = time.time()
if hours: time_elapsed = now - started # in seconds
human_readable.append("{:.0f}h".format(hours)) download_rate = bytes_downloaded / time_elapsed
if minutes: remaining_bytes = total_bytes - bytes_downloaded
human_readable.append("{:.0f}m".format(minutes)) eta = remaining_bytes / download_rate
if seconds or not human_readable: return format_seconds(eta)
human_readable.append("{:.0f}s".format(seconds))
return ''.join(human_readable)
@staticmethod
def get_available_port(min_port, max_port):
"""
Find a random available port within the given range.
"""
with socket.socket() as tmpsock:
while True:
try:
tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
break
except OSError as e:
raise OSError(e)
_, port = tmpsock.getsockname()
return port
def estimated_time_remaining(bytes_downloaded, total_bytes, started): @staticmethod
now = time.time() def dir_size(start_path):
time_elapsed = now - started # in seconds """
download_rate = bytes_downloaded / time_elapsed Calculates the total size, in bytes, of all of the files in a directory.
remaining_bytes = total_bytes - bytes_downloaded """
eta = remaining_bytes / download_rate total_size = 0
return format_seconds(eta) for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
def get_available_port(min_port, max_port): if not os.path.islink(fp):
""" total_size += os.path.getsize(fp)
Find a random available port within the given range. return total_size
"""
with socket.socket() as tmpsock:
while True:
try:
tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port)))
break
except OSError as e:
raise OSError(e)
_, port = tmpsock.getsockname()
return port
def dir_size(start_path):
"""
Calculates the total size, in bytes, of all of the files in a directory.
"""
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
total_size += os.path.getsize(fp)
return total_size
class ShutdownTimer(threading.Thread): class ShutdownTimer(threading.Thread):
""" """
Background thread sleeps t hours and returns. Background thread sleeps t hours and returns.
""" """
def __init__(self, time): def __init__(self, common, time):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.common = common
self.setDaemon(True) self.setDaemon(True)
self.time = time self.time = time
def run(self): def run(self):
log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) self.common.log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time))
time.sleep(self.time) time.sleep(self.time)
return 1 return 1

View File

@ -125,22 +125,22 @@ 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): def __init__(self, common):
common.log('Onion', '__init__') self.common = common
self.common.log('Onion', '__init__')
self.stealth = False self.stealth = False
self.service_id = None self.service_id = None
self.system = common.get_platform()
# Is bundled tor supported? # Is bundled tor supported?
if (self.system == 'Windows' or self.system == '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) = 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
@ -149,13 +149,13 @@ class Onion(object):
self.connected_to_tor = False self.connected_to_tor = False
def connect(self, settings=False, config=False, tor_status_update_func=None): def connect(self, settings=False, config=False, tor_status_update_func=None):
common.log('Onion', 'connect') self.common.log('Onion', 'connect')
# Either use settings that are passed in, or load them from disk # Either use settings that are passed in, or load them from disk
if settings: if settings:
self.settings = settings self.settings = settings
else: else:
self.settings = Settings(config) self.settings = Settings(self.common, config)
self.settings.load() self.settings.load()
# The Tor controller # The Tor controller
@ -168,29 +168,29 @@ class Onion(object):
# Create a torrc for this session # Create a torrc for this session
self.tor_data_directory = tempfile.TemporaryDirectory() self.tor_data_directory = tempfile.TemporaryDirectory()
if self.system == 'Windows': if self.common.platform == 'Windows':
# Windows needs to use network ports, doesn't support unix sockets # Windows needs to use network ports, doesn't support unix sockets
torrc_template = open(common.get_resource_path('torrc_template-windows')).read() torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read()
try: try:
self.tor_control_port = 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
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 = 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')
else: else:
# Linux, Mac and BSD can use unix sockets # Linux, Mac and BSD can use unix sockets
with open(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_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')
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 = 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')
@ -208,17 +208,17 @@ class Onion(object):
# 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('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
with open(common.get_resource_path('torrc_template-obfs4')) as o: 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_amazon'): elif self.settings.get('tor_bridges_use_meek_lite_amazon'):
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
with open(common.get_resource_path('torrc_template-meek_lite_amazon')) as o: with open(self.common.get_resource_path('torrc_template-meek_lite_amazon')) 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('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
with open(common.get_resource_path('torrc_template-meek_lite_azure')) as o: 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)
@ -232,7 +232,7 @@ class Onion(object):
# Execute a tor subprocess # Execute a tor subprocess
start_ts = time.time() start_ts = time.time()
if self.system == '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
@ -245,7 +245,7 @@ class Onion(object):
# Connect to the controller # Connect to the controller
try: try:
if self.system == 'Windows': if self.common.platform == 'Windows':
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:
@ -270,7 +270,7 @@ class Onion(object):
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
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
@ -322,7 +322,7 @@ class Onion(object):
socket_file_path = '' socket_file_path = ''
if not found_tor: if not found_tor:
try: try:
if self.system == '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)
@ -334,11 +334,11 @@ 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.system == 'Linux' or self.system == '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(os.geteuid())
elif self.system == 'Darwin': elif self.common.platform == 'Darwin':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
elif self.system == 'Windows': 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'))
@ -424,7 +424,7 @@ class Onion(object):
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.
""" """
common.log('Onion', 'start_onion_service') self.common.log('Onion', 'start_onion_service')
self.auth_string = None self.auth_string = None
if not self.supports_ephemeral: if not self.supports_ephemeral:
@ -447,11 +447,11 @@ class Onion(object):
if self.settings.get('private_key'): if self.settings.get('private_key'):
key_type = "RSA1024" key_type = "RSA1024"
key_content = self.settings.get('private_key') key_content = self.settings.get('private_key')
common.log('Onion', 'Starting a hidden service with a saved private key') self.common.log('Onion', 'Starting a hidden service with a saved private key')
else: else:
key_type = "NEW" key_type = "NEW"
key_content = "RSA1024" key_content = "RSA1024"
common.log('Onion', 'Starting a hidden service with a new private key') self.common.log('Onion', 'Starting a hidden service with a new private key')
try: try:
if basic_auth != None: if basic_auth != None:
@ -498,17 +498,17 @@ class Onion(object):
""" """
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.
""" """
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:
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:
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
@ -545,7 +545,7 @@ class Onion(object):
""" """
Returns a (address, port) tuple for the Tor SOCKS port Returns a (address, port) tuple for the Tor SOCKS port
""" """
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)

View File

@ -28,8 +28,10 @@ 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, onion, local_only=False, stay_open=False, shutdown_timeout=0): def __init__(self, common, onion, local_only=False, stay_open=False, shutdown_timeout=0):
common.log('OnionShare', '__init__') self.common = common
self.common.log('OnionShare', '__init__')
# The Onion object # The Onion object
self.onion = onion self.onion = onion
@ -53,7 +55,7 @@ class OnionShare(object):
self.shutdown_timer = None self.shutdown_timer = None
def set_stealth(self, stealth): def set_stealth(self, stealth):
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
@ -62,11 +64,11 @@ class OnionShare(object):
""" """
Start the onionshare onion service. Start the onionshare onion service.
""" """
common.log('OnionShare', 'start_onion_service') self.common.log('OnionShare', 'start_onion_service')
# Choose a random port # Choose a random port
try: try:
self.port = 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'))
@ -75,7 +77,7 @@ class OnionShare(object):
return return
if self.shutdown_timeout > 0: if self.shutdown_timeout > 0:
self.shutdown_timer = ShutdownTimer(self.shutdown_timeout) self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout)
self.onion_host = self.onion.start_onion_service(self.port) self.onion_host = self.onion.start_onion_service(self.port)
@ -86,7 +88,7 @@ class OnionShare(object):
""" """
Shut everything down and clean up temporary files, etc. Shut everything down and clean up temporary files, etc.
""" """
common.log('OnionShare', 'cleanup') self.common.log('OnionShare', 'cleanup')
# cleanup files # cleanup files
for filename in self.cleanup_filenames: for filename in self.cleanup_filenames:

View File

@ -22,7 +22,7 @@ import json
import os import os
import platform import platform
from . import strings, common from . import strings
class Settings(object): class Settings(object):
@ -32,8 +32,10 @@ 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, config=False): def __init__(self, common, config=False):
common.log('Settings', '__init__') self.common = common
self.common.log('Settings', '__init__')
# Default config # Default config
self.filename = self.build_filename() self.filename = self.build_filename()
@ -43,11 +45,11 @@ class Settings(object):
if os.path.isfile(config): if os.path.isfile(config):
self.filename = config self.filename = config
else: else:
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')
# 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': common.get_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,
@ -110,12 +112,12 @@ class Settings(object):
""" """
Load the settings from file. Load the settings from file.
""" """
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:
common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
with open(self.filename, 'r') as f: 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()
@ -126,7 +128,7 @@ class Settings(object):
""" """
Save settings to file. Save settings to file.
""" """
common.log('Settings', 'save') self.common.log('Settings', 'save')
try: try:
os.makedirs(os.path.dirname(self.filename)) os.makedirs(os.path.dirname(self.filename))

View File

@ -41,14 +41,16 @@ class Web(object):
""" """
The Web object is the OnionShare web server, powered by flask The Web object is the OnionShare web server, powered by flask
""" """
def __init__(self, debug, stay_open, gui_mode, receive_mode=False): def __init__(self, common, stay_open, gui_mode, receive_mode=False):
self.common = common
# The flask app # The flask app
self.app = Flask(__name__, self.app = Flask(__name__,
static_folder=common.get_resource_path('static'), static_folder=common.get_resource_path('static'),
template_folder=common.get_resource_path('templates')) template_folder=common.get_resource_path('templates'))
# Debug mode? # Debug mode?
if debug: if self.common.debug:
self.debug_mode() self.debug_mode()
# Stay open after the first download? # Stay open after the first download?
@ -107,7 +109,7 @@ class Web(object):
self.client_cancel = False self.client_cancel = False
# shutting down the server only works within the context of flask, so the easiest way to do it is over http # shutting down the server only works within the context of flask, so the easiest way to do it is over http
self.shutdown_slug = common.random_string(16) self.shutdown_slug = self.common.random_string(16)
# Define the ewb app routes # Define the ewb app routes
self.common_routes() self.common_routes()
@ -143,7 +145,7 @@ class Web(object):
file_info=self.file_info, file_info=self.file_info,
filename=os.path.basename(self.zip_filename), filename=os.path.basename(self.zip_filename),
filesize=self.zip_filesize, filesize=self.zip_filesize,
filesize_human=common.human_readable_filesize(self.zip_filesize))) filesize_human=self.common.human_readable_filesize(self.zip_filesize)))
return self.add_security_headers(r) return self.add_security_headers(r)
@self.app.route("/<slug_candidate>/download") @self.app.route("/<slug_candidate>/download")
@ -206,10 +208,9 @@ class Web(object):
percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100 percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
plat = common.get_platform() if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD':
if not self.gui_mode or plat == 'Linux' or plat == 'BSD':
sys.stdout.write( sys.stdout.write(
"\r{0:s}, {1:.2f}% ".format(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.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes}) self.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
@ -224,7 +225,7 @@ class Web(object):
fp.close() fp.close()
if common.get_platform() != 'Darwin': if self.common.platform != 'Darwin':
sys.stdout.write("\n") sys.stdout.write("\n")
# Download is finished # Download is finished
@ -315,17 +316,17 @@ class Web(object):
} }
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'] = 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'] = common.dir_size(filename) info['size'] = self.common.dir_size(filename)
info['size_human'] = 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['files'], key=lambda k: k['basename'])
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
# zip up the files and folders # zip up the files and folders
z = ZipWriter(processed_size_callback=processed_size_callback) z = ZipWriter(self.common, processed_size_callback=processed_size_callback)
for info in self.file_info['files']: for info in self.file_info['files']:
z.add_file(info['filename']) z.add_file(info['filename'])
for info in self.file_info['dirs']: for info in self.file_info['dirs']:
@ -353,7 +354,7 @@ class Web(object):
if persistent_slug: if persistent_slug:
self.slug = persistent_slug self.slug = persistent_slug
else: else:
self.slug = common.build_slug() self.slug = self.common.build_slug()
def debug_mode(self): def debug_mode(self):
""" """
@ -424,11 +425,13 @@ 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, zip_filename=None, processed_size_callback=None): def __init__(self, common, zip_filename=None, processed_size_callback=None):
self.common = common
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(), 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

View File

@ -22,7 +22,8 @@ import os, sys, platform, argparse
from .alert import Alert from .alert import Alert
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from onionshare import strings, common from onionshare import strings
from onionshare.common import Common
from onionshare.web import Web from onionshare.web import Web
from onionshare.onion import Onion from onionshare.onion import Onion
from onionshare.onionshare import OnionShare from onionshare.onionshare import OnionShare
@ -35,9 +36,8 @@ 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): def __init__(self, common):
system = common.get_platform() if common.platform == 'Linux' or common.platform == 'BSD':
if system == 'Linux' or system == '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)
@ -54,12 +54,14 @@ def main():
""" """
The main() function implements all of the logic that the GUI version of onionshare uses. The main() function implements all of the logic that the GUI version of onionshare uses.
""" """
common = Common()
strings.load_strings(common) strings.load_strings(common)
print(strings._('version_string').format(common.get_version())) print(strings._('version_string').format(common.version))
# Start the Qt app # Start the Qt app
global qtapp global qtapp
qtapp = Application() qtapp = Application(common)
# Parse arguments # Parse arguments
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48))
@ -84,34 +86,33 @@ def main():
debug = bool(args.debug) debug = bool(args.debug)
# Debug mode? # Debug mode?
if debug: common.debug = debug
common.set_debug(debug)
# Validation # Validation
if filenames: if filenames:
valid = True valid = True
for filename in filenames: for filename in filenames:
if not os.path.isfile(filename) and not os.path.isdir(filename): if not os.path.isfile(filename) and not os.path.isdir(filename):
Alert(strings._("not_a_file", True).format(filename)) Alert(self.common, strings._("not_a_file", True).format(filename))
valid = False valid = False
if not os.access(filename, os.R_OK): if not os.access(filename, os.R_OK):
Alert(strings._("not_a_readable_file", True).format(filename)) Alert(self.common, strings._("not_a_readable_file", True).format(filename))
valid = False valid = False
if not valid: if not valid:
sys.exit() sys.exit()
# Create the Web object # Create the Web object
web = Web(debug, stay_open, True) web = Web(common, stay_open, True)
# Start the Onion # Start the Onion
onion = Onion() onion = Onion(common)
# Start the OnionShare app # Start the OnionShare app
app = OnionShare(onion, local_only, stay_open, shutdown_timeout) app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout)
# Launch the gui # Launch the gui
web.stay_open = stay_open web.stay_open = stay_open
gui = OnionShareGui(web, onion, qtapp, app, filenames, config) gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config)
# Clean up when app quits # Clean up when app quits
def shutdown(): def shutdown():

View File

@ -19,18 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import common
class Alert(QtWidgets.QMessageBox): class Alert(QtWidgets.QMessageBox):
""" """
An alert box dialog. An alert box dialog.
""" """
def __init__(self, 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)
common.log('Alert', '__init__')
self.common = common
self.common.log('Alert', '__init__')
self.setWindowTitle("OnionShare") self.setWindowTitle("OnionShare")
self.setWindowIcon(QtGui.QIcon(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)

View File

@ -21,11 +21,13 @@ import time
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from onionshare import strings, common from onionshare import strings
class Download(object): class Download(object):
def __init__(self, download_id, total_bytes): def __init__(self, common, download_id, total_bytes):
self.common = common
self.download_id = download_id self.download_id = download_id
self.started = time.time() self.started = time.time()
self.total_bytes = total_bytes self.total_bytes = total_bytes
@ -64,7 +66,7 @@ class Download(object):
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_download_progress_complete').format( pb_fmt = strings._('gui_download_progress_complete').format(
common.format_seconds(time.time() - self.started)) self.common.format_seconds(time.time() - self.started))
else: else:
elapsed = time.time() - self.started elapsed = time.time() - self.started
if elapsed < 10: if elapsed < 10:
@ -72,10 +74,10 @@ class Download(object):
# 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_download_progress_starting').format( pb_fmt = strings._('gui_download_progress_starting').format(
common.human_readable_filesize(downloaded_bytes)) self.common.human_readable_filesize(downloaded_bytes))
else: else:
pb_fmt = strings._('gui_download_progress_eta').format( pb_fmt = strings._('gui_download_progress_eta').format(
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)
@ -85,7 +87,7 @@ class Download(object):
@property @property
def estimated_time_remaining(self): def estimated_time_remaining(self):
return common.estimated_time_remaining(self.downloaded_bytes, return self.common.estimated_time_remaining(self.downloaded_bytes,
self.total_bytes, self.total_bytes,
self.started) self.started)
@ -95,8 +97,11 @@ class Downloads(QtWidgets.QWidget):
The downloads chunk of the GUI. This lists all of the active download The downloads chunk of the GUI. This lists all of the active download
progress bars. progress bars.
""" """
def __init__(self): def __init__(self, common):
super(Downloads, self).__init__() super(Downloads, self).__init__()
self.common = common
self.downloads = {} self.downloads = {}
self.layout = QtWidgets.QVBoxLayout() self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout) self.setLayout(self.layout)
@ -108,7 +113,7 @@ class Downloads(QtWidgets.QWidget):
self.parent().show() self.parent().show()
# add it to the list # add it to the list
download = Download(download_id, total_bytes) download = Download(self.common, download_id, total_bytes)
self.downloads[download_id] = download self.downloads[download_id] = download
self.layout.insertWidget(-1, download.progress_bar) self.layout.insertWidget(-1, download.progress_bar)

View File

@ -21,21 +21,24 @@ import os
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from .alert import Alert from .alert import Alert
from onionshare import strings, common from onionshare import strings
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, 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)
self.common = common
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setAlignment(QtCore.Qt.AlignCenter) self.setAlignment(QtCore.Qt.AlignCenter)
if image: if image:
self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(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', True)) self.setText(strings._('gui_drag_and_drop', True))
self.setStyleSheet('color: #999999;') self.setStyleSheet('color: #999999;')
@ -53,9 +56,12 @@ 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, parent): def __init__(self, common, parent):
self.parent = parent self.parent = parent
super(DropCountLabel, self).__init__(parent=parent) super(DropCountLabel, self).__init__(parent=parent)
self.common = common
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setAlignment(QtCore.Qt.AlignCenter) self.setAlignment(QtCore.Qt.AlignCenter)
self.setText(strings._('gui_drag_and_drop', True)) self.setText(strings._('gui_drag_and_drop', True))
@ -74,16 +80,19 @@ class FileList(QtWidgets.QListWidget):
files_dropped = QtCore.pyqtSignal() files_dropped = QtCore.pyqtSignal()
files_updated = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal()
def __init__(self, parent=None): def __init__(self, common, parent=None):
super(FileList, self).__init__(parent) super(FileList, self).__init__(parent)
self.common = common
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setIconSize(QtCore.QSize(32, 32)) self.setIconSize(QtCore.QSize(32, 32))
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.setMinimumHeight(205) self.setMinimumHeight(205)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.drop_here_image = DropHereLabel(self, True) self.drop_here_image = DropHereLabel(self.common, self, True)
self.drop_here_text = DropHereLabel(self, False) self.drop_here_text = DropHereLabel(self.common, self, False)
self.drop_count = DropCountLabel(self) self.drop_count = DropCountLabel(self.common, self)
self.resizeEvent(None) self.resizeEvent(None)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
@ -206,7 +215,7 @@ class FileList(QtWidgets.QListWidget):
if filename not in filenames: if filename not in filenames:
if not os.access(filename, os.R_OK): if not os.access(filename, os.R_OK):
Alert(strings._("not_a_readable_file", True).format(filename)) Alert(self.common, strings._("not_a_readable_file", True).format(filename))
return return
fileinfo = QtCore.QFileInfo(filename) fileinfo = QtCore.QFileInfo(filename)
@ -215,10 +224,10 @@ class FileList(QtWidgets.QListWidget):
if os.path.isfile(filename): if os.path.isfile(filename):
size_bytes = fileinfo.size() size_bytes = fileinfo.size()
size_readable = common.human_readable_filesize(size_bytes) size_readable = self.common.human_readable_filesize(size_bytes)
else: else:
size_bytes = common.dir_size(filename) size_bytes = self.common.dir_size(filename)
size_readable = common.human_readable_filesize(size_bytes) size_readable = self.common.human_readable_filesize(size_bytes)
# Create a new item # Create a new item
item = QtWidgets.QListWidgetItem() item = QtWidgets.QListWidgetItem()
@ -245,7 +254,7 @@ 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(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)
@ -277,12 +286,15 @@ 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): def __init__(self, common):
super(FileSelection, self).__init__() super(FileSelection, self).__init__()
self.common = common
self.server_on = False self.server_on = False
# File list # File list
self.file_list = FileList() self.file_list = FileList(self.common)
self.file_list.itemSelectionChanged.connect(self.update) self.file_list.itemSelectionChanged.connect(self.update)
self.file_list.files_dropped.connect(self.update) self.file_list.files_dropped.connect(self.update)
self.file_list.files_updated.connect(self.update) self.file_list.files_updated.connect(self.update)

View File

@ -24,7 +24,7 @@ import queue
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings, common from onionshare import strings, common
from onionshare.common import ShutdownTimer from onionshare.common import Common, ShutdownTimer
from onionshare.settings import Settings from onionshare.settings import Settings
from onionshare.onion import * from onionshare.onion import *
@ -47,12 +47,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
starting_server_step3 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal()
starting_server_error = QtCore.pyqtSignal(str) starting_server_error = QtCore.pyqtSignal(str)
def __init__(self, web, onion, qtapp, app, filenames, config=False): def __init__(self, common, web, onion, qtapp, app, filenames, config=False):
super(OnionShareGui, self).__init__() super(OnionShareGui, self).__init__()
self._initSystemTray() self.common = common
self.common.log('OnionShareGui', '__init__')
common.log('OnionShareGui', '__init__') self._initSystemTray()
self.web = web self.web = web
self.onion = onion self.onion = onion
@ -60,22 +61,22 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.app = app self.app = app
self.setWindowTitle('OnionShare') self.setWindowTitle('OnionShare')
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.setMinimumWidth(430) self.setMinimumWidth(430)
# Load settings # Load settings
self.config = config self.config = config
self.settings = Settings(self.config) self.settings = Settings(self.common, self.config)
self.settings.load() self.settings.load()
# File selection # File selection
self.file_selection = FileSelection() self.file_selection = FileSelection(self.common)
if filenames: if filenames:
for filename in filenames: for filename in filenames:
self.file_selection.file_list.add_file(filename) self.file_selection.file_list.add_file(filename)
# Server status # Server status
self.server_status = ServerStatus(self.qtapp, self.app, self.web, self.file_selection, self.settings) self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection, self.settings)
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_started.connect(self.start_server) self.server_status.server_started.connect(self.start_server)
self.server_status.server_started.connect(self.update_server_status_indicator) self.server_status.server_started.connect(self.update_server_status_indicator)
@ -107,7 +108,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.filesize_warning.hide() self.filesize_warning.hide()
# Downloads # Downloads
self.downloads = Downloads() self.downloads = Downloads(self.common)
self.downloads_container = QtWidgets.QScrollArea() self.downloads_container = QtWidgets.QScrollArea()
self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidget(self.downloads)
self.downloads_container.setWidgetResizable(True) self.downloads_container.setWidgetResizable(True)
@ -147,13 +148,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.settings_button.setDefault(False) self.settings_button.setDefault(False)
self.settings_button.setFlat(True) self.settings_button.setFlat(True)
self.settings_button.setFixedWidth(40) self.settings_button.setFixedWidth(40)
self.settings_button.setIcon( QtGui.QIcon(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)
# Server status indicator on the status bar # Server status indicator on the status bar
self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png')) self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png'))
self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png')) self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png'))
self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.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()
@ -221,7 +222,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.timer.timeout.connect(self.check_for_requests) self.timer.timeout.connect(self.check_for_requests)
# Start the "Connecting to Tor" dialog, which calls onion.connect() # Start the "Connecting to Tor" dialog, which calls onion.connect()
tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) tor_con = TorConnectionDialog(self.common, self.qtapp, self.settings, self.onion)
tor_con.canceled.connect(self._tor_connection_canceled) tor_con.canceled.connect(self._tor_connection_canceled)
tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.open_settings.connect(self._tor_connection_open_settings)
tor_con.start() tor_con.start()
@ -244,7 +245,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
for index in range(self.file_selection.file_list.count()): for index in range(self.file_selection.file_list.count()):
item = self.file_selection.file_list.item(index) item = self.file_selection.file_list.item(index)
total_size_bytes += item.size_bytes total_size_bytes += item.size_bytes
total_size_readable = 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', True).format(file_count, total_size_readable)) self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable))
@ -259,7 +260,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.adjustSize() self.adjustSize()
def update_server_status_indicator(self): def update_server_status_indicator(self):
common.log('OnionShareGui', 'update_server_status_indicator') self.common.log('OnionShareGui', 'update_server_status_indicator')
# Set the status image # Set the status image
if self.server_status.status == self.server_status.STATUS_STOPPED: if self.server_status.status == self.server_status.STATUS_STOPPED:
@ -273,8 +274,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.server_status_label.setText(strings._('gui_status_indicator_started', True)) self.server_status_label.setText(strings._('gui_status_indicator_started', True))
def _initSystemTray(self): def _initSystemTray(self):
system = common.get_platform()
menu = QtWidgets.QMenu() menu = QtWidgets.QMenu()
self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True))
self.settingsAction.triggered.connect(self.open_settings) self.settingsAction.triggered.connect(self.open_settings)
@ -285,10 +284,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.systemTray = QtWidgets.QSystemTrayIcon(self) self.systemTray = QtWidgets.QSystemTrayIcon(self)
# The convention is Mac systray icons are always grayscale # The convention is Mac systray icons are always grayscale
if system == 'Darwin': if self.common.platform == 'Darwin':
self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png'))) self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png')))
else: else:
self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.systemTray.setContextMenu(menu) self.systemTray.setContextMenu(menu)
self.systemTray.show() self.systemTray.show()
@ -297,10 +296,10 @@ 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.
""" """
common.log('OnionShareGui', '_tor_connection_canceled') self.common.log('OnionShareGui', '_tor_connection_canceled')
def ask(): def ask():
a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True))
quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True))
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
@ -310,12 +309,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
if a.clickedButton() == settings_button: if a.clickedButton() == settings_button:
# Open settings # Open settings
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
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)
@ -327,7 +326,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
The TorConnectionDialog wants to open the Settings dialog The TorConnectionDialog wants to open the Settings dialog
""" """
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)
@ -336,10 +335,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
Open the SettingsDialog. Open the SettingsDialog.
""" """
common.log('OnionShareGui', 'open_settings') self.common.log('OnionShareGui', 'open_settings')
def reload_settings(): def reload_settings():
common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
self.settings.load() self.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.
# If we've reloaded settings, we probably succeeded in obtaining a new # If we've reloaded settings, we probably succeeded in obtaining a new
@ -356,7 +355,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
if not self.settings.get('shutdown_timeout'): if not self.settings.get('shutdown_timeout'):
self.server_status.shutdown_timeout_container.hide() self.server_status.shutdown_timeout_container.hide()
d = SettingsDialog(self.onion, self.qtapp, self.config) d = SettingsDialog(self.common, self.onion, self.qtapp, self.config)
d.settings_saved.connect(reload_settings) d.settings_saved.connect(reload_settings)
d.exec_() d.exec_()
@ -368,7 +367,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
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.
""" """
common.log('OnionShareGui', 'start_server') self.common.log('OnionShareGui', 'start_server')
self.set_server_active(True) self.set_server_active(True)
@ -405,8 +404,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
# 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)
common.log('OnionshareGui', 'start_server', 'Starting an onion thread') self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread')
self.t = OnionThread(function=start_onion_service, kwargs={'self': self}) self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self})
self.t.daemon = True self.t.daemon = True
self.t.start() self.t.start()
@ -414,7 +413,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
Step 2 in starting the onionshare server. Zipping up files. Step 2 in starting the onionshare server. Zipping up files.
""" """
common.log('OnionShareGui', 'start_server_step2') self.common.log('OnionShareGui', 'start_server_step2')
# add progress bar to the status bar, indicating the crunching of files. # add progress bar to the status bar, indicating the crunching of files.
self._zip_progress_bar = ZipProgressBar(0) self._zip_progress_bar = ZipProgressBar(0)
@ -451,7 +450,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
Step 3 in starting the onionshare server. This displays the large filesize Step 3 in starting the onionshare server. This displays the large filesize
warning, if applicable. warning, if applicable.
""" """
common.log('OnionShareGui', 'start_server_step3') self.common.log('OnionShareGui', 'start_server_step3')
# Remove zip progress bar # Remove zip progress bar
if self._zip_progress_bar is not None: if self._zip_progress_bar is not None:
@ -469,7 +468,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.timeout = now.secsTo(self.server_status.timeout) self.timeout = now.secsTo(self.server_status.timeout)
# Set the shutdown timeout value # Set the shutdown timeout value
if self.timeout > 0: if self.timeout > 0:
self.app.shutdown_timer = ShutdownTimer(self.timeout) self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout)
self.app.shutdown_timer.start() self.app.shutdown_timer.start()
# The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
else: else:
@ -480,11 +479,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
If there's an error when trying to start the onion service If there's an error when trying to start the onion service
""" """
common.log('OnionShareGui', 'start_server_error') self.common.log('OnionShareGui', 'start_server_error')
self.set_server_active(False) self.set_server_active(False)
Alert(error, QtWidgets.QMessageBox.Warning) Alert(self.common, error, QtWidgets.QMessageBox.Warning)
self.server_status.stop_server() self.server_status.stop_server()
if self._zip_progress_bar is not None: if self._zip_progress_bar is not None:
self.status_bar.removeWidget(self._zip_progress_bar) self.status_bar.removeWidget(self._zip_progress_bar)
@ -503,7 +502,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
Stop the onionshare server. Stop the onionshare server.
""" """
common.log('OnionShareGui', 'stop_server') self.common.log('OnionShareGui', 'stop_server')
if self.server_status.status != self.server_status.STATUS_STOPPED: if self.server_status.status != self.server_status.STATUS_STOPPED:
try: try:
@ -527,13 +526,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
Check for updates in a new thread, if enabled. Check for updates in a new thread, if enabled.
""" """
system = common.get_platform() if self.common.platform == 'Windows' or self.common.platform == 'Darwin':
if system == 'Windows' or system == 'Darwin':
if self.settings.get('use_autoupdate'): if self.settings.get('use_autoupdate'):
def update_available(update_url, installed_version, latest_version): def update_available(update_url, installed_version, latest_version):
Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version))
self.update_thread = UpdateThread(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)
self.update_thread.start() self.update_thread.start()
@ -544,7 +542,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
if os.path.isfile(filename): if os.path.isfile(filename):
total_size += os.path.getsize(filename) total_size += os.path.getsize(filename)
if os.path.isdir(filename): if os.path.isdir(filename):
total_size += common.dir_size(filename) total_size += Common.dir_size(filename)
return total_size return total_size
def check_for_requests(self): def check_for_requests(self):
@ -594,7 +592,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
elif event["type"] == self.web.REQUEST_RATE_LIMIT: elif event["type"] == self.web.REQUEST_RATE_LIMIT:
self.stop_server() self.stop_server()
Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
elif event["type"] == self.web.REQUEST_PROGRESS: elif event["type"] == self.web.REQUEST_PROGRESS:
self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
@ -655,7 +653,7 @@ 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.
""" """
common.log('OnionShareGui', 'copy_url') self.common.log('OnionShareGui', 'copy_url')
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
@ -663,7 +661,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
""" """
common.log('OnionShareGui', 'copy_hidservauth') self.common.log('OnionShareGui', 'copy_hidservauth')
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
@ -697,9 +695,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
Update the 'Downloads completed' info widget. Update the 'Downloads completed' info widget.
""" """
if count == 0: if count == 0:
self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png') self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png')
else: else:
self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png') self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png')
self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count)) self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count))
self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count))
@ -708,17 +706,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
Update the 'Downloads in progress' info widget. Update the 'Downloads in progress' info widget.
""" """
if count == 0: if count == 0:
self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png') self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png')
else: else:
self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png') self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png')
self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count))
self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
def closeEvent(self, e): def closeEvent(self, e):
common.log('OnionShareGui', 'closeEvent') self.common.log('OnionShareGui', 'closeEvent')
try: try:
if self.server_status.status != self.server_status.STATUS_STOPPED: if self.server_status.status != self.server_status.STATUS_STOPPED:
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', True)) dialog.setWindowTitle(strings._('gui_quit_title', True))
dialog.setText(strings._('gui_quit_warning', True)) dialog.setText(strings._('gui_quit_warning', True))
@ -803,9 +801,12 @@ class OnionThread(QtCore.QThread):
decided to cancel (in which case do not proceed with obtaining decided to cancel (in which case do not proceed with obtaining
the Onion address and starting the web server). the Onion address and starting the web server).
""" """
def __init__(self, function, kwargs=None): def __init__(self, common, function, kwargs=None):
super(OnionThread, self).__init__() super(OnionThread, self).__init__()
common.log('OnionThread', '__init__')
self.common = common
self.common.log('OnionThread', '__init__')
self.function = function self.function = function
if not kwargs: if not kwargs:
self.kwargs = {} self.kwargs = {}
@ -813,6 +814,6 @@ class OnionThread(QtCore.QThread):
self.kwargs = kwargs self.kwargs = kwargs
def run(self): def run(self):
common.log('OnionThread', 'run') self.common.log('OnionThread', 'run')
self.function(**self.kwargs) self.function(**self.kwargs)

View File

@ -21,7 +21,7 @@ import platform
from .alert import Alert from .alert import Alert
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings, common, settings from onionshare import strings
class ServerStatus(QtWidgets.QWidget): class ServerStatus(QtWidgets.QWidget):
""" """
@ -38,8 +38,11 @@ class ServerStatus(QtWidgets.QWidget):
STATUS_WORKING = 1 STATUS_WORKING = 1
STATUS_STARTED = 2 STATUS_STARTED = 2
def __init__(self, qtapp, app, web, file_selection, settings): def __init__(self, common, qtapp, app, web, file_selection, settings):
super(ServerStatus, self).__init__() super(ServerStatus, self).__init__()
self.common = common
self.status = self.STATUS_STOPPED self.status = self.STATUS_STOPPED
self.qtapp = qtapp self.qtapp = qtapp
@ -129,7 +132,7 @@ class ServerStatus(QtWidgets.QWidget):
if self.status == self.STATUS_STARTED: if self.status == self.STATUS_STARTED:
self.url_description.show() self.url_description.show()
info_image = common.get_resource_path('images/info.png') info_image = self.common.get_resource_path('images/info.png')
self.url_description.setText(strings._('gui_url_description', True).format(info_image)) self.url_description.setText(strings._('gui_url_description', True).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.settings.get('save_private_key'): if self.settings.get('save_private_key'):
@ -212,7 +215,7 @@ class ServerStatus(QtWidgets.QWidget):
self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
# If the timeout has actually passed already before the user hit Start, refuse to start the server. # If the timeout has actually passed already before the user hit Start, refuse to start the server.
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout:
Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning))
else: else:
self.start_server() self.start_server()
else: else:
@ -252,7 +255,7 @@ class ServerStatus(QtWidgets.QWidget):
""" """
Cancel the server. Cancel the server.
""" """
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.shutdown_timeout_reset() self.shutdown_timeout_reset()
self.update() self.update()

View File

@ -34,9 +34,12 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
settings_saved = QtCore.pyqtSignal() settings_saved = QtCore.pyqtSignal()
def __init__(self, onion, qtapp, config=False): def __init__(self, common, onion, qtapp, config=False):
super(SettingsDialog, self).__init__() super(SettingsDialog, self).__init__()
common.log('SettingsDialog', '__init__')
self.common = common
self.common.log('SettingsDialog', '__init__')
self.onion = onion self.onion = onion
self.qtapp = qtapp self.qtapp = qtapp
@ -44,7 +47,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.setModal(True) self.setModal(True)
self.setWindowTitle(strings._('gui_settings_window_title', True)) self.setWindowTitle(strings._('gui_settings_window_title', True))
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.system = platform.system() self.system = platform.system()
@ -156,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog):
# obfs4 option radio # obfs4 option radio
# if the obfs4proxy binary is missing, we can't use obfs4 transports # if the obfs4proxy binary is missing, we can't use obfs4 transports
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = 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()
if not os.path.isfile(self.obfs4proxy_file_path): if not os.path.isfile(self.obfs4proxy_file_path):
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True))
self.tor_bridges_use_obfs4_radio.setEnabled(False) self.tor_bridges_use_obfs4_radio.setEnabled(False)
@ -166,7 +169,7 @@ class SettingsDialog(QtWidgets.QDialog):
# meek_lite-amazon option radio # meek_lite-amazon option radio
# if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports # if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = 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()
if not os.path.isfile(self.obfs4proxy_file_path): if not os.path.isfile(self.obfs4proxy_file_path):
self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True))
self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False) self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False)
@ -176,7 +179,7 @@ class SettingsDialog(QtWidgets.QDialog):
# meek_lite-azure option radio # meek_lite-azure option radio
# if the obfs4proxy binary is missing, we can't use meek_lite-azure transports # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = 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()
if not os.path.isfile(self.obfs4proxy_file_path): if not os.path.isfile(self.obfs4proxy_file_path):
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True))
self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False)
@ -330,7 +333,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.save_button.clicked.connect(self.save_clicked) self.save_button.clicked.connect(self.save_clicked)
self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True))
self.cancel_button.clicked.connect(self.cancel_clicked) self.cancel_button.clicked.connect(self.cancel_clicked)
version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version())) version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version))
version_label.setStyleSheet('color: #666666') version_label.setStyleSheet('color: #666666')
self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True))
self.help_button.clicked.connect(self.help_clicked) self.help_button.clicked.connect(self.help_clicked)
@ -372,7 +375,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.cancel_button.setFocus() self.cancel_button.setFocus()
# Load settings, and fill them in # Load settings, and fill them in
self.old_settings = Settings(self.config) self.old_settings = Settings(self.common, self.config)
self.old_settings.load() self.old_settings.load()
close_after_first_download = self.old_settings.get('close_after_first_download') close_after_first_download = self.old_settings.get('close_after_first_download')
@ -470,7 +473,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Connection type bundled was toggled. If checked, hide authentication fields. Connection type bundled was toggled. If checked, hide authentication fields.
""" """
common.log('SettingsDialog', 'connection_type_bundled_toggled') self.common.log('SettingsDialog', 'connection_type_bundled_toggled')
if checked: if checked:
self.authenticate_group.hide() self.authenticate_group.hide()
self.connection_type_socks.hide() self.connection_type_socks.hide()
@ -515,7 +518,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Connection type automatic was toggled. If checked, hide authentication fields. Connection type automatic was toggled. If checked, hide authentication fields.
""" """
common.log('SettingsDialog', 'connection_type_automatic_toggled') self.common.log('SettingsDialog', 'connection_type_automatic_toggled')
if checked: if checked:
self.authenticate_group.hide() self.authenticate_group.hide()
self.connection_type_socks.hide() self.connection_type_socks.hide()
@ -526,7 +529,7 @@ class SettingsDialog(QtWidgets.QDialog):
Connection type control port was toggled. If checked, show extra fields Connection type control port was toggled. If checked, show extra fields
for Tor control address and port. If unchecked, hide those extra fields. for Tor control address and port. If unchecked, hide those extra fields.
""" """
common.log('SettingsDialog', 'connection_type_control_port_toggled') self.common.log('SettingsDialog', 'connection_type_control_port_toggled')
if checked: if checked:
self.authenticate_group.show() self.authenticate_group.show()
self.connection_type_control_port_extras.show() self.connection_type_control_port_extras.show()
@ -541,7 +544,7 @@ class SettingsDialog(QtWidgets.QDialog):
Connection type socket file was toggled. If checked, show extra fields Connection type socket file was toggled. If checked, show extra fields
for socket file. If unchecked, hide those extra fields. for socket file. If unchecked, hide those extra fields.
""" """
common.log('SettingsDialog', 'connection_type_socket_file_toggled') self.common.log('SettingsDialog', 'connection_type_socket_file_toggled')
if checked: if checked:
self.authenticate_group.show() self.authenticate_group.show()
self.connection_type_socket_file_extras.show() self.connection_type_socket_file_extras.show()
@ -554,14 +557,14 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Authentication option no authentication was toggled. Authentication option no authentication was toggled.
""" """
common.log('SettingsDialog', 'authenticate_no_auth_toggled') self.common.log('SettingsDialog', 'authenticate_no_auth_toggled')
def authenticate_password_toggled(self, checked): def authenticate_password_toggled(self, checked):
""" """
Authentication option password was toggled. If checked, show extra fields Authentication option password was toggled. If checked, show extra fields
for password auth. If unchecked, hide those extra fields. for password auth. If unchecked, hide those extra fields.
""" """
common.log('SettingsDialog', 'authenticate_password_toggled') self.common.log('SettingsDialog', 'authenticate_password_toggled')
if checked: if checked:
self.authenticate_password_extras.show() self.authenticate_password_extras.show()
else: else:
@ -572,7 +575,7 @@ class SettingsDialog(QtWidgets.QDialog):
Toggle the 'Copy HidServAuth' button Toggle the 'Copy HidServAuth' button
to copy the saved HidServAuth to clipboard. to copy the saved HidServAuth to clipboard.
""" """
common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard')
clipboard = self.qtapp.clipboard() clipboard = self.qtapp.clipboard()
clipboard.setText(self.old_settings.get('hidservauth_string')) clipboard.setText(self.old_settings.get('hidservauth_string'))
@ -581,7 +584,7 @@ class SettingsDialog(QtWidgets.QDialog):
Test Tor Settings button clicked. With the given settings, see if we can Test Tor Settings button clicked. With the given settings, see if we can
successfully connect and authenticate to Tor. successfully connect and authenticate to Tor.
""" """
common.log('SettingsDialog', 'test_tor_clicked') self.common.log('SettingsDialog', 'test_tor_clicked')
settings = self.settings_from_fields() settings = self.settings_from_fields()
try: try:
@ -600,13 +603,13 @@ class SettingsDialog(QtWidgets.QDialog):
onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
# If an exception hasn't been raised yet, the Tor settings work # If an exception hasn't been raised yet, the Tor settings work
Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
# Clean up # Clean up
onion.cleanup() onion.cleanup()
except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
Alert(e.args[0], QtWidgets.QMessageBox.Warning) Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning)
if settings.get('connection_type') == 'bundled': if settings.get('connection_type') == 'bundled':
self.tor_status.hide() self.tor_status.hide()
self._enable_buttons() self._enable_buttons()
@ -615,14 +618,14 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Check for Updates button clicked. Manually force an update check. Check for Updates button clicked. Manually force an update check.
""" """
common.log('SettingsDialog', 'check_for_updates') self.common.log('SettingsDialog', 'check_for_updates')
# Disable buttons # Disable buttons
self._disable_buttons() self._disable_buttons()
self.qtapp.processEvents() self.qtapp.processEvents()
def update_timestamp(): def update_timestamp():
# Update the last checked label # Update the last checked label
settings = Settings(self.config) settings = Settings(self.common, self.config)
settings.load() settings.load()
autoupdate_timestamp = settings.get('autoupdate_timestamp') autoupdate_timestamp = settings.get('autoupdate_timestamp')
self._update_autoupdate_timestamp(autoupdate_timestamp) self._update_autoupdate_timestamp(autoupdate_timestamp)
@ -636,22 +639,22 @@ class SettingsDialog(QtWidgets.QDialog):
# Check for updates # Check for updates
def update_available(update_url, installed_version, latest_version): def update_available(update_url, installed_version, latest_version):
Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version))
close_forced_update_thread() close_forced_update_thread()
def update_not_available(): def update_not_available():
Alert(strings._('update_not_available', True)) Alert(self.common, strings._('update_not_available', True))
close_forced_update_thread() close_forced_update_thread()
def update_error(): def update_error():
Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning)
close_forced_update_thread() close_forced_update_thread()
def update_invalid_version(): def update_invalid_version():
Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning)
close_forced_update_thread() close_forced_update_thread()
forced_update_thread = UpdateThread(self.onion, self.config, force=True) forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True)
forced_update_thread.update_available.connect(update_available) forced_update_thread.update_available.connect(update_available)
forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_not_available.connect(update_not_available)
forced_update_thread.update_error.connect(update_error) forced_update_thread.update_error.connect(update_error)
@ -662,7 +665,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Save button clicked. Save current settings to disk. Save button clicked. Save current settings to disk.
""" """
common.log('SettingsDialog', 'save_clicked') self.common.log('SettingsDialog', 'save_clicked')
settings = self.settings_from_fields() settings = self.settings_from_fields()
if settings: if settings:
@ -672,7 +675,7 @@ class SettingsDialog(QtWidgets.QDialog):
# the Onion object # the Onion object
reboot_onion = False reboot_onion = False
if self.onion.is_authenticated(): if self.onion.is_authenticated():
common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
def changed(s1, s2, keys): def changed(s1, s2, keys):
""" """
Compare the Settings objects s1 and s2 and return true if any values Compare the Settings objects s1 and s2 and return true if any values
@ -694,20 +697,20 @@ class SettingsDialog(QtWidgets.QDialog):
reboot_onion = True reboot_onion = True
else: else:
common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor')
# Tor isn't connected, so try connecting # Tor isn't connected, so try connecting
reboot_onion = True reboot_onion = True
# Do we need to reinitialize Tor? # Do we need to reinitialize Tor?
if reboot_onion: if reboot_onion:
# Reinitialize the Onion object # Reinitialize the Onion object
common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion')
self.onion.cleanup() self.onion.cleanup()
tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) tor_con = TorConnectionDialog(self.common, self.qtapp, settings, self.onion)
tor_con.start() tor_con.start()
common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor))
if self.onion.is_authenticated() and not tor_con.wasCanceled(): if self.onion.is_authenticated() and not tor_con.wasCanceled():
self.settings_saved.emit() self.settings_saved.emit()
@ -721,9 +724,9 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Cancel button clicked. Cancel button clicked.
""" """
common.log('SettingsDialog', 'cancel_clicked') self.common.log('SettingsDialog', 'cancel_clicked')
if not self.onion.is_authenticated(): if not self.onion.is_authenticated():
Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning)
sys.exit() sys.exit()
else: else:
self.close() self.close()
@ -732,7 +735,7 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Help button clicked. Help button clicked.
""" """
common.log('SettingsDialog', 'help_clicked') self.common.log('SettingsDialog', 'help_clicked')
help_site = 'https://github.com/micahflee/onionshare/wiki' help_site = 'https://github.com/micahflee/onionshare/wiki'
QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site))
@ -740,8 +743,8 @@ class SettingsDialog(QtWidgets.QDialog):
""" """
Return a Settings object that's full of values from the settings dialog. Return a Settings object that's full of values from the settings dialog.
""" """
common.log('SettingsDialog', 'settings_from_fields') self.common.log('SettingsDialog', 'settings_from_fields')
settings = Settings(self.config) settings = Settings(self.common, self.config)
settings.load() # To get the last update timestamp settings.load() # To get the last update timestamp
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
@ -846,24 +849,24 @@ class SettingsDialog(QtWidgets.QDialog):
new_bridges = ''.join(new_bridges) new_bridges = ''.join(new_bridges)
settings.set('tor_bridges_use_custom_bridges', new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges)
else: else:
Alert(strings._('gui_settings_tor_bridges_invalid', True)) Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True))
settings.set('no_bridges', True) settings.set('no_bridges', True)
return False return False
return settings return settings
def closeEvent(self, e): def closeEvent(self, e):
common.log('SettingsDialog', 'closeEvent') self.common.log('SettingsDialog', 'closeEvent')
# On close, if Tor isn't connected, then quit OnionShare altogether # On close, if Tor isn't connected, then quit OnionShare altogether
if not self.onion.is_authenticated(): if not self.onion.is_authenticated():
common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor')
# 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)
def _update_autoupdate_timestamp(self, autoupdate_timestamp): def _update_autoupdate_timestamp(self, autoupdate_timestamp):
common.log('SettingsDialog', '_update_autoupdate_timestamp') self.common.log('SettingsDialog', '_update_autoupdate_timestamp')
if autoupdate_timestamp: if autoupdate_timestamp:
dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
@ -880,7 +883,7 @@ class SettingsDialog(QtWidgets.QDialog):
self._enable_buttons() self._enable_buttons()
def _disable_buttons(self): def _disable_buttons(self):
common.log('SettingsDialog', '_disable_buttons') self.common.log('SettingsDialog', '_disable_buttons')
self.check_for_updates_button.setEnabled(False) self.check_for_updates_button.setEnabled(False)
self.connection_type_test_button.setEnabled(False) self.connection_type_test_button.setEnabled(False)
@ -888,7 +891,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.cancel_button.setEnabled(False) self.cancel_button.setEnabled(False)
def _enable_buttons(self): def _enable_buttons(self):
common.log('SettingsDialog', '_enable_buttons') self.common.log('SettingsDialog', '_enable_buttons')
# We can't check for updates if we're still not connected to Tor # We can't check for updates if we're still not connected to Tor
if not self.onion.connected_to_tor: if not self.onion.connected_to_tor:
self.check_for_updates_button.setEnabled(False) self.check_for_updates_button.setEnabled(False)

View File

@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings, common from onionshare import strings
from onionshare.onion import * from onionshare.onion import *
from .alert import Alert from .alert import Alert
@ -30,16 +30,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
""" """
open_settings = QtCore.pyqtSignal() open_settings = QtCore.pyqtSignal()
def __init__(self, qtapp, settings, onion): def __init__(self, common, qtapp, settings, onion):
super(TorConnectionDialog, self).__init__(None) super(TorConnectionDialog, self).__init__(None)
common.log('TorConnectionDialog', '__init__')
self.common = common
self.common.log('TorConnectionDialog', '__init__')
self.qtapp = qtapp self.qtapp = qtapp
self.settings = settings self.settings = settings
self.onion = onion self.onion = onion
self.setWindowTitle("OnionShare") self.setWindowTitle("OnionShare")
self.setWindowIcon(QtGui.QIcon(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)
@ -55,9 +58,9 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
self._tor_status_update(0, '') self._tor_status_update(0, '')
def start(self): def start(self):
common.log('TorConnectionDialog', 'start') self.common.log('TorConnectionDialog', 'start')
t = TorConnectionThread(self, self.settings, self.onion) t = TorConnectionThread(self.common, self, self.settings, self.onion)
t.tor_status_update.connect(self._tor_status_update) t.tor_status_update.connect(self._tor_status_update)
t.connected_to_tor.connect(self._connected_to_tor) t.connected_to_tor.connect(self._connected_to_tor)
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor) t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
@ -77,14 +80,14 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary)) self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary))
def _connected_to_tor(self): def _connected_to_tor(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):
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()
@ -92,12 +95,12 @@ 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):
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("{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning)
# Open settings # Open settings
self.open_settings.emit() self.open_settings.emit()
@ -113,16 +116,19 @@ class TorConnectionThread(QtCore.QThread):
canceled_connecting_to_tor = QtCore.pyqtSignal() canceled_connecting_to_tor = QtCore.pyqtSignal()
error_connecting_to_tor = QtCore.pyqtSignal(str) error_connecting_to_tor = QtCore.pyqtSignal(str)
def __init__(self, dialog, settings, onion): def __init__(self, common, dialog, settings, onion):
super(TorConnectionThread, self).__init__() super(TorConnectionThread, self).__init__()
common.log('TorConnectionThread', '__init__')
self.common = common
self.common.log('TorConnectionThread', '__init__')
self.dialog = dialog self.dialog = dialog
self.settings = settings self.settings = settings
self.onion = onion self.onion = onion
def run(self): def run(self):
common.log('TorConnectionThread', 'run') self.common.log('TorConnectionThread', 'run')
# Connect to the Onion # Connect to the Onion
try: try:
@ -133,11 +139,11 @@ class TorConnectionThread(QtCore.QThread):
self.canceled_connecting_to_tor.emit() self.canceled_connecting_to_tor.emit()
except BundledTorCanceled as e: except BundledTorCanceled as e:
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:
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):

View File

@ -25,7 +25,7 @@ from onionshare import socks
from onionshare.settings import Settings from onionshare.settings import Settings
from onionshare.onion import Onion from onionshare.onion import Onion
from . import strings, common from . import strings
class UpdateCheckerCheckError(Exception): class UpdateCheckerCheckError(Exception):
""" """
@ -55,16 +55,19 @@ class UpdateChecker(QtCore.QObject):
update_error = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal()
update_invalid_version = QtCore.pyqtSignal() update_invalid_version = QtCore.pyqtSignal()
def __init__(self, onion, config=False): def __init__(self, common, onion, config=False):
super(UpdateChecker, self).__init__() super(UpdateChecker, self).__init__()
common.log('UpdateChecker', '__init__')
self.common = common
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):
common.log('UpdateChecker', 'check', 'force={}'.format(force)) self.common.log('UpdateChecker', 'check', 'force={}'.format(force))
# Load the settings # Load the settings
settings = Settings(config) settings = Settings(self.common, config)
settings.load() settings.load()
# If force=True, then definitely check # If force=True, then definitely check
@ -87,11 +90,11 @@ class UpdateChecker(QtCore.QObject):
# Check for updates # Check for updates
if check_for_updates: if check_for_updates:
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(common.get_version(), platform.system()) 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
@ -104,7 +107,7 @@ class UpdateChecker(QtCore.QObject):
else: else:
onion_domain = 'elx57ue5uyfplgva.onion' onion_domain = 'elx57ue5uyfplgva.onion'
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)
@ -122,10 +125,10 @@ class UpdateChecker(QtCore.QObject):
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')
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:
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
@ -145,7 +148,7 @@ class UpdateChecker(QtCore.QObject):
# 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 = common.get_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
@ -159,17 +162,20 @@ class UpdateThread(QtCore.QThread):
update_error = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal()
update_invalid_version = QtCore.pyqtSignal() update_invalid_version = QtCore.pyqtSignal()
def __init__(self, onion, config=False, force=False): def __init__(self, common, onion, config=False, force=False):
super(UpdateThread, self).__init__() super(UpdateThread, self).__init__()
common.log('UpdateThread', '__init__')
self.common = common
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):
common.log('UpdateThread', 'run') self.common.log('UpdateThread', 'run')
u = UpdateChecker(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)
u.update_not_available.connect(self._update_not_available) u.update_not_available.connect(self._update_not_available)
u.update_error.connect(self._update_error) u.update_error.connect(self._update_error)
@ -179,25 +185,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
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):
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):
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):
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): def _update_invalid_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() self.update_invalid_version.emit()

View File

@ -157,13 +157,13 @@ class TestGetAvailablePort:
class TestGetPlatform: class TestGetPlatform:
def test_darwin(self, platform_darwin): def test_darwin(self, platform_darwin):
assert common.get_platform() == 'Darwin' assert common.platform == 'Darwin'
def test_linux(self, platform_linux): def test_linux(self, platform_linux):
assert common.get_platform() == 'Linux' assert common.platform == 'Linux'
def test_windows(self, platform_windows): def test_windows(self, platform_windows):
assert common.get_platform() == 'Windows' assert common.platform == 'Windows'
# TODO: double-check these tests # TODO: double-check these tests
@ -235,14 +235,6 @@ class TestGetTorPaths:
(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 TestGetVersion:
def test_get_version(self, sys_onionshare_dev_mode):
with open(common.get_resource_path('version.txt')) as f:
version = f.read().strip()
assert version == common.get_version()
class TestHumanReadableFilesize: class TestHumanReadableFilesize:
@pytest.mark.parametrize('test_input,expected', ( @pytest.mark.parametrize('test_input,expected', (
(1024 ** 0, '1.0 B'), (1024 ** 0, '1.0 B'),
@ -284,13 +276,3 @@ class TestLog:
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)
class TestSetDebug:
def test_debug_true(self, set_debug_false):
common.set_debug(True)
assert common.debug is True
def test_debug_false(self, set_debug_true):
common.set_debug(False)
assert common.debug is False

View File

@ -28,7 +28,7 @@ from onionshare import common, settings, strings
@pytest.fixture @pytest.fixture
def custom_version(monkeypatch): def custom_version(monkeypatch):
monkeypatch.setattr(common, 'get_version', lambda: 'DUMMY_VERSION_1.2.3') monkeypatch.setattr(common, 'version', 'DUMMY_VERSION_1.2.3')
@pytest.fixture @pytest.fixture