mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-01-04 05:29:28 -05:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abd31d0249 | ||
|
|
9ae7ee6e2d | ||
|
|
bf52430da8 | ||
|
|
7005b3ee86 | ||
|
|
8f2ea239c5 | ||
|
|
9ee2a8a98c | ||
|
|
6f0daf9d1b | ||
|
|
28ed424fa8 | ||
|
|
fe3e20b108 |
@@ -1,4 +1,4 @@
|
||||
Release Notes - SABnzbd 4.2.2 Release Candidate 1
|
||||
Release Notes - SABnzbd 4.2.2 Release Candidate 2
|
||||
=========================================================
|
||||
|
||||
This is the second bug-fix release of SABnzbd 4.2.0.
|
||||
@@ -6,9 +6,11 @@ This is the second bug-fix release of SABnzbd 4.2.0.
|
||||
## Bug-fixes and changes since 4.2.1:
|
||||
|
||||
* **Bug-fixes:**
|
||||
* RSS readout could result in a crash if duplicate detection is enabled.
|
||||
* RSS readout could result in a crash if Duplicate Detection was enabled.
|
||||
* Passwords were not always correctly parsed.
|
||||
* Warnings could show even if `helpful_warnings` was disabled.
|
||||
* Duplicate Detection would trigger again on URLs if they were resumed.
|
||||
* Windows: Fatal crash could occur if ran as Service.
|
||||
|
||||
* **Changes:**
|
||||
* Parsing of filenames from the NZB was extended to allow more exotic formatting.
|
||||
|
||||
@@ -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==23.2
|
||||
pyinstaller-hooks-contrib==2023.12
|
||||
pyinstaller-hooks-contrib==2024.0
|
||||
altgraph==0.17.4
|
||||
wrapt==1.16.0
|
||||
setuptools==69.0.3
|
||||
@@ -14,7 +14,7 @@ importlib_resources==6.1.1; python_version < '3.10'
|
||||
zipp==3.17.0; python_version < '3.10'
|
||||
|
||||
# orjson does not support 32bit Windows, also exclude based on Python-version
|
||||
orjson==3.9.10; python_version > '3.8'
|
||||
orjson==3.9.12; python_version > '3.8'
|
||||
|
||||
# For the Windows build
|
||||
pefile==2023.2.7; sys_platform == 'win32'
|
||||
|
||||
@@ -140,6 +140,12 @@ select.form-control,
|
||||
color: #EBEBEB;
|
||||
}
|
||||
|
||||
.btn-default:not(.navbar-btn):hover,
|
||||
select:hover,
|
||||
input:hover {
|
||||
background-color: #666666;
|
||||
}
|
||||
|
||||
/* Needed to force the text-color */
|
||||
.table-striped>tbody>tr:nth-child(odd)>td,
|
||||
.table>tbody>tr:nth-child(odd)>td,
|
||||
|
||||
@@ -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.2.2" date="2024-02-01" type="stable"/>
|
||||
<release version="4.2.2" date="2024-01-31" type="stable"/>
|
||||
<release version="4.2.1" date="2024-01-05" type="stable"/>
|
||||
<release version="4.2.0" date="2024-01-03" type="stable"/>
|
||||
<release version="4.1.0" date="2023-09-26" type="stable"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
|
||||
"Project-Id-Version: SABnzbd-4.2.2RC1\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
|
||||
"Project-Id-Version: SABnzbd-4.2.2RC1\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
|
||||
"Project-Id-Version: SABnzbd-4.2.2RC1\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
@@ -171,6 +171,7 @@ class DuplicateStatus:
|
||||
DUPLICATE_ALTERNATIVE = "Duplicate Alternative" # Alternative duplicate for a queued job
|
||||
SMART_DUPLICATE = "Smart Duplicate" # Simple Series duplicate
|
||||
SMART_DUPLICATE_ALTERNATIVE = "Smart Duplicate Alternative" # Alternative duplicate for a queued job
|
||||
DUPLICATE_IGNORED = "Duplicate Ignored"
|
||||
|
||||
|
||||
class AddNzbFileResult:
|
||||
|
||||
@@ -168,5 +168,8 @@ def local_ipv6():
|
||||
|
||||
|
||||
def public_ipv6():
|
||||
if local_ipv6():
|
||||
return public_ip(family=socket.AF_INET6)
|
||||
if local_address := local_ipv6():
|
||||
if public_address := public_ip(family=socket.AF_INET6):
|
||||
return public_address
|
||||
elif not sabnzbd.misc.is_lan_addr(local_address):
|
||||
return local_address
|
||||
|
||||
@@ -53,6 +53,7 @@ from sabnzbd.misc import (
|
||||
helpful_warning,
|
||||
recursive_html_escape,
|
||||
is_none,
|
||||
get_cpu_name,
|
||||
)
|
||||
from sabnzbd.happyeyeballs import happyeyeballs
|
||||
from sabnzbd.filesystem import (
|
||||
@@ -69,7 +70,6 @@ import sabnzbd.cfg as cfg
|
||||
import sabnzbd.notifier as notifier
|
||||
import sabnzbd.newsunpack
|
||||
from sabnzbd.utils.servertests import test_nntp_server_dict
|
||||
from sabnzbd.utils.getperformance import getcpu
|
||||
import sabnzbd.utils.ssdp
|
||||
from sabnzbd.constants import (
|
||||
DEF_STD_CONFIG,
|
||||
@@ -430,7 +430,7 @@ class MainPage:
|
||||
info["have_rss_defined"] = bool(config.get_rss())
|
||||
info["have_watched_dir"] = bool(cfg.dirscan_dir())
|
||||
|
||||
info["cpumodel"] = getcpu()
|
||||
info["cpumodel"] = get_cpu_name()
|
||||
info["cpusimd"] = sabnzbd.decoder.SABCTOOLS_SIMD
|
||||
|
||||
# Have logout only with HTML and if inet=5, only when we are external
|
||||
|
||||
@@ -20,9 +20,11 @@ sabnzbd.misc - misc classes
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import ssl
|
||||
import sys
|
||||
import logging
|
||||
import functools
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import re
|
||||
@@ -51,11 +53,13 @@ from sabnzbd.constants import (
|
||||
)
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.encoding import ubtou
|
||||
from sabnzbd.decorators import cache_maintainer
|
||||
from sabnzbd.encoding import ubtou, platform_btou
|
||||
from sabnzbd.filesystem import userxbit, make_script_path, remove_file
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
try:
|
||||
import winreg
|
||||
import win32process
|
||||
import win32con
|
||||
|
||||
@@ -381,8 +385,6 @@ _SERVICE_PARM = "CommandLine"
|
||||
|
||||
def get_serv_parms(service):
|
||||
"""Get the service command line parameters from Registry"""
|
||||
import winreg
|
||||
|
||||
service_parms = []
|
||||
try:
|
||||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, _SERVICE_KEY + service)
|
||||
@@ -402,8 +404,6 @@ def get_serv_parms(service):
|
||||
|
||||
def set_serv_parms(service, args):
|
||||
"""Set the service command line parameters in Registry"""
|
||||
import winreg
|
||||
|
||||
serv = []
|
||||
for arg in args:
|
||||
serv.append(arg[0])
|
||||
@@ -739,6 +739,51 @@ def get_macos_memory():
|
||||
return float(system_output.split()[1])
|
||||
|
||||
|
||||
@cache_maintainer(clear_time=3600)
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def get_cpu_name():
|
||||
"""Find the CPU name (which needs a different method per OS), and return it
|
||||
If none found, return platform.platform()"""
|
||||
|
||||
cputype = None
|
||||
|
||||
try:
|
||||
if sabnzbd.WIN32:
|
||||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0")
|
||||
cputype = winreg.QueryValueEx(key, "ProcessorNameString")[0]
|
||||
winreg.CloseKey(key)
|
||||
|
||||
elif sabnzbd.MACOS:
|
||||
cputype = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"]).strip()
|
||||
|
||||
else:
|
||||
with open("/proc/cpuinfo") as fp:
|
||||
for myline in fp.readlines():
|
||||
if myline.startswith("model name"):
|
||||
# Typical line:
|
||||
# model name : Intel(R) Xeon(R) CPU E5335 @ 2.00GHz
|
||||
cputype = myline.split(":", 1)[1] # get everything after the first ":"
|
||||
break # we're done
|
||||
cputype = platform_btou(cputype)
|
||||
except:
|
||||
# An exception, maybe due to a subprocess call gone wrong
|
||||
pass
|
||||
|
||||
if cputype:
|
||||
# OK, found. Remove unwanted spaces:
|
||||
cputype = " ".join(cputype.split())
|
||||
else:
|
||||
try:
|
||||
# Not found, so let's fall back to platform()
|
||||
cputype = platform.platform()
|
||||
except:
|
||||
# Can fail on special platforms (like Snapcraft or embedded)
|
||||
pass
|
||||
|
||||
logging.debug("CPU model = %s", cputype)
|
||||
return cputype
|
||||
|
||||
|
||||
def on_cleanup_list(filename: str, skip_nzb: bool = False) -> bool:
|
||||
"""Return True if a filename matches the clean-up list"""
|
||||
cleanup_list = cfg.cleanup_list()
|
||||
|
||||
@@ -210,6 +210,7 @@ def send_notify_osd(title, message):
|
||||
def send_notification_center(title: str, msg: str, notification_type: str, actions: Optional[Dict[str, str]] = None):
|
||||
"""Send message to macOS Notification Center.
|
||||
Only 1 button is possible on macOS!"""
|
||||
logging.debug("Sending macOS notification")
|
||||
try:
|
||||
subtitle = T(NOTIFICATION_TYPES.get(notification_type, "other"))
|
||||
button_text = button_action = None
|
||||
@@ -228,7 +229,7 @@ def send_notification_center(title: str, msg: str, notification_type: str, actio
|
||||
|
||||
def send_prowl(title, msg, notification_type, force=False, test=None):
|
||||
"""Send message to Prowl"""
|
||||
|
||||
logging.debug("Sending Prowl notification")
|
||||
if test:
|
||||
apikey = test.get("prowl_apikey")
|
||||
else:
|
||||
@@ -261,7 +262,7 @@ def send_prowl(title, msg, notification_type, force=False, test=None):
|
||||
|
||||
def send_pushover(title, msg, notification_type, force=False, test=None):
|
||||
"""Send message to pushover"""
|
||||
|
||||
logging.debug("Sending Pushover notification")
|
||||
if test:
|
||||
apikey = test.get("pushover_token")
|
||||
userkey = test.get("pushover_userkey")
|
||||
@@ -328,7 +329,7 @@ def do_send_pushover(body):
|
||||
|
||||
def send_pushbullet(title, msg, notification_type, force=False, test=None):
|
||||
"""Send message to Pushbullet"""
|
||||
|
||||
logging.debug("Sending Pushbullet notification")
|
||||
if test:
|
||||
apikey = test.get("pushbullet_apikey")
|
||||
device = test.get("pushbullet_device")
|
||||
@@ -363,6 +364,7 @@ def send_pushbullet(title, msg, notification_type, force=False, test=None):
|
||||
|
||||
def send_nscript(title, msg, notification_type, force=False, test=None):
|
||||
"""Run user's notification script"""
|
||||
logging.debug("Sending notification script notification")
|
||||
if test:
|
||||
script = test.get("nscript_script")
|
||||
env_params = {"notification_parameters": test.get("nscript_parameters")}
|
||||
@@ -406,6 +408,12 @@ def send_nscript(title, msg, notification_type, force=False, test=None):
|
||||
|
||||
|
||||
def send_windows(title: str, msg: str, notification_type: str, actions: Optional[Dict[str, str]] = None):
|
||||
"""Send Windows notifications, either fancy with buttons (Windows 10+) or basic ones"""
|
||||
# Skip any notifications if ran as a Windows Service, it can result in crashes
|
||||
if sabnzbd.WIN_SERVICE:
|
||||
return None
|
||||
|
||||
logging.debug("Sending Windows notification")
|
||||
try:
|
||||
if _HAVE_WINDOWS_TOASTER:
|
||||
notification_sender = InteractableWindowsToaster("SABnzbd", notifierAUMID="SABnzbd")
|
||||
|
||||
@@ -62,6 +62,7 @@ def add_nzbfile(
|
||||
reuse: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
nzo_id: Optional[str] = None,
|
||||
dup_check: bool = True,
|
||||
):
|
||||
"""Add file, either a single NZB-file or an archive.
|
||||
All other parameters are passed to the NZO-creation.
|
||||
@@ -119,6 +120,7 @@ def add_nzbfile(
|
||||
url=url,
|
||||
password=password,
|
||||
nzo_id=nzo_id,
|
||||
dup_check=dup_check,
|
||||
)
|
||||
else:
|
||||
return process_single_nzb(
|
||||
@@ -136,6 +138,7 @@ def add_nzbfile(
|
||||
url=url,
|
||||
password=password,
|
||||
nzo_id=nzo_id,
|
||||
dup_check=dup_check,
|
||||
)
|
||||
|
||||
|
||||
@@ -151,10 +154,10 @@ def process_nzb_archive_file(
|
||||
nzbname: Optional[str] = None,
|
||||
reuse: Optional[str] = None,
|
||||
nzo_info: Optional[Dict[str, Any]] = None,
|
||||
dup_check: bool = True,
|
||||
url: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
nzo_id: Optional[str] = None,
|
||||
dup_check: bool = True,
|
||||
) -> Tuple[AddNzbFileResult, List[str]]:
|
||||
"""Analyse archive and create job(s).
|
||||
Accepts archive files with ONLY nzb/nfo/folder files in it.
|
||||
@@ -266,10 +269,10 @@ def process_single_nzb(
|
||||
nzbname: Optional[str] = None,
|
||||
reuse: Optional[str] = None,
|
||||
nzo_info: Optional[Dict[str, Any]] = None,
|
||||
dup_check: bool = True,
|
||||
url: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
nzo_id: Optional[str] = None,
|
||||
dup_check: bool = True,
|
||||
) -> Tuple[AddNzbFileResult, List[str]]:
|
||||
"""Analyze file and create a job from it
|
||||
Supports NZB, NZB.BZ2, NZB.GZ and GZ.NZB-in-disguise
|
||||
|
||||
@@ -979,7 +979,7 @@ class NzbQueue:
|
||||
|
||||
# Unfortunately we need a copy, since we might remove items from the list
|
||||
for nzo in self.__nzo_list[:]:
|
||||
if not nzo.duplicate:
|
||||
if not nzo.duplicate or nzo.duplicate == DuplicateStatus.DUPLICATE_IGNORED:
|
||||
continue
|
||||
|
||||
# URL's do not have an MD5!
|
||||
|
||||
@@ -1433,12 +1433,13 @@ class NzbObject(TryList):
|
||||
# If user resumes after encryption warning, no more auto-pauses
|
||||
self.encrypted = 2
|
||||
# If user resumes after warning, reset duplicate/oversized/incomplete/unwanted indicators
|
||||
self.duplicate = None
|
||||
self.oversized = False
|
||||
self.incomplete = False
|
||||
if self.unwanted_ext:
|
||||
# If user resumes after "unwanted" warning, no more auto-pauses
|
||||
self.unwanted_ext = 2
|
||||
if self.duplicate:
|
||||
self.duplicate = DuplicateStatus.DUPLICATE_IGNORED
|
||||
|
||||
@synchronized(NZO_LOCK)
|
||||
def add_parfile(self, parfile: NzbFile) -> bool:
|
||||
|
||||
@@ -34,7 +34,7 @@ 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
|
||||
from sabnzbd.constants import DEF_TIMEOUT, FUTURE_Q_FOLDER, VALID_NZB_FILES, Status, VALID_ARCHIVES, DuplicateStatus
|
||||
import sabnzbd.misc as misc
|
||||
import sabnzbd.filesystem
|
||||
import sabnzbd.cfg as cfg
|
||||
@@ -243,6 +243,10 @@ class URLGrabber(Thread):
|
||||
|
||||
# Check if nzb file
|
||||
if sabnzbd.filesystem.get_ext(filename) in VALID_ARCHIVES + VALID_NZB_FILES:
|
||||
# If the user resumed a duplicate detected URL, skip the check
|
||||
dup_check = future_nzo.duplicate != DuplicateStatus.DUPLICATE_IGNORED
|
||||
|
||||
# Add the new job to the queue
|
||||
res, _ = sabnzbd.nzbparser.add_nzbfile(
|
||||
path,
|
||||
pp=future_nzo.pp,
|
||||
@@ -255,6 +259,7 @@ class URLGrabber(Thread):
|
||||
keep=False,
|
||||
password=future_nzo.password,
|
||||
nzo_id=future_nzo.nzo_id,
|
||||
dup_check=dup_check,
|
||||
)
|
||||
if res is AddNzbFileResult.RETRY:
|
||||
logging.info("Incomplete NZB, retry after 5 min %s", url)
|
||||
|
||||
@@ -1,56 +1,9 @@
|
||||
import platform
|
||||
import subprocess
|
||||
import locale
|
||||
import logging
|
||||
import time
|
||||
|
||||
from .pystone import pystones
|
||||
|
||||
|
||||
def getcpu():
|
||||
# find the CPU name (which needs a different method per OS), and return it
|
||||
# If none found, return platform.platform().
|
||||
|
||||
cputype = None
|
||||
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
import winreg
|
||||
|
||||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0")
|
||||
cputype = winreg.QueryValueEx(key, "ProcessorNameString")[0]
|
||||
winreg.CloseKey(key)
|
||||
|
||||
elif platform.system() == "Darwin":
|
||||
cputype = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"]).strip()
|
||||
|
||||
elif platform.system() == "Linux":
|
||||
with open("/proc/cpuinfo") as fp:
|
||||
for myline in fp.readlines():
|
||||
if myline.startswith("model name"):
|
||||
# Typical line:
|
||||
# model name : Intel(R) Xeon(R) CPU E5335 @ 2.00GHz
|
||||
cputype = myline.split(":", 1)[1] # get everything after the first ":"
|
||||
break # we're done
|
||||
cputype = cputype.decode(locale.getpreferredencoding())
|
||||
except:
|
||||
# An exception, maybe due to a subprocess call gone wrong
|
||||
pass
|
||||
|
||||
if cputype:
|
||||
# OK, found. Remove unwanted spaces:
|
||||
cputype = " ".join(cputype.split())
|
||||
else:
|
||||
try:
|
||||
# Not found, so let's fall back to platform()
|
||||
cputype = platform.platform()
|
||||
except:
|
||||
# Can fail on special platforms (like Snapcraft or embedded)
|
||||
pass
|
||||
|
||||
logging.debug("CPU model = %s", cputype)
|
||||
return cputype
|
||||
|
||||
|
||||
def getpystone():
|
||||
# Start calculation
|
||||
maxpystone = 0
|
||||
@@ -69,4 +22,3 @@ def getpystone():
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(getpystone())
|
||||
print(getcpu())
|
||||
|
||||
@@ -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.2.2RC1"
|
||||
__version__ = "4.2.2RC2"
|
||||
__baseline__ = "unknown"
|
||||
|
||||
Reference in New Issue
Block a user