mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-24 16:19:31 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d10639542d | ||
|
|
c0f0b7eb31 | ||
|
|
d6d70325db | ||
|
|
46954165d2 | ||
|
|
58e7d520bf | ||
|
|
a4f8040324 | ||
|
|
8d5cc9a3e6 | ||
|
|
4592ce4d55 | ||
|
|
b62b38b5af | ||
|
|
14b1d4630c |
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
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
Release Notes - SABnzbd 4.3.3 Beta 2
|
||||
Release Notes - SABnzbd 4.3.3
|
||||
=========================================================
|
||||
|
||||
This is the third bug fix release of SABnzbd 4.3.0.
|
||||
|
||||
## Bug fixes and changes since 4.3.2
|
||||
* Jobs could get stuck at 99%.
|
||||
* 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.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
|
||||
pyinstaller==5.13.2
|
||||
packaging==24.1
|
||||
pyinstaller-hooks-contrib==2024.7
|
||||
pyinstaller-hooks-contrib==2024.8
|
||||
altgraph==0.17.4
|
||||
wrapt==1.16.0
|
||||
setuptools==72.1.0
|
||||
@@ -10,10 +10,10 @@ setuptools==72.1.0
|
||||
# Required on 32bit Windows, exclude it based on Python-version
|
||||
importlib_metadata==8.2.0; python_version < '3.10'
|
||||
importlib_resources==6.4.0; python_version < '3.10'
|
||||
zipp==3.19.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.6; python_version > '3.8'
|
||||
orjson==3.10.7; python_version > '3.8'
|
||||
|
||||
# For the Windows build
|
||||
pefile==2023.2.7; sys_platform == 'win32'
|
||||
|
||||
@@ -208,7 +208,7 @@ ul.tabs a,
|
||||
#subscriptions,
|
||||
.RSS form[action="add_rss_feed"] tr:nth-child(even),
|
||||
.Config .table {
|
||||
border: 1px solid #555555 !important;
|
||||
border: 1px solid #555555;
|
||||
}
|
||||
|
||||
.Categories form:first-of-type tr:last-of-type,
|
||||
|
||||
@@ -19,6 +19,7 @@ body {
|
||||
float: left;
|
||||
overflow: visible;
|
||||
border: 1px solid #dfdede;
|
||||
border-bottom: none !important;
|
||||
background-color: #FFF;
|
||||
width: 100%
|
||||
}
|
||||
@@ -1222,7 +1223,6 @@ input[type="checkbox"] {
|
||||
}
|
||||
.value-and-select select {
|
||||
min-width: 30px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.dotOne, .dotTwo, .dotThree {
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
|
||||
<span data-bind="text: statusInfo.pystone"></span>
|
||||
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
|
||||
<small title="$cpumodel $cpusimd" data-tooltip="true">$cpumodel $cpusimd</small>
|
||||
<small title="$cpumodel $cpusimd $docker" data-tooltip="true">$cpumodel $cpusimd $docker</small>
|
||||
</div>
|
||||
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +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-01" type="stable"/>
|
||||
<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"/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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
|
||||
@@ -15,7 +15,7 @@ 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.3.0
|
||||
more-itertools==10.4.0
|
||||
zc.lockfile==3.0.post1
|
||||
python-dateutil==2.9.0.post0
|
||||
tempora==5.7.0
|
||||
@@ -24,7 +24,7 @@ sgmllib3k==1.0.0
|
||||
portend==3.2.0
|
||||
chardet==5.2.0
|
||||
PySocks==1.7.1
|
||||
puremagic==1.26
|
||||
puremagic==1.27
|
||||
guessit==3.8.0
|
||||
babelfish==0.6.1
|
||||
rebulk==3.2.0
|
||||
@@ -56,7 +56,7 @@ notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
|
||||
# Apprise Requirements
|
||||
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
|
||||
|
||||
|
||||
@@ -101,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -434,6 +434,7 @@ class MainPage:
|
||||
|
||||
info["cpumodel"] = get_cpu_name()
|
||||
info["cpusimd"] = sabnzbd.decoder.SABCTOOLS_SIMD
|
||||
info["docker"] = "Docker" if sabnzbd.DOCKER else ""
|
||||
|
||||
# Have logout only with HTML and if inet=5, only when we are external
|
||||
info["have_logout"] = (
|
||||
|
||||
@@ -30,7 +30,7 @@ from typing import Optional, Tuple, Union
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.constants import DEF_NETWORKING_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
|
||||
@@ -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
|
||||
|
||||
@@ -898,8 +898,8 @@ class NzbQueue:
|
||||
for article in nzf.articles[:]:
|
||||
if article.all_servers_in_try_list(active_servers):
|
||||
logging.debug("Removing article %s with bad trylist in file %s", article, nzf.filename)
|
||||
sabnzbd.NzbQueue.register_article(article, success=False)
|
||||
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()
|
||||
|
||||
@@ -1592,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
|
||||
|
||||
@@ -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.3Beta2"
|
||||
__version__ = "4.3.3"
|
||||
__baseline__ = "unknown"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -912,11 +912,9 @@ class TestCreateAllDirs(ffs.TestCase, PermissionCheckerHelper):
|
||||
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
|
||||
# Account for pyfakefs (>= 5.4.0) always applying the fake fs umask when creating dirs
|
||||
perms_base_int_umasked = perms_base_int & ~self.fs.umask
|
||||
self.assert_dir_perms(test_base, perms_base_int_umasked)
|
||||
self.assert_dir_perms(test_base, perms_base_int)
|
||||
|
||||
# Create directories with permissions
|
||||
new_dir = os.path.join(test_base, "se 1", "ep1")
|
||||
@@ -930,7 +928,7 @@ class TestCreateAllDirs(ffs.TestCase, PermissionCheckerHelper):
|
||||
# Get the current permissions, since os.mkdir masks that out
|
||||
perms_test_int = int("0777", 8) & ~sabnzbd.ORG_UMASK
|
||||
self.assert_dir_perms(new_dir, perms_test_int)
|
||||
self.assert_dir_perms(test_base, perms_base_int_umasked)
|
||||
self.assert_dir_perms(test_base, perms_base_int)
|
||||
|
||||
|
||||
class TestSetPermissionsWin(ffs.TestCase):
|
||||
@@ -947,9 +945,7 @@ class TestSetPermissions(ffs.TestCase, PermissionCheckerHelper):
|
||||
self.setUpPyfakefs()
|
||||
self.fs.path_separator = "/"
|
||||
self.fs.is_case_sensitive = True
|
||||
# All-permissive umask as a workaround for changes in pyfakefs (>= 5.4.0),
|
||||
# causing it to always take the umask into account when creating dirs
|
||||
self.fs.umask = int("0000", 8) # rwxrwxrwx
|
||||
self.fs.umask = int("0022", 8) # rwxr-xr-x
|
||||
|
||||
def _runner(self, perms_before_test):
|
||||
"""
|
||||
@@ -970,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)
|
||||
|
||||
@@ -987,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)
|
||||
|
||||
@@ -1001,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:]
|
||||
|
||||
Reference in New Issue
Block a user