Compare commits

...

20 Commits

Author SHA1 Message Date
Safihre
af0b53990c Update text files for 4.2.3RC3 2024-03-04 08:46:42 +01:00
Safihre
e3861954ba Support NNTP code 220 after ARTICLE request
Closes #2817
2024-03-02 21:40:56 +01:00
Safihre
006dd8dc77 Do not log NNTP data in unknown status code warning
See #2817
2024-03-02 21:23:25 +01:00
Safihre
dbff203c62 Update text files for 4.2.3RC2 2024-02-26 22:30:38 +01:00
Safihre
f45eb891cd Correct translatable texts about connection threads 2024-02-26 22:23:51 +01:00
Safihre
77b58240cf Reduce Server test timeout to 10s
Unless otherwise specified
Related to #2802
2024-02-26 22:23:51 +01:00
Safihre
97ae1ff10e Refactor part of database.py
Use autocommit and skip unnecessary checks on every connect
2024-02-26 22:23:51 +01:00
Safihre
8734a4f24b Update text files for 4.2.3RC1 2024-02-19 22:19:43 +01:00
Shane Mc Cormack
480fce55a8 Handle NNTP error code 451 (#2808)
* Handle error code 451.

This is used by some servers to show that an article was intentionally removed.
Fix #2807

* Add a warning when an unknown status code is given for an article.

* Make warning message translatable.
2024-02-19 22:06:29 +01:00
thezoggy
d4136fadd2 Add missing tooltips (#2800)
Co-authored-by: Safihre <safihre@sabnzbd.org>
2024-02-18 13:42:48 +01:00
Safihre
308bc375bd Update log message about version check 2024-02-18 13:42:31 +01:00
Safihre
3bbcf6a41e Update standby command on macOS 2024-02-18 13:41:33 +01:00
Safihre
3d5d10a4c1 Remove parsing of Group command code
Since we never request it.
2024-02-18 13:41:05 +01:00
Safihre
0e979c14f0 Remove Send Group option
Closes #2715
2024-02-18 13:38:31 +01:00
Safihre
70f49114ac Wrong archive password is used for Retry
Closes #2790
2024-02-18 13:38:13 +01:00
Safihre
699d75bb9f Update text files for 4.2.2 2024-01-30 21:46:56 +01:00
Safihre
95822704c8 Update black formatting 2024-01-30 21:36:34 +01:00
Safihre
76e5f69e67 Only attempt Windows Toasts on Windows 10 and above 2024-01-29 16:34:03 +01:00
Safihre
abd31d0249 Update text files for 4.2.2RC2 2024-01-24 13:28:19 +01:00
Safihre
9ae7ee6e2d Add logging which notification will be sent 2024-01-22 15:15:44 +01:00
24 changed files with 130 additions and 145 deletions

View File

@@ -81,7 +81,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.1"
PYTHON_VERSION: "3.12.2"
MACOSX_DEPLOYMENT_TARGET: "10.9"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64

View File

@@ -1,14 +1,28 @@
Release Notes - SABnzbd 4.2.2 Release Candidate 1
Release Notes - SABnzbd 4.2.3 Release Candidate 3
=========================================================
This is the second bug-fix release of SABnzbd 4.2.0.
This is the third bug-fix release of SABnzbd 4.2.0.
## Bug-fixes and changes since 4.2.2:
* **Bug-fixes:**
* Handle new status code for missing articles, which would result in timeouts.
This specifically affects Giganews and its resellers.
* Retry of failed job would not use the password provided.
* Optimize database handling in order to prevent locking errors.
* macOS: System standby after finishing the queue would not always work.
* **Changes:**
* Remove `Send Group` option for Servers.
## 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 or on older Windows versions.
* **Changes:**
* Parsing of filenames from the NZB was extended to allow more exotic formatting.

View File

@@ -106,11 +106,6 @@
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/ssl-ciphers" target="_blank">https://sabnzbd.org/wiki/advanced/ssl-ciphers</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group" value="1" />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="required">$T('srv-required')</label>
<input type="checkbox" name="required" id="required" value="1" />
@@ -248,11 +243,6 @@
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="send_group$cur">$T('srv-send_group')</label>
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date$cur">$T('srv-expire_date')</label>
<input type="date" name="expire_date" id="expire_date$cur" value="$server['expire_date']" />

View File

@@ -140,7 +140,7 @@
</label>
</div>
<a href="#" class="hover-button" data-bind="visible: history.isMultiEditing(), click: history.doMultiDelete">
<a href="#" class="hover-button" title="$T('nzo-delete')" data-bind="visible: history.isMultiEditing(), click: history.doMultiDelete">
<span class="glyphicon glyphicon-trash"></span>
</a>
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-bind="visible: !history.isMultiEditing()" data-toggle="modal" data-tooltip="true" data-placement="left">

View File

@@ -174,7 +174,7 @@
<label for="multiedit-checkall-queue">
<input type="checkbox" name="multieditCheckAll" id="multiedit-checkall-queue" title="$T('Glitter-checkAll')" data-bind="click: queue.checkAllJobs" data-tooltip="true" data-placement="top" />
</label>
<a href="#" class="hover-button" data-bind="click: queue.doMultiDelete">
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: queue.doMultiDelete">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>

View File

@@ -30,7 +30,8 @@
<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.3" date="2024-03-10" 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"/>

View File

@@ -447,7 +447,6 @@ class ConfigServer:
self.expire_date = OptionStr(name, "expire_date", add=False)
self.quota = OptionStr(name, "quota", add=False)
self.usage_at_start = OptionNumber(name, "usage_at_start", add=False)
self.send_group = OptionBool(name, "send_group", False, add=False)
self.priority = OptionNumber(name, "priority", 0, 0, 99, add=False)
self.notes = OptionStr(name, "notes", add=False)
@@ -473,7 +472,6 @@ class ConfigServer:
"ssl",
"ssl_verify",
"ssl_ciphers",
"send_group",
"enable",
"required",
"optional",
@@ -516,7 +514,6 @@ class ConfigServer:
output_dict["expire_date"] = self.expire_date()
output_dict["quota"] = self.quota()
output_dict["usage_at_start"] = self.usage_at_start()
output_dict["send_group"] = self.send_group()
output_dict["priority"] = self.priority()
output_dict["notes"] = self.notes()
return output_dict

View File

@@ -75,6 +75,7 @@ DEF_LOG_CHERRY = "cherrypy.log"
DEF_ARTICLE_CACHE_DEFAULT = "500M"
DEF_ARTICLE_CACHE_MAX = "1G"
DEF_TIMEOUT = 60
DEF_TEST_TIMEOUT = 10
DEF_SCANRATE = 5
DEF_HTTPS_CERT_FILE = "server.cert"
DEF_HTTPS_KEY_FILE = "server.key"

View File

@@ -38,7 +38,7 @@ from sabnzbd.encoding import ubtou, utob
from sabnzbd.misc import int_conv, caller_name, opts_to_pp, to_units
from sabnzbd.filesystem import remove_file, clip_path
DB_LOCK = threading.RLock()
DB_LOCK = threading.Lock()
class HistoryDB:
@@ -50,66 +50,69 @@ class HistoryDB:
# These class attributes will be accessed directly because
# they need to be shared by all instances
db_path = None # Will contain full path to history database
done_cleaning = False # Ensure we only do one Vacuum per session
db_path = None # Full path to history database
startup_done = False
@synchronized(DB_LOCK)
def __init__(self):
"""Determine database path and create connection"""
self.connection: Optional[Connection] = None
self.cursor: Optional[Cursor] = None
if not HistoryDB.db_path:
HistoryDB.db_path = os.path.join(sabnzbd.cfg.admin_dir.get_path(), DB_HISTORY_NAME)
self.connect()
def connect(self):
"""Create a connection to the database"""
create_table = not os.path.exists(HistoryDB.db_path)
if not HistoryDB.db_path:
HistoryDB.db_path = os.path.join(sabnzbd.cfg.admin_dir.get_path(), DB_HISTORY_NAME)
create_table = not HistoryDB.startup_done and not os.path.exists(HistoryDB.db_path)
self.connection = sqlite3.connect(HistoryDB.db_path)
self.connection.isolation_level = None # autocommit attribute only introduced in Python 3.12
self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor()
if create_table:
self.create_history_db()
elif not HistoryDB.done_cleaning:
# Run VACUUM on sqlite
# Perform initialization only once
if not HistoryDB.startup_done:
if create_table:
self.create_history_db()
# When an object (table, index, or trigger) is dropped from the database, it leaves behind empty space
# http://www.sqlite.org/lang_vacuum.html
HistoryDB.done_cleaning = True
self.execute("VACUUM")
self.execute("PRAGMA user_version;")
try:
version = self.cursor.fetchone()["user_version"]
except (IndexError, TypeError):
version = 0
# See if we need to perform any updates
self.execute("PRAGMA user_version;")
try:
version = self.cursor.fetchone()["user_version"]
except (IndexError, TypeError):
version = 0
# Add any new columns added since last DB version
# Use "and" to stop when database has been reset due to corruption
if version < 1:
_ = (
self.execute("PRAGMA user_version = 1;", save=True)
and self.execute("ALTER TABLE history ADD COLUMN series TEXT;", save=True)
and self.execute("ALTER TABLE history ADD COLUMN md5sum TEXT;", save=True)
)
if version < 2:
_ = self.execute("PRAGMA user_version = 2;", save=True) and self.execute(
"ALTER TABLE history ADD COLUMN password TEXT;", save=True
)
if version < 3:
# Transfer data to new column (requires WHERE-hack), original column should be removed later
_ = (
self.execute("PRAGMA user_version = 3;", save=True)
and self.execute("ALTER TABLE history ADD COLUMN duplicate_key TEXT;", save=True)
and self.execute("UPDATE history SET duplicate_key = series WHERE 1 = 1;", save=True)
)
# Add any new columns added since last DB version
# Use "and" to stop when database has been reset due to corruption
if version < 1:
_ = (
self.execute("PRAGMA user_version = 1;")
and self.execute("ALTER TABLE history ADD COLUMN series TEXT;")
and self.execute("ALTER TABLE history ADD COLUMN md5sum TEXT;")
)
if version < 2:
_ = self.execute("PRAGMA user_version = 2;") and self.execute(
"ALTER TABLE history ADD COLUMN password TEXT;"
)
if version < 3:
# Transfer data to new column (requires WHERE-hack), original column should be removed later
_ = (
self.execute("PRAGMA user_version = 3;")
and self.execute("ALTER TABLE history ADD COLUMN duplicate_key TEXT;")
and self.execute("UPDATE history SET duplicate_key = series WHERE 1 = 1;")
)
HistoryDB.startup_done = True
def execute(self, command: str, args: Sequence = (), save: bool = False) -> bool:
def execute(self, command: str, args: Sequence = ()) -> bool:
"""Wrapper for executing SQL commands"""
for tries in (4, 3, 2, 1, 0):
try:
self.cursor.execute(command, args)
if save:
self.connection.commit()
return True
except:
error = str(sys.exc_info()[1])
@@ -129,6 +132,7 @@ class HistoryDB:
remove_file(HistoryDB.db_path)
except:
pass
HistoryDB.startup_done = False
self.connect()
# Return False in case of "duplicate column" error
# because the column addition in connect() must be terminated
@@ -141,6 +145,7 @@ class HistoryDB:
try:
self.connection.rollback()
except:
# Can fail in case of automatic rollback
logging.debug("Rollback Failed:", exc_info=True)
return False
@@ -178,10 +183,9 @@ class HistoryDB:
"password" TEXT,
"duplicate_key" TEXT
)
""",
save=True,
"""
)
self.execute("PRAGMA user_version = 3;", save=True)
self.execute("PRAGMA user_version = 3;")
def close(self):
"""Close database connection"""
@@ -196,9 +200,7 @@ class HistoryDB:
"""Remove all completed jobs from the database, optional with `search` pattern"""
search = convert_search(search)
logging.info("Removing all completed jobs from history")
return self.execute(
"""DELETE FROM history WHERE name LIKE ? AND status = ?""", (search, Status.COMPLETED), save=True
)
return self.execute("""DELETE FROM history WHERE name LIKE ? AND status = ?""", (search, Status.COMPLETED))
def get_failed_paths(self, search=None):
"""Return list of all storage paths of failed jobs (may contain non-existing or empty paths)"""
@@ -215,9 +217,7 @@ class HistoryDB:
"""Remove all failed jobs from the database, optional with `search` pattern"""
search = convert_search(search)
logging.info("Removing all failed jobs from history")
return self.execute(
"""DELETE FROM history WHERE name LIKE ? AND status = ?""", (search, Status.FAILED), save=True
)
return self.execute("""DELETE FROM history WHERE name LIKE ? AND status = ?""", (search, Status.FAILED))
def remove_history(self, jobs=None):
"""Remove all jobs in the list `jobs`, empty list will remove all completed jobs"""
@@ -228,7 +228,7 @@ class HistoryDB:
jobs = [jobs]
for job in jobs:
self.execute("""DELETE FROM history WHERE nzo_id = ?""", (job,), save=True)
self.execute("""DELETE FROM history WHERE nzo_id = ?""", (job,))
logging.info("[%s] Removing job %s from history", caller_name(), job)
def auto_history_purge(self):
@@ -247,9 +247,7 @@ class HistoryDB:
if days_to_keep > 0:
logging.info("Removing completed jobs older than %s days from history", days_to_keep)
return self.execute(
"""DELETE FROM history WHERE status = ? AND completed < ?""",
(Status.COMPLETED, seconds_to_keep),
save=True,
"""DELETE FROM history WHERE status = ? AND completed < ?""", (Status.COMPLETED, seconds_to_keep)
)
else:
# How many to keep?
@@ -261,7 +259,6 @@ class HistoryDB:
SELECT id FROM history WHERE status = ? ORDER BY completed DESC LIMIT ?
)""",
(Status.COMPLETED, Status.COMPLETED, to_keep),
save=True,
)
def add_history_db(self, nzo, storage: str, postproc_time: int, script_output: str, script_line: str):
@@ -274,7 +271,6 @@ class HistoryDB:
downloaded, fail_message, url_info, bytes, duplicate_key, md5sum, password)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
t,
save=True,
)
logging.info("Added job %s to history", nzo.final_name)

View File

@@ -209,7 +209,7 @@ def decode_uu(article: Article, raw_data: bytes) -> bytes:
"""Try to uu-decode an article. The raw_data may or may not contain headers.
If there are headers, they will be separated from the body by at least one
empty line. In case of no headers, the first line seems to always be the nntp
response code (222) directly followed by the msg body."""
response code (220/222) directly followed by the msg body."""
if not raw_data:
logging.debug("No data to decode")
raise BadUu
@@ -232,7 +232,7 @@ def decode_uu(article: Article, raw_data: bytes) -> bytes:
uu_start = raw_data[:limit].index(b"") + 1
except ValueError:
# No empty line, look for a response code instead
if raw_data[0].startswith(b"222 "):
if raw_data[0].startswith(b"220 ") or raw_data[0].startswith(b"222 "):
uu_start = 1
else:
# Invalid data?

View File

@@ -80,7 +80,6 @@ class Server:
"required",
"optional",
"retention",
"send_group",
"username",
"password",
"busy_threads",
@@ -111,7 +110,6 @@ class Server:
use_ssl,
ssl_verify,
ssl_ciphers,
send_group,
username=None,
password=None,
required=False,
@@ -134,15 +132,6 @@ class Server:
self.required: bool = required
self.optional: bool = optional
self.retention: int = retention
self.send_group: bool = send_group
# TODO: Remove after next release
if send_group:
helpful_warning(
"You have 'Send Group' enabled for %s. Could you let us know why? https://github.com/sabnzbd/sabnzbd/discussions/2715",
self.displayname,
)
self.username: Optional[str] = username
self.password: Optional[str] = password
@@ -331,7 +320,6 @@ class Downloader(Thread):
required = srv.required()
optional = srv.optional()
retention = int(srv.retention() * 24 * 3600) # days ==> seconds
send_group = srv.send_group()
create = True
if oldserver:
@@ -358,7 +346,6 @@ class Downloader(Thread):
ssl,
ssl_verify,
ssl_ciphers,
send_group,
username,
password,
required,
@@ -742,7 +729,9 @@ class Downloader(Thread):
time.sleep(0.01)
sabnzbd.BPSMeter.update()
if nw.status_code != 222 and not done:
# Response code depends on request command:
# # 220 = ARTICLE, 222 = BODY
if nw.status_code not in (220, 222) and not done:
if not nw.connected or nw.status_code == 480:
if not self.__finish_connect_nw(nw):
return
@@ -754,13 +743,7 @@ class Downloader(Thread):
done = True
logging.debug("Article <%s> is present", article.article)
elif nw.status_code == 211:
logging.debug("group command ok -> %s", nw.nntp_msg)
nw.group = nw.article.nzf.nzo.group
nw.reset_data_buffer()
self.__request_article(nw)
elif nw.status_code in (411, 423, 430):
elif nw.status_code in (411, 423, 430, 451):
done = True
logging.debug(
"Thread %s@%s: Article %s missing (error=%s)",
@@ -783,6 +766,17 @@ class Downloader(Thread):
nw.reset_data_buffer()
self.__request_article(nw)
else:
logging.warning(
T("%s@%s: Received unknown status code %s for article %s"),
nw.thrdnum,
nw.server.host,
nw.status_code,
article.article,
)
done = True
nw.reset_data_buffer()
if done:
# Successful data, clear "bad" counter
server.bad_cons = 0
@@ -975,16 +969,9 @@ class Downloader(Thread):
def __request_article(self, nw: NewsWrapper):
try:
nzo = nw.article.nzf.nzo
if nw.server.send_group and nzo.group != nw.group:
group = nzo.group
if sabnzbd.LOG_ALL:
logging.debug("Thread %s@%s: GROUP <%s>", nw.thrdnum, nw.server.host, group)
nw.send_group(group)
else:
if sabnzbd.LOG_ALL:
logging.debug("Thread %s@%s: BODY %s", nw.thrdnum, nw.server.host, nw.article.article)
nw.body()
if sabnzbd.LOG_ALL:
logging.debug("Thread %s@%s: BODY %s", nw.thrdnum, nw.server.host, nw.article.article)
nw.body()
# Mark as ready to be read
self.add_socket(nw.nntp.fileno, nw)
except socket.error as err:

View File

@@ -50,7 +50,6 @@ from sabnzbd.misc import (
is_lan_addr,
is_local_addr,
is_loopback_addr,
helpful_warning,
recursive_html_escape,
is_none,
get_cpu_name,
@@ -81,7 +80,7 @@ from sabnzbd.constants import (
GUESSIT_SORT_TYPES,
VALID_NZB_FILES,
VALID_ARCHIVES,
DEF_TIMEOUT,
DEF_TEST_TIMEOUT,
)
from sabnzbd.lang import list_languages
from sabnzbd.api import (
@@ -1144,7 +1143,7 @@ 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_TIMEOUT)):
if not happyeyeballs(host, int_conv(port), int_conv(kwargs.get("timeout"), default=DEF_TEST_TIMEOUT)):
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port), ajax)
# Default server name is just the host name
@@ -1162,7 +1161,7 @@ def handle_server(kwargs, root=None, new_svr=False):
if new_svr:
server = unique_svr_name(server)
for kw in ("ssl", "send_group", "enable", "required", "optional"):
for kw in ("ssl", "enable", "required", "optional"):
if kw not in kwargs.keys():
kwargs[kw] = None
if svr and not new_svr:

View File

@@ -482,7 +482,7 @@ def check_latest_version():
# Fetch version info
data = get_from_url("https://sabnzbd.org/latest.txt")
if not data:
logging.info("Cannot retrieve version information from GitHub.com")
logging.info("Cannot retrieve version information from sabnzbd.org")
logging.debug("Traceback: ", exc_info=True)
return

View File

@@ -177,13 +177,6 @@ class NewsWrapper:
self.nntp.sock.sendall(command)
self.reset_data_buffer()
def send_group(self, group: str):
"""Send the NNTP GROUP command"""
self.timeout = time.time() + self.server.timeout
command = utob("GROUP %s\r\n" % group)
self.nntp.sock.sendall(command)
self.reset_data_buffer()
def recv_chunk(self) -> Tuple[int, bool]:
"""Receive data, return #bytes, done, skip"""
# Resize the buffer in the extremely unlikely case that it got full
@@ -390,7 +383,7 @@ class NNTP:
# Ignore if the socket was already closed, resulting in errors
if not self.closed:
msg = "Failed to connect: %s %s@%s:%s (%s)" % (
msg = T("Failed to connect: %s %s@%s:%s (%s)") % (
str(error),
self.nw.thrdnum,
self.nw.server.host,

View File

@@ -23,6 +23,7 @@ sabnzbd.notifier - Send notifications to any notification services
import os.path
import logging
import platform
import urllib.request
import urllib.parse
import http.client
@@ -34,7 +35,7 @@ import sabnzbd
import sabnzbd.cfg
from sabnzbd.encoding import utob
from sabnzbd.filesystem import make_script_path
from sabnzbd.misc import build_and_run_command
from sabnzbd.misc import build_and_run_command, int_conv
from sabnzbd.newsunpack import create_env
if sabnzbd.WIN32:
@@ -42,11 +43,15 @@ if sabnzbd.WIN32:
from win32comext.shell import shell
from windows_toasts import InteractableWindowsToaster, Toast, ToastActivatedEventArgs, ToastButton
# Only Windows 10 and above are supported
if int_conv(platform.release()) < 10:
raise OSError
# Set a custom AUMID to display the right icon, it is written to the registry by the installer
shell.SetCurrentProcessExplicitAppUserModelID("SABnzbd")
_HAVE_WINDOWS_TOASTER = True
except:
# Only supported on Windows 10 and above
# Sending toasts on non-supported platforms results in segfaults
_HAVE_WINDOWS_TOASTER = False
try:
@@ -210,6 +215,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 +234,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 +267,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 +334,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 +369,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")}
@@ -411,6 +418,7 @@ def send_windows(title: str, msg: str, notification_type: str, actions: Optional
if sabnzbd.WIN_SERVICE:
return None
logging.debug("Sending Windows notification")
try:
if _HAVE_WINDOWS_TOASTER:
notification_sender = InteractableWindowsToaster("SABnzbd", notifierAUMID="SABnzbd")

View File

@@ -1884,11 +1884,15 @@ class NzbObject(TryList):
return None, None, None
# Only a subset we want to apply directly to the NZO
for attrib in ("final_name", "priority", "password", "url"):
for attrib in ("final_name", "priority", "url"):
# Only set if it is present and has a value
if attribs.get(attrib):
setattr(self, attrib, attribs[attrib])
# Only set password if it wasn't already set
if not self.password and attribs.get("password"):
self.password = attribs["password"]
# Rest is to be used directly in the NZO-init flow
return attribs["cat"], attribs["pp"], attribs["script"]

View File

@@ -92,7 +92,7 @@ def osx_shutdown():
def osx_standby():
"""Make macOS system sleep, returns after wakeup"""
try:
subprocess.call(["osascript", "-e", 'tell app "System Events" to sleep'])
subprocess.call(["pmset", "sleepnow"])
time.sleep(10)
except:
logging.error(T("Failed to standby system"))

View File

@@ -567,8 +567,6 @@ SKIN_TEXT = {
"button-clrServer": TT("Clear Counters"), #: Button: Clear server's byte counters
"srv-testing": TT("Testing server details..."),
"srv-bandwidth": TT("Bandwidth"),
"srv-send_group": TT("Send Group"),
"srv-explain-send_group": TT("Send group command before requesting articles."),
"srv-notes": TT("Personal notes"),
"srv-article-availability": TT("Article availability"),
"srv-articles-tried": TT(

View File

@@ -29,7 +29,7 @@ def generate_key(key_size=2048, output_file="key.pem"):
private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
encryption_algorithm=serialization.NoEncryption(),
# encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase")
)
)

View File

@@ -241,7 +241,6 @@ kDNSServiceInterfaceIndexLocalOnly = -1
class BonjourError(Exception):
"""
Exception representing an error returned by the DNS-SD library.
@@ -321,7 +320,6 @@ _DNSServiceErrorType = ctypes.c_int32
class DNSRecordRef(ctypes.c_void_p):
"""
A DNSRecordRef pointer. DO NOT CREATE INSTANCES OF THIS CLASS!
@@ -367,7 +365,6 @@ class _DNSRecordRef_or_null(DNSRecordRef):
class DNSServiceRef(DNSRecordRef):
"""
A DNSServiceRef pointer. DO NOT CREATE INSTANCES OF THIS CLASS!
@@ -1740,7 +1737,6 @@ def DNSServiceConstructFullName(service=None, regtype=_NO_DEFAULT, domain=_NO_DE
class TXTRecord(object):
"""
A mapping representing a DNS TXT record. The TXT record's

View File

@@ -20,9 +20,8 @@ sabnzbd.utils.servertests - Debugging server connections. Currently only NNTP se
"""
import socket
import sys
from sabnzbd.constants import DEF_TIMEOUT
from sabnzbd.constants import DEF_TEST_TIMEOUT
from sabnzbd.newswrapper import NewsWrapper, NNTPPermanentError
from sabnzbd.downloader import Server, clues_login, clues_too_many
from sabnzbd.config import get_servers
@@ -37,7 +36,7 @@ def test_nntp_server_dict(kwargs):
password = kwargs.get("password", "").strip()
server = kwargs.get("server", "").strip()
connections = int_conv(kwargs.get("connections", 0))
timeout = int_conv(kwargs.get("timeout", DEF_TIMEOUT))
timeout = int_conv(kwargs.get("timeout", DEF_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()
@@ -56,7 +55,7 @@ def test_nntp_server_dict(kwargs):
if not timeout:
# Lower value during new server testing
timeout = 10
timeout = DEF_TEST_TIMEOUT
if "*" in password and not password.strip("*"):
# If the password is masked, try retrieving it from the config
@@ -78,7 +77,6 @@ def test_nntp_server_dict(kwargs):
use_ssl=ssl,
ssl_verify=ssl_verify,
ssl_ciphers=ssl_ciphers,
send_group=False,
username=username,
password=password,
)

View File

@@ -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.3RC3"
__baseline__ = "unknown"

View File

@@ -769,7 +769,10 @@ class TestQueueApi(ApiTestFunctions):
("my_scripted_script_.py", True, True),
("유닉스.py", True, True),
pytest.param(
"유닉스.sh", True, True, marks=pytest.mark.skipif(sys.platform.startswith("win"), reason="Not for Windows")
"유닉스.sh",
True,
True,
marks=pytest.mark.skipif(sys.platform.startswith("win"), reason="Not for Windows"),
),
pytest.param(
"لغة برمجة نصية",

View File

@@ -150,11 +150,11 @@ class TestPostProc:
"order": 0,
"pp": None,
"script": None,
"dir": os.path.join(
SAB_CACHE_DIR, ("category_dir_for_" + category + ("*" if not has_jobdir else ""))
)
if has_catdir
else None,
"dir": (
os.path.join(SAB_CACHE_DIR, ("category_dir_for_" + category + ("*" if not has_jobdir else "")))
if has_catdir
else None
),
"newzbin": "",
"priority": 0,
},