2014-09-02 17:30:01 -07:00
|
|
|
# -*- coding: utf-8 -*-
|
2014-09-02 12:10:42 -07:00
|
|
|
"""
|
|
|
|
OnionShare | https://onionshare.org/
|
|
|
|
|
2018-04-24 10:07:59 -07:00
|
|
|
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
2014-09-02 12:10:42 -07:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
2017-05-24 21:20:07 -06:00
|
|
|
import base64
|
|
|
|
import hashlib
|
|
|
|
import inspect
|
|
|
|
import os
|
|
|
|
import platform
|
|
|
|
import random
|
|
|
|
import socket
|
|
|
|
import sys
|
|
|
|
import tempfile
|
2017-11-08 20:25:59 +11:00
|
|
|
import threading
|
2017-05-24 21:20:07 -06:00
|
|
|
import time
|
2014-09-16 00:08:27 +00:00
|
|
|
|
2018-03-13 03:28:47 -07:00
|
|
|
from .settings import Settings
|
|
|
|
|
2018-05-19 20:51:01 -07:00
|
|
|
|
2018-03-08 10:18:31 -08:00
|
|
|
class Common(object):
|
|
|
|
"""
|
|
|
|
The Common object is shared amongst all parts of OnionShare.
|
|
|
|
"""
|
2019-10-12 21:01:25 -07:00
|
|
|
|
2019-04-18 19:53:21 -07:00
|
|
|
def __init__(self, verbose=False):
|
|
|
|
self.verbose = verbose
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
# The platform OnionShare is running on
|
|
|
|
self.platform = platform.system()
|
2019-10-12 21:01:25 -07:00
|
|
|
if self.platform.endswith("BSD") or self.platform == "DragonFly":
|
|
|
|
self.platform = "BSD"
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
# The current version of OnionShare
|
2019-10-12 21:01:25 -07:00
|
|
|
with open(self.get_resource_path("version.txt")) as f:
|
2018-03-08 10:18:31 -08:00
|
|
|
self.version = f.read().strip()
|
|
|
|
|
2018-03-13 03:28:47 -07:00
|
|
|
def load_settings(self, config=None):
|
|
|
|
"""
|
|
|
|
Loading settings, optionally from a custom config json file.
|
|
|
|
"""
|
|
|
|
self.settings = Settings(self, config)
|
|
|
|
self.settings.load()
|
|
|
|
|
2018-03-08 10:18:31 -08:00
|
|
|
def log(self, module, func, msg=None):
|
|
|
|
"""
|
2019-04-18 19:53:21 -07:00
|
|
|
If verbose mode is on, log error messages to stdout
|
2018-03-08 10:18:31 -08:00
|
|
|
"""
|
2019-04-18 19:53:21 -07:00
|
|
|
if self.verbose:
|
2018-03-08 10:18:31 -08:00
|
|
|
timestamp = time.strftime("%b %d %Y %X")
|
|
|
|
|
|
|
|
final_msg = "[{}] {}.{}".format(timestamp, module, func)
|
|
|
|
if msg:
|
2019-10-12 21:01:25 -07:00
|
|
|
final_msg = "{}: {}".format(final_msg, msg)
|
2018-03-08 10:18:31 -08:00
|
|
|
print(final_msg)
|
|
|
|
|
|
|
|
def get_resource_path(self, filename):
|
|
|
|
"""
|
|
|
|
Returns the absolute path of a resource, regardless of whether OnionShare is installed
|
|
|
|
systemwide, and whether regardless of platform
|
|
|
|
"""
|
|
|
|
# On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
|
2019-10-12 21:01:25 -07:00
|
|
|
if self.platform == "Windows":
|
|
|
|
filename = filename.replace("/", "\\")
|
2018-03-08 10:18:31 -08:00
|
|
|
|
2019-10-12 21:01:25 -07:00
|
|
|
if getattr(sys, "onionshare_dev_mode", False):
|
2018-03-08 10:18:31 -08:00
|
|
|
# Look for resources directory relative to python file
|
2019-10-12 21:01:25 -07:00
|
|
|
prefix = os.path.join(
|
|
|
|
os.path.dirname(
|
|
|
|
os.path.dirname(
|
|
|
|
os.path.abspath(inspect.getfile(inspect.currentframe()))
|
|
|
|
)
|
|
|
|
),
|
|
|
|
"share",
|
|
|
|
)
|
2018-03-08 10:18:31 -08:00
|
|
|
if not os.path.exists(prefix):
|
|
|
|
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
|
2019-10-12 21:01:25 -07:00
|
|
|
prefix = os.path.join(
|
|
|
|
os.path.dirname(
|
|
|
|
os.path.dirname(os.path.dirname(os.path.dirname(prefix)))
|
|
|
|
),
|
|
|
|
"share",
|
|
|
|
)
|
|
|
|
|
|
|
|
elif self.platform == "BSD" or self.platform == "Linux":
|
2018-03-08 10:18:31 -08:00
|
|
|
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
|
2019-10-12 21:01:25 -07:00
|
|
|
prefix = os.path.join(sys.prefix, "share/onionshare")
|
2018-03-08 10:18:31 -08:00
|
|
|
|
2019-10-12 21:01:25 -07:00
|
|
|
elif getattr(sys, "frozen", False):
|
2018-03-08 10:18:31 -08:00
|
|
|
# Check if app is "frozen"
|
|
|
|
# https://pythonhosted.org/PyInstaller/#run-time-information
|
2019-10-12 21:01:25 -07:00
|
|
|
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")
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
return os.path.join(prefix, filename)
|
|
|
|
|
|
|
|
def get_tor_paths(self):
|
2019-10-12 21:01:25 -07:00
|
|
|
if self.platform == "Linux":
|
|
|
|
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"
|
|
|
|
|
|
|
|
return (
|
|
|
|
tor_path,
|
|
|
|
tor_geo_ip_file_path,
|
|
|
|
tor_geo_ipv6_file_path,
|
|
|
|
obfs4proxy_file_path,
|
|
|
|
)
|
2018-03-08 10:18:31 -08:00
|
|
|
|
2018-11-25 17:17:56 -08:00
|
|
|
def build_data_dir(self):
|
|
|
|
"""
|
|
|
|
Returns the path of the OnionShare data directory.
|
|
|
|
"""
|
2019-10-12 21:01:25 -07:00
|
|
|
if self.platform == "Windows":
|
2018-11-25 17:17:56 -08:00
|
|
|
try:
|
2019-10-12 21:01:25 -07:00
|
|
|
appdata = os.environ["APPDATA"]
|
|
|
|
onionshare_data_dir = "{}\\OnionShare".format(appdata)
|
2018-11-25 17:17:56 -08:00
|
|
|
except:
|
|
|
|
# If for some reason we don't have the 'APPDATA' environment variable
|
|
|
|
# (like running tests in Linux while pretending to be in Windows)
|
2019-10-12 21:01:25 -07:00
|
|
|
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
|
|
|
elif self.platform == "Darwin":
|
|
|
|
onionshare_data_dir = os.path.expanduser(
|
|
|
|
"~/Library/Application Support/OnionShare"
|
|
|
|
)
|
2018-11-25 17:17:56 -08:00
|
|
|
else:
|
2019-10-12 21:01:25 -07:00
|
|
|
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
2018-12-19 16:04:31 -08:00
|
|
|
|
|
|
|
os.makedirs(onionshare_data_dir, 0o700, True)
|
|
|
|
return onionshare_data_dir
|
2018-11-25 17:17:56 -08:00
|
|
|
|
2019-05-20 22:18:49 -07:00
|
|
|
def build_password(self):
|
2018-03-08 10:18:31 -08:00
|
|
|
"""
|
|
|
|
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
|
|
|
"""
|
2019-10-12 21:01:25 -07:00
|
|
|
with open(self.get_resource_path("wordlist.txt")) as f:
|
2018-03-08 10:18:31 -08:00
|
|
|
wordlist = f.read().split()
|
|
|
|
|
|
|
|
r = random.SystemRandom()
|
2019-10-12 21:01:25 -07:00
|
|
|
return "-".join(r.choice(wordlist) for _ in range(2))
|
2018-03-08 10:18:31 -08:00
|
|
|
|
2018-05-07 16:21:22 -07:00
|
|
|
def define_css(self):
|
|
|
|
"""
|
|
|
|
This defines all of the stylesheets used in GUI mode, to avoid repeating code.
|
|
|
|
This method is only called in GUI mode.
|
|
|
|
"""
|
|
|
|
self.css = {
|
|
|
|
# OnionShareGui styles
|
2019-10-12 21:01:25 -07:00
|
|
|
"mode_switcher_selected_style": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
color: #ffffff;
|
|
|
|
background-color: #4e064f;
|
|
|
|
border: 0;
|
|
|
|
border-right: 1px solid #69266b;
|
|
|
|
font-weight: bold;
|
|
|
|
border-radius: 0;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"mode_switcher_unselected_style": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
color: #ffffff;
|
|
|
|
background-color: #601f61;
|
|
|
|
border: 0;
|
|
|
|
font-weight: normal;
|
|
|
|
border-radius: 0;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"settings_button": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
background-color: #601f61;
|
|
|
|
border: 0;
|
|
|
|
border-left: 1px solid #69266b;
|
|
|
|
border-radius: 0;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"server_status_indicator_label": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
font-style: italic;
|
|
|
|
color: #666666;
|
|
|
|
padding: 2px;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"status_bar": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QStatusBar {
|
|
|
|
font-style: italic;
|
|
|
|
color: #666666;
|
|
|
|
}
|
|
|
|
QStatusBar::item {
|
|
|
|
border: 0px;
|
|
|
|
}""",
|
2019-09-03 22:31:13 -07:00
|
|
|
# Common styles between modes and their child widgets
|
2019-10-12 21:01:25 -07:00
|
|
|
"mode_info_label": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
font-size: 12px;
|
|
|
|
color: #666666;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"server_status_url": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
background-color: #ffffff;
|
|
|
|
color: #000000;
|
|
|
|
padding: 10px;
|
|
|
|
border: 1px solid #666666;
|
2018-10-09 20:51:10 -07:00
|
|
|
font-size: 12px;
|
2018-05-07 16:21:22 -07:00
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"server_status_url_buttons": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
color: #3f7fcf;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"server_status_button_stopped": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
background-color: #5fa416;
|
|
|
|
color: #ffffff;
|
|
|
|
padding: 10px;
|
|
|
|
border: 0;
|
|
|
|
border-radius: 5px;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"server_status_button_working": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
background-color: #4c8211;
|
|
|
|
color: #ffffff;
|
|
|
|
padding: 10px;
|
|
|
|
border: 0;
|
|
|
|
border-radius: 5px;
|
|
|
|
font-style: italic;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"server_status_button_started": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QPushButton {
|
|
|
|
background-color: #d0011b;
|
|
|
|
color: #ffffff;
|
|
|
|
padding: 10px;
|
|
|
|
border: 0;
|
|
|
|
border-radius: 5px;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"downloads_uploads_empty": """
|
2018-09-28 18:30:32 -07:00
|
|
|
QWidget {
|
|
|
|
background-color: #ffffff;
|
|
|
|
border: 1px solid #999999;
|
|
|
|
}
|
|
|
|
QWidget QLabel {
|
|
|
|
background-color: none;
|
|
|
|
border: 0px;
|
|
|
|
}
|
2018-09-28 17:01:48 -07:00
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"downloads_uploads_empty_text": """
|
2018-09-28 17:01:48 -07:00
|
|
|
QLabel {
|
|
|
|
color: #999999;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"downloads_uploads_label": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
font-weight: bold;
|
|
|
|
font-size 14px;
|
|
|
|
text-align: center;
|
2018-09-28 18:30:32 -07:00
|
|
|
background-color: none;
|
|
|
|
border: none;
|
2018-05-07 16:21:22 -07:00
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"downloads_uploads_clear": """
|
2018-09-28 17:01:48 -07:00
|
|
|
QPushButton {
|
|
|
|
color: #3f7fcf;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"download_uploads_indicator": """
|
2018-09-28 19:54:46 -07:00
|
|
|
QLabel {
|
|
|
|
color: #ffffff;
|
|
|
|
background-color: #f44449;
|
|
|
|
font-weight: bold;
|
|
|
|
font-size: 10px;
|
2018-09-29 14:40:55 -07:00
|
|
|
padding: 2px;
|
2018-10-09 21:49:05 -07:00
|
|
|
border-radius: 7px;
|
2018-09-29 14:40:55 -07:00
|
|
|
text-align: center;
|
2018-09-28 19:54:46 -07:00
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"downloads_uploads_progress_bar": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QProgressBar {
|
|
|
|
border: 1px solid #4e064f;
|
|
|
|
background-color: #ffffff !important;
|
|
|
|
text-align: center;
|
|
|
|
color: #9b9b9b;
|
2018-09-28 19:05:26 -07:00
|
|
|
font-size: 14px;
|
2018-05-07 16:21:22 -07:00
|
|
|
}
|
|
|
|
QProgressBar::chunk {
|
|
|
|
background-color: #4e064f;
|
|
|
|
width: 10px;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"history_individual_file_timestamp_label": """
|
2019-09-03 22:31:13 -07:00
|
|
|
QLabel {
|
|
|
|
color: #666666;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"history_individual_file_status_code_label_2xx": """
|
2019-09-03 22:31:13 -07:00
|
|
|
QLabel {
|
|
|
|
color: #008800;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"history_individual_file_status_code_label_4xx": """
|
2019-09-03 22:31:13 -07:00
|
|
|
QLabel {
|
|
|
|
color: #cc0000;
|
|
|
|
}""",
|
2018-05-07 16:21:22 -07:00
|
|
|
# Share mode and child widget styles
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_zip_progess_bar": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QProgressBar {
|
|
|
|
border: 1px solid #4e064f;
|
|
|
|
background-color: #ffffff !important;
|
|
|
|
text-align: center;
|
|
|
|
color: #9b9b9b;
|
|
|
|
}
|
|
|
|
QProgressBar::chunk {
|
|
|
|
border: 0px;
|
|
|
|
background-color: #4e064f;
|
|
|
|
width: 10px;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_filesize_warning": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
padding: 10px 0;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #333333;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_file_selection_drop_here_label": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
color: #999999;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_file_selection_drop_count_label": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
color: #ffffff;
|
|
|
|
background-color: #f44449;
|
|
|
|
font-weight: bold;
|
|
|
|
padding: 5px 10px;
|
|
|
|
border-radius: 10px;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_file_list_drag_enter": """
|
2018-05-07 16:21:22 -07:00
|
|
|
FileList {
|
|
|
|
border: 3px solid #538ad0;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_file_list_drag_leave": """
|
2018-05-07 16:21:22 -07:00
|
|
|
FileList {
|
|
|
|
border: none;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"share_file_list_item_size": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
color: #666666;
|
|
|
|
font-size: 11px;
|
|
|
|
}""",
|
2018-07-14 16:43:21 +10:00
|
|
|
# Receive mode and child widget styles
|
2019-10-12 21:01:25 -07:00
|
|
|
"receive_file": """
|
2018-05-19 22:36:08 -07:00
|
|
|
QWidget {
|
|
|
|
background-color: #ffffff;
|
|
|
|
}
|
|
|
|
""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"receive_file_size": """
|
2018-05-19 22:36:08 -07:00
|
|
|
QLabel {
|
|
|
|
color: #666666;
|
|
|
|
font-size: 11px;
|
|
|
|
}""",
|
2018-05-07 16:21:22 -07:00
|
|
|
# Settings dialog
|
2019-10-12 21:01:25 -07:00
|
|
|
"settings_version": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
color: #666666;
|
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"settings_tor_status": """
|
2018-05-07 16:21:22 -07:00
|
|
|
QLabel {
|
|
|
|
background-color: #ffffff;
|
|
|
|
color: #000000;
|
|
|
|
padding: 10px;
|
2018-09-17 16:11:52 -07:00
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"settings_whats_this": """
|
2018-09-17 16:11:52 -07:00
|
|
|
QLabel {
|
|
|
|
font-size: 12px;
|
2018-12-05 20:14:52 -08:00
|
|
|
}""",
|
2019-10-12 21:01:25 -07:00
|
|
|
"settings_connect_to_tor": """
|
2018-12-05 20:14:52 -08:00
|
|
|
QLabel {
|
|
|
|
font-style: italic;
|
2019-10-12 21:01:25 -07:00
|
|
|
}""",
|
2018-05-07 16:21:22 -07:00
|
|
|
}
|
|
|
|
|
2018-03-08 10:18:31 -08:00
|
|
|
@staticmethod
|
|
|
|
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]
|
2019-10-12 21:01:25 -07:00
|
|
|
s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8")
|
2018-03-08 10:18:31 -08:00
|
|
|
if not output_len:
|
|
|
|
return s
|
|
|
|
return s[:output_len]
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def human_readable_filesize(b):
|
|
|
|
"""
|
|
|
|
Returns filesize in a human readable format.
|
|
|
|
"""
|
|
|
|
thresh = 1024.0
|
|
|
|
if b < thresh:
|
2019-10-12 21:01:25 -07:00
|
|
|
return "{:.1f} B".format(b)
|
|
|
|
units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
2018-03-08 10:18:31 -08:00
|
|
|
u = 0
|
2014-08-26 18:22:59 -07:00
|
|
|
b /= thresh
|
2018-03-08 10:18:31 -08:00
|
|
|
while b >= thresh:
|
|
|
|
b /= thresh
|
|
|
|
u += 1
|
2019-10-12 21:01:25 -07:00
|
|
|
return "{:.1f} {}".format(b, units[u])
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
@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)
|
|
|
|
|
|
|
|
human_readable = []
|
|
|
|
if days:
|
|
|
|
human_readable.append("{:.0f}d".format(days))
|
|
|
|
if hours:
|
|
|
|
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))
|
2019-10-12 21:01:25 -07:00
|
|
|
return "".join(human_readable)
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def estimated_time_remaining(bytes_downloaded, total_bytes, started):
|
|
|
|
now = time.time()
|
|
|
|
time_elapsed = now - started # in seconds
|
|
|
|
download_rate = bytes_downloaded / time_elapsed
|
|
|
|
remaining_bytes = total_bytes - bytes_downloaded
|
|
|
|
eta = remaining_bytes / download_rate
|
2018-03-13 02:22:26 -07:00
|
|
|
return Common.format_seconds(eta)
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
@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:
|
2018-09-27 21:22:10 -07:00
|
|
|
pass
|
2018-03-08 10:18:31 -08:00
|
|
|
_, port = tmpsock.getsockname()
|
|
|
|
return port
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
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
|
2014-08-26 18:22:59 -07:00
|
|
|
|
2014-11-18 18:29:32 +01:00
|
|
|
|
2019-03-25 15:05:54 +11:00
|
|
|
class AutoStopTimer(threading.Thread):
|
2017-11-08 20:25:59 +11:00
|
|
|
"""
|
|
|
|
Background thread sleeps t hours and returns.
|
|
|
|
"""
|
2019-10-12 21:01:25 -07:00
|
|
|
|
2018-03-08 10:18:31 -08:00
|
|
|
def __init__(self, common, time):
|
2017-11-08 20:25:59 +11:00
|
|
|
threading.Thread.__init__(self)
|
2018-03-08 10:18:31 -08:00
|
|
|
|
|
|
|
self.common = common
|
|
|
|
|
2017-11-08 20:25:59 +11:00
|
|
|
self.setDaemon(True)
|
|
|
|
self.time = time
|
|
|
|
|
|
|
|
def run(self):
|
2019-10-12 21:01:25 -07:00
|
|
|
self.common.log(
|
|
|
|
"AutoStopTimer", "Server will shut down after {} seconds".format(self.time)
|
|
|
|
)
|
2017-11-09 11:29:55 +11:00
|
|
|
time.sleep(self.time)
|
2017-11-08 20:25:59 +11:00
|
|
|
return 1
|