mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-01-06 06:28:45 -05:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46954165d2 | ||
|
|
58e7d520bf | ||
|
|
a4f8040324 | ||
|
|
8d5cc9a3e6 | ||
|
|
4592ce4d55 | ||
|
|
b62b38b5af | ||
|
|
14b1d4630c | ||
|
|
621d586c2f | ||
|
|
4966f9c753 | ||
|
|
059d82f6f0 | ||
|
|
bca41db6b7 | ||
|
|
613ba0b05f | ||
|
|
5f3b03ed87 | ||
|
|
f6fe801000 | ||
|
|
8ff34660d8 | ||
|
|
0c1b8dd60a | ||
|
|
8e8ee7a3ab | ||
|
|
9145a90e33 | ||
|
|
02b4a116dd | ||
|
|
e504b288a2 | ||
|
|
5128f788f0 | ||
|
|
044fe7a26a | ||
|
|
4ed2565101 | ||
|
|
abbd77bac4 | ||
|
|
38c9a52e1d | ||
|
|
f89114ca7e | ||
|
|
773d567eed | ||
|
|
ee717b679e | ||
|
|
f50810fb58 | ||
|
|
08b1b20b34 | ||
|
|
edca79af83 | ||
|
|
dd5dcd0ec9 | ||
|
|
820824e443 | ||
|
|
4c2dfdee43 | ||
|
|
ece4437c3a | ||
|
|
74daa15ce4 | ||
|
|
4f81bc8a26 | ||
|
|
e77d15f75e | ||
|
|
8668852574 | ||
|
|
7e944f393e | ||
|
|
1646fbfd17 | ||
|
|
72b0521325 | ||
|
|
8aa53fd43f | ||
|
|
aa67edb2d9 | ||
|
|
0054b17f41 | ||
|
|
2af2cc7370 | ||
|
|
5aa7aafebb | ||
|
|
3bd0f7c1e0 | ||
|
|
9c8d21f6db | ||
|
|
4947effeb7 | ||
|
|
b8fd9e6e31 | ||
|
|
2a02c93e4b | ||
|
|
a0ef520e06 | ||
|
|
a9eb32eba6 | ||
|
|
592ef0e645 | ||
|
|
cce53ee058 | ||
|
|
93755aa6d8 | ||
|
|
b1d42c7c22 | ||
|
|
8286b7b830 | ||
|
|
fbaa3c0420 | ||
|
|
ba6c30cf24 |
@@ -40,3 +40,4 @@ f06891926661986fff52d6eb4b4cb120c71972d1
|
||||
9bcbcaefdfecc85aedfd8e2f8aaa1ca7f959404e
|
||||
433dcab02b29f7bd3827e237434034deecc1b549
|
||||
9f6a9f991222efccc87b45a701086c95629c67b6
|
||||
f89114ca7e1b20bf8e645ecd0b52b707ec857aa9
|
||||
|
||||
2
.github/workflows/build_release.yml
vendored
2
.github/workflows/build_release.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
# We need the official Python, because the GA ones only support newer macOS versions
|
||||
# The deployment target is picked up by the Python build tools automatically
|
||||
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
|
||||
PYTHON_VERSION: "3.12.3"
|
||||
PYTHON_VERSION: "3.12.5"
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.9"
|
||||
# We need to force compile for universal2 support
|
||||
CFLAGS: -arch x86_64 -arch arm64
|
||||
|
||||
14
README.mkd
14
README.mkd
@@ -1,7 +1,17 @@
|
||||
Release Notes - SABnzbd 4.3.2 Release Candidate 2
|
||||
Release Notes - SABnzbd 4.3.3 Release Candidate 2
|
||||
=========================================================
|
||||
|
||||
This is the second bug fix release of SABnzbd 4.3.0.
|
||||
This is the third bug fix release of SABnzbd 4.3.0.
|
||||
|
||||
## Bug fixes and changes since 4.3.2
|
||||
* Reduced chance of jobs getting stuck at 99%.
|
||||
* Prevent crash in case of invalid articles.
|
||||
* Correct handling of empty or `Default` category when adding a job.
|
||||
* History API-output could contain inconsistent variable types.
|
||||
* Skip external IPv6 check if only link local addresses are available.
|
||||
* Shortened timeouts when resolving addresses during checks.
|
||||
* Windows: Could not repair or extract on ARM platforms.
|
||||
* Windows: Add file version information to installer.
|
||||
|
||||
## Bug fixes and changes since 4.3.1
|
||||
|
||||
|
||||
182
SABnzbd.py
182
SABnzbd.py
@@ -65,7 +65,7 @@ import sabnzbd
|
||||
import sabnzbd.lang
|
||||
import sabnzbd.interface
|
||||
from sabnzbd.constants import (
|
||||
DEF_TIMEOUT,
|
||||
DEF_NETWORKING_TIMEOUT,
|
||||
DEF_LOG_ERRFILE,
|
||||
DEF_MAIN_TMPL,
|
||||
DEF_STD_WEB_DIR,
|
||||
@@ -170,7 +170,8 @@ class GUIHandler(logging.Handler):
|
||||
# This prevents endless looping if the notification service itself throws an error/warning
|
||||
# We don't check based on message content, because if it includes a timestamp it's not unique
|
||||
if not any(
|
||||
stored_warning["origin"] == warning["origin"] and stored_warning["time"] + DEF_TIMEOUT > time.time()
|
||||
stored_warning["origin"] == warning["origin"]
|
||||
and stored_warning["time"] + DEF_NETWORKING_TIMEOUT > time.time()
|
||||
for stored_warning in self.store
|
||||
):
|
||||
if record.levelno == logging.WARNING:
|
||||
@@ -297,14 +298,14 @@ def daemonize():
|
||||
os.dup2(f.fileno(), sys.stderr.fileno())
|
||||
|
||||
|
||||
def abort_and_show_error(browserhost, cherryport, err=""):
|
||||
def abort_and_show_error(browserhost, web_port, err=""):
|
||||
"""Abort program because of CherryPy troubles"""
|
||||
logging.error(T("Failed to start web-interface") + " : " + str(err))
|
||||
if not sabnzbd.DAEMON:
|
||||
if "49" in err:
|
||||
panic_host(browserhost, cherryport)
|
||||
panic_host(browserhost, web_port)
|
||||
else:
|
||||
panic_port(browserhost, cherryport)
|
||||
panic_port(browserhost, web_port)
|
||||
sabnzbd.halt()
|
||||
exit_sab(2)
|
||||
|
||||
@@ -530,19 +531,19 @@ def check_resolve(host):
|
||||
return True
|
||||
|
||||
|
||||
def get_webhost(cherryhost, cherryport, https_port):
|
||||
def get_webhost(web_host, web_port, https_port):
|
||||
"""Determine the webhost address and port,
|
||||
return (host, port, browserhost)
|
||||
"""
|
||||
if cherryhost == "0.0.0.0" and not check_resolve("127.0.0.1"):
|
||||
cherryhost = ""
|
||||
elif cherryhost == "::" and not check_resolve("::1"):
|
||||
cherryhost = ""
|
||||
if web_host == "0.0.0.0" and not check_resolve("127.0.0.1"):
|
||||
web_host = ""
|
||||
elif web_host == "::" and not check_resolve("::1"):
|
||||
web_host = ""
|
||||
|
||||
if cherryhost is None:
|
||||
cherryhost = sabnzbd.cfg.cherryhost()
|
||||
if web_host is None:
|
||||
web_host = sabnzbd.cfg.web_host()
|
||||
else:
|
||||
sabnzbd.cfg.cherryhost.set(cherryhost)
|
||||
sabnzbd.cfg.web_host.set(web_host)
|
||||
|
||||
# Get IP address, but discard APIPA/IPV6
|
||||
# If only APIPA's or IPV6 are found, fall back to localhost
|
||||
@@ -554,10 +555,10 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
# Hostname does not resolve
|
||||
try:
|
||||
# Valid user defined name?
|
||||
info = socket.getaddrinfo(cherryhost, None)
|
||||
info = socket.getaddrinfo(web_host, None)
|
||||
except socket.error:
|
||||
if not is_localhost(cherryhost):
|
||||
cherryhost = "0.0.0.0"
|
||||
if not is_localhost(web_host):
|
||||
web_host = "0.0.0.0"
|
||||
try:
|
||||
info = socket.getaddrinfo(localhost, None)
|
||||
except socket.error:
|
||||
@@ -574,75 +575,75 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
hostip = ip
|
||||
|
||||
# A blank host will use the local ip address
|
||||
if cherryhost == "":
|
||||
if web_host == "":
|
||||
if ipv6 and ipv4:
|
||||
# To protect Firefox users, use numeric IP
|
||||
cherryhost = hostip
|
||||
web_host = hostip
|
||||
browserhost = hostip
|
||||
else:
|
||||
cherryhost = socket.gethostname()
|
||||
browserhost = cherryhost
|
||||
web_host = socket.gethostname()
|
||||
browserhost = web_host
|
||||
|
||||
# 0.0.0.0 will listen on all ipv4 interfaces (no ipv6 addresses)
|
||||
elif cherryhost == "0.0.0.0":
|
||||
elif web_host == "0.0.0.0":
|
||||
# Just take the gamble for this
|
||||
cherryhost = "0.0.0.0"
|
||||
web_host = "0.0.0.0"
|
||||
browserhost = localhost
|
||||
|
||||
# :: will listen on all ipv6 interfaces (no ipv4 addresses)
|
||||
elif cherryhost in ("::", "[::]"):
|
||||
cherryhost = cherryhost.strip("[").strip("]")
|
||||
elif web_host in ("::", "[::]"):
|
||||
web_host = web_host.strip("[").strip("]")
|
||||
# Assume '::1' == 'localhost'
|
||||
browserhost = localhost
|
||||
|
||||
# IPV6 address
|
||||
elif "[" in cherryhost or ":" in cherryhost:
|
||||
browserhost = cherryhost
|
||||
elif "[" in web_host or ":" in web_host:
|
||||
browserhost = web_host
|
||||
|
||||
# IPV6 numeric address
|
||||
elif cherryhost.replace(".", "").isdigit():
|
||||
elif web_host.replace(".", "").isdigit():
|
||||
# IPV4 numerical
|
||||
browserhost = cherryhost
|
||||
browserhost = web_host
|
||||
|
||||
elif cherryhost == localhost:
|
||||
cherryhost = localhost
|
||||
elif web_host == localhost:
|
||||
web_host = localhost
|
||||
browserhost = localhost
|
||||
|
||||
else:
|
||||
# If on APIPA, use numerical IP, to help FireFoxers
|
||||
if ipv6 and ipv4:
|
||||
cherryhost = hostip
|
||||
browserhost = cherryhost
|
||||
web_host = hostip
|
||||
browserhost = web_host
|
||||
|
||||
# Some systems don't like brackets in numerical ipv6
|
||||
if sabnzbd.MACOS:
|
||||
cherryhost = cherryhost.strip("[]")
|
||||
web_host = web_host.strip("[]")
|
||||
else:
|
||||
try:
|
||||
socket.getaddrinfo(cherryhost, None)
|
||||
socket.getaddrinfo(web_host, None)
|
||||
except socket.error:
|
||||
cherryhost = cherryhost.strip("[]")
|
||||
web_host = web_host.strip("[]")
|
||||
|
||||
if ipv6 and ipv4 and cherryhost == "" and sabnzbd.WIN32:
|
||||
if ipv6 and ipv4 and web_host == "" and sabnzbd.WIN32:
|
||||
helpful_warning(T("Please be aware the 0.0.0.0 hostname will need an IPv6 address for external access"))
|
||||
|
||||
if cherryhost == "localhost" and not sabnzbd.WIN32 and not sabnzbd.MACOS:
|
||||
if web_host == "localhost" and not sabnzbd.WIN32 and not sabnzbd.MACOS:
|
||||
# On the Ubuntu family, localhost leads to problems for CherryPy
|
||||
ips = ip_extract()
|
||||
if "127.0.0.1" in ips and "::1" in ips:
|
||||
cherryhost = "127.0.0.1"
|
||||
web_host = "127.0.0.1"
|
||||
if ips[0] != "127.0.0.1":
|
||||
browserhost = "127.0.0.1"
|
||||
|
||||
# This is to please Chrome on macOS
|
||||
if cherryhost == "localhost" and sabnzbd.MACOS:
|
||||
cherryhost = "127.0.0.1"
|
||||
if web_host == "localhost" and sabnzbd.MACOS:
|
||||
web_host = "127.0.0.1"
|
||||
browserhost = "localhost"
|
||||
|
||||
if cherryport is None:
|
||||
cherryport = sabnzbd.cfg.cherryport.get_int()
|
||||
if web_port is None:
|
||||
web_port = sabnzbd.cfg.web_port.get_int()
|
||||
else:
|
||||
sabnzbd.cfg.cherryport.set(str(cherryport))
|
||||
sabnzbd.cfg.web_port.set(str(web_port))
|
||||
|
||||
if https_port is None:
|
||||
https_port = sabnzbd.cfg.https_port.get_int()
|
||||
@@ -651,12 +652,12 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
# if the https port was specified, assume they want HTTPS enabling also
|
||||
sabnzbd.cfg.enable_https.set(True)
|
||||
|
||||
if cherryport == https_port and sabnzbd.cfg.enable_https():
|
||||
if web_port == https_port and sabnzbd.cfg.enable_https():
|
||||
sabnzbd.cfg.enable_https.set(False)
|
||||
# Should have a translated message, but that's not available yet
|
||||
logging.error(T("HTTP and HTTPS ports cannot be the same"))
|
||||
|
||||
return cherryhost, cherryport, browserhost, https_port
|
||||
return web_host, web_port, browserhost, https_port
|
||||
|
||||
|
||||
def attach_server(host, port, cert=None, key=None, chain=None):
|
||||
@@ -841,8 +842,8 @@ def main():
|
||||
fork = False
|
||||
pause = False
|
||||
inifile = None
|
||||
cherryhost = None
|
||||
cherryport = None
|
||||
web_host = None
|
||||
web_port = None
|
||||
https_port = None
|
||||
cherrypylogging = None
|
||||
clean_up = False
|
||||
@@ -880,14 +881,11 @@ def main():
|
||||
elif opt in ("-t", "--templates"):
|
||||
web_dir = arg
|
||||
elif opt in ("-s", "--server"):
|
||||
(cherryhost, cherryport) = split_host(arg)
|
||||
(web_host, web_port) = split_host(arg)
|
||||
elif opt in ("-n", "--nobrowser"):
|
||||
autobrowser = False
|
||||
elif opt in ("-b", "--browser"):
|
||||
try:
|
||||
autobrowser = bool(int(arg))
|
||||
except ValueError:
|
||||
autobrowser = True
|
||||
autobrowser = sabnzbd.misc.bool_conv(arg)
|
||||
elif opt == "--autorestarted":
|
||||
autorestarted = True
|
||||
elif opt in ("-c", "--clean"):
|
||||
@@ -1006,24 +1004,24 @@ def main():
|
||||
sabnzbd.cfg.ipv6_hosting.set(ipv6_hosting)
|
||||
|
||||
# Determine web host address
|
||||
cherryhost, cherryport, browserhost, https_port = get_webhost(cherryhost, cherryport, https_port)
|
||||
web_host, web_port, browserhost, https_port = get_webhost(web_host, web_port, https_port)
|
||||
enable_https = sabnzbd.cfg.enable_https()
|
||||
|
||||
# When this is a daemon, just check and bail out if port in use
|
||||
if sabnzbd.DAEMON:
|
||||
if enable_https and https_port:
|
||||
try:
|
||||
portend.free(cherryhost, https_port, timeout=0.05)
|
||||
portend.free(web_host, https_port, timeout=0.05)
|
||||
except IOError:
|
||||
abort_and_show_error(browserhost, cherryport)
|
||||
abort_and_show_error(browserhost, web_port)
|
||||
except:
|
||||
abort_and_show_error(browserhost, cherryport, "49")
|
||||
abort_and_show_error(browserhost, web_port, "49")
|
||||
try:
|
||||
portend.free(cherryhost, cherryport, timeout=0.05)
|
||||
portend.free(web_host, web_port, timeout=0.05)
|
||||
except IOError:
|
||||
abort_and_show_error(browserhost, cherryport)
|
||||
abort_and_show_error(browserhost, web_port)
|
||||
except:
|
||||
abort_and_show_error(browserhost, cherryport, "49")
|
||||
abort_and_show_error(browserhost, web_port, "49")
|
||||
|
||||
# Windows instance is reachable through registry
|
||||
url = None
|
||||
@@ -1034,7 +1032,7 @@ def main():
|
||||
|
||||
# SSL
|
||||
if enable_https:
|
||||
port = https_port or cherryport
|
||||
port = https_port or web_port
|
||||
try:
|
||||
portend.free(browserhost, port, timeout=0.05)
|
||||
except IOError as error:
|
||||
@@ -1046,7 +1044,7 @@ def main():
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
# Bail out if we have fixed our ports after first start-up
|
||||
if sabnzbd.cfg.fixed_ports():
|
||||
abort_and_show_error(browserhost, cherryport)
|
||||
abort_and_show_error(browserhost, web_port)
|
||||
# Find free port to bind
|
||||
newport = find_free_port(browserhost, port)
|
||||
if newport > 0:
|
||||
@@ -1056,34 +1054,34 @@ def main():
|
||||
sabnzbd.cfg.https_port.set(newport)
|
||||
else:
|
||||
# In case HTTPS == HTTP port
|
||||
cherryport = newport
|
||||
sabnzbd.cfg.cherryport.set(newport)
|
||||
web_port = newport
|
||||
sabnzbd.cfg.web_port.set(newport)
|
||||
except:
|
||||
# Something else wrong, probably badly specified host
|
||||
abort_and_show_error(browserhost, cherryport, "49")
|
||||
abort_and_show_error(browserhost, web_port, "49")
|
||||
|
||||
# NonSSL check if there's no HTTPS or we only use 1 port
|
||||
if not (enable_https and not https_port):
|
||||
try:
|
||||
portend.free(browserhost, cherryport, timeout=0.05)
|
||||
portend.free(browserhost, web_port, timeout=0.05)
|
||||
except IOError as error:
|
||||
if str(error) == "Port not bound.":
|
||||
pass
|
||||
else:
|
||||
if not url:
|
||||
url = "http://%s:%s%s/api?" % (browserhost, cherryport, sabnzbd.cfg.url_base())
|
||||
url = "http://%s:%s%s/api?" % (browserhost, web_port, sabnzbd.cfg.url_base())
|
||||
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
|
||||
# Bail out if we have fixed our ports after first start-up
|
||||
if sabnzbd.cfg.fixed_ports():
|
||||
abort_and_show_error(browserhost, cherryport)
|
||||
abort_and_show_error(browserhost, web_port)
|
||||
# Find free port to bind
|
||||
port = find_free_port(browserhost, cherryport)
|
||||
port = find_free_port(browserhost, web_port)
|
||||
if port > 0:
|
||||
sabnzbd.cfg.cherryport.set(port)
|
||||
cherryport = port
|
||||
sabnzbd.cfg.web_port.set(port)
|
||||
web_port = port
|
||||
except:
|
||||
# Something else wrong, probably badly specified host
|
||||
abort_and_show_error(browserhost, cherryport, "49")
|
||||
abort_and_show_error(browserhost, web_port, "49")
|
||||
|
||||
# We found a port, now we never check again
|
||||
sabnzbd.cfg.fixed_ports.set(True)
|
||||
@@ -1277,29 +1275,29 @@ def main():
|
||||
# Starting of the webserver
|
||||
# Determine if this system has multiple definitions for 'localhost'
|
||||
hosts = all_localhosts()
|
||||
multilocal = len(hosts) > 1 and cherryhost in ("localhost", "0.0.0.0")
|
||||
multilocal = len(hosts) > 1 and web_host in ("localhost", "0.0.0.0")
|
||||
|
||||
# For 0.0.0.0 CherryPy will always pick IPv4, so make sure the secondary localhost is IPv6
|
||||
if multilocal and cherryhost == "0.0.0.0" and hosts[1] == "127.0.0.1":
|
||||
if multilocal and web_host == "0.0.0.0" and hosts[1] == "127.0.0.1":
|
||||
hosts[1] = "::1"
|
||||
|
||||
# The Windows binary requires numeric localhost as primary address
|
||||
if cherryhost == "localhost":
|
||||
cherryhost = hosts[0]
|
||||
if web_host == "localhost":
|
||||
web_host = hosts[0]
|
||||
|
||||
if enable_https:
|
||||
if https_port:
|
||||
# Extra HTTP port for primary localhost
|
||||
attach_server(cherryhost, cherryport)
|
||||
attach_server(web_host, web_port)
|
||||
if multilocal:
|
||||
# Extra HTTP port for secondary localhost
|
||||
attach_server(hosts[1], cherryport)
|
||||
attach_server(hosts[1], web_port)
|
||||
# Extra HTTPS port for secondary localhost
|
||||
attach_server(hosts[1], https_port, https_cert, https_key, https_chain)
|
||||
cherryport = https_port
|
||||
web_port = https_port
|
||||
elif multilocal:
|
||||
# Extra HTTPS port for secondary localhost
|
||||
attach_server(hosts[1], cherryport, https_cert, https_key, https_chain)
|
||||
attach_server(hosts[1], web_port, https_cert, https_key, https_chain)
|
||||
|
||||
cherrypy.config.update(
|
||||
{
|
||||
@@ -1311,7 +1309,7 @@ def main():
|
||||
)
|
||||
elif multilocal:
|
||||
# Extra HTTP port for secondary localhost
|
||||
attach_server(hosts[1], cherryport)
|
||||
attach_server(hosts[1], web_port)
|
||||
|
||||
if no_login:
|
||||
sabnzbd.cfg.username.set("")
|
||||
@@ -1334,8 +1332,8 @@ def main():
|
||||
cherrypy.config.update(
|
||||
{
|
||||
"server.environment": "production",
|
||||
"server.socket_host": cherryhost,
|
||||
"server.socket_port": cherryport,
|
||||
"server.socket_host": web_host,
|
||||
"server.socket_port": web_port,
|
||||
"server.shutdown_timeout": 0,
|
||||
"engine.autoreload.on": False,
|
||||
"tools.encode.on": True,
|
||||
@@ -1403,7 +1401,7 @@ def main():
|
||||
|
||||
# Set authentication for CherryPy
|
||||
sabnzbd.interface.set_auth(cherrypy.config)
|
||||
logging.info("Starting web-interface on %s:%s", cherryhost, cherryport)
|
||||
logging.info("Starting web-interface on %s:%s", web_host, web_port)
|
||||
|
||||
sabnzbd.cfg.log_level.callback(guard_loglevel)
|
||||
|
||||
@@ -1413,7 +1411,7 @@ def main():
|
||||
# Since the webserver is started by cherrypy in a separate thread, we can't really catch any
|
||||
# start-up errors. This try/except only catches very few errors, the rest is only shown in the console.
|
||||
logging.error(T("Failed to start web-interface: "), exc_info=True)
|
||||
abort_and_show_error(browserhost, cherryport)
|
||||
abort_and_show_error(browserhost, web_port)
|
||||
|
||||
# Create a record of the active cert/key/chain files, for use with config.create_config_backup()
|
||||
if enable_https:
|
||||
@@ -1423,16 +1421,16 @@ def main():
|
||||
|
||||
# Set URL for browser
|
||||
if enable_https:
|
||||
sabnzbd.BROWSER_URL = "https://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
|
||||
sabnzbd.BROWSER_URL = "https://%s:%s%s" % (browserhost, web_port, sabnzbd.cfg.url_base())
|
||||
else:
|
||||
sabnzbd.BROWSER_URL = "http://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
|
||||
sabnzbd.BROWSER_URL = "http://%s:%s%s" % (browserhost, web_port, sabnzbd.cfg.url_base())
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
# Write URL for uploads and version check directly to registry
|
||||
set_connection_info(f"{sabnzbd.BROWSER_URL}/api?apikey={sabnzbd.cfg.api_key()}")
|
||||
|
||||
if pid_path or pid_file:
|
||||
sabnzbd.pid_file(pid_path, pid_file, cherryport)
|
||||
sabnzbd.pid_file(pid_path, pid_file, web_port)
|
||||
|
||||
# Stop here in case of fatal errors
|
||||
if sabnzbd.NO_DOWNLOADING:
|
||||
@@ -1460,11 +1458,11 @@ def main():
|
||||
autorestarted = False
|
||||
|
||||
# Start SSDP and Bonjour if SABnzbd isn't listening on localhost only
|
||||
if sabnzbd.cfg.enable_broadcast() and not is_localhost(cherryhost):
|
||||
if sabnzbd.cfg.enable_broadcast() and not is_localhost(web_host):
|
||||
# Try to find a LAN IP address for SSDP/Bonjour
|
||||
if is_lan_addr(cherryhost):
|
||||
if is_lan_addr(web_host):
|
||||
# A specific listening address was configured, use that
|
||||
external_host = cherryhost
|
||||
external_host = web_host
|
||||
else:
|
||||
# Fall back to the IPv4 address of the LAN interface
|
||||
external_host = local_ipv4()
|
||||
@@ -1478,13 +1476,13 @@ def main():
|
||||
(not sabnzbd.cfg.local_ranges()) or any(ip_in_subnet(external_host, r) for r in sabnzbd.cfg.local_ranges())
|
||||
):
|
||||
# Start Bonjour and SSDP
|
||||
sabnzbd.zconfig.set_bonjour(external_host, cherryport)
|
||||
sabnzbd.zconfig.set_bonjour(external_host, web_port)
|
||||
|
||||
# Set URL for browser for external hosts
|
||||
ssdp_url = "%s://%s:%s%s" % (
|
||||
("https" if enable_https else "http"),
|
||||
external_host,
|
||||
cherryport,
|
||||
web_port,
|
||||
sabnzbd.cfg.url_base(),
|
||||
)
|
||||
ssdp.start_ssdp(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- mode: python -*-
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from PyInstaller.building.api import EXE, COLLECT, PYZ
|
||||
@@ -8,13 +7,13 @@ from PyInstaller.building.build_main import Analysis
|
||||
from PyInstaller.building.osx import BUNDLE
|
||||
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
|
||||
|
||||
from builder.constants import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION
|
||||
from builder.constants import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION, RELEASE_VERSION_TUPLE
|
||||
|
||||
# Add extra files in the PyInstaller-spec
|
||||
extra_pyinstaller_files = []
|
||||
|
||||
# Add hidden imports
|
||||
extra_hiddenimports = ["Cheetah.DummyTransaction", "cheroot.ssl.builtin", "certifi"]
|
||||
extra_hiddenimports = ["Cheetah.DummyTransaction", "cheroot.ssl.builtin", "certifi", "pkg_resources.extern"]
|
||||
extra_hiddenimports.extend(collect_submodules("apprise"))
|
||||
extra_hiddenimports.extend(collect_submodules("babelfish.converters"))
|
||||
extra_hiddenimports.extend(collect_submodules("guessit.data"))
|
||||
@@ -45,16 +44,12 @@ else:
|
||||
EXTRA_FOLDERS += ["win/multipar/", "win/par2/", "win/unrar/", "win/7zip/"]
|
||||
EXTRA_FILES += ["portable.cmd"]
|
||||
|
||||
# Parse the version info
|
||||
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
|
||||
version_tuple = (int(version_regexed.group(1)), int(version_regexed.group(2)), int(version_regexed.group(3)), 0)
|
||||
|
||||
# Detailed instructions are in the PyInstaller documentation
|
||||
# We don't include the alpha/beta/rc in the counters
|
||||
version_info = VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
filevers=version_tuple,
|
||||
prodvers=version_tuple,
|
||||
filevers=RELEASE_VERSION_TUPLE,
|
||||
prodvers=RELEASE_VERSION_TUPLE,
|
||||
mask=0x3F,
|
||||
flags=0x0,
|
||||
OS=0x40004,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
# Constants
|
||||
VERSION_FILE = "sabnzbd/version.py"
|
||||
@@ -33,6 +34,10 @@ RELEASE_VERSION = __version__
|
||||
# Pre-releases are longer than 6 characters (e.g. 3.1.0Beta1 vs 3.1.0, but also 3.0.11)
|
||||
PRERELEASE = len(RELEASE_VERSION) > 5
|
||||
|
||||
# Parse the version info for Windows file properties information
|
||||
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
|
||||
RELEASE_VERSION_TUPLE = (int(version_regexed.group(1)), int(version_regexed.group(2)), int(version_regexed.group(3)), 0)
|
||||
|
||||
# Define release name
|
||||
RELEASE_NAME = "SABnzbd-%s" % RELEASE_VERSION
|
||||
RELEASE_TITLE = "SABnzbd %s" % RELEASE_VERSION
|
||||
|
||||
@@ -32,6 +32,7 @@ from typing import List
|
||||
|
||||
from constants import (
|
||||
RELEASE_VERSION,
|
||||
RELEASE_VERSION_TUPLE,
|
||||
VERSION_FILE,
|
||||
RELEASE_README,
|
||||
RELEASE_NAME,
|
||||
@@ -258,8 +259,8 @@ if __name__ == "__main__":
|
||||
[
|
||||
"makensis.exe",
|
||||
"/V3",
|
||||
"/DSAB_PRODUCT=%s" % RELEASE_NAME,
|
||||
"/DSAB_VERSION=%s" % RELEASE_VERSION,
|
||||
"/DSAB_VERSIONKEY=%s" % ".".join(map(str, RELEASE_VERSION_TUPLE)),
|
||||
"/DSAB_FILE=%s" % RELEASE_INSTALLER,
|
||||
"NSIS_Installer.nsi.tmp",
|
||||
]
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
# Basic build requirements
|
||||
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
|
||||
pyinstaller==5.13.2
|
||||
packaging==24.0
|
||||
pyinstaller-hooks-contrib==2024.6
|
||||
packaging==24.1
|
||||
pyinstaller-hooks-contrib==2024.8
|
||||
altgraph==0.17.4
|
||||
wrapt==1.16.0
|
||||
setuptools==69.5.1
|
||||
setuptools==72.1.0
|
||||
|
||||
# Required on 32bit Windows, exclude it based on Python-version
|
||||
importlib_metadata==7.1.0; python_version < '3.10'
|
||||
importlib_metadata==8.2.0; python_version < '3.10'
|
||||
importlib_resources==6.4.0; python_version < '3.10'
|
||||
zipp==3.18.2; python_version < '3.10'
|
||||
zipp==3.20.0; python_version < '3.10'
|
||||
|
||||
# orjson does not support 32bit Windows, also exclude based on Python-version
|
||||
orjson==3.10.3; python_version > '3.8'
|
||||
orjson==3.10.7; python_version > '3.8'
|
||||
|
||||
# For the Windows build
|
||||
pefile==2023.2.7; sys_platform == 'win32'
|
||||
pywin32-ctypes==0.2.2; sys_platform == 'win32'
|
||||
|
||||
# For the macOS build
|
||||
dmgbuild==1.6.1; sys_platform == 'darwin'
|
||||
dmgbuild==1.6.2; sys_platform == 'darwin'
|
||||
mac-alias==2.2.2; sys_platform == 'darwin'
|
||||
macholib==1.16.3; sys_platform == 'darwin'
|
||||
ds-store==1.3.1; sys_platform == 'darwin'
|
||||
|
||||
@@ -68,7 +68,18 @@ Unicode true
|
||||
|
||||
;------------------------------------------------------------------
|
||||
; Define names of the product
|
||||
Name "${SAB_PRODUCT}"
|
||||
Name "SABnzbd ${SAB_VERSION}"
|
||||
VIProductVersion "${SAB_VERSIONKEY}"
|
||||
VIFileVersion "${SAB_VERSIONKEY}"
|
||||
|
||||
VIAddVersionKey "Comments" "SABnzbd ${SAB_VERSION}"
|
||||
VIAddVersionKey "CompanyName" "The SABnzbd-Team"
|
||||
VIAddVersionKey "FileDescription" "SABnzbd ${SAB_VERSION}"
|
||||
VIAddVersionKey "FileVersion" "${SAB_VERSION}"
|
||||
VIAddVersionKey "LegalCopyright" "The SABnzbd-Team"
|
||||
VIAddVersionKey "ProductName" "SABnzbd ${SAB_VERSION}"
|
||||
VIAddVersionKey "ProductVersion" "${SAB_VERSION}"
|
||||
|
||||
OutFile "${SAB_FILE}"
|
||||
InstallDir "$PROGRAMFILES\SABnzbd"
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="port">$T('opt-port')</label>
|
||||
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
|
||||
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" min="0" max="65535" />
|
||||
<span class="desc">$T('explain-port')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="https_port">$T('opt-https_port')</label>
|
||||
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" />
|
||||
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" min="0" max="65535" />
|
||||
<span class="desc">$T('explain-https_port')</span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<label class="config wide" for="${section_label}_prio_$type">
|
||||
$T($notify_types[$type]).replace('/', ' / ')
|
||||
</label>
|
||||
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if int($getVar($section_label + '_prio_' + $type)) > 0 then 'checked="checked"' else ""#--> />
|
||||
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if $getVar($section_label + '_prio_' + $type) then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
<!--#end for#-->
|
||||
<!--#end def#-->
|
||||
|
||||
<!--#def show_cat_box($section_label)#-->
|
||||
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col2-cats" <!--#if $getVar($section_label + '_enable') then '' else 'style="display:none"'#-->>
|
||||
<hr>
|
||||
<b>$T('affectedCat')</b><br/>
|
||||
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats" size="$len($categories)">
|
||||
@@ -58,12 +58,12 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="email_full">$T('opt-email_full')</label>
|
||||
<input type="checkbox" name="email_full" id="email_full" value="1" <!--#if int($email_full) > 0 then 'checked="checked"' else ""#--> />
|
||||
<input type="checkbox" name="email_full" id="email_full" value="1" <!--#if $email_full then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-email_full')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="email_rss">$T('opt-email_rss')</label>
|
||||
<input type="checkbox" name="email_rss" id="email_rss" value="1" <!--#if int($email_rss) > 0 then 'checked="checked"' else ""#--> />
|
||||
<input type="checkbox" name="email_rss" id="email_rss" value="1" <!--#if $email_rss then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-email_rss')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -107,12 +107,12 @@
|
||||
<h3>$T('section-NC')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if int($ncenter_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if $ncenter_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $ncenter_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
$show_notify_checkboxes('ncenter')
|
||||
<div class="field-pair no-field-pair-bg">
|
||||
@@ -132,13 +132,13 @@
|
||||
<h3>$T('section-AC')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="acenter_enable" id="acenter_enable" value="1" <!--#if int($acenter_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="acenter_enable" id="acenter_enable" value="1" <!--#if $acenter_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
$show_cat_box('acenter')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $acenter_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
$show_notify_checkboxes('acenter')
|
||||
<div class="field-pair no-field-pair-bg">
|
||||
@@ -158,13 +158,13 @@
|
||||
<h3>$T('section-OSD') <a href="$help_uri#toc4" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if int($ntfosd_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if $ntfosd_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
$show_cat_box('ntfosd')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $ntfosd_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
$show_notify_checkboxes('ntfosd')
|
||||
<div class="field-pair no-field-pair-bg">
|
||||
@@ -183,7 +183,7 @@
|
||||
<h3>Apprise <a href="$help_uri#apprise" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="apprise_enable" id="apprise_enable" value="1" <!--#if int($apprise_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="apprise_enable" id="apprise_enable" value="1" <!--#if $apprise_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="apprise_enable"> $T('opt-apprise_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
$show_cat_box('apprise')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($apprise_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $apprise_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="apprise_urls">$T('opt-apprise_urls')</label>
|
||||
@@ -208,7 +208,7 @@
|
||||
<label class="config" for="${section_label}_target_${type}">
|
||||
$T($notify_types[$type]).replace('/', ' / ')
|
||||
</label>
|
||||
<input type="checkbox" name="${section_label}_target_${type}_enable" id="${section_label}_target_${type}_enable" value="1" <!--#if int($getVar($section_label + '_target_' + $type + '_enable')) > 0 then 'checked="checked"' else ""#--> />
|
||||
<input type="checkbox" name="${section_label}_target_${type}_enable" id="${section_label}_target_${type}_enable" value="1" <!--#if $getVar($section_label + '_target_' + $type + '_enable') then 'checked="checked"' else ""#--> />
|
||||
<input type="text" name="${section_label}_target_${type}" id="${section_label}_target_${type}" value="$getVar($section_label + '_target_' + $type)" placeholder="$T('opt-apprise_urls')" />
|
||||
</div>
|
||||
<!--#end for#-->
|
||||
@@ -228,14 +228,14 @@
|
||||
<h3>$T('section-NScript') <a href="$help_uri#nscript" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if $nscript_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-nscript_enable')</em><br><a href="$help_uri#nscript" target="_blank">$T('readwiki')</a>
|
||||
$show_cat_box('nscript')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $nscript_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
|
||||
@@ -267,14 +267,14 @@
|
||||
<h3>$T('section-Prowl')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="prowl_enable" id="prowl_enable" value="1" <!--#if int($prowl_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="prowl_enable" id="prowl_enable" value="1" <!--#if $prowl_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="prowl_enable"> $T('opt-prowl_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-prowl_enable')</em>
|
||||
$show_cat_box('prowl')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $prowl_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="prowl_apikey">$T('opt-prowl_apikey')</label>
|
||||
@@ -313,14 +313,14 @@
|
||||
<h3>$T('section-Pushover')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if int($pushover_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if $pushover_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="pushover_enable"> $T('opt-pushover_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-pushover_enable')</em>
|
||||
$show_cat_box('pushover')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $pushover_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="pushover_token">$T('opt-pushover_token')</label>
|
||||
@@ -378,14 +378,14 @@
|
||||
<h3>$T('section-Pushbullet')</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if int($pushbullet_enable) > 0 then 'checked="checked"' else ""#--> /></td>
|
||||
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if $pushbullet_enable then 'checked="checked"' else ""#--> /></td>
|
||||
<td><label for="pushbullet_enable"> $T('opt-pushbullet_enable')</label></td>
|
||||
</tr>
|
||||
</table>
|
||||
<em>$T('explain-pushbullet_enable')</em>
|
||||
$show_cat_box('pushbullet')
|
||||
</div>
|
||||
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
|
||||
<div class="col1" <!--#if $pushbullet_enable then '' else 'style="display:none"'#-->>
|
||||
<fieldset>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="pushbullet_apikey">$T('opt-pushbullet_apikey')</label>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="port">$T('srv-port')</label>
|
||||
<input type="number" name="port" id="port" size="8" value="563" min="0" />
|
||||
<input type="number" name="port" id="port" size="8" value="563" min="0" max="65535" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="ssl">$T('srv-ssl')</label>
|
||||
@@ -185,7 +185,7 @@
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="port$cur">$T('srv-port')</label>
|
||||
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" required />
|
||||
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" max="65535" required />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="propagation_delay">$T('opt-propagation_delay')</label>
|
||||
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" /> <i>$T('minutes')</i>
|
||||
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" min="0" /> <i>$T('minutes')</i>
|
||||
<span class="desc">$T('explain-propagation_delay')</span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
<div class="alert alert-danger" role="alert">$error</div>
|
||||
<!--#end if#-->
|
||||
|
||||
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" required autofocus>
|
||||
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" required>
|
||||
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" autocomplete="username" required autofocus>
|
||||
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" autocomplete="current-password" required>
|
||||
|
||||
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
|
||||
|
||||
@@ -62,4 +62,4 @@
|
||||
} catch(err) { }
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -528,7 +528,7 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('category')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText'"></select>
|
||||
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: ''"></select>
|
||||
<span class="glyphicon glyphicon-tag"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -537,7 +537,7 @@
|
||||
<div class="col-sm-6">
|
||||
<!-- This list is different from the one during download! -->
|
||||
<select name="Priority" class="form-control">
|
||||
<option value="-100">$T('default')</option>
|
||||
<option value=""></option>
|
||||
<option value="2">$T('pr-force')</option>
|
||||
<option value="1">$T('pr-high')</option>
|
||||
<option value="0">$T('pr-normal')</option>
|
||||
@@ -550,14 +550,14 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('swtag-pp')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
|
||||
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: ''"></select>
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('eoq-scripts')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '$T('default')', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
|
||||
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
|
||||
<span class="glyphicon glyphicon-flash"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -704,6 +704,7 @@ function ViewModel() {
|
||||
data.append("apikey", apiKey);
|
||||
|
||||
// Add this one
|
||||
debugger
|
||||
$.ajax({
|
||||
url: "./api",
|
||||
type: "POST",
|
||||
|
||||
@@ -57,13 +57,13 @@
|
||||
<div class="form-group">
|
||||
<label for="port" class="col-sm-4 control-label">$T('srv-port')</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" />
|
||||
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" min="0" max="65535" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="connections" class="col-sm-4 control-label">$T('srv-connections')</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" />
|
||||
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" min="1" max="500" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<url type="faq">https://sabnzbd.org/wiki/faq</url>
|
||||
<url type="contact">https://sabnzbd.org/live-chat.html</url>
|
||||
<releases>
|
||||
<release version="4.3.3" date="2024-08-20" type="stable"/>
|
||||
<release version="4.3.2" date="2024-05-30" type="stable"/>
|
||||
<release version="4.3.1" date="2024-05-03" type="stable"/>
|
||||
<release version="4.3.0" date="2024-05-01" type="stable"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.2RC1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: ION, 2020\n"
|
||||
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.2RC1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Gjelbrim Haskaj, 2024\n"
|
||||
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.2RC1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Fred L <88com88@gmail.com>, 2024\n"
|
||||
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: ION, 2024\n"
|
||||
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2024\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Kangwei Li <lkw20010211@gmail.com>, 2023\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.2RC1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2022\n"
|
||||
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Lorenz B, 2024\n"
|
||||
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Ester Molla Aragones <moarages@gmail.com>, 2020\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Fred L <88com88@gmail.com>, 2024\n"
|
||||
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: ION, 2024\n"
|
||||
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2024\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Petter Ramme, 2024\n"
|
||||
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.3.1\n"
|
||||
"Project-Id-Version: SABnzbd-4.3.3Beta2\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
# Main requirements
|
||||
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
|
||||
apprise==1.8.0
|
||||
sabctools==8.2.0
|
||||
apprise==1.8.1
|
||||
sabctools==8.2.5
|
||||
CT3==3.3.3.post1
|
||||
cffi==1.16.0
|
||||
cffi==1.17.0
|
||||
pycparser==2.22
|
||||
feedparser==6.0.11
|
||||
configobj==5.0.8
|
||||
cheroot==10.0.1
|
||||
six==1.16.0
|
||||
cherrypy==18.9.0
|
||||
jaraco.functools==4.0.1
|
||||
cherrypy==18.10.0
|
||||
jaraco.functools==4.0.2
|
||||
jaraco.collections==5.0.0
|
||||
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
|
||||
jaraco.classes==3.4.0
|
||||
jaraco.context==4.3.0
|
||||
more-itertools==10.2.0
|
||||
more-itertools==10.4.0
|
||||
zc.lockfile==3.0.post1
|
||||
python-dateutil==2.9.0.post0
|
||||
tempora==5.5.1
|
||||
tempora==5.7.0
|
||||
pytz==2024.1
|
||||
sgmllib3k==1.0.0
|
||||
portend==3.2.0
|
||||
chardet==5.2.0
|
||||
PySocks==1.7.1
|
||||
puremagic==1.23
|
||||
puremagic==1.27
|
||||
guessit==3.8.0
|
||||
babelfish==0.6.1
|
||||
rebulk==3.2.0
|
||||
|
||||
# Recent cryptography versions require Rust. If you run into issues compiling this
|
||||
# SABnzbd will also work with older pre-Rust versions such as cryptography==3.3.2
|
||||
cryptography==42.0.7
|
||||
cryptography==43.0.0
|
||||
|
||||
# We recommend using "orjson" as it is 2x as fast as "ujson". However, it requires
|
||||
# Rust so SABnzbd works just as well with "ujson" or the Python built in "json" module
|
||||
@@ -39,34 +39,34 @@ ujson==5.10.0
|
||||
|
||||
# Windows system integration
|
||||
pywin32==306; sys_platform == 'win32'
|
||||
windows-toasts==1.1.1; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-runtime==2.0.1; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.Data.Xml.Dom==2.0.1; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.Foundation==2.0.1; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.Foundation.Collections==2.0.1; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.UI.Notifications==2.0.1; sys_platform == 'win32' and python_version > '3.8'
|
||||
windows-toasts==1.2.0; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-runtime==2.1.0; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.Data.Xml.Dom==2.1.0; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.Foundation==2.1.0; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.Foundation.Collections==2.1.0; sys_platform == 'win32' and python_version > '3.8'
|
||||
winrt-Windows.UI.Notifications==2.1.0; sys_platform == 'win32' and python_version > '3.8'
|
||||
|
||||
# macOS system calls
|
||||
pyobjc-core==10.2; sys_platform == 'darwin'
|
||||
pyobjc-framework-Cocoa==10.2; sys_platform == 'darwin'
|
||||
pyobjc-core==10.3.1; sys_platform == 'darwin'
|
||||
pyobjc-framework-Cocoa==10.3.1; sys_platform == 'darwin'
|
||||
|
||||
# Linux notifications
|
||||
notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
|
||||
|
||||
# Apprise Requirements
|
||||
requests==2.31.0
|
||||
requests==2.32.3
|
||||
requests-oauthlib==2.0.0
|
||||
PyYAML==6.0.1
|
||||
PyYAML==6.0.2
|
||||
markdown==3.6
|
||||
paho-mqtt==1.6.1 # Pinned, newer versions don't work with AppRise yet
|
||||
|
||||
# Requests Requirements
|
||||
charset_normalizer==3.3.2
|
||||
idna==3.7
|
||||
urllib3==2.2.1
|
||||
certifi==2024.2.2
|
||||
urllib3==2.2.2
|
||||
certifi==2024.7.4
|
||||
oauthlib==3.2.2
|
||||
PyJWT==2.8.0
|
||||
PyJWT==2.9.0
|
||||
blinker==1.8.2
|
||||
|
||||
# Optional support for *nix tray icon.
|
||||
|
||||
@@ -37,7 +37,7 @@ KERNEL32 = LIBC = MACOSLIBC = None
|
||||
|
||||
if os.name == "nt":
|
||||
WIN32 = True
|
||||
WIN64 = platform.uname().machine == "AMD64"
|
||||
WIN64 = platform.uname().machine in ["AMD64", "ARM64"] # includes emulation of X86_64 on Windows ARM64
|
||||
from sabnzbd.utils.apireg import del_connection_info
|
||||
|
||||
try:
|
||||
@@ -245,8 +245,8 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
|
||||
|
||||
# Set call backs for Config items
|
||||
cfg.cache_limit.callback(cfg.new_limit)
|
||||
cfg.cherryhost.callback(cfg.guard_restart)
|
||||
cfg.cherryport.callback(cfg.guard_restart)
|
||||
cfg.web_host.callback(cfg.guard_restart)
|
||||
cfg.web_port.callback(cfg.guard_restart)
|
||||
cfg.web_dir.callback(cfg.guard_restart)
|
||||
cfg.web_color.callback(cfg.guard_restart)
|
||||
cfg.username.callback(cfg.guard_restart)
|
||||
|
||||
@@ -54,7 +54,7 @@ from sabnzbd.constants import (
|
||||
AddNzbFileResult,
|
||||
PP_LOOKUP,
|
||||
STAGES,
|
||||
DEF_TEST_TIMEOUT,
|
||||
DEF_NETWORKING_TEST_TIMEOUT,
|
||||
)
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
@@ -76,6 +76,7 @@ from sabnzbd.misc import (
|
||||
change_queue_complete_action,
|
||||
clean_comma_separated_list,
|
||||
match_str,
|
||||
bool_conv,
|
||||
)
|
||||
from sabnzbd.filesystem import diskspace, get_ext, clip_path, remove_all, list_scripts, purge_log_files, pathbrowser
|
||||
from sabnzbd.encoding import xml_name, utob
|
||||
@@ -180,7 +181,7 @@ def _api_queue_delete(value: str, kwargs: Dict[str, Union[str, List[str]]]) -> b
|
||||
removed = sabnzbd.NzbQueue.remove_all(kwargs.get("search"))
|
||||
return report(keyword="", data={"status": bool(removed), "nzo_ids": removed})
|
||||
elif items := clean_comma_separated_list(value):
|
||||
delete_all_data = int_conv(kwargs.get("del_files"))
|
||||
delete_all_data = bool_conv(kwargs.get("del_files"))
|
||||
removed = sabnzbd.NzbQueue.remove_multiple(items, delete_all_data=delete_all_data)
|
||||
return report(keyword="", data={"status": bool(removed), "nzo_ids": removed})
|
||||
else:
|
||||
@@ -422,8 +423,8 @@ def _api_change_opts(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> byt
|
||||
def _api_fullstatus(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
|
||||
"""API: full history status"""
|
||||
status = build_status(
|
||||
calculate_performance=int_conv(kwargs.get("calculate_performance", 0)),
|
||||
skip_dashboard=int_conv(kwargs.get("skip_dashboard", 1)),
|
||||
calculate_performance=bool_conv(kwargs.get("calculate_performance")),
|
||||
skip_dashboard=bool_conv(kwargs.get("skip_dashboard")),
|
||||
)
|
||||
return report(keyword="status", data=status)
|
||||
|
||||
@@ -487,7 +488,7 @@ def _api_history(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
|
||||
search = kwargs.get("search")
|
||||
categories = clean_comma_separated_list(kwargs.get("cat") or kwargs.get("category"))
|
||||
statuses = clean_comma_separated_list(kwargs.get("status"))
|
||||
failed_only = int_conv(kwargs.get("failed_only"))
|
||||
failed_only = bool_conv(kwargs.get("failed_only"))
|
||||
nzo_ids = clean_comma_separated_list(kwargs.get("nzo_ids"))
|
||||
|
||||
archive = True
|
||||
@@ -498,7 +499,7 @@ def _api_history(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
|
||||
archive = False
|
||||
|
||||
special = value.lower()
|
||||
del_files = bool(int_conv(kwargs.get("del_files")))
|
||||
del_files = bool_conv(kwargs.get("del_files"))
|
||||
if special in ("all", "failed", "completed"):
|
||||
history_db = sabnzbd.get_db_connection()
|
||||
if special in ("all", "failed"):
|
||||
@@ -868,9 +869,9 @@ def _api_undefined(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes
|
||||
|
||||
def _api_browse(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
|
||||
"""Return tree of local path"""
|
||||
compact = bool(int_conv(kwargs.get("compact")))
|
||||
show_files = bool(int_conv(kwargs.get("show_files")))
|
||||
show_hidden = bool(int_conv(kwargs.get("show_hidden_folders")))
|
||||
compact = bool_conv(kwargs.get("compact"))
|
||||
show_files = bool_conv(kwargs.get("show_files"))
|
||||
show_hidden = bool_conv(kwargs.get("show_hidden_folders"))
|
||||
|
||||
if compact:
|
||||
# Used for typeahead
|
||||
@@ -1267,7 +1268,7 @@ def test_nntp_server_dict(kwargs: Dict[str, Union[str, List[str]]]) -> Tuple[boo
|
||||
password = kwargs.get("password", "").strip()
|
||||
server = kwargs.get("server", "").strip()
|
||||
connections = int_conv(kwargs.get("connections", 0))
|
||||
timeout = int_conv(kwargs.get("timeout", DEF_TEST_TIMEOUT))
|
||||
timeout = int_conv(kwargs.get("timeout", DEF_NETWORKING_TEST_TIMEOUT))
|
||||
ssl = int_conv(kwargs.get("ssl", 0))
|
||||
ssl_verify = int_conv(kwargs.get("ssl_verify", 1))
|
||||
ssl_ciphers = kwargs.get("ssl_ciphers", "").strip()
|
||||
@@ -1286,7 +1287,7 @@ def test_nntp_server_dict(kwargs: Dict[str, Union[str, List[str]]]) -> Tuple[boo
|
||||
|
||||
if not timeout:
|
||||
# Lower value during new server testing
|
||||
timeout = DEF_TEST_TIMEOUT
|
||||
timeout = DEF_NETWORKING_TEST_TIMEOUT
|
||||
|
||||
if "*" in password and not password.strip("*"):
|
||||
# If the password is masked, try retrieving it from the config
|
||||
@@ -1318,7 +1319,7 @@ def test_nntp_server_dict(kwargs: Dict[str, Union[str, List[str]]]) -> Tuple[boo
|
||||
if not test_server.addrinfo:
|
||||
# Try if we can connect on port 80 (so web server), forcing a short timeout
|
||||
test_server.port = 80
|
||||
test_server.timeout = DEF_TEST_TIMEOUT
|
||||
test_server.timeout = DEF_NETWORKING_TEST_TIMEOUT
|
||||
test_server.request_addrinfo_blocking()
|
||||
if test_server.addrinfo:
|
||||
return False, T(
|
||||
@@ -1384,7 +1385,7 @@ def test_nntp_server_dict(kwargs: Dict[str, Union[str, List[str]]]) -> Tuple[boo
|
||||
return return_status
|
||||
|
||||
|
||||
def build_status(calculate_performance: int = False, skip_dashboard: int = False) -> Dict[str, Any]:
|
||||
def build_status(calculate_performance: bool = False, skip_dashboard: bool = False) -> Dict[str, Any]:
|
||||
# build up header full of basic information
|
||||
info = build_header(trans_functions=False)
|
||||
|
||||
|
||||
@@ -309,8 +309,8 @@ version_check = OptionNumber("misc", "check_new_rel", 1)
|
||||
autobrowser = OptionBool("misc", "auto_browser", True)
|
||||
language = OptionStr("misc", "language", "en")
|
||||
enable_https_verification = OptionBool("misc", "enable_https_verification", True)
|
||||
cherryhost = OptionStr("misc", "host", DEF_HOST, validation=validate_host)
|
||||
cherryport = OptionStr("misc", "port", DEF_PORT)
|
||||
web_host = OptionStr("misc", "host", DEF_HOST, validation=validate_host)
|
||||
web_port = OptionStr("misc", "port", DEF_PORT)
|
||||
https_port = OptionStr("misc", "https_port")
|
||||
username = OptionStr("misc", "username")
|
||||
password = OptionPassword("misc", "password")
|
||||
|
||||
@@ -205,7 +205,7 @@ class OptionBool(Option):
|
||||
|
||||
def set(self, value: Any):
|
||||
# Store the value as integer, easier to parse when reading the config.
|
||||
super().set(sabnzbd.misc.int_conv(value))
|
||||
super().set(sabnzbd.misc.bool_conv(value))
|
||||
|
||||
def __call__(self) -> int:
|
||||
"""get() replacement"""
|
||||
|
||||
@@ -49,7 +49,7 @@ RENAMES_FILE = "__renames__"
|
||||
ATTRIB_FILE = "SABnzbd_attrib"
|
||||
REPAIR_REQUEST = "repair-all.sab"
|
||||
|
||||
SABCTOOLS_VERSION_REQUIRED = "8.2.0"
|
||||
SABCTOOLS_VERSION_REQUIRED = "8.2.5"
|
||||
|
||||
DB_HISTORY_VERSION = 1
|
||||
DB_HISTORY_NAME = "history%s.db" % DB_HISTORY_VERSION
|
||||
@@ -74,8 +74,9 @@ DEF_LOG_ERRFILE = "sabnzbd.error.log"
|
||||
DEF_LOG_CHERRY = "cherrypy.log"
|
||||
DEF_ARTICLE_CACHE_DEFAULT = "500M"
|
||||
DEF_ARTICLE_CACHE_MAX = "1G"
|
||||
DEF_TIMEOUT = 60
|
||||
DEF_TEST_TIMEOUT = 5
|
||||
DEF_NETWORKING_TIMEOUT = 60
|
||||
DEF_NETWORKING_TEST_TIMEOUT = 5
|
||||
DEF_NETWORKING_SHORT_TIMEOUT = 3
|
||||
DEF_SCANRATE = 5
|
||||
DEF_HTTPS_CERT_FILE = "server.cert"
|
||||
DEF_HTTPS_KEY_FILE = "server.key"
|
||||
@@ -100,6 +101,7 @@ SOFT_QUEUE_LIMIT = 0.5
|
||||
# Percentage of cache to use before adding file to assembler
|
||||
ASSEMBLER_WRITE_THRESHOLD = 5
|
||||
NNTP_BUFFER_SIZE = int(800 * KIBI)
|
||||
NTTP_MAX_BUFFER_SIZE = int(10 * MEBI)
|
||||
|
||||
REPAIR_PRIORITY = 3
|
||||
FORCE_PRIORITY = 2
|
||||
|
||||
@@ -35,7 +35,7 @@ from sabnzbd.constants import DB_HISTORY_NAME, STAGES, Status, PP_LOOKUP
|
||||
from sabnzbd.bpsmeter import this_week, this_month
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.encoding import ubtou, utob
|
||||
from sabnzbd.misc import int_conv, caller_name, opts_to_pp, to_units
|
||||
from sabnzbd.misc import caller_name, opts_to_pp, to_units, bool_conv
|
||||
from sabnzbd.filesystem import remove_file, clip_path
|
||||
|
||||
DB_LOCK = threading.Lock()
|
||||
@@ -596,7 +596,7 @@ def unpack_history_info(item: sqlite3.Row) -> Dict[str, Any]:
|
||||
item["archive"] = bool(item["archive"])
|
||||
|
||||
# Retry and retry for failed URL-fetch
|
||||
item["retry"] = int_conv(item["status"] == Status.FAILED and item["path"] and os.path.exists(item["path"]))
|
||||
item["retry"] = bool_conv(item["status"] == Status.FAILED and item["path"] and os.path.exists(item["path"]))
|
||||
if item["report"] == "future":
|
||||
item["retry"] = True
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ class Server:
|
||||
def reset_article_queue(self):
|
||||
logging.debug("Resetting article queue for %s", self)
|
||||
for article in self.article_queue:
|
||||
sabnzbd.NzbQueue.reset_try_lists(article, remove_fetcher_from_trylist=False)
|
||||
sabnzbd.NzbQueue.reset_try_lists(article)
|
||||
self.article_queue = []
|
||||
|
||||
def request_addrinfo(self):
|
||||
@@ -510,8 +510,8 @@ class Downloader(Thread):
|
||||
# Handle broken articles directly
|
||||
if not data_view:
|
||||
if not article.search_new_server():
|
||||
sabnzbd.NzbQueue.register_article(article, success=False)
|
||||
article.nzf.nzo.increase_bad_articles_counter("missing_articles")
|
||||
sabnzbd.NzbQueue.register_article(article, success=False)
|
||||
return
|
||||
|
||||
# Decode and send to article cache
|
||||
@@ -704,9 +704,14 @@ class Downloader(Thread):
|
||||
except ssl.SSLWantReadError:
|
||||
return
|
||||
except (ConnectionError, ConnectionAbortedError):
|
||||
# The ConnectionAbortedError is thrown by sabctools in case of fatal SSL-layer problems
|
||||
# The ConnectionAbortedError is also thrown by sabctools in case of fatal SSL-layer problems
|
||||
self.__reset_nw(nw, "Server closed connection", wait=False)
|
||||
return
|
||||
except BufferError:
|
||||
# The BufferError is thrown when exceeding maximum buffer size
|
||||
# Make sure to discard the article
|
||||
self.__reset_nw(nw, "Maximum data buffer size exceeded", wait=False, retry_article=False)
|
||||
return
|
||||
|
||||
article = nw.article
|
||||
server = nw.server
|
||||
@@ -960,14 +965,9 @@ class Downloader(Thread):
|
||||
self.decode(nw.article)
|
||||
nw.article.tries = 0
|
||||
else:
|
||||
# Retry again with the same server
|
||||
logging.debug(
|
||||
"Re-adding article %s from %s to server %s",
|
||||
nw.article.article,
|
||||
nw.article.nzf.filename,
|
||||
nw.article.fetcher,
|
||||
)
|
||||
nw.article.fetcher.article_queue.append(nw.article)
|
||||
# Allow all servers again on this server
|
||||
# Do not use the article_queue, as the server could already have been disabled when we get here!
|
||||
sabnzbd.NzbQueue.reset_try_lists(nw.article)
|
||||
|
||||
# Reset connection object
|
||||
nw.hard_reset(wait)
|
||||
|
||||
@@ -25,7 +25,7 @@ import socket
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Callable
|
||||
from typing import Callable, Optional
|
||||
|
||||
import socks
|
||||
|
||||
@@ -33,9 +33,10 @@ import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.encoding import ubtou
|
||||
from sabnzbd.happyeyeballs import happyeyeballs, family_type
|
||||
from sabnzbd.constants import DEF_NETWORKING_SHORT_TIMEOUT
|
||||
|
||||
|
||||
def timeout(max_timeout: float):
|
||||
def timeout(max_timeout: int):
|
||||
"""Timeout decorator, parameter in seconds."""
|
||||
|
||||
def timeout_decorator(item: Callable) -> Callable:
|
||||
@@ -56,29 +57,29 @@ def timeout(max_timeout: float):
|
||||
return timeout_decorator
|
||||
|
||||
|
||||
@timeout(3.0)
|
||||
@timeout(DEF_NETWORKING_SHORT_TIMEOUT)
|
||||
def addresslookup(myhost):
|
||||
return socket.getaddrinfo(myhost, 80)
|
||||
|
||||
|
||||
@timeout(3.0)
|
||||
@timeout(DEF_NETWORKING_SHORT_TIMEOUT)
|
||||
def addresslookup4(myhost):
|
||||
return socket.getaddrinfo(myhost, 80, socket.AF_INET)
|
||||
|
||||
|
||||
@timeout(3.0)
|
||||
@timeout(DEF_NETWORKING_SHORT_TIMEOUT)
|
||||
def addresslookup6(myhost):
|
||||
return socket.getaddrinfo(myhost, 80, socket.AF_INET6)
|
||||
|
||||
|
||||
def active_socks5_proxy():
|
||||
def active_socks5_proxy() -> Optional[str]:
|
||||
"""Return the active proxy"""
|
||||
if socket.socket == socks.socksocket:
|
||||
return "%s:%s" % socks.socksocket.default_proxy[1:3]
|
||||
return None
|
||||
|
||||
|
||||
def dnslookup():
|
||||
def dnslookup() -> bool:
|
||||
"""Perform a basic DNS lookup"""
|
||||
start = time.time()
|
||||
try:
|
||||
@@ -90,7 +91,7 @@ def dnslookup():
|
||||
return result
|
||||
|
||||
|
||||
def local_ipv4():
|
||||
def local_ipv4() -> Optional[str]:
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
|
||||
# Option: use 100.64.1.1 (IANA-Reserved IPv4 Prefix for Shared Address Space)
|
||||
@@ -103,12 +104,17 @@ def local_ipv4():
|
||||
return ipv4
|
||||
|
||||
|
||||
def public_ip(family=socket.AF_UNSPEC):
|
||||
def public_ip(family: int = socket.AF_UNSPEC) -> Optional[str]:
|
||||
"""
|
||||
Reports the client's public IP address (IPv4 or IPv6, if specified by family), as reported by selftest host
|
||||
"""
|
||||
start = time.time()
|
||||
if resolvehostaddress := happyeyeballs(sabnzbd.cfg.selftest_host(), port=443, family=family):
|
||||
if resolvehostaddress := happyeyeballs(
|
||||
sabnzbd.cfg.selftest_host(),
|
||||
port=443,
|
||||
timeout=DEF_NETWORKING_SHORT_TIMEOUT,
|
||||
family=family,
|
||||
):
|
||||
resolvehostip = resolvehostaddress.ipaddress
|
||||
else:
|
||||
logging.debug("Error resolving my IP address: resolvehost not found")
|
||||
@@ -126,7 +132,7 @@ def public_ip(family=socket.AF_UNSPEC):
|
||||
req = urllib.request.Request(resolveurl)
|
||||
req.add_header("Host", sabnzbd.cfg.selftest_host())
|
||||
req.add_header("User-Agent", "SABnzbd/%s" % sabnzbd.__version__)
|
||||
with urllib.request.urlopen(req, timeout=2) as open_req:
|
||||
with urllib.request.urlopen(req, timeout=DEF_NETWORKING_SHORT_TIMEOUT) as open_req:
|
||||
client_ip = ubtou(open_req.read().strip())
|
||||
|
||||
# Make sure it's a valid IPv4 or IPv6 address
|
||||
@@ -146,11 +152,11 @@ def public_ip(family=socket.AF_UNSPEC):
|
||||
return client_ip
|
||||
|
||||
|
||||
def public_ipv4():
|
||||
def public_ipv4() -> Optional[str]:
|
||||
return public_ip(family=socket.AF_INET)
|
||||
|
||||
|
||||
def local_ipv6():
|
||||
def local_ipv6() -> Optional[str]:
|
||||
"""
|
||||
return IPv6 address on local LAN interface. So a first check if there is IPv6 connectivity
|
||||
"""
|
||||
@@ -167,8 +173,8 @@ def local_ipv6():
|
||||
return ipv6_address
|
||||
|
||||
|
||||
def public_ipv6():
|
||||
if local_address := local_ipv6():
|
||||
def public_ipv6() -> Optional[str]:
|
||||
if (local_address := local_ipv6()) and not sabnzbd.misc.ip_in_subnet(local_address, "fe80::/10"):
|
||||
if public_address := public_ip(family=socket.AF_INET6):
|
||||
return public_address
|
||||
elif not sabnzbd.misc.is_lan_addr(local_address):
|
||||
|
||||
@@ -34,7 +34,7 @@ from typing import Tuple, Union, Optional
|
||||
from more_itertools import roundrobin
|
||||
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.constants import DEF_TIMEOUT
|
||||
from sabnzbd.constants import DEF_NETWORKING_TIMEOUT
|
||||
from sabnzbd.decorators import cache_maintainer
|
||||
|
||||
# How long to delay between connection attempts? The RFC suggests 250ms, but this is
|
||||
@@ -118,7 +118,12 @@ def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo, timeout: in
|
||||
|
||||
@cache_maintainer(clear_time=10)
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def happyeyeballs(host: str, port: int, timeout: int = DEF_TIMEOUT, family=socket.AF_UNSPEC) -> Optional[AddrInfo]:
|
||||
def happyeyeballs(
|
||||
host: str,
|
||||
port: int,
|
||||
timeout: int = DEF_NETWORKING_TIMEOUT,
|
||||
family=socket.AF_UNSPEC,
|
||||
) -> Optional[AddrInfo]:
|
||||
"""Return the fastest result of getaddrinfo() based on RFC 6555/8305 (Happy Eyeballs),
|
||||
including IPv6 addresses if desired. Returns None in case no addresses were returned
|
||||
by getaddrinfo or if no connection could be made to any of the addresses.
|
||||
|
||||
@@ -80,7 +80,7 @@ from sabnzbd.constants import (
|
||||
GUESSIT_SORT_TYPES,
|
||||
VALID_NZB_FILES,
|
||||
VALID_ARCHIVES,
|
||||
DEF_TEST_TIMEOUT,
|
||||
DEF_NETWORKING_TEST_TIMEOUT,
|
||||
)
|
||||
from sabnzbd.lang import list_languages
|
||||
from sabnzbd.api import (
|
||||
@@ -577,7 +577,7 @@ class Wizard:
|
||||
def get_access_info():
|
||||
"""Build up a list of url's that sabnzbd can be accessed from"""
|
||||
# Access_url is used to provide the user a link to SABnzbd depending on the host
|
||||
cherryhost = cfg.cherryhost()
|
||||
web_host = cfg.web_host()
|
||||
host = socket.gethostname().lower()
|
||||
logging.info("hostname is", host)
|
||||
socks = [host]
|
||||
@@ -587,7 +587,7 @@ def get_access_info():
|
||||
except:
|
||||
addresses = []
|
||||
|
||||
if cherryhost == "0.0.0.0":
|
||||
if web_host == "0.0.0.0":
|
||||
# Grab a list of all ips for the hostname
|
||||
for addr in addresses:
|
||||
address = addr[4][0]
|
||||
@@ -595,7 +595,7 @@ def get_access_info():
|
||||
if ":" not in address and address not in socks:
|
||||
socks.append(address)
|
||||
socks.insert(0, "localhost")
|
||||
elif cherryhost == "::":
|
||||
elif web_host == "::":
|
||||
# Grab a list of all ips for the hostname
|
||||
for addr in addresses:
|
||||
address = addr[4][0]
|
||||
@@ -605,8 +605,8 @@ def get_access_info():
|
||||
if address not in socks:
|
||||
socks.append(address)
|
||||
socks.insert(0, "localhost")
|
||||
elif cherryhost:
|
||||
socks = [cherryhost]
|
||||
elif web_host:
|
||||
socks = [web_host]
|
||||
|
||||
# Add the current requested URL as the base
|
||||
access_url = urllib.parse.urljoin(cherrypy.request.base, cfg.url_base())
|
||||
@@ -617,9 +617,9 @@ def get_access_info():
|
||||
if cfg.enable_https() and cfg.https_port():
|
||||
url = "https://%s:%s%s" % (sock, cfg.https_port(), cfg.url_base())
|
||||
elif cfg.enable_https():
|
||||
url = "https://%s:%s%s" % (sock, cfg.cherryport(), cfg.url_base())
|
||||
url = "https://%s:%s%s" % (sock, cfg.web_port(), cfg.url_base())
|
||||
else:
|
||||
url = "http://%s:%s%s" % (sock, cfg.cherryport(), cfg.url_base())
|
||||
url = "http://%s:%s%s" % (sock, cfg.web_port(), cfg.url_base())
|
||||
urls.append(url)
|
||||
|
||||
# Return a unique list
|
||||
@@ -1144,7 +1144,9 @@ def handle_server(kwargs, root=None, new_svr=False):
|
||||
kwargs["connections"] = "1"
|
||||
|
||||
if kwargs.get("enable") == "1":
|
||||
if not happyeyeballs(host, int_conv(port), int_conv(kwargs.get("timeout"), default=DEF_TEST_TIMEOUT)):
|
||||
if not happyeyeballs(
|
||||
host, int_conv(port), int_conv(kwargs.get("timeout"), default=DEF_NETWORKING_TEST_TIMEOUT)
|
||||
):
|
||||
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port), ajax)
|
||||
|
||||
# Default server name is just the host name
|
||||
@@ -2156,8 +2158,10 @@ class ConfigNotify:
|
||||
|
||||
for section in NOTIFY_OPTIONS:
|
||||
for option in NOTIFY_OPTIONS[section]:
|
||||
# Use get_string to make sure lists are displayed correctly
|
||||
conf[option] = config.get_config(section, option).get_string()
|
||||
conf[option] = config.get_config(section, option)()
|
||||
|
||||
# Use get_string to make sure lists are displayed correctly
|
||||
conf["email_to"] = cfg.email_to.get_string()
|
||||
|
||||
return template_filtered_response(
|
||||
file=os.path.join(sabnzbd.WEB_DIR_CONFIG, "config_notify.tmpl"),
|
||||
|
||||
@@ -30,6 +30,7 @@ from typing import Dict
|
||||
|
||||
import sabctools
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import DEF_NETWORKING_SHORT_TIMEOUT
|
||||
from sabnzbd.happyeyeballs import happyeyeballs, family_type
|
||||
|
||||
TEST_HOSTNAME = "sabnzbd.org"
|
||||
@@ -37,7 +38,6 @@ TEST_PORT = 443
|
||||
TEST_FILE = "/tests/internetspeed/100MB.bin"
|
||||
TEST_FILE_SIZE = 100 * 1024 * 1024
|
||||
TEST_REQUEST = f"GET {TEST_FILE} HTTP/1.1\nHost: {TEST_HOSTNAME}\nUser-Agent: SABnzbd/{sabnzbd.__version__}\n\n"
|
||||
SOCKET_TIMEOUT = 3
|
||||
BUFFER_SIZE = 5 * 1024 * 1024 # Each connection will allocate its own buffer, so mind the memory usage!
|
||||
|
||||
NR_CONNECTIONS = 5
|
||||
@@ -75,33 +75,45 @@ def internetspeed_worker(secure_sock: ssl.SSLSocket, socket_speed: Dict[ssl.SSLS
|
||||
pass
|
||||
|
||||
|
||||
def internetspeed_interal(test_time_limit: int = TIME_LIMIT, family: int = socket.AF_UNSPEC) -> float:
|
||||
def internetspeed_interal(family: int = socket.AF_UNSPEC) -> float:
|
||||
"""Measure internet speed from a test-download using our optimized SSL-code"""
|
||||
|
||||
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
socket_speed = {}
|
||||
|
||||
try:
|
||||
addrinfo = happyeyeballs(TEST_HOSTNAME, TEST_PORT, SOCKET_TIMEOUT, family)
|
||||
if not (addrinfo := happyeyeballs(TEST_HOSTNAME, TEST_PORT, DEF_NETWORKING_SHORT_TIMEOUT, family)):
|
||||
# no addrinfo from happyeyeballs, so no connection was possible
|
||||
return 0.0 # no speed at all
|
||||
|
||||
for _ in range(NR_CONNECTIONS):
|
||||
sock = socket.socket(addrinfo.family, addrinfo.type)
|
||||
sock.settimeout(SOCKET_TIMEOUT)
|
||||
sock.settimeout(DEF_NETWORKING_SHORT_TIMEOUT)
|
||||
sock.connect(addrinfo.sockaddr)
|
||||
secure_sock = context.wrap_socket(sock, server_hostname=TEST_HOSTNAME)
|
||||
secure_sock.setblocking(False)
|
||||
socket_speed[secure_sock] = 0
|
||||
|
||||
for secure_sock in socket_speed:
|
||||
threading.Thread(target=internetspeed_worker, args=(secure_sock, socket_speed), daemon=True).start()
|
||||
threading.Thread(
|
||||
target=internetspeed_worker,
|
||||
args=(secure_sock, socket_speed),
|
||||
daemon=True,
|
||||
).start()
|
||||
except Exception:
|
||||
logging.info("Internet Bandwidth connection failure", exc_info=True)
|
||||
return 0.0
|
||||
|
||||
# We let the workers finish
|
||||
time.sleep(test_time_limit + 0.5)
|
||||
time.sleep(TIME_LIMIT + 0.5)
|
||||
|
||||
speed = sum(socket_speed.values()) / 1024 / 1024
|
||||
logging.debug("Internet Bandwidth (%s) = %.2f MB/s - %.2f Mbps", family_type(family), speed, speed * 8.05)
|
||||
logging.debug(
|
||||
"Internet Bandwidth (%s) = %.2f MB/s - %.2f Mbps",
|
||||
family_type(family),
|
||||
speed,
|
||||
speed * 8.05,
|
||||
)
|
||||
return speed
|
||||
|
||||
|
||||
|
||||
@@ -167,10 +167,9 @@ def calc_age(date: datetime.datetime, trans: bool = False) -> str:
|
||||
|
||||
def safe_lower(txt: Any) -> str:
|
||||
"""Return lowercased string. Return '' for None"""
|
||||
if txt:
|
||||
if txt := str_conv(txt):
|
||||
return txt.lower()
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
|
||||
def is_none(inp: Any) -> bool:
|
||||
@@ -219,17 +218,18 @@ def cat_pp_script_sanitizer(
|
||||
script: Optional[str] = None,
|
||||
) -> Tuple[Optional[Union[int, str]], Optional[str], Optional[str]]:
|
||||
"""Basic sanitizer from outside input to a bit more predictable values"""
|
||||
# * and Default are valid values
|
||||
if safe_lower(cat) in ("", "none"):
|
||||
cat = None
|
||||
|
||||
# Cannot use "not pp" because pp can also be 0
|
||||
if pp in ("", "-1"):
|
||||
if safe_lower(pp) in ("", "-1", "none"):
|
||||
pp = None
|
||||
|
||||
# Check for valid script is performed in NzbObject init
|
||||
if not script or script.lower() == "default":
|
||||
if not script or safe_lower(script) == "default":
|
||||
script = None
|
||||
|
||||
if not cat or cat.lower() in ("default", "*"):
|
||||
cat = None
|
||||
|
||||
return cat, pp, script
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ def cat_to_opts(cat, pp=None, script=None, priority=None) -> Tuple[str, int, str
|
||||
def pp_to_opts(pp: Optional[int]) -> Tuple[bool, bool, bool]:
|
||||
"""Convert numeric processing options to (repair, unpack, delete)"""
|
||||
# Convert the pp to an int
|
||||
pp = sabnzbd.interface.int_conv(pp)
|
||||
pp = int_conv(pp)
|
||||
if pp == 0:
|
||||
return False, False, False
|
||||
if pp == 1:
|
||||
@@ -882,7 +882,18 @@ def format_time_string(seconds: float) -> str:
|
||||
return " ".join(completestr)
|
||||
|
||||
|
||||
def int_conv(value: Any, default: Any = 0) -> int:
|
||||
def str_conv(value: Any, default: str = "") -> str:
|
||||
"""Safe conversion to str (None will be converted to empty string)
|
||||
Returns empty string or requested default value"""
|
||||
if value is None:
|
||||
return default
|
||||
try:
|
||||
return str(value)
|
||||
except:
|
||||
return default
|
||||
|
||||
|
||||
def int_conv(value: Any, default: int = 0) -> int:
|
||||
"""Safe conversion to int (can handle None)
|
||||
Returns 0 or requested default value"""
|
||||
try:
|
||||
@@ -891,6 +902,12 @@ def int_conv(value: Any, default: Any = 0) -> int:
|
||||
return default
|
||||
|
||||
|
||||
def bool_conv(value: Any) -> bool:
|
||||
"""Safe conversion to bool (can handle None)
|
||||
Returns False in case of None or non-convertable value"""
|
||||
return bool(int_conv(value))
|
||||
|
||||
|
||||
def create_https_certificates(ssl_cert, ssl_key):
|
||||
"""Create self-signed HTTPS certificates and store in paths 'ssl_cert' and 'ssl_key'"""
|
||||
try:
|
||||
|
||||
@@ -30,14 +30,14 @@ from typing import Optional, Tuple, Union
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.constants import DEF_TIMEOUT, NNTP_BUFFER_SIZE
|
||||
from sabnzbd.constants import DEF_NETWORKING_TIMEOUT, NNTP_BUFFER_SIZE, NTTP_MAX_BUFFER_SIZE
|
||||
from sabnzbd.encoding import utob, ubtou
|
||||
from sabnzbd.happyeyeballs import AddrInfo
|
||||
from sabnzbd.decorators import synchronized, DOWNLOADER_LOCK
|
||||
from sabnzbd.misc import int_conv
|
||||
|
||||
# Set pre-defined socket timeout
|
||||
socket.setdefaulttimeout(DEF_TIMEOUT)
|
||||
socket.setdefaulttimeout(DEF_NETWORKING_TIMEOUT)
|
||||
|
||||
|
||||
class NNTPPermanentError(Exception):
|
||||
@@ -230,6 +230,10 @@ class NewsWrapper:
|
||||
|
||||
def increase_data_buffer(self):
|
||||
"""Resize the buffer in the extremely unlikely case that it overflows"""
|
||||
# Sanity check before we go any further
|
||||
if len(self.data) > NTTP_MAX_BUFFER_SIZE:
|
||||
raise BufferError("Maximum data buffer size exceeded")
|
||||
|
||||
# Input needs to be integer, floats don't work
|
||||
new_buffer = sabctools.bytearray_malloc(len(self.data) + NNTP_BUFFER_SIZE // 2)
|
||||
new_buffer[: len(self.data)] = self.data
|
||||
|
||||
@@ -873,11 +873,10 @@ class NzbQueue:
|
||||
def stop_idle_jobs(self):
|
||||
"""Detect jobs that have zero files left and send them to post processing"""
|
||||
# Only check servers that are active
|
||||
active_servers = [server for server in sabnzbd.Downloader.servers[:] if server.active]
|
||||
nr_servers = len(active_servers)
|
||||
active_servers = set(server for server in sabnzbd.Downloader.servers[:] if server.active)
|
||||
empty = []
|
||||
|
||||
if nr_servers <= 0:
|
||||
if len(active_servers) <= 0:
|
||||
logging.debug("Skipping stop_idle_jobs because no servers are active")
|
||||
return
|
||||
|
||||
@@ -888,18 +887,19 @@ class NzbQueue:
|
||||
|
||||
# Stall prevention by checking if all servers are in the trylist
|
||||
# This is a CPU-cheaper alternative to prevent stalling
|
||||
if len(nzo.try_list) >= nr_servers:
|
||||
if nzo.all_servers_in_try_list(active_servers):
|
||||
# Maybe the NZF's need a reset too?
|
||||
for nzf in nzo.files:
|
||||
if nzo.removed_from_queue:
|
||||
break
|
||||
|
||||
if len(nzf.try_list) >= nr_servers:
|
||||
if nzf.all_servers_in_try_list(active_servers):
|
||||
# Check for articles where all active servers have already been tried
|
||||
for article in nzf.articles[:]:
|
||||
if article.all_servers_in_try_list(active_servers):
|
||||
sabnzbd.NzbQueue.register_article(article, success=False)
|
||||
logging.debug("Removing article %s with bad trylist in file %s", article, nzf.filename)
|
||||
nzo.increase_bad_articles_counter("missing_articles")
|
||||
sabnzbd.NzbQueue.register_article(article, success=False)
|
||||
|
||||
logging.info("Resetting bad trylist for file %s in job %s", nzf.filename, nzo.final_name)
|
||||
nzf.reset_try_list()
|
||||
|
||||
@@ -129,13 +129,10 @@ class TryList:
|
||||
with TRYLIST_LOCK:
|
||||
return server in self.try_list
|
||||
|
||||
def all_servers_in_try_list(self, servers: List[Server]) -> bool:
|
||||
def all_servers_in_try_list(self, all_servers: Set[Server]) -> bool:
|
||||
"""Check if all servers have been tried"""
|
||||
with TRYLIST_LOCK:
|
||||
for server in servers:
|
||||
if server not in self.try_list:
|
||||
return False
|
||||
return True
|
||||
return all_servers.issubset(self.try_list)
|
||||
|
||||
def add_to_try_list(self, server: Server):
|
||||
"""Register server as having been tried already"""
|
||||
@@ -836,8 +833,9 @@ class NzbObject(TryList):
|
||||
self.password = self.meta.get("password", [None])[0]
|
||||
|
||||
# Check if we expect propagation delay
|
||||
if (propagation_delay := self.avg_stamp + float(cfg.propagation_delay() * 60)) > time.time():
|
||||
self.propagation_delay = propagation_delay
|
||||
if propagation_delay := cfg.propagation_delay():
|
||||
if (propagation_delay := self.avg_stamp + float(propagation_delay * 60)) > time.time():
|
||||
self.propagation_delay = propagation_delay
|
||||
|
||||
# Run user pre-queue script if set and valid
|
||||
if not reuse and make_script_path(cfg.pre_script()):
|
||||
@@ -1594,7 +1592,8 @@ class NzbObject(TryList):
|
||||
|
||||
@synchronized(NZO_LOCK)
|
||||
def increase_bad_articles_counter(self, bad_article_type: str):
|
||||
"""Record information about bad articles"""
|
||||
"""Record information about bad articles. Should be called before
|
||||
register_article, which triggers the availability check."""
|
||||
if bad_article_type not in self.nzo_info:
|
||||
self.nzo_info[bad_article_type] = 0
|
||||
self.nzo_info[bad_article_type] += 1
|
||||
|
||||
@@ -518,8 +518,8 @@ class SABnzbdDelegate(NSObject):
|
||||
self.setMenuTitle_("\n\n%s\n" % (T("Stopping...")))
|
||||
|
||||
def restartSafeHost_(self, sender):
|
||||
sabnzbd.cfg.cherryhost.set("127.0.0.1")
|
||||
sabnzbd.cfg.cherryport.set("8080")
|
||||
sabnzbd.cfg.web_host.set("127.0.0.1")
|
||||
sabnzbd.cfg.web_port.set("8080")
|
||||
sabnzbd.cfg.enable_https.set(False)
|
||||
sabnzbd.config.save_config()
|
||||
self.setMenuTitle_("\n\n%s\n" % (T("Stopping...")))
|
||||
|
||||
@@ -281,15 +281,6 @@ class PostProcessor(Thread):
|
||||
# aren't on 24/7 and typically don't benefit from the daily scheduled call at midnight
|
||||
database.scheduled_history_purge()
|
||||
|
||||
# Check for Apprise notification script
|
||||
# We don't have a much better place to do this, since there's no notifier-thread
|
||||
# TODO: Remove in 4.4
|
||||
if sabnzbd.cfg.nscript_script() == "sabnzbd-notify.py":
|
||||
helpful_warning(
|
||||
"NZB-Notify has been integrated into SABnzbd. You can now use the "
|
||||
"Apprise section to configure the same notifications."
|
||||
)
|
||||
|
||||
# Start looping
|
||||
check_eoq = False
|
||||
while not self.__stop:
|
||||
|
||||
@@ -182,7 +182,7 @@ class SABTrayThread(SysTrayIconThread):
|
||||
sabnzbd.trigger_restart()
|
||||
|
||||
def defhost(self, icon):
|
||||
sabnzbd.cfg.cherryhost.set("127.0.0.1")
|
||||
sabnzbd.cfg.web_host.set("127.0.0.1")
|
||||
sabnzbd.cfg.enable_https.set(False)
|
||||
sabnzbd.config.save_config()
|
||||
self.hover_text = T("Restart")
|
||||
|
||||
@@ -34,7 +34,14 @@ import base64
|
||||
from typing import Tuple, Optional, Union, List, Dict, Any
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import DEF_TIMEOUT, FUTURE_Q_FOLDER, VALID_NZB_FILES, Status, VALID_ARCHIVES, DuplicateStatus
|
||||
from sabnzbd.constants import (
|
||||
DEF_NETWORKING_TIMEOUT,
|
||||
FUTURE_Q_FOLDER,
|
||||
VALID_NZB_FILES,
|
||||
Status,
|
||||
VALID_ARCHIVES,
|
||||
DuplicateStatus,
|
||||
)
|
||||
import sabnzbd.misc as misc
|
||||
import sabnzbd.filesystem
|
||||
import sabnzbd.cfg as cfg
|
||||
@@ -360,7 +367,7 @@ def _analyse(fetch_request: HTTPResponse, future_nzo: NzbObject):
|
||||
msg = ""
|
||||
|
||||
# Increasing wait-time in steps for standard errors
|
||||
when = DEF_TIMEOUT * (future_nzo.url_tries + 1)
|
||||
when = DEF_NETWORKING_TIMEOUT * (future_nzo.url_tries + 1)
|
||||
logging.debug("No usable response from indexer, retry after %s sec", when)
|
||||
return None, msg, True, when, data
|
||||
|
||||
|
||||
@@ -6,21 +6,26 @@ Functions to check if the path filesystem uses FAT
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
debug = False
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
|
||||
def getcmdoutput(cmd):
|
||||
"""execectue cmd, and give back output lines as array"""
|
||||
with os.popen(cmd) as p:
|
||||
outputlines = p.readlines()
|
||||
return outputlines
|
||||
def getcmdoutput(cmd: List[str]) -> List[str]:
|
||||
"""execute cmd, and return a list of output lines"""
|
||||
subprocess_kwargs = {
|
||||
"bufsize": 0,
|
||||
"shell": False,
|
||||
"text": True,
|
||||
"encoding": "utf8",
|
||||
"stdout": subprocess.PIPE,
|
||||
"stderr": subprocess.STDOUT,
|
||||
}
|
||||
return subprocess.run(cmd, **subprocess_kwargs).stdout.splitlines()
|
||||
|
||||
|
||||
def isFAT(check_dir):
|
||||
"""Check if "check_dir" is on FAT. FAT considered harmful (for big files)
|
||||
Works for Linux, Windows, MacOS
|
||||
NB: On Windows, full path with drive letter is needed!
|
||||
def isFAT(check_dir: str) -> bool:
|
||||
"""Check if the given directory is on FAT, which has a maximum file size of only 4 GiB.
|
||||
Works for Linux, Windows, MacOS; on Windows, a full path with drive letter is needed!
|
||||
"""
|
||||
if not (os.path.isdir(check_dir) or os.path.isfile(check_dir)):
|
||||
# Not a dir, not a file ... so not FAT:
|
||||
@@ -30,9 +35,6 @@ def isFAT(check_dir):
|
||||
# We're dealing with OS calls, so put everything in a try/except, just in case:
|
||||
try:
|
||||
if "linux" in sys.platform:
|
||||
# On Linux:
|
||||
# df -T /home/sander/weg
|
||||
|
||||
"""
|
||||
Example output of a 500GB external USB drive formatted with FAT:
|
||||
$ df -T /media/sander/INTENSO
|
||||
@@ -40,18 +42,13 @@ def isFAT(check_dir):
|
||||
/dev/sda1 vfat 488263616 163545248 324718368 34% /media/sander/INTENSO
|
||||
"""
|
||||
|
||||
dfcmd = "df -T " + check_dir + " 2>&1"
|
||||
for thisline in getcmdoutput(dfcmd):
|
||||
for thisline in getcmdoutput(["df", "-T", check_dir]):
|
||||
if thisline.find("/") == 0:
|
||||
# Starts with /, so a real, local device
|
||||
fstype = thisline.split()[1]
|
||||
if debug:
|
||||
print(("File system type:", fstype))
|
||||
if fstype.lower().find("fat") >= 0 and fstype.lower().find("exfat") < 0:
|
||||
# FAT, but not EXFAT
|
||||
FAT = True
|
||||
if debug:
|
||||
print("FAT found")
|
||||
break
|
||||
elif "win32" in sys.platform:
|
||||
import win32api
|
||||
@@ -61,14 +58,11 @@ def isFAT(check_dir):
|
||||
check_dir = check_dir.replace("\\\\?\\UNC\\", "\\\\", 1).replace("\\\\?\\", "", 1)
|
||||
try:
|
||||
result = win32api.GetVolumeInformation(os.path.splitdrive(check_dir)[0])
|
||||
if debug:
|
||||
print(result)
|
||||
if result[4].startswith("FAT"):
|
||||
FAT = True
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
elif "darwin" in sys.platform:
|
||||
# MacOS formerly known as OSX
|
||||
"""
|
||||
MacOS needs a two-step approach:
|
||||
|
||||
@@ -80,35 +74,37 @@ def isFAT(check_dir):
|
||||
# Then: device => filesystem type
|
||||
server:~ sander$ mount | grep /dev/disk9s1
|
||||
/dev/disk9s1 on /Volumes/CARTUNES (msdos, local, nodev, nosuid, noowners)
|
||||
|
||||
|
||||
"""
|
||||
dfcmd = "df " + check_dir
|
||||
for thisline in getcmdoutput(dfcmd):
|
||||
for thisline in getcmdoutput(["df", check_dir]):
|
||||
if thisline.find("/") == 0:
|
||||
if debug:
|
||||
print(thisline)
|
||||
# Starts with /, so a real, local device
|
||||
device = thisline.split()[0]
|
||||
mountcmd = "mount | grep " + device
|
||||
mountoutput = os.popen(mountcmd).readline().strip()
|
||||
if debug:
|
||||
print(mountoutput)
|
||||
|
||||
# Run the equivalent of "mount | grep $device"
|
||||
p_mount = subprocess.Popen(["mount"], stdout=subprocess.PIPE)
|
||||
p_grep = subprocess.Popen(
|
||||
["grep", device + "[[:space:]]"],
|
||||
stdin=p_mount.stdout,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
encoding="utf8",
|
||||
)
|
||||
p_mount.stdout.close()
|
||||
mountoutput = p_grep.communicate()[0].strip()
|
||||
|
||||
if "msdos" in mountoutput.split("(")[1]:
|
||||
FAT = True
|
||||
break
|
||||
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
return FAT
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if debug:
|
||||
print((sys.platform))
|
||||
try:
|
||||
dir_to_check = sys.argv[1]
|
||||
except:
|
||||
except Exception:
|
||||
print("Specify dir on the command line")
|
||||
sys.exit(0)
|
||||
if isFAT(dir_to_check):
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
# You MUST use double quotes (so " and not ')
|
||||
# Do not forget to update the appdata file for every major release!
|
||||
|
||||
__version__ = "4.3.2RC2"
|
||||
__version__ = "4.3.3RC2"
|
||||
__baseline__ = "unknown"
|
||||
|
||||
@@ -57,7 +57,7 @@ stages:
|
||||
action_line: !anything
|
||||
size: !re_match "[0-9][0-9.]*\ [A-Z]"
|
||||
loaded: !anybool
|
||||
retry: !anyint
|
||||
retry: !anybool
|
||||
archive: !anybool
|
||||
noofslots: !anyint
|
||||
ppslots: !anyint
|
||||
@@ -125,7 +125,7 @@ stages:
|
||||
<action_line>!anystr</action_line>
|
||||
<size>!anystr</size>
|
||||
<loaded>!anybool</loaded>
|
||||
<retry>!anyint</retry>
|
||||
<retry>!anybool</retry>
|
||||
<archive>!anybool</archive>
|
||||
</slot>
|
||||
</slots>
|
||||
|
||||
@@ -3,7 +3,7 @@ pytest==7.4.4
|
||||
setuptools
|
||||
selenium
|
||||
requests
|
||||
pyfakefs<5.4.0
|
||||
pyfakefs>=5.6.0
|
||||
werkzeug<2.1.0 # Breaks httpbin in newer versions
|
||||
pytest-httpbin
|
||||
pytest-httpserver
|
||||
|
||||
@@ -893,10 +893,11 @@ class TestCreateAllDirs(ffs.TestCase, PermissionCheckerHelper):
|
||||
|
||||
@set_config({"permissions": "0600"})
|
||||
def test_permissions_600(self):
|
||||
self._permissions_runner("/test_base600")
|
||||
with pytest.raises(OSError): # pyfakefs checks fake permissions now...
|
||||
self._permissions_runner("/test_base600")
|
||||
self._permissions_runner("/test_base600_nomask", apply_permissions=False)
|
||||
|
||||
@set_config({"permissions": "0700"})
|
||||
@set_config({"permissions": "0450"})
|
||||
def test_permissions_450(self):
|
||||
with pytest.raises(OSError):
|
||||
self._permissions_runner("/test_base450", perms_base="0450")
|
||||
@@ -904,13 +905,14 @@ class TestCreateAllDirs(ffs.TestCase, PermissionCheckerHelper):
|
||||
def test_no_permissions(self):
|
||||
self._permissions_runner("/test_base_perm700", perms_base="0700")
|
||||
self._permissions_runner("/test_base_perm750", perms_base="0750")
|
||||
with pytest.raises(OSError): # pyfakefs checks fake permissions now...
|
||||
self._permissions_runner("/test_base_perm600", perms_base="0600")
|
||||
self._permissions_runner("/test_base_perm777", perms_base="0777")
|
||||
self._permissions_runner("/test_base_perm600", perms_base="0600")
|
||||
|
||||
def _permissions_runner(self, test_base, perms_base="0700", apply_permissions=True):
|
||||
# Create base directory and set the base permissions
|
||||
perms_base_int = int(perms_base, 8)
|
||||
self.fs.create_dir(test_base, perms_base_int)
|
||||
self.fs.create_dir(test_base, perms_base_int, apply_umask=False)
|
||||
assert os.path.exists(test_base) is True
|
||||
self.assert_dir_perms(test_base, perms_base_int)
|
||||
|
||||
@@ -943,7 +945,7 @@ class TestSetPermissions(ffs.TestCase, PermissionCheckerHelper):
|
||||
self.setUpPyfakefs()
|
||||
self.fs.path_separator = "/"
|
||||
self.fs.is_case_sensitive = True
|
||||
self.fs.umask = int("0755", 8) # rwxr-xr-x
|
||||
self.fs.umask = int("0022", 8) # rwxr-xr-x
|
||||
|
||||
def _runner(self, perms_before_test):
|
||||
"""
|
||||
@@ -964,7 +966,7 @@ class TestSetPermissions(ffs.TestCase, PermissionCheckerHelper):
|
||||
|
||||
# Setup and verify fake dir
|
||||
test_dir = "/test"
|
||||
self.fs.create_dir(test_dir, perms_before_test)
|
||||
self.fs.create_dir(test_dir, perms_before_test, apply_umask=False)
|
||||
assert os.path.exists(test_dir) is True
|
||||
self.assert_dir_perms(test_dir, perms_before_test)
|
||||
|
||||
@@ -981,10 +983,10 @@ class TestSetPermissions(ffs.TestCase, PermissionCheckerHelper):
|
||||
# Create the folder, so it has the expected permissions
|
||||
if not os.path.exists(basefolder):
|
||||
try:
|
||||
self.fs.create_dir(basefolder, perms_before_test)
|
||||
self.fs.create_dir(basefolder, perms_before_test, apply_umask=False)
|
||||
except PermissionError:
|
||||
ffs.set_uid(0)
|
||||
self.fs.create_file(file, perms_before_test)
|
||||
self.fs.create_file(file, perms_before_test, apply_umask=False)
|
||||
assert os.path.exists(basefolder) is True
|
||||
self.assert_dir_perms(basefolder, perms_before_test)
|
||||
|
||||
@@ -995,10 +997,10 @@ class TestSetPermissions(ffs.TestCase, PermissionCheckerHelper):
|
||||
|
||||
# Then, create the file
|
||||
try:
|
||||
self.fs.create_file(file, file_perms_before_test)
|
||||
self.fs.create_file(file, file_perms_before_test, apply_umask=False)
|
||||
except PermissionError:
|
||||
ffs.set_uid(0)
|
||||
self.fs.create_file(file, file_perms_before_test)
|
||||
self.fs.create_file(file, file_perms_before_test, apply_umask=False)
|
||||
|
||||
assert os.path.exists(file) is True
|
||||
assert stat.filemode(os.stat(file).st_mode)[1:] == stat.filemode(file_perms_before_test)[1:]
|
||||
|
||||
@@ -88,6 +88,27 @@ class TestMisc:
|
||||
assert misc.cmp(2, 1) > 0
|
||||
assert misc.cmp(1, 1) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cat, pp, script, expected",
|
||||
[
|
||||
(None, None, None, (None, None, None)),
|
||||
("", "", "", (None, None, None)),
|
||||
("none", "-1", "default", (None, None, None)),
|
||||
("SomeCategory", "5", "SomeScript", ("SomeCategory", "5", "SomeScript")),
|
||||
("none", 0, "default", (None, 0, None)),
|
||||
("Movies", "", "default", ("Movies", None, None)),
|
||||
("", "10", "default", (None, "10", None)),
|
||||
("none", "15", "", (None, "15", None)),
|
||||
("none", 0, "Default", (None, 0, None)),
|
||||
("other", "-1", "Default", ("other", None, None)),
|
||||
("none", "None", "default", (None, None, None)),
|
||||
("some", "none", "script", ("some", None, "script")),
|
||||
("none", "NONE", "Default", (None, None, None)),
|
||||
],
|
||||
)
|
||||
def test_cat_pp_script_sanitizer(self, cat, pp, script, expected):
|
||||
assert misc.cat_pp_script_sanitizer(cat, pp, script) == expected
|
||||
|
||||
def test_cat_to_opts(self):
|
||||
# Need to create the Default category, as we would in normal instance
|
||||
# Otherwise it will try to save the config
|
||||
@@ -241,6 +262,33 @@ class TestMisc:
|
||||
assert "13:11:10" == misc.format_time_left(60 * 60 * 13 + 60 * 11 + 10, short_format=True)
|
||||
assert "1:09:11:10" == misc.format_time_left(60 * 60 * 33 + 60 * 11 + 10, short_format=True)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value, default, expected, description",
|
||||
[
|
||||
(None, "", "", "Test with None value and default empty string"),
|
||||
(None, "default", "default", "Test with None value and default 'default'"),
|
||||
(0, "", "0", "Test with zero value"),
|
||||
(1, "", "1", "Test with one value"),
|
||||
(-1, "", "-1", "Test with negative one value"),
|
||||
(100, "", "100", "Test with 100 value"),
|
||||
("abc", "", "abc", "Test with alphabetic string"),
|
||||
("", "", "", "Test with empty string"),
|
||||
(True, "", "True", "Test with boolean True value"),
|
||||
(False, "", "False", "Test with boolean False value"),
|
||||
(0.0, "", "0.0", "Test with float zero value"),
|
||||
(1.5, "", "1.5", "Test with positive float value"),
|
||||
(-2.7, "", "-2.7", "Test with negative float value"),
|
||||
(complex(1, 1), "", "(1+1j)", "Test with complex number"),
|
||||
([], "", "[]", "Test with empty list"),
|
||||
([1, 2, 3], "", "[1, 2, 3]", "Test with list of integers"),
|
||||
({}, "", "{}", "Test with empty dictionary"),
|
||||
({"key": "value"}, "", "{'key': 'value'}", "Test with dictionary"),
|
||||
(set(), "", "set()", "Test with empty set"),
|
||||
],
|
||||
)
|
||||
def test_str_conv(self, value, default, expected, description):
|
||||
assert misc.str_conv(value, default) == expected
|
||||
|
||||
def test_int_conv(self):
|
||||
assert 0 == misc.int_conv("0")
|
||||
assert 10 == misc.int_conv("10")
|
||||
@@ -250,6 +298,32 @@ class TestMisc:
|
||||
assert 1 == misc.int_conv(True)
|
||||
assert 0 == misc.int_conv(object)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value, expected, description",
|
||||
[
|
||||
(None, False, "Test with None value"),
|
||||
(0, False, "Test with zero value"),
|
||||
("0", False, "Test with zero string"),
|
||||
(1, True, "Test with one value"),
|
||||
(-1, True, "Test with negative one value"),
|
||||
(100, True, "Test with 100 value"),
|
||||
("1", True, "Test with one string"),
|
||||
("100", True, "Test with 100 string"),
|
||||
("", False, "Test with empty string"),
|
||||
("abc", False, "Test with non-numeric string"),
|
||||
("true", False, "Test with 'true' string"),
|
||||
(True, True, "Test with boolean True value"),
|
||||
(False, False, "Test with boolean False value"),
|
||||
(0.0, False, "Test with float zero value"),
|
||||
(1.5, True, "Test with positive float value"),
|
||||
(-2.7, True, "Test with negative float value"),
|
||||
("1.5", False, "Test with float string value"),
|
||||
("0.0", False, "Test with float zero string value"),
|
||||
],
|
||||
)
|
||||
def test_bool_conv(self, value, expected, description):
|
||||
assert misc.bool_conv(value) == expected, description
|
||||
|
||||
def test_create_https_certificates(self):
|
||||
cert_file = "test.cert"
|
||||
key_file = "test.key"
|
||||
|
||||
@@ -36,9 +36,7 @@ class TestCertGen:
|
||||
private_key = generate_key(output_file=os.path.join(SAB_CACHE_DIR, "test_key.pem"))
|
||||
assert private_key.key_size == 2048
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"key_size, file_name", [(512, "test_key.pem"), (1024, "test_123_key.pem"), (4096, "123_key.pem")]
|
||||
)
|
||||
@pytest.mark.parametrize("key_size, file_name", [(1024, "test_123_key.pem"), (4096, "123_key.pem")])
|
||||
def test_generate_key_custom(self, key_size, file_name):
|
||||
# Generate private key
|
||||
private_key = generate_key(key_size=key_size, output_file=os.path.join(SAB_CACHE_DIR, file_name))
|
||||
|
||||
Reference in New Issue
Block a user