Compare commits

...

25 Commits

Author SHA1 Message Date
Safihre
210f254f63 Update text files for 4.2.0Alpha2 2023-10-27 14:40:22 +02:00
Safihre
ecdccda1ce Remove support to upgrade from 2.3.9 and older 2023-10-27 14:40:22 +02:00
Safihre
ed66ac91e0 Remove old nzo.md5packs attribute 2023-10-27 14:40:22 +02:00
SABnzbd Automation
e571165c15 Update translatable texts
[skip ci]
2023-10-27 10:20:00 +00:00
Safihre
1513664b5f Lock all config dict operations
Closes #2685
2023-10-27 12:19:12 +02:00
SABnzbd Automation
0132d81c43 Update translatable texts
[skip ci]
2023-10-25 14:19:40 +00:00
Safihre
8d32da8b27 Refactor of some parts of Config saving 2023-10-25 16:06:28 +02:00
Safihre
b5fbc8af86 Refactor handling of Complete vs Incomplete check
Closes #2717
2023-10-25 16:06:28 +02:00
SABnzbd Automation
d0166b5a5c Update translatable texts
[skip ci]
2023-10-25 10:01:25 +00:00
renovate[bot]
ada77d6970 Update all dependencies (#2716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-25 12:00:42 +02:00
Safihre
9f8758b242 Mark newshosting Happy EyeBalls tests as xfail 2023-10-25 11:18:41 +02:00
SABnzbd Automation
5ca629ebea Update translatable texts
[skip ci]
2023-10-24 18:30:36 +00:00
Safihre
f9f3820652 Refactor the way we getaddrinfo and use Happy Eyeballs (#2713)
* Refactor the way we getaddrinfo and use  Happy Eyeballs

* Move tests to right directory

* Do not run Happy Eyeballs for only 1 address

* Process feedback

* Make sure we always have a canonname

* Show IP and resolved name in Status Window

* Simplify Status server updates

* Remove unused imports
2023-10-24 20:29:50 +02:00
Safihre
08e61ecf19 Build all binaries without unspecified dependencies 2023-10-23 15:44:26 +02:00
SABnzbd Automation
d15f0cafce Update translatable texts
[skip ci]
2023-10-23 11:53:27 +00:00
Safihre
1b85253940 Limit recursive unpack to 2 additional levels
Closes #2714
2023-10-23 13:50:40 +02:00
Safihre
b329ff007e Wrap DEF_FILE_MAX check in try/except
See #2714
2023-10-23 13:42:56 +02:00
Safihre
f6918d598a Add thread name to start-up logging of Downloader 2023-10-23 12:16:36 +02:00
Safihre
0cdfdd82d4 Renovate ignored tests requirements 2023-10-23 09:04:53 +02:00
SABnzbd Automation
de3649dba4 Update translatable texts
[skip ci]
2023-10-22 18:15:07 +00:00
Safihre
9ba975ac44 Remove &nbsp from translation string 2023-10-22 20:12:37 +02:00
Safihre
2b0ea92da8 Ask users why they still have Send Group enabled 2023-10-20 17:06:58 +02:00
Safihre
b79a1e973d Revert removal of SABnzbd-console.exe
Sad-face.. See pyinstaller/pyinstaller/issues/8022
2023-10-20 16:26:15 +02:00
renovate[bot]
1be4cf986d Update all dependencies 2023-10-16 09:37:20 +02:00
SABnzbd Automation
18c4226b90 Update translatable texts
[skip ci]
2023-10-15 19:25:19 +00:00
86 changed files with 812 additions and 872 deletions

View File

@@ -8,7 +8,6 @@
"before 8am on Monday"
],
"ignorePaths": [
"tests/**",
".github/workflows/**"
],
"pip_requirements": {
@@ -22,7 +21,8 @@
},
"ignoreDeps": [
"jaraco.text",
"sabctools"
"sabctools",
"werkzeug"
],
"packageRules": [
{

View File

@@ -63,8 +63,8 @@ jobs:
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt
pip install --upgrade -r builder/requirements.txt
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary (32bit and legacy)
run: python builder/package.py binary
- name: Upload Windows standalone binary (32bit and legacy)
@@ -112,19 +112,18 @@ jobs:
# We have to manually take a few steps:
# 1. Because building cryptography is hard, and we cannot force pip to fetch universal2 version we
# first install the x86 version (and it's dependencies) and then manually fetch the universal2 build
# https://github.com/pyca/cryptography/issues/5918
# 2. Due to PyObjC we cannot run pip on the main requirements without installing dependencies
# 3. We need to build the PyInstaller bootloader:
# https://github.com/pypa/pip/issues/5453
# 2. We need to build the PyInstaller bootloader:
# https://github.com/pyinstaller/pyinstaller/issues/6235
if: steps.cache-virtualenv.outputs.cache-hit != 'true'
run: |
python3 --version
pip3 install --upgrade pip wheel
pip3 install --upgrade -r requirements.txt --no-binary cffi
pip3 install --upgrade -r requirements.txt --no-binary cffi --no-dependencies
pip3 uninstall cryptography -y
pip3 download -r builder/osx/requirements.txt --platform macosx_10_12_universal2 --only-binary :all: --no-deps --dest .
pip3 download -r builder/osx/requirements.txt --platform macosx_10_12_universal2 --only-binary :all: --no-dependencies --dest .
pip3 install -r builder/osx/requirements.txt --no-cache-dir --no-index --find-links .
PYINSTALLER_COMPILE_BOOTLOADER=1 pip3 install --upgrade -r builder/requirements.txt --no-binary pyinstaller --no-dependencies

View File

@@ -21,10 +21,10 @@ jobs:
- name: Push/pull Transifex translations
if: env.TX_TOKEN
# Add --translation to the push command in order to update Transifex using local translation edits
# However, this prevents modifying existing translations in Transifex as they will be overwritten by the push!
# However, this prevents modifying existing translations in Transifex as they will be overwritten by the push!
run: |
curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash
./tx push --source
./tx push --source
./tx pull --all --force
- name: Compile translations to validate them
run: |

View File

@@ -1,17 +1,25 @@
Release Notes - SABnzbd 4.2.0 Alpha 1
Release Notes - SABnzbd 4.2.0 Alpha 2
=========================================================
## Changes since 4.1.0
- Numerous smaller performance improvements were made.
- Windows: Removed `SABnzbd-console.exe`.
If you need console logging, run `SABnzbd.exe --console`.
- Reduced recursive unpacking to 2 levels, instead of 5.
- IPv6 addresses are preferred during server address selection.
- Stricter check if `Complete Folder` is inside `Download Folder`.
- Windows: Reduced size of installer.
- Windows/macOS: Updated to Python 3.12.
## Bugfixes since 4.1.0
- Multi-select in the queue was broken for some users.
- Prevent crash during saving of configuration.
- Removing a failed download from the history could break active downloads.
## Upgrade notices
- Direct upgrade is possible from version 3.0.0 and newer.
Upgrading from older versions will require `Queue repair`.
- Downgrading from version 4.2.0 or newer to 3.7.2 or older will
require `Queue repair` due to changes in the internal data format.
## Known problems and solutions
- Read the file "ISSUES.txt"

View File

@@ -860,7 +860,7 @@ def main():
ipv6_hosting = None
inet_exposure = None
service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
_service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
for opt, arg in sab_opts:
if opt == "--servicecall":
@@ -949,8 +949,9 @@ def main():
sabnzbd.DIR_LANGUAGE = real_path(sabnzbd.DIR_PROG, DEF_LANGUAGE)
org_dir = os.getcwd()
# Need console logging if requested or just running as script
console_logging = (console_logging or not hasattr(sys, "frozen")) and not sabnzbd.DAEMON
# Need console logging if requested, for SABnzbd.py and SABnzbd-console.exe
console_logging = console_logging or sys.executable.endswith("console.exe") or not hasattr(sys, "frozen")
console_logging = console_logging and not sabnzbd.DAEMON
LOGLEVELS = (logging.FATAL, logging.WARNING, logging.INFO, logging.DEBUG)
@@ -1639,6 +1640,12 @@ if sabnzbd.WIN32:
"access to network shares."
)
# Only SABnzbd-console.exe can print to the console, so the service is installed
# from there. But we run SABnzbd.exe so nothing is logged. Logging can cause the
# Windows Service to stop because the output buffers are full.
if hasattr(sys, "frozen"):
_exe_name_ = "SABnzbd.exe"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

View File

@@ -113,8 +113,7 @@ exe = EXE(
[],
exclude_binaries=True,
name="SABnzbd",
console=True,
hide_console="hide-early",
console=False,
append_pkg=False,
icon="icons/sabnzbd.ico",
contents_directory=".",
@@ -126,6 +125,29 @@ exe = EXE(
coll = COLLECT(exe, pyi_analysis.binaries, pyi_analysis.zipfiles, pyi_analysis.datas, name="SABnzbd")
# We need to run again for the console-app
if sys.platform == "win32":
# Enable console=True for this one
console_exe = EXE(
pyz,
pyi_analysis.scripts,
[],
exclude_binaries=True,
name="SABnzbd-console",
append_pkg=False,
icon="icons/sabnzbd.ico",
contents_directory=".",
version=version_info,
)
console_coll = COLLECT(
console_exe,
pyi_analysis.binaries,
pyi_analysis.zipfiles,
pyi_analysis.datas,
name="SABnzbd-console",
)
# Build the APP on macOS
if sys.platform == "darwin":
info_plist = {

View File

@@ -1,3 +1,3 @@
# Special requirements for macOS universal2 binary release
# This way dependabot can auto-update them
cryptography==41.0.4
cryptography==41.0.5

View File

@@ -227,6 +227,9 @@ if __name__ == "__main__":
# Run PyInstaller and check output
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
shutil.copytree("dist/SABnzbd-console", "dist/SABnzbd", dirs_exist_ok=True)
safe_remove("dist/SABnzbd-console")
# Remove unwanted DLL's
shutil.rmtree("dist/SABnzbd/Pythonwin")
if BUILDING_64BIT:

View File

@@ -19,7 +19,6 @@ import hashlib
import json
import os
import re
import shutil
import xml.etree.ElementTree as ET
import github

View File

@@ -2,15 +2,19 @@
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.1.0
packaging==23.2
pyinstaller-hooks-contrib==2023.9
pyinstaller-hooks-contrib==2023.10
altgraph==0.17.4
wrapt==1.15.0
setuptools==68.2.2
certifi
# orjson does not support 32bit Windows, exclude it based on Python-version
# This way we also test ujson on Python 3.8 in the CI-tests
orjson==3.9.7; python_version > '3.8'
# Required on 32bit Windows, exclude it based on Python-version
importlib_metadata==6.8.0; python_version < '3.10'
importlib_resources==6.1.0; 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.9; python_version > '3.8'
# For the Windows build
pefile==2023.2.7; sys_platform == 'win32'

View File

@@ -225,15 +225,20 @@
</div>
<div class="row" data-bind="visible: serverssl">
<div class="col-sm-6">$T('srv-ssl')</div>
<div class="col-sm-6">
<span class="glyphicon glyphicon-ok"></span> <span data-bind="text: serversslinfo"></span>
<div class="col-sm-6 col-dot-overflow">
<span class="glyphicon glyphicon-ok"></span>
<span data-bind="text: serversslinfo"></span>
</div>
</div>
<div class="row">
<div class="col-sm-6"># $T('connections')</div>
<div class="col-sm-6">
<span data-bind="text: serverconnections().length"></span> /
<span data-bind="text: servertotalconn"></span>
<span data-bind="text: servertotalconn"></span><br>
<!-- ko if: serveripaddress() -->
<span data-bind="text: servercanonname"></span><br>
<span data-bind="text: serveripaddress"></span>
<!-- /ko -->
</div>
</div>
<div class="row">
@@ -249,11 +254,11 @@
</div>
</div>
</div>
<div class="row" data-bind="visible: !isFinite(serveractiveconn())">
<div class="row" data-bind="visible: serverwarning()">
<div class="col-sm-12">
<div class="alert alert-warning">
<span class="glyphicon glyphicon-info-sign"></span>
<span data-bind="text: serveractiveconn()"></span>
<span data-bind="text: serverwarning()"></span>
</div>
</div>
</div>

View File

@@ -783,43 +783,7 @@ function ViewModel() {
}
// Update the servers
if (self.statusInfo.servers().length !== data.status.servers.length) {
// Empty them, in case of update
self.statusInfo.servers([])
// Initial add
$.each(data.status.servers, function() {
self.statusInfo.servers.push({
'servername': ko.observable(this.servername),
'serveroptional': ko.observable(this.serveroptional),
'serverpriority': ko.observable(this.serverpriority),
'servertotalconn': ko.observable(this.servertotalconn),
'serverssl': ko.observable(this.serverssl),
'serversslinfo': ko.observable(this.serversslinfo),
'serveractiveconn': ko.observable(this.serveractiveconn),
'servererror': ko.observable(this.servererror),
'serveractive': ko.observable(this.serveractive),
'serverconnections': ko.observableArray(this.serverconnections),
'serverbps': ko.observable(this.serverbps)
})
})
} else {
// Update
$.each(data.status.servers, function(index) {
var activeServer = self.statusInfo.servers()[index];
activeServer.servername(this.servername),
activeServer.serveroptional(this.serveroptional),
activeServer.serverpriority(this.serverpriority),
activeServer.servertotalconn(this.servertotalconn),
activeServer.serverssl(this.serverssl),
activeServer.serversslinfo(this.serversslinfo),
activeServer.serveractiveconn(this.serveractiveconn),
activeServer.servererror(this.servererror),
activeServer.serveractive(this.serveractive),
activeServer.serverconnections(this.serverconnections),
activeServer.serverbps(this.serverbps)
})
}
ko.mapping.fromJS(data.status.servers, {}, self.statusInfo.servers)
// Add tooltips to possible new items
if (!isMobile) $('#modal-options [data-tooltip="true"]').tooltip({ trigger: 'hover', container: 'body' })

View File

@@ -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.2.0" date="2023-11-26" type="stable"/>
<release version="4.1.0" date="2023-09-26" type="stable"/>
<release version="4.0.3" date="2023-06-16" type="stable"/>
<release version="4.0.2" date="2023-06-09" type="stable"/>

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -149,7 +149,7 @@ msgid "Test Notification"
msgstr ""
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgid "Resolving address"
msgstr ""
#. No value, used in dropdown menus
@@ -233,10 +233,6 @@ msgstr ""
msgid "Incorrect parameter"
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr ""
@@ -245,7 +241,7 @@ msgstr ""
msgid "Server address required"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr ""
@@ -263,7 +259,15 @@ msgid "Permissions setting of %s might deny SABnzbd access to the files and fold
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgid "UNC path \"%s\" not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid "The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"
msgstr ""
#. Warning message
@@ -380,7 +384,7 @@ msgid "Paused"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
@@ -613,10 +617,6 @@ msgstr ""
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid "The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -744,7 +744,6 @@ msgstr ""
msgid "Running script"
msgstr ""
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr ""

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# Pavel C <quoing_transifex@mess.cz>, 2023
# Safihre <safihre@sabnzbd.org>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2023\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -166,7 +166,7 @@ msgid "Test Notification"
msgstr "Otestovat notifikace"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgid "Resolving address"
msgstr "Překládám adresu"
#. No value, used in dropdown menus
@@ -258,10 +258,6 @@ msgstr "Kvóta přesažena, pozastavuji stahování"
msgid "Incorrect parameter"
msgstr "Nesprávný parametr"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC cesta \"%s\" zde není povolena"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s není validní emailová adresa"
@@ -270,7 +266,7 @@ msgstr "%s není validní emailová adresa"
msgid "Server address required"
msgstr "Adresa serveru je vyžadována"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr ""
@@ -290,8 +286,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Chyba: Fronta nené prázdná, nelze změnit složku."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC cesta \"%s\" zde není povolena"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -414,7 +420,7 @@ msgid "Paused"
msgstr "Pozastaveno"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Musíte nastavit maximální rychlost linky předtím než začnete nastavovat "
@@ -661,12 +667,6 @@ msgstr "Přihlášené selhalo, zkontrolujte jméno a heslo."
msgid "Unsuccessful login attempt from %s"
msgstr "Nezdařený pokus o přihlášení od %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -799,7 +799,6 @@ msgstr ""
msgid "Running script"
msgstr "Běžící skript"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -166,8 +166,8 @@ msgid "Test Notification"
msgstr "Afprøv notifikation"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp; Server løsning"
msgid "Resolving address"
msgstr "Server løsning"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -258,10 +258,6 @@ msgstr "Kvote brugt, pause downloading"
msgid "Incorrect parameter"
msgstr "Fejl parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC søgning \"%s\" er ikke tilladt her"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s er ikke en godkendt e-mail adresse"
@@ -270,7 +266,7 @@ msgstr "%s er ikke en godkendt e-mail adresse"
msgid "Server address required"
msgstr "Kræver serveradresse"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Ugyldig server adresse."
@@ -290,8 +286,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Fejl: Køen er ikke tom, kan ikke skifte mappe."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC søgning \"%s\" er ikke tilladt her"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -412,7 +418,7 @@ msgid "Paused"
msgstr "Sat på pause"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Du skal angive den maksimale båndbredde, før du kan angive en båndbredde "
@@ -669,12 +675,6 @@ msgstr "Godkendelse mislykkedes, kontrollere brugernavn/adgangskode."
msgid "Unsuccessful login attempt from %s"
msgstr "Mislykkede login forsøg fra %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -805,7 +805,6 @@ msgstr "Film sortering"
msgid "Running script"
msgstr "Køre script"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Udpakning af nesting for dybt [%s]"

View File

@@ -15,7 +15,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
@@ -182,8 +182,8 @@ msgid "Test Notification"
msgstr "Benachrichtigungen testen"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Adresse wird aufgelöst …"
msgid "Resolving address"
msgstr "Adresse wird aufgelöst …"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -277,10 +277,6 @@ msgstr "Kontingent aufgebraucht, Downloads werden angehalten"
msgid "Incorrect parameter"
msgstr "Fehlerhafter Parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-Pfad \"%s\" ist hier nicht erlaubt"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s ist keine gültige E-Mail-Adresse"
@@ -289,7 +285,7 @@ msgstr "%s ist keine gültige E-Mail-Adresse"
msgid "Server address required"
msgstr "Server-Adresse wird benötigt"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Ungültige Server-Adresse."
@@ -311,10 +307,20 @@ msgstr ""
"erstellten Dateien und Ordner von SABnzbd verweigern."
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-Pfad \"%s\" ist hier nicht erlaubt"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
"Fehler: Ordner kann nicht geändert werden, da die Warteschlange nicht leer "
"ist."
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Der \"Abgeschlossene Downloads\"-Ordner darf kein Unterordner des "
"\"Temporäre Downloads\"-Ordners sein."
#. Warning message
#: sabnzbd/cfg.py
@@ -442,7 +448,7 @@ msgid "Paused"
msgstr "Angehalten"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Bevor ein Bandbreitenlimit gesetzt werden kann, muss die maximale Bandbreite"
@@ -706,14 +712,6 @@ msgstr ""
msgid "Unsuccessful login attempt from %s"
msgstr "Fehlerhafter Login Versuch von %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Der \"Abgeschlossene Downloads\"-Ordner darf kein Unterordner des "
"\"Temporäre Downloads\"-Ordners sein."
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr "Invalides Backup Archiv"
@@ -849,7 +847,6 @@ msgstr "Film Sortierung"
msgid "Running script"
msgstr "Ausführen des Skripts"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Entpacken zu tief verschachtelt [%s]"

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -175,8 +175,8 @@ msgid "Test Notification"
msgstr "Notificación de prueba"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Resolviendo sitio"
msgid "Resolving address"
msgstr "Resolviendo sitio"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -273,10 +273,6 @@ msgstr "Quota gastado, pausando cola"
msgid "Incorrect parameter"
msgstr "Parámetro incorrecto"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Ruta de acceso UNC \"%s\" no permitido aqui"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s no es una dirección de correo electrónico válida."
@@ -285,7 +281,7 @@ msgstr "%s no es una dirección de correo electrónico válida."
msgid "Server address required"
msgstr "Se necesita la dirección del servidor"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Dirección del servidor no válida."
@@ -305,8 +301,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Error: Cola no esta vacía, no se puede cambiar el directorio"
msgid "UNC path \"%s\" not allowed here"
msgstr "Ruta de acceso UNC \"%s\" no permitido aqui"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -433,7 +439,7 @@ msgid "Paused"
msgstr "En pausa"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Debe establecer un ancho de banda máximo antes de poder establecer un límite"
@@ -691,12 +697,6 @@ msgstr "Autenticación fallida, compruebe el usuario o la contraseña."
msgid "Unsuccessful login attempt from %s"
msgstr "Intento fallido de inicio de sesión desde %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -834,7 +834,6 @@ msgstr "Clasificación de películas"
msgid "Running script"
msgstr "Ejecutando script"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -168,8 +168,8 @@ msgid "Test Notification"
msgstr "Testaa ilmoitusta"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Selvitetään osoitetta"
msgid "Resolving address"
msgstr "Selvitetään osoitetta"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -256,10 +256,6 @@ msgstr "Latausrajoitus saavutettu, keskeytetään lataukset"
msgid "Incorrect parameter"
msgstr "Virheellinen parametri"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "TUNT polku \"%s\" ei ole sallittu"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s ei ole kelvollinen sähköpostiosoite"
@@ -268,7 +264,7 @@ msgstr "%s ei ole kelvollinen sähköpostiosoite"
msgid "Server address required"
msgstr "Palvelimen osoite vaaditaan"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Virheellinen palvelimen osoite."
@@ -288,8 +284,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Virhe: Jono ei ole tyhjä, kansiota ei voida vaihtaa."
msgid "UNC path \"%s\" not allowed here"
msgstr "TUNT polku \"%s\" ei ole sallittu"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -410,7 +416,7 @@ msgid "Paused"
msgstr "Keskeytetty"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Sinun täytyy määrittää enimmäiskaista ennen kaistarajoituksen käyttöönottoa."
@@ -666,12 +672,6 @@ msgstr "Varmennus epäonnistui, tarkista käyttäjänimi/salasana."
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -802,7 +802,6 @@ msgstr ""
msgid "Running script"
msgstr "Ajetaan skripti"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Purkaessa havaittiin liikaa pakkauskerroksia [%s]"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2023\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
@@ -177,8 +177,8 @@ msgid "Test Notification"
msgstr "Test de Notification"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Résolution de l'adresse"
msgid "Resolving address"
msgstr "Résolution de l'adresse"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -275,10 +275,6 @@ msgstr "Quota atteint, téléchargement mis en pause"
msgid "Incorrect parameter"
msgstr "Paramètre incorrect"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Le chemin UNC \"%s\" n'est pas autorisé ici"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s n'est pas une adresse email valide"
@@ -287,7 +283,7 @@ msgstr "%s n'est pas une adresse email valide"
msgid "Server address required"
msgstr "Adresse du serveur requise"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Adresse du serveur erronée"
@@ -309,9 +305,20 @@ msgstr ""
"fichiers et dossiers qu'il crée."
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgid "UNC path \"%s\" not allowed here"
msgstr "Le chemin UNC \"%s\" n'est pas autorisé ici"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr "La file d'attente n'est pas vide, impossible de changer de dossier."
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Erreur : La file d'attente n'est pas vide, impossible de changer le dossier."
"Le dossier des téléchargements terminés ne peut pas être le même dossier que"
" les téléchargements temporaires, ni être l'un de ses sous-dossiers"
#. Warning message
#: sabnzbd/cfg.py
@@ -439,7 +446,7 @@ msgid "Paused"
msgstr "En pause"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Vous devez définir une bande passante maximale avant de pouvoir définir une "
@@ -706,14 +713,6 @@ msgstr "Echec d'authentification, vérifiez les identifiant/mot de passe."
msgid "Unsuccessful login attempt from %s"
msgstr "Echec de la tentative de connexion de %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Le dossier des téléchargements terminés ne peut pas être le même dossier que"
" les téléchargements temporaires, ni être l'un de ses sous-dossiers"
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr "Archives de sauvegarde non valides"
@@ -851,7 +850,6 @@ msgstr "Tri des films"
msgid "Running script"
msgstr "Exécution du script"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Arborescence trop profonde dans le fichier compressé [%s]"

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# ION, 2023
# Safihre <safihre@sabnzbd.org>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: ION, 2023\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -165,8 +165,8 @@ msgid "Test Notification"
msgstr "בחן התראה"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;פותר כתובת"
msgid "Resolving address"
msgstr "פותר כתובת"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -254,10 +254,6 @@ msgstr "מכסה נוצלה, משהה הורדה"
msgid "Incorrect parameter"
msgstr "פרמטר שגוי"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "נתיב UNC \"%s\" אינו מותר כאן"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s אינה כתובת דוא״ל תקפה"
@@ -266,7 +262,7 @@ msgstr "%s אינה כתובת דוא״ל תקפה"
msgid "Server address required"
msgstr "כתובת שרת דרושה"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "כתובת שרת בלתי תקפה."
@@ -287,8 +283,20 @@ msgstr ""
"הגדרת הרשאות של %s עשויה לדחות גישה מן SABnzbd אל הקבצים והתיקיות שהוא יוצר."
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "שגיאה: התור אינו ריק, לא ניתן לשנות תיקייה."
msgid "UNC path \"%s\" not allowed here"
msgstr "נתיב UNC \"%s\" אינו מותר כאן"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"תיקיית ההורדות השלמות אינה יכולה להיות אותה תיקייה או תת־תיקייה של תיקיית "
"ההורדות הזמניות"
#. Warning message
#: sabnzbd/cfg.py
@@ -411,7 +419,7 @@ msgid "Paused"
msgstr "מושהה"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr "אתה חייב לקבוע רוחב פס מרבי לפני שאתה קובע מגבלת רוחב פס"
@@ -664,14 +672,6 @@ msgstr "אימות נכשל, בדוק שם משתמש/סיסמה."
msgid "Unsuccessful login attempt from %s"
msgstr "ניסיון כניסה בלתי מוצלח מן %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"תיקיית ההורדות השלמות אינה יכולה להיות אותה תיקייה או תת־תיקייה של תיקיית "
"ההורדות הזמניות"
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr "ארכיון בלתי תקף של גיבוי"
@@ -804,7 +804,6 @@ msgstr "מיון סרטים"
msgid "Running script"
msgstr "מריץ תסריט"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "פריקת קינון ארוכה מדי [%s]"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -164,8 +164,8 @@ msgid "Test Notification"
msgstr "Test varslingen"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Løs adresse"
msgid "Resolving address"
msgstr "Løs adresse"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -252,10 +252,6 @@ msgstr "Kvote oppbrukt, setter nedlasting på pause"
msgid "Incorrect parameter"
msgstr "Feil parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-sti \"%s\" er ikke tillatt her"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s er ikke en godkjent e-post-adresse"
@@ -264,7 +260,7 @@ msgstr "%s er ikke en godkjent e-post-adresse"
msgid "Server address required"
msgstr "Krever server-adresse"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Ugyldig server-adresse."
@@ -284,8 +280,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Feil: Køen er ikke tom, kan ikke bytte mappe."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-sti \"%s\" er ikke tillatt her"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -406,7 +412,7 @@ msgid "Paused"
msgstr "Pauset"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr "Du må sette maks båndbredde før du kan sette en båndbreddebegrensning"
@@ -661,12 +667,6 @@ msgstr "Godkjenning mislyktes, kontroller brukernavn og passord."
msgid "Unsuccessful login attempt from %s"
msgstr "Mislykket påloggingsforsøk fra %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -797,7 +797,6 @@ msgstr ""
msgid "Running script"
msgstr "Kjører skript"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Utpakking nestet for dypt [%s]"

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
@@ -172,8 +172,8 @@ msgid "Test Notification"
msgstr "Test melding"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Adres opzoeken"
msgid "Resolving address"
msgstr "Adres opzoeken"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -268,10 +268,6 @@ msgstr "Quotum verbruikt, download is gestopt"
msgid "Incorrect parameter"
msgstr "Incorrecte parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-pad '%s' hier niet toegestaan."
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s is geen geldig e-mailadres"
@@ -280,7 +276,7 @@ msgstr "%s is geen geldig e-mailadres"
msgid "Server address required"
msgstr "Serveradres verplicht"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Ongeldige servernaam"
@@ -302,8 +298,20 @@ msgstr ""
"tot de aangemaakte bestanden en mappen."
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Fout: Wachtrij is niet leeg, andere map kiezen niet mogelijk."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-pad '%s' hier niet toegestaan."
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"De Map voor verwerkte downloads mag niet een map in de Tijdelijke download "
"map zijn."
#. Warning message
#: sabnzbd/cfg.py
@@ -432,7 +440,7 @@ msgid "Paused"
msgstr "Gepauzeerd"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Je moet eerst een maximumbandbreedte instellen voordat je een limiet kunt "
@@ -695,14 +703,6 @@ msgstr "Inloggen mislukt, controleer gebruikersnaam en wachtwoord."
msgid "Unsuccessful login attempt from %s"
msgstr "Mislukte login poging van %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"De Map voor verwerkte downloads mag niet een map in de Tijdelijke download "
"map zijn."
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr "Ongeldig backup bestand"
@@ -838,7 +838,6 @@ msgstr "Film sorteren"
msgid "Running script"
msgstr "Script uitvoeren"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Teveel niveaus om uit te pakken [%s]"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -160,8 +160,8 @@ msgid "Test Notification"
msgstr "Powiadomienie testowe"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Rozwiązywanie adresu"
msgid "Resolving address"
msgstr "Rozwiązywanie adresu"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -248,10 +248,6 @@ msgstr "Przekroczono limit, wstrzymywanie pobierania"
msgid "Incorrect parameter"
msgstr "Błędny parametr"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Ścieżka UNC \"%s\" niedozwolona"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s nie jest prawidłowym adresem email"
@@ -260,7 +256,7 @@ msgstr "%s nie jest prawidłowym adresem email"
msgid "Server address required"
msgstr "Wymagane jest podanie adresu serwera"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Nieprawidłowy adres serwera."
@@ -280,8 +276,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Błąd: Kolejka nie jest pusta, nie można zmienić katalogu."
msgid "UNC path \"%s\" not allowed here"
msgstr "Ścieżka UNC \"%s\" niedozwolona"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -402,7 +408,7 @@ msgid "Paused"
msgstr "Wstrzymano"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Przed ustawieniem limitu przepustowości należy ustawić maksymalną "
@@ -661,12 +667,6 @@ msgstr "Błąd połączenia, sprawdź nazwę użytkownika i hasło."
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -797,7 +797,6 @@ msgstr ""
msgid "Running script"
msgstr "Uruchamianie skryptu"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Zbyt głęboki poziom zagnieżdżenia podczas rozpakowywania [%s]"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -164,8 +164,8 @@ msgid "Test Notification"
msgstr "Notificação de teste"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Resolvendo endereço"
msgid "Resolving address"
msgstr "Resolvendo endereço"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -252,10 +252,6 @@ msgstr "Quota esgotada, pausando o download"
msgid "Incorrect parameter"
msgstr "Parâmetro incorreto"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "O caminho UNC \"%s\" não é permitido aqui"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s não é um endereço de e-mail válido"
@@ -264,7 +260,7 @@ msgstr "%s não é um endereço de e-mail válido"
msgid "Server address required"
msgstr "Endereço do servidor necessário"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Endereço do servidor inválido."
@@ -284,8 +280,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Erro: A fila não está vazia. Não será possível mudar de pasta."
msgid "UNC path \"%s\" not allowed here"
msgstr "O caminho UNC \"%s\" não é permitido aqui"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -408,7 +414,7 @@ msgid "Paused"
msgstr "Pausado"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Você deve definir a largura de banda máxima antes de definir um limite de "
@@ -665,12 +671,6 @@ msgstr "Falha de autenticação, verifique usuário / senha."
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -801,7 +801,6 @@ msgstr ""
msgid "Running script"
msgstr "Executando script"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Aninhamento de descompactação com muitos níveis [%s]"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -169,8 +169,8 @@ msgid "Test Notification"
msgstr "Notificări Test"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Reolvare adresă"
msgid "Resolving address"
msgstr "Reolvare adresă"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -263,10 +263,6 @@ msgstr "Cotă epuizată, întrerupem descărcarea"
msgid "Incorrect parameter"
msgstr "Parametru Incorect"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "cale UNC \"%s\" nu este premisă aici"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s nu este o adresă email validă"
@@ -275,7 +271,7 @@ msgstr "%s nu este o adresă email validă"
msgid "Server address required"
msgstr "Adresă server necesară"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Adresă server invalidă"
@@ -295,8 +291,20 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Eroare: Coada nu este goală, nu pot schimba dosar."
msgid "UNC path \"%s\" not allowed here"
msgstr "cale UNC \"%s\" nu este premisă aici"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Directorul de descărcări finalizate nu poate fi același, sau un subdirector "
"al directorului de descărcări temporare"
#. Warning message
#: sabnzbd/cfg.py
@@ -421,7 +429,7 @@ msgid "Paused"
msgstr "Întrerupt"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Trebuie să seta-ţi lățimea de bandă maximă înainte de a seta o limită de "
@@ -678,14 +686,6 @@ msgstr "Autentificare nereuşită, verifică nume utilizator/parolă."
msgid "Unsuccessful login attempt from %s"
msgstr "Încercare de conectare nereușită de la %s"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Directorul de descărcări finalizate nu poate fi același, sau un subdirector "
"al directorului de descărcări temporare"
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -820,7 +820,6 @@ msgstr ""
msgid "Running script"
msgstr "Rulare script"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Numărul de arhive încorporate este prea mare [%s]"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -164,8 +164,8 @@ msgid "Test Notification"
msgstr "Тестовое уведомление"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Разрешение адреса"
msgid "Resolving address"
msgstr "Разрешение адреса"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -252,10 +252,6 @@ msgstr "Квота исчерпана. Загрузка приостановле
msgid "Incorrect parameter"
msgstr "Неправильный параметр"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-путь «%s» здесь не допускается"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s не является допустимым адресом электронной почты"
@@ -264,7 +260,7 @@ msgstr "%s не является допустимым адресом элект
msgid "Server address required"
msgstr "Требуется адрес сервера"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Недопустимый адрес сервера."
@@ -284,8 +280,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Ошибка: очередь не пустая, папку нельзя изменить."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-путь «%s» здесь не допускается"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -406,7 +412,7 @@ msgid "Paused"
msgstr "Приостановлено"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
@@ -661,12 +667,6 @@ msgstr "Ошибка проверки подлинности. Проверьте
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -797,7 +797,6 @@ msgstr ""
msgid "Running script"
msgstr "Запуск сценария"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -162,8 +162,8 @@ msgid "Test Notification"
msgstr "Probno obaveštenje"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Решавање адресе"
msgid "Resolving address"
msgstr "Решавање адресе"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -250,10 +250,6 @@ msgstr "Kvota utrošena, pauziram preuzimanja"
msgid "Incorrect parameter"
msgstr "Погрешан параметар"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC путања \"%s\" није дозвољена"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s nije ispravna email adresa"
@@ -262,7 +258,7 @@ msgstr "%s nije ispravna email adresa"
msgid "Server address required"
msgstr "Потребна је адреса сервера"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Погрешна адреса сервера."
@@ -282,8 +278,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Грешка: ред није празан, фасцикла се не може променити."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC путања \"%s\" није дозвољена"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -404,7 +410,7 @@ msgid "Paused"
msgstr "Паузирано"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr ""
"Требате да поставите максимални проток пре него што поставите ограничење"
@@ -658,12 +664,6 @@ msgstr "Аутентификација погрешна, проверити им
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -794,7 +794,6 @@ msgstr ""
msgid "Running script"
msgstr "Покретање скрипта"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Previše ugnježdenih nivoa pri raspakivanju [%s]"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"
@@ -162,8 +162,8 @@ msgid "Test Notification"
msgstr "Testa notifikation"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Lösa adress"
msgid "Resolving address"
msgstr "Lösa adress"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -250,10 +250,6 @@ msgstr "Din kvot är uppnådd, pausar nerladdning"
msgid "Incorrect parameter"
msgstr "Fel parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC sökväg \"%s\" är inte tillåten här"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s är inte en godkänd e-mail adress"
@@ -262,7 +258,7 @@ msgstr "%s är inte en godkänd e-mail adress"
msgid "Server address required"
msgstr "Kräver serveradress"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "Ogiltig serveradress"
@@ -282,8 +278,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "Fel: Kön är inte tom, kan inte byta mapp."
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC sökväg \"%s\" är inte tillåten här"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -405,7 +411,7 @@ msgid "Paused"
msgstr "Pausad"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr "Du måste ange maximal bandbredd innan du kan ange bandbreddsgräns"
@@ -660,12 +666,6 @@ msgstr "Autentisering misslyckades, kontrollera användarnamn och lösenord."
msgid "Unsuccessful login attempt from %s"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -796,7 +796,6 @@ msgstr ""
msgid "Running script"
msgstr "Kör skript"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "Nästling för djup [%s]"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
@@ -160,8 +160,8 @@ msgid "Test Notification"
msgstr "测试通知"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;正在解析地址"
msgid "Resolving address"
msgstr "正在解析地址"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -248,10 +248,6 @@ msgstr "配额已耗尽,暂停下载"
msgid "Incorrect parameter"
msgstr "参数不正确"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "此处不允许使用 UNC 路径 \"%s\""
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s 不是有效的电子邮箱地址"
@@ -260,7 +256,7 @@ msgstr "%s 不是有效的电子邮箱地址"
msgid "Server address required"
msgstr "服务器地址必填"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py, sabnzbd/utils/servertests.py
msgid "Invalid server address."
msgstr "服务器地址无效。"
@@ -280,8 +276,18 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "Error: Queue not empty, cannot change folder."
msgstr "错误: 队列非空,无法变更文件夹。"
msgid "UNC path \"%s\" not allowed here"
msgstr "此处不允许使用 UNC 路径 \"%s\""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#. Warning message
#: sabnzbd/cfg.py
@@ -402,7 +408,7 @@ msgid "Paused"
msgstr "已暂停"
#. Warning message
#: sabnzbd/downloader.py, sabnzbd/interface.py, sabnzbd/skintext.py
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "You must set a maximum bandwidth before you can set a bandwidth limit"
msgstr "设置带宽限制前,您必须设置最大带宽值"
@@ -653,12 +659,6 @@ msgstr "身份认证失败,请检查用户名/密码。"
msgid "Unsuccessful login attempt from %s"
msgstr "%s 中有失败的登陆请求"
#: sabnzbd/interface.py
msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
@@ -789,7 +789,6 @@ msgstr "电影排序"
msgid "Running script"
msgstr "正在执行脚本"
#. Warning message
#: sabnzbd/newsunpack.py
msgid "Unpack nesting too deep [%s]"
msgstr "解压嵌套层级过深 [%s]"

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2021\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: ION, 2021\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2021\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha1\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"

View File

@@ -30,7 +30,7 @@ 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==41.0.4
cryptography==41.0.5
# 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

View File

@@ -109,11 +109,6 @@ import sabnzbd.bpsmeter
import sabnzbd.scheduler as scheduler
import sabnzbd.notifier as notifier
from sabnzbd.decorators import synchronized
from sabnzbd.constants import (
DEFAULT_PRIORITY,
VALID_ARCHIVES,
REPAIR_REQUEST,
)
import sabnzbd.utils.ssdp
# Storage for the threads, variables are filled during initialization
@@ -242,11 +237,11 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
if cfg.wait_for_dfolder():
filesystem.wait_for_download_folder()
# Set the folders to be created, then the check_incomplete_vs_complete
# check will create them by calling get_path on them
# Create the folders, now that we waited for them to be available
cfg.download_dir.set_create(True)
cfg.download_dir.create_path()
cfg.complete_dir.set_create(True)
filesystem.check_incomplete_vs_complete()
cfg.complete_dir.create_path()
# Set call backs for Config items
cfg.cache_limit.callback(cfg.new_limit)

View File

@@ -1292,11 +1292,11 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
info["servers"] = []
# Servers-list could be modified during iteration, so we need a copy
for server in sabnzbd.Downloader.servers[:]:
connected = sum(nw.connected for nw in server.idle_threads.copy())
activeconn = sum(nw.connected for nw in server.idle_threads.copy())
serverconnections = []
for nw in server.busy_threads.copy():
if nw.connected:
connected += 1
activeconn += 1
if nw.article:
serverconnections.append(
{
@@ -1307,25 +1307,31 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
}
)
if server.warning and not (connected or server.errormsg):
connected = server.warning
if server.request and not server.info:
connected = T("&nbsp;Resolving address").replace("&nbsp;", "")
server_info = {
"servername": server.displayname,
"serveractiveconn": connected,
"serveractive": server.active,
"serveractiveconn": activeconn,
"servertotalconn": server.threads,
"serverconnections": serverconnections,
"serverssl": server.ssl,
"serversslinfo": server.ssl_info,
"serveractive": server.active,
"serveripaddress": None,
"servercanonname": None,
"serverwarning": server.warning,
"servererror": server.errormsg,
"serverpriority": server.priority,
"serveroptional": server.optional,
"serverbps": to_units(sabnzbd.BPSMeter.server_bps.get(server.id, 0)),
}
# Only add this information if we are connected
if activeconn and server.addrinfo:
server_info["serveripaddress"] = server.addrinfo.ipaddress
server_info["servercanonname"] = server.addrinfo.canonname
if server.request and not server.addrinfo:
server_info["serverwarning"] = T("Resolving address")
info["servers"].append(server_info)
return info

View File

@@ -25,7 +25,7 @@ import re
import argparse
import socket
import ipaddress
from typing import List, Tuple
from typing import List, Tuple, Union
import sabnzbd
from sabnzbd.config import (
@@ -51,7 +51,11 @@ from sabnzbd.constants import (
DEF_HTTPS_CERT_FILE,
DEF_HTTPS_KEY_FILE,
)
from sabnzbd.filesystem import long_path
from sabnzbd.filesystem import same_directory, real_path
# Validators currently only are made for string/list-of-strings
# and return those on success or an error message.
ValidateResult = Union[Tuple[None, str], Tuple[None, List[str]], Tuple[str, None]]
##############################################################################
@@ -64,7 +68,7 @@ class ErrorCatchingArgumentParser(argparse.ArgumentParser):
raise ValueError
def clean_nice_ionice_parameters(value):
def clean_nice_ionice_parameters(value: str) -> ValidateResult:
"""Verify that the passed parameters are not exploits"""
if value:
parser = ErrorCatchingArgumentParser()
@@ -87,30 +91,20 @@ def clean_nice_ionice_parameters(value):
return None, value
def all_lowercase(value):
"""Lowercase everything!"""
def all_lowercase(value: Union[str, List]) -> Tuple[None, Union[str, List]]:
"""Lowercase and strip everything!"""
if isinstance(value, list):
# If list, for each item
return None, [item.lower() for item in value]
return None, value.lower()
return None, [item.lower().strip() for item in value]
return None, value.lower().strip()
def lower_case_ext(value):
def lower_case_ext(value: Union[str, List]) -> Tuple[None, Union[str, List]]:
"""Generate lower case extension(s), without dot"""
if isinstance(value, list):
return None, [item.lower().strip(" .") for item in value]
return None, value.lower().strip(" .")
def validate_no_unc(root, value, default):
"""Check if path isn't a UNC path"""
# Only need to check the 'value' part
if value and not value.startswith(r"\\"):
return validate_notempty(root, value, default)
else:
return T('UNC path "%s" not allowed here') % value, None
def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
"""Don't split single indexer tags like "TV > HD"
into ['TV', '>', 'HD']
@@ -121,7 +115,7 @@ def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
return None, value
def validate_strip_right_slash(value):
def validate_strip_right_slash(value: str) -> Tuple[None, str]:
"""Strips the right slash"""
if value:
return None, value.rstrip("/")
@@ -131,8 +125,7 @@ def validate_strip_right_slash(value):
RE_VAL = re.compile(r"[^@ ]+@[^.@ ]+\.[^.@ ]")
def validate_email(value):
global email_endjob, email_full, email_rss
def validate_email(value: Union[List, str]) -> ValidateResult:
if email_endjob() or email_full() or email_rss():
if isinstance(value, list):
values = value
@@ -144,18 +137,16 @@ def validate_email(value):
return None, value
def validate_server(value):
def validate_server(value: str) -> ValidateResult:
"""Check if server non-empty"""
global email_endjob, email_full, email_rss
if value == "" and (email_endjob() or email_full() or email_rss()):
return T("Server address required"), None
else:
return None, value
def validate_host(value):
def validate_host(value: str) -> ValidateResult:
"""Check if host is valid: an IP address, or a name/FQDN that resolves to an IP address"""
# easy: value is a plain IPv4 or IPv6 address:
try:
ipaddress.ip_address(value)
@@ -195,7 +186,7 @@ def validate_host(value):
return T("Invalid server address."), None
def validate_script(value):
def validate_script(value: str) -> ValidateResult:
"""Check if value is a valid script"""
if not sabnzbd.__INITIALIZED__ or (value and sabnzbd.filesystem.is_valid_script(value)):
return None, value
@@ -204,7 +195,7 @@ def validate_script(value):
return T("%s is not a valid script") % value, None
def validate_permissions(value: str):
def validate_permissions(value: str) -> ValidateResult:
"""Check the permissions for correct input"""
# Octal verification
if not value:
@@ -225,18 +216,46 @@ def validate_permissions(value: str):
return None, value
def validate_safedir(root, value, default):
def validate_safedir(root: str, value: str, default: str) -> ValidateResult:
"""Allow only when queues are empty and no UNC"""
if not sabnzbd.__INITIALIZED__ or (sabnzbd.PostProcessor.empty() and sabnzbd.NzbQueue.is_empty()):
return validate_no_unc(root, value, default)
if value.startswith(r"\\"):
return T('UNC path "%s" not allowed here') % value, None
else:
return validate_default_if_empty(root, value, default)
else:
return T("Error: Queue not empty, cannot change folder."), None
return T("Queue not empty, cannot change folder."), None
def validate_scriptdir_not_appdir(root, value, default):
def validate_download_vs_complete_dir(root: str, value: str, default: str):
"""Make sure download_dir and complete_dir are not identical
or that download_dir is not a subfolder of complete_dir"""
# Check what new value we are trying to set
if default == DEF_COMPLETE_DIR:
check_download_dir = download_dir.get_path()
check_complete_dir = real_path(root, value)
elif default == DEF_DOWNLOAD_DIR:
check_download_dir = real_path(root, value)
check_complete_dir = complete_dir.get_path()
else:
raise ValueError("Validator can only be used for download_dir/complete_dir")
if same_directory(check_download_dir, check_complete_dir):
return (
T("The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"),
None,
)
elif default == DEF_COMPLETE_DIR:
# The complete_dir allows UNC
return validate_default_if_empty(root, value, default)
else:
return validate_safedir(root, value, default)
def validate_scriptdir_not_appdir(root: str, value: str, default: str) -> Tuple[None, str]:
"""Warn users to not use the Program Files folder for their scripts"""
# Need to add seperator so /mnt/sabnzbd and /mnt/sabnzbd-data are not detected as equal
if value and long_path(os.path.join(root, value)).startswith(long_path(sabnzbd.DIR_PROG) + os.pathsep):
if value and same_directory(sabnzbd.DIR_PROG, os.path.join(root, value)):
# Warn, but do not block
sabnzbd.misc.helpful_warning(
T(
@@ -246,7 +265,7 @@ def validate_scriptdir_not_appdir(root, value, default):
return None, value
def validate_notempty(root, value, default):
def validate_default_if_empty(root: str, value: str, default: str) -> Tuple[None, str]:
"""If value is empty, return default"""
if value:
return None, value
@@ -311,11 +330,21 @@ socks5_proxy_url = OptionStr("misc", "socks5_proxy_url")
##############################################################################
permissions = OptionStr("misc", "permissions", validation=validate_permissions)
download_dir = OptionDir(
"misc", "download_dir", DEF_DOWNLOAD_DIR, create=False, apply_permissions=True, validation=validate_safedir
"misc",
"download_dir",
DEF_DOWNLOAD_DIR,
create=False, # Flag is modified and directory is created during initialize!
apply_permissions=True,
validation=validate_download_vs_complete_dir,
)
download_free = OptionStr("misc", "download_free")
complete_dir = OptionDir(
"misc", "complete_dir", DEF_COMPLETE_DIR, create=False, apply_permissions=True, validation=validate_notempty
"misc",
"complete_dir",
DEF_COMPLETE_DIR,
create=False, # Flag is modified and directory is created during initialize!
apply_permissions=True,
validation=validate_download_vs_complete_dir,
)
complete_free = OptionStr("misc", "complete_free")
fulldisk_autoresume = OptionBool("misc", "fulldisk_autoresume", False)
@@ -326,7 +355,7 @@ backup_dir = OptionDir("misc", "backup_dir")
dirscan_dir = OptionDir("misc", "dirscan_dir", writable=False)
dirscan_speed = OptionNumber("misc", "dirscan_speed", DEF_SCANRATE, minval=0, maxval=3600)
password_file = OptionDir("misc", "password_file", "", create=False)
log_dir = OptionDir("misc", "log_dir", "logs", validation=validate_notempty)
log_dir = OptionDir("misc", "log_dir", "logs", validation=validate_default_if_empty)
##############################################################################

View File

@@ -46,8 +46,7 @@ from sabnzbd.constants import (
from sabnzbd.decorators import synchronized
from sabnzbd.filesystem import clip_path, real_path, create_real_path, renamer, remove_file, is_writable
CONFIG_LOCK = threading.Lock()
SAVE_CONFIG_LOCK = threading.Lock()
CONFIG_LOCK = threading.RLock()
CFG_OBJ: configobj.ConfigObj # Holds INI structure
@@ -236,6 +235,11 @@ class OptionDir(Option):
self.__writable: bool = writable
super().__init__(section, keyword, default_val, add=add, public=public, protect=protect)
def create_path(self, path: Optional[str] = None):
if not path:
path = self.get()
return create_real_path(self.keyword, self.__root, path, self.__apply_permissions, self.__writable)
def get(self) -> str:
"""Return value, corrected for platform"""
p = super().get()
@@ -245,15 +249,12 @@ class OptionDir(Option):
return p.replace("\\", "/") if "\\" in p else p
def get_path(self) -> str:
"""Return full absolute path"""
value = self.get()
"""Return full absolute path, create it if necessary"""
path = ""
if value:
if value := self.get():
path = real_path(self.__root, value)
if self.__create and not os.path.exists(path):
_, path, _ = create_real_path(
self.keyword, self.__root, value, self.__apply_permissions, self.__writable
)
_, path, _ = self.create_path(value)
return path
def get_clipped_path(self) -> str:
@@ -262,8 +263,7 @@ class OptionDir(Option):
def test_path(self) -> bool:
"""Return True if path exists"""
value = self.get()
if value:
if value := self.get():
return os.path.exists(real_path(self.__root, value))
else:
return False
@@ -285,9 +285,7 @@ class OptionDir(Option):
error, value = self.__validation(self.__root, value, super().default)
if not error:
if value and (self.__create or create):
res, path, error = create_real_path(
self.keyword, self.__root, value, self.__apply_permissions, self.__writable
)
_, path, error = self.create_path(value)
if not error:
super().set(value)
return error
@@ -783,6 +781,7 @@ def delete_from_database(section, keyword):
CFG_MODIFIED = True
@synchronized(CONFIG_LOCK)
def get_dconfig(section, keyword, nested=False):
"""Return a config values dictionary,
Single item or slices based on 'section', 'keyword'
@@ -829,6 +828,7 @@ def get_dconfig(section, keyword, nested=False):
return True, data
@synchronized(CONFIG_LOCK)
def get_config(section: str, keyword: str) -> Optional[AllConfigTypes]:
"""Return a config object, based on 'section', 'keyword'"""
try:
@@ -838,6 +838,7 @@ def get_config(section: str, keyword: str) -> Optional[AllConfigTypes]:
return None
@synchronized(CONFIG_LOCK)
def set_config(kwargs):
"""Set a config item, using values in dictionary"""
try:
@@ -848,6 +849,7 @@ def set_config(kwargs):
return True
@synchronized(CONFIG_LOCK)
def delete(section: str, keyword: str):
"""Delete specific config item"""
try:
@@ -862,7 +864,7 @@ def delete(section: str, keyword: str):
# This does input and output of configuration to an INI file.
# It translates this data structure to the config database.
##############################################################################
@synchronized(SAVE_CONFIG_LOCK)
@synchronized(CONFIG_LOCK)
def read_config(path):
"""Read the complete INI file and check its version number
if OK, pass values to config-database
@@ -947,7 +949,7 @@ def _read_config(path, try_backup=False):
return True, ""
@synchronized(SAVE_CONFIG_LOCK)
@synchronized(CONFIG_LOCK)
def save_config(force=False):
"""Update Setup file with current option values"""
global CFG_OBJ, CFG_DATABASE, CFG_MODIFIED
@@ -1102,6 +1104,7 @@ def restore_config_backup(config_backup_data: bytes):
logging.info("Traceback: ", exc_info=True)
@synchronized(CONFIG_LOCK)
def get_servers() -> Dict[str, ConfigServer]:
global CFG_DATABASE
try:
@@ -1110,6 +1113,7 @@ def get_servers() -> Dict[str, ConfigServer]:
return {}
@synchronized(CONFIG_LOCK)
def get_sorters() -> Dict[str, ConfigSorter]:
global CFG_DATABASE
try:
@@ -1128,6 +1132,7 @@ def get_ordered_sorters() -> List[Dict]:
return sorters
@synchronized(CONFIG_LOCK)
def get_categories() -> Dict[str, ConfigCat]:
"""Return link to categories section.
This section will always contain special category '*'
@@ -1179,6 +1184,7 @@ def get_ordered_categories() -> List[Dict]:
return categories
@synchronized(CONFIG_LOCK)
def get_rss() -> Dict[str, ConfigRSS]:
global CFG_DATABASE
try:

View File

@@ -23,7 +23,7 @@ import time
import select
import logging
from math import ceil
from threading import Thread, RLock
from threading import Thread, RLock, current_thread
import socket
import sys
import ssl
@@ -34,8 +34,8 @@ from sabnzbd.decorators import synchronized, NzbQueueLocker, DOWNLOADER_CV, DOWN
from sabnzbd.newswrapper import NewsWrapper, NNTPPermanentError
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.misc import from_units, get_server_addrinfo, helpful_warning, int_conv, MultiAddQueue
from sabnzbd.utils.happyeyeballs import happyeyeballs
from sabnzbd.misc import from_units, helpful_warning, int_conv, MultiAddQueue
from sabnzbd.happyeyeballs import happyeyeballs, AddrInfo
from sabnzbd.constants import SOFT_QUEUE_LIMIT
@@ -91,7 +91,7 @@ class Server:
"bad_cons",
"errormsg",
"warning",
"info",
"addrinfo",
"ssl_info",
"request",
"have_body",
@@ -136,6 +136,13 @@ class Server:
self.retention: int = retention
self.send_group: bool = send_group
# TODO: Remove for final 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
@@ -147,7 +154,7 @@ class Server:
self.bad_cons: int = 0
self.errormsg: str = ""
self.warning: str = ""
self.info: Optional[List] = None # Will hold getaddrinfo() list
self.addrinfo: Union[AddrInfo, None, bool] = None # Will hold fasted address information
self.ssl_info: str = "" # Will hold the type and cipher of SSL connection
self.request: bool = False # True if a getaddrinfo() request is pending
self.have_body: bool = True # Assume server has "BODY", until proven otherwise
@@ -163,35 +170,6 @@ class Server:
# Tell the BPSMeter about this server
sabnzbd.BPSMeter.init_server_stats(self.id)
@property
def hostip(self) -> str:
"""In case a server still has active connections, we use the same IP again.
If new connection then use happyeyeballs if there are multiple options.
In case of problems: return the host name itself
"""
# Check if already a successful ongoing connection
if self.busy_threads:
active_thread = next(iter(self.busy_threads))
if active_thread.nntp:
# Re-use that IP
logging.debug("%s: Re-using address %s", self.host, active_thread.nntp.host)
return active_thread.nntp.host
# Determine IP
ip = None
if self.info:
# RFC6555 / Happy Eyeballs in case of multiple options
if len(self.info) > 1:
ip = happyeyeballs(self.host, port=self.port)
# Just 1 IP found, or problem with happyeyeballs, return first one
if not ip:
ip = self.info[0][4][0]
else:
ip = self.host
logging.debug("%s: Connecting to address %s", self.host, ip)
return ip
def deactivate(self):
"""Deactivate server and reset queued articles"""
self.active = False
@@ -234,22 +212,22 @@ class Server:
sabnzbd.NzbQueue.reset_try_lists(article, remove_fetcher_from_trylist=False)
self.article_queue = []
def request_info(self):
"""Launch async request to resolve server address.
getaddrinfo() can be very slow. In some situations this can lead
to delayed starts and timeouts on connections.
def request_addrinfo(self):
"""Launch async request to resolve server address and perform Happy Eyeballs.
In some situations this can be slow and result in delayed starts and timeouts on connections.
Because of this, the results will be cached in the server object."""
if not self.request:
self.request = True
Thread(target=self._request_info_internal).start()
Thread(target=self.request_addrinfo_blocking).start()
def _request_info_internal(self):
"""Async attempt to run getaddrinfo() for specified server"""
def request_addrinfo_blocking(self):
"""Blocking attempt to run getaddrinfo() and Happy Eyeballs for specified server"""
logging.debug("Retrieving server address information for %s", self.host)
self.info = get_server_addrinfo(self.host, self.port)
if not self.info:
self.addrinfo = happyeyeballs(self.host, self.port)
if not self.addrinfo:
self.bad_cons += self.threads
self.info = False
# Notify next call to maybe_block_server
self.addrinfo = False
else:
self.bad_cons = 0
self.request = False
@@ -497,7 +475,7 @@ class Downloader(Thread):
def maybe_block_server(self, server: Server):
# Was it resolving problem?
if server.info is False:
if server.addrinfo is False:
# Warn about resolving issues
errormsg = T("Cannot connect to server %s [%s]") % (server.host, T("Server name does not resolve"))
if server.errormsg != errormsg:
@@ -528,7 +506,7 @@ class Downloader(Thread):
self.__reset_nw(nw, "forcing disconnect", warn=False, wait=False, retry_article=False)
# Make sure server address resolution is refreshed
server.info = None
server.addrinfo = None
@staticmethod
def decode(article, data_view: Optional[memoryview] = None):
@@ -581,7 +559,7 @@ class Downloader(Thread):
for server in self.servers:
# Skip this server if there's no point searching for new stuff to do
if server.info and not server.busy_threads and server.next_article_search > now:
if server.addrinfo and not server.busy_threads and server.next_article_search > now:
continue
if server.next_busy_threads_check < now:
@@ -625,11 +603,11 @@ class Downloader(Thread):
else:
nw.timeout = None
if not server.info:
if not server.addrinfo:
# Only request info if there's stuff in the queue
if not sabnzbd.NzbQueue.is_empty():
self.maybe_block_server(server)
server.request_info()
server.request_addrinfo()
break
nw.article = server.get_article()
@@ -661,7 +639,7 @@ class Downloader(Thread):
if nw.nntp:
self.__reset_nw(nw, "forcing disconnect", wait=False, count_article_try=False)
# Make sure server address resolution is refreshed
server.info = None
server.addrinfo = None
server.reset_article_queue()
self.force_disconnect = False
@@ -720,7 +698,7 @@ class Downloader(Thread):
Wrapped in try/except because in case of an exception, logging
might get lost and the queue.join() would block forever."""
try:
logging.debug("Starting Downloader receive thread")
logging.debug("Starting Downloader receive thread: %s", current_thread().name)
while True:
# The read_fds is passed by reference, so we can access its items!
self.process_nw(read_fds[nw_queue.get()])

View File

@@ -234,21 +234,25 @@ def sanitize_filename(name: str) -> str:
# preserving the extension (max ext length 20)
# Note: some filesystem can handle up to 255 UTF chars (which is more than 255 bytes) in the filename,
# but we stay on the safe side: max DEF_FILE_MAX bytes
if len(utob(name)) + len(utob(ext)) > DEF_FILE_MAX:
logging.debug("Filename %s is too long, so truncating", name + ext)
# Too long filenames are often caused by incorrect non-ascii chars,
# so brute-force remove those non-ascii chars
name = ubtou(name.encode("ascii", "ignore"))
# Now it's plain ASCII, so no need for len(str.encode()) anymore; plain len() is enough
if len(name) + len(ext) > DEF_FILE_MAX:
# still too long, limit the extension
maxextlength = 20 # max length of an extension
if len(ext) > maxextlength:
# allow first <maxextlength> chars, including the starting dot
ext = ext[:maxextlength]
try:
if len(utob(name)) + len(utob(ext)) > DEF_FILE_MAX:
logging.debug("Filename %s is too long, so truncating", name + ext)
# Too long filenames are often caused by incorrect non-ascii chars,
# so brute-force remove those non-ascii chars
name = ubtou(name.encode("ascii", "ignore"))
# Now it's plain ASCII, so no need for len(str.encode()) anymore; plain len() is enough
if len(name) + len(ext) > DEF_FILE_MAX:
# Still too long, limit the basename
name = name[: DEF_FILE_MAX - len(ext)]
# still too long, limit the extension
maxextlength = 20 # max length of an extension
if len(ext) > maxextlength:
# allow first <maxextlength> chars, including the starting dot
ext = ext[:maxextlength]
if len(name) + len(ext) > DEF_FILE_MAX:
# Still too long, limit the basename
name = name[: DEF_FILE_MAX - len(ext)]
except UnicodeError:
# Just in case of strange encoding problems, like #2714
pass
lowext = ext.lower()
if lowext == ".par2" and lowext != ext:
@@ -407,10 +411,10 @@ def create_real_path(
return False, path, None
def same_file(a: str, b: str) -> int:
def same_directory(a: str, b: str) -> int:
"""Return 0 if A and B have nothing in common
return 1 if A and B are actually the same path
return 2 if B is a subfolder of A
return 2 if B is a sub-folder of A
"""
if sabnzbd.WIN32 or sabnzbd.MACOS:
a = clip_path(a.lower())
@@ -419,6 +423,13 @@ def same_file(a: str, b: str) -> int:
a = os.path.normpath(os.path.abspath(a))
b = os.path.normpath(os.path.abspath(b))
# Need to add seperator so /mnt/sabnzbd and /mnt/sabnzbd-data are not detected as equal
# But only if it doesn't already end in a slash, for example C:\
if not a.endswith(os.sep):
a = a + os.sep
if not b.endswith(os.sep):
b = b + os.sep
# If it's the same file, it's also a sub-folder
is_subfolder = 0
if b.startswith(a):
@@ -865,11 +876,11 @@ def renamer(old: str, new: str, create_local_directories: bool = False) -> str:
oldpath, _ = os.path.split(old)
# Check not outside directory
# In case of "same_file() == 1": same directory, so nothing to do
if same_file(oldpath, path) == 0:
if same_directory(oldpath, path) == 0:
# Outside current directory, this is most likely malicious
logging.error(T("Blocked attempt to create directory %s"), path)
raise OSError("Refusing to go outside directory")
elif same_file(oldpath, path) == 2:
elif same_directory(oldpath, path) == 2:
# Sub-directory, so create if does not yet exist:
create_all_dirs(path)
@@ -1193,20 +1204,6 @@ def load_admin(data_id: str, remove=False, silent=False) -> Any:
return load_data(data_id, sabnzbd.cfg.admin_dir.get_path(), remove=remove, silent=silent)
def check_incomplete_vs_complete():
"""Make sure download_dir and complete_dir are not identical
or that download_dir is not a subfolder of complete_dir"""
complete = sabnzbd.cfg.complete_dir.get_path()
if same_file(sabnzbd.cfg.download_dir.get_path(), complete):
if real_path("X", sabnzbd.cfg.download_dir()) == long_path(sabnzbd.cfg.download_dir()):
# Abs path, so set download_dir as an abs path inside the complete_dir
sabnzbd.cfg.download_dir.set(os.path.join(complete, "incomplete"))
else:
sabnzbd.cfg.download_dir.set("incomplete")
return False
return True
def wait_for_download_folder():
"""Wait for download folder to become available"""
while not sabnzbd.cfg.download_dir.test_path():

126
sabnzbd/happyeyeballs.py Normal file
View File

@@ -0,0 +1,126 @@
#!/usr/bin/python3 -OO
# Copyright 2007-2023 The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
sabnzbd.happyeyeballs - Python implementation of RFC 6555 / Happy Eyeballs: find the quickest IPv4/IPv6 connection
"""
# Python implementation of RFC 6555 / Happy Eyeballs: find the quickest IPv4/IPv6 connection
# See https://tools.ietf.org/html/rfc6555
# Method: Start parallel sessions using threads, and only wait for the quickest successful socket connect
# See https://tools.ietf.org/html/rfc6555#section-4.1
# We do not implement caching, as the lookup result is stored in the Server object
import socket
import threading
import time
import logging
import queue
from dataclasses import dataclass
from typing import Tuple, Union, Optional
from sabnzbd import cfg as cfg
# We always prefer IPv6 connections
IP4_DELAY = 0.1
# For typing and convenience!
@dataclass
class AddrInfo:
family: socket.AddressFamily
type: socket.SocketKind
proto: int
canonname: str
sockaddr: Union[Tuple[str, int], Tuple[str, int, int, int]]
ipaddress: str = ""
def __post_init__(self):
# For easy access
self.ipaddress = self.sockaddr[0]
# Called by each thread
def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo, ipv4_delay: int):
"""Connect to the ip, and put the result into the queue"""
try:
s = socket.socket(addrinfo.family, addrinfo.type)
s.settimeout(3)
# Delay IPv4 connects in case we need it
if ipv4_delay and addrinfo.family == socket.AddressFamily.AF_INET:
time.sleep(ipv4_delay)
try:
s.connect(addrinfo.sockaddr)
finally:
s.close()
result_queue.put((addrinfo, True))
except:
# We got an exception, so no successful connect on IP & port:
result_queue.put((addrinfo, False))
def happyeyeballs(host: str, port: int) -> Optional[AddrInfo]:
"""Return the fastest result of getaddrinfo() based on RFC 6555 / Happy Eyeballs,
including IPv6 addresses if desired. Returns None in case no addresses were returned
or if no connection could be made to any of the addresses"""
try:
# Time how long it took us
start = time.time()
# Get address information, by default both IPV4 and IPV6
family = socket.AF_UNSPEC
if not cfg.ipv6_servers():
family = socket.AF_INET
all_addrinfo = []
ipv4_delay = 0
last_canonname = ""
for addrinfo in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, flags=socket.AI_CANONNAME):
# Convert to AddrInfo
all_addrinfo.append(addrinfo := AddrInfo(*addrinfo))
# We only want delay for IPv4 in case we got any IPv6
if addrinfo.family == socket.AddressFamily.AF_INET6:
ipv4_delay = IP4_DELAY
# The canonname is only reported once per alias
if addrinfo.canonname:
last_canonname = addrinfo.canonname
elif last_canonname:
addrinfo.canonname = last_canonname
logging.debug("Available addresses for %s (port=%d): %d", host, port, len(all_addrinfo))
# Fill queue used for threads that will return the results
# Even if there is just 1 result, we still check if we can connect
result_queue: queue.Queue[Tuple[AddrInfo, bool]] = queue.Queue()
for addrinfo in all_addrinfo:
threading.Thread(target=do_socket_connect, args=(result_queue, addrinfo, ipv4_delay), daemon=True).start()
# start reading from the Queue for message from the threads:
result = None
for _ in range(len(all_addrinfo)):
connect_result = result_queue.get()
if connect_result[1]:
result = connect_result[0]
break
logging.info("Quickest IP address for %s (port=%d): %s (%s)", host, port, result.ipaddress, result.canonname)
logging.debug("Happy Eyeballs lookup and port connect took: %d ms", int(1000 * (time.time() - start)))
return result
except Exception as e:
logging.debug("Failed Happy Eyeballs lookup: %s", e)
return None

View File

@@ -47,20 +47,19 @@ from sabnzbd.misc import (
get_base_url,
is_ipv4_addr,
is_ipv6_addr,
get_server_addrinfo,
is_lan_addr,
is_local_addr,
is_loopback_addr,
ip_in_subnet,
helpful_warning,
recursive_html_escape,
)
from sabnzbd.happyeyeballs import happyeyeballs
from sabnzbd.filesystem import (
real_path,
globber,
globber_full,
clip_path,
same_file,
same_directory,
setname_from_path,
)
from sabnzbd.encoding import xml_name, utob
@@ -180,8 +179,7 @@ def secured_expose(
# Some pages need correct API key
if check_api_key:
msg = check_apikey(kwargs)
if msg:
if msg := check_apikey(kwargs):
cherrypy.response.status = 403
if cfg.api_warnings():
return msg
@@ -738,21 +736,9 @@ class ConfigFolders:
@secured_expose(check_api_key=True, check_configlock=True)
def saveDirectories(self, **kwargs):
for kw in LIST_DIRPAGE + LIST_BOOL_DIRPAGE:
value = kwargs.get(kw)
if value is not None or kw in LIST_BOOL_DIRPAGE:
if kw in ("complete_dir", "dirscan_dir", "backup_dir"):
msg = config.get_config("misc", kw).set(value, create=True)
else:
msg = config.get_config("misc", kw).set(value)
if msg:
# return sabnzbd.api.report('json', error=msg)
return badParameterResponse(msg, kwargs.get("ajax"))
if msg := config.get_config("misc", kw).set(kwargs.get(kw)):
return badParameterResponse(msg, kwargs.get("ajax"))
if not sabnzbd.filesystem.check_incomplete_vs_complete():
return badParameterResponse(
T("The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"),
kwargs.get("ajax"),
)
config.save_config()
if kwargs.get("ajax"):
return sabnzbd.api.report()
@@ -832,8 +818,7 @@ class ConfigSwitches:
@secured_expose(check_api_key=True, check_configlock=True)
def saveSwitches(self, **kwargs):
for kw in SWITCH_LIST:
msg = config.get_config("misc", kw).set(kwargs.get(kw))
if msg:
if msg := config.get_config("misc", kw).set(kwargs.get(kw)):
return badParameterResponse(msg, kwargs.get("ajax"))
config.save_config()
@@ -933,10 +918,7 @@ class ConfigSpecial:
@secured_expose(check_api_key=True, check_configlock=True)
def saveSpecial(self, **kwargs):
for kw in SPECIAL_BOOL_LIST + SPECIAL_VALUE_LIST + SPECIAL_LIST_LIST:
item = config.get_config("misc", kw)
value = kwargs.get(kw)
msg = item.set(value)
if msg:
if msg := config.get_config("misc", kw).set(kwargs.get(kw)):
return badParameterResponse(msg)
config.save_config()
@@ -960,6 +942,8 @@ GENERAL_LIST = (
"socks5_proxy_url",
"auto_browser",
"check_new_rel",
"bandwidth_max",
"bandwidth_perc",
)
@@ -994,8 +978,6 @@ class ConfigGeneral:
for kw in GENERAL_LIST:
conf[kw] = config.get_config("misc", kw)()
conf["bandwidth_max"] = cfg.bandwidth_max()
conf["bandwidth_perc"] = cfg.bandwidth_perc()
conf["nzb_key"] = cfg.nzb_key()
conf["caller_url"] = cherrypy.request.base + cfg.url_base()
@@ -1008,10 +990,7 @@ class ConfigGeneral:
def saveGeneral(self, **kwargs):
# Handle general options
for kw in GENERAL_LIST:
item = config.get_config("misc", kw)
value = kwargs.get(kw)
msg = item.set(value)
if msg:
if msg := config.get_config("misc", kw).set(kwargs.get(kw)):
return badParameterResponse(msg, ajax=kwargs.get("ajax"))
# Handle special options
@@ -1020,16 +999,6 @@ class ConfigGeneral:
web_dir = kwargs.get("web_dir")
change_web_dir(web_dir)
bandwidth_max = kwargs.get("bandwidth_max")
if bandwidth_max is not None:
cfg.bandwidth_max.set(bandwidth_max)
bandwidth_perc = kwargs.get("bandwidth_perc")
if bandwidth_perc is not None:
cfg.bandwidth_perc.set(bandwidth_perc)
bandwidth_perc = cfg.bandwidth_perc()
if bandwidth_perc and not bandwidth_max:
helpful_warning(T("You must set a maximum bandwidth before you can set a bandwidth limit"))
config.save_config()
# Update CherryPy authentication
@@ -1181,7 +1150,7 @@ def handle_server(kwargs, root=None, new_svr=False):
kwargs["connections"] = "1"
if kwargs.get("enable") == "1":
if not get_server_addrinfo(host, int_conv(port)):
if not happyeyeballs(host, int_conv(port)):
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port), ajax)
# Default server name is just the host name
@@ -1802,7 +1771,7 @@ class ConfigCats:
if newname:
# Check if this cat-dir is not sub-folder of incomplete
if same_file(cfg.download_dir.get_path(), real_path(cfg.complete_dir.get_path(), kwargs["dir"])):
if same_directory(cfg.download_dir.get_path(), real_path(cfg.complete_dir.get_path(), kwargs["dir"])):
return T("Category folder cannot be a subfolder of the Temporary Download Folder.")
# Delete current one and replace with new one
@@ -2168,7 +2137,8 @@ class ConfigNotify:
def saveNotify(self, **kwargs):
for section in NOTIFY_OPTIONS:
for option in NOTIFY_OPTIONS[section]:
config.get_config(section, option).set(kwargs.get(option))
if msg := config.get_config(section, option).set(kwargs.get(option)):
return badParameterResponse(msg, kwargs.get("ajax"))
config.save_config()
if kwargs.get("ajax"):
return sabnzbd.api.report()

View File

@@ -1027,19 +1027,6 @@ def ip_extract() -> List[str]:
return ips
def get_server_addrinfo(host: str, port: int) -> socket.getaddrinfo:
"""Return getaddrinfo() based on user settings"""
try:
if cfg.ipv6_servers():
# Standard IPV4 or IPV6
return socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
else:
# Only IPv4
return socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
except:
return []
def get_base_url(url: str) -> str:
"""Return only the true root domain for the favicon, so api.oznzb.com -> oznzb.com
But also api.althub.co.za -> althub.co.za

View File

@@ -274,8 +274,10 @@ def unpacker(
depth: int = 0,
) -> Tuple[Union[int, bool], List[str]]:
"""Do a recursive unpack from all archives in 'download_path' to 'workdir_complete'"""
if depth > 5:
logging.warning(T("Unpack nesting too deep [%s]"), nzo.final_name)
if depth > 2:
# Prevent going to deep down the rabbit-hole
nzo.set_unpack_info("Unpack", T("Unpack nesting too deep [%s]") % nzo.final_name)
logging.info(T("Unpack nesting too deep [%s]"), nzo.final_name)
return False, []
depth += 1

View File

@@ -21,7 +21,7 @@ sabnzbd.newswrapper
import errno
import socket
from threading import Thread, RLock
from threading import Thread
import time
import logging
import ssl
@@ -32,7 +32,7 @@ import sabnzbd
import sabnzbd.cfg
from sabnzbd.constants import DEF_TIMEOUT, NNTP_BUFFER_SIZE
from sabnzbd.encoding import utob, ubtou
from sabnzbd.misc import is_ipv4_addr, is_ipv6_addr, get_server_addrinfo
from sabnzbd.happyeyeballs import AddrInfo
from sabnzbd.decorators import synchronized, DOWNLOADER_LOCK
# Set pre-defined socket timeout
@@ -103,16 +103,15 @@ class NewsWrapper:
def init_connect(self):
"""Setup the connection in NNTP object"""
# Server-info is normally requested by initialization of
# servers in Downloader, but not when testing servers
if self.blocking and not self.server.info:
self.server.info = get_server_addrinfo(self.server.host, self.server.port)
# Sanity check, especially for the server test
if not self.server.addrinfo:
raise socket.error(errno.EADDRNOTAVAIL, T("Invalid server address."))
# Construct buffer and NNTP object
self.data = sabctools.bytearray_malloc(NNTP_BUFFER_SIZE)
self.data_view = memoryview(self.data)
self.reset_data_buffer()
self.nntp = NNTP(self, self.server.hostip)
self.nntp = NNTP(self, self.server.addrinfo)
self.timeout = time.time() + self.server.timeout
def finish_connect(self, code: int):
@@ -260,24 +259,14 @@ class NewsWrapper:
class NNTP:
# Pre-define attributes to save memory
__slots__ = ("nw", "host", "error_msg", "sock", "fileno")
__slots__ = ("nw", "addrinfo", "error_msg", "sock", "fileno")
def __init__(self, nw: NewsWrapper, host):
def __init__(self, nw: NewsWrapper, addrinfo: AddrInfo):
self.nw: NewsWrapper = nw
self.host: str = host # Store the fastest ip
# Add local reference to prevent crash in case the server.addrinfo is reset
self.addrinfo: AddrInfo = addrinfo
self.error_msg: Optional[str] = None
if not self.nw.server.info:
raise socket.error(errno.EADDRNOTAVAIL, "Address not available - Check for internet or DNS problems")
af, socktype, proto, _, _ = self.nw.server.info[0]
# there will be a connect to host (or self.host, so let's force set 'af' to the correct value
if is_ipv4_addr(self.host):
af = socket.AF_INET
if is_ipv6_addr(self.host):
af = socket.AF_INET6
# Create SSL-context if it is needed and not created yet
if self.nw.server.ssl and not self.nw.server.ssl_context:
# Setup the SSL socket
@@ -313,7 +302,7 @@ class NNTP:
self.nw.server.ssl_context.verify_mode = ssl.CERT_NONE
# Create socket and store fileno of the socket
self.sock: Union[socket.socket, ssl.SSLSocket] = socket.socket(af, socktype, proto)
self.sock: Union[socket.socket, ssl.SSLSocket] = socket.socket(self.addrinfo.family, self.addrinfo.type)
self.fileno: int = self.sock.fileno()
# Open the connection in a separate thread due to avoid blocking
@@ -331,7 +320,7 @@ class NNTP:
self.sock.settimeout(self.nw.server.timeout)
# Connect
self.sock.connect((self.host, self.nw.server.port))
self.sock.connect(self.addrinfo.sockaddr)
# Secured or unsecured?
if self.nw.server.ssl:
@@ -390,8 +379,13 @@ class NNTP:
if self.nw.blocking:
raise socket.error(errno.ECONNREFUSED, str(error))
else:
msg = "Failed to connect: %s" % (str(error))
msg = "%s %s@%s:%s (%s)" % (msg, self.nw.thrdnum, self.nw.server.host, self.nw.server.port, self.host)
msg = "Failed to connect: %s %s@%s:%s (%s)" % (
str(error),
self.nw.thrdnum,
self.nw.server.host,
self.nw.server.port,
self.addrinfo.canonname,
)
self.error_msg = msg
self.nw.server.next_busy_threads_check = 0
if self.nw.server.warning == msg:
@@ -411,4 +405,4 @@ class NNTP:
logging.info("%s@%s: Failed to close socket (error=%s)", self.nw.thrdnum, self.nw.server.host, str(e))
def __repr__(self):
return "<NNTP: %s:%s>" % (self.host, self.nw.server.port)
return "<NNTP: %s:%s>" % (self.addrinfo.canonname, self.nw.server.port)

View File

@@ -376,10 +376,6 @@ class NzbFile(TryList):
logging.debug("Finishing import on %s", self.filename)
raw_article_db = sabnzbd.filesystem.load_data(self.nzf_id, self.nzo.admin_path, remove=False)
if raw_article_db:
# Convert 2.x.x jobs
if isinstance(raw_article_db, dict):
raw_article_db = [raw_article_db[partnum] for partnum in sorted(raw_article_db)]
for raw_article in raw_article_db:
self.add_article(raw_article)
@@ -475,10 +471,6 @@ class NzbFile(TryList):
setattr(self, item, None)
super().__setstate__(dict_.get("try_list", []))
# Convert 2.x.x jobs
if isinstance(self.decodetable, dict):
self.decodetable = [self.decodetable[partnum] for partnum in sorted(self.decodetable)]
def __eq__(self, other: "NzbFile"):
"""Assume it's the same file if the number bytes and first article
are the same or if there are no articles left, use the filenames.
@@ -538,7 +530,6 @@ NzbObjectSaver = (
"avg_date",
"md5of16k",
"extrapars",
"md5packs",
"par2packs",
"files",
"files_table",
@@ -668,7 +659,6 @@ class NzbObject(TryList):
self.bad_articles: int = 0 # How many bad (non-recoverable) articles
self.extrapars: Dict[str, List[NzbFile]] = {} # Holds the extra parfile names for all sets
self.md5packs = {} # TODO: Remove in 4.0.0. Kept for backwards compatibility
self.par2packs: Dict[str, Dict[str, FilePar2Info]] = {} # Holds the par2info for each file in each set
self.md5of16k: Dict[bytes, str] = {} # Holds the md5s of the first-16k of all files in the NZB (hash: name)
@@ -2003,27 +1993,8 @@ class NzbObject(TryList):
self.url_tries = 0
self.to_be_removed = False
self.direct_unpacker = None
if self.meta is None:
self.meta = {}
if self.servercount is None:
self.servercount = {}
if self.md5of16k is None:
self.md5of16k = {}
if self.renames is None:
self.renames = {}
if self.bad_articles is None:
self.bad_articles = 0
self.first_articles_count = 0
if self.bytes_missing is None:
self.bytes_missing = 0
if self.bytes_tried is None:
# Fill with old info
self.bytes_tried = 0
for nzf in self.finished_files:
# Emulate behavior of 1.0.x
self.bytes_tried += nzf.bytes
for nzf in self.files:
self.bytes_tried += nzf.bytes - nzf.bytes_left
# Attributes added since 3.0.0
if self.bytes_par2 is None:
self.bytes_par2 = 0
for nzf in self.files + self.finished_files:

View File

@@ -1,130 +0,0 @@
#!/usr/bin/python3
# Python implementation of RFC 6555 / Happy Eyeballs: find the quickest IPv4/IPv6 connection
# See https://tools.ietf.org/html/rfc6555
# Method: Start parallel sessions using threads, and only wait for the quickest successful socket connect
# See https://tools.ietf.org/html/rfc6555#section-4.1
# You can run this as a standalone program, or as a module:
"""
from happyeyeballs import happyeyeballs
print happyeyeballs('newszilla.xs4all.nl', port=119)
"""
import socket
import threading
import time
import logging
import queue
# Called by each thread
def do_socket_connect(result_queue: queue.Queue, ip: str, port: int, ipv4delay: int):
"""Connect to the ip, and put the result into the queue"""
try:
# Create socket
if ip.find(":") >= 0:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
if ip.find(".") >= 0:
time.sleep(ipv4delay) # IPv4 ... so a delay for IPv4 if we prefer IPv6. Note: ipv4delay could be 0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
try:
# Connect ...
s.connect((ip, port))
finally:
# always close
s.close()
result_queue.put((ip, True))
except:
# We got an exception, so no successful connect on IP & port:
result_queue.put((ip, False))
def happyeyeballs(host: str, port: int = 80, preferipv6: bool = False) -> str:
"""Happyeyeballs function, with caching of the results"""
# Find out if a cached result is available, and recent enough:
timecurrent = int(time.time()) # current time in seconds since epoch
retentionseconds = 100
hostkey = (host, port, preferipv6) # Example key: ('ssl.astraweb.com', 563, True)
try:
# Let's check the time:
timecached = happyeyeballs.happylist[hostkey][1]
if timecurrent - timecached <= retentionseconds:
return happyeyeballs.happylist[hostkey][0]
except:
# Exception, so entry not there, so we have to fill it out
pass
# we only arrive here if the entry has to be determined. So let's do that:
# We have to determine the (new) best IP address
start = time.perf_counter()
ipv4delay = 0
try:
# Check if there is an AAAA / IPv6 result for this host:
socket.getaddrinfo(host, port, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME)
# preferipv6, AND at least one IPv6 found, so give IPv4 (!) a delay so IPv6 has a head start and is preferred
if preferipv6:
ipv4delay = 0.1
except:
pass
result_queue = queue.Queue() # queue used for threads giving back the results
try:
# Get all IP (IPv4 and IPv6) addresses:
allinfo = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)
for info in allinfo:
address = info[4][0]
resolver_thread = threading.Thread(target=do_socket_connect, args=(result_queue, address, port, ipv4delay))
resolver_thread.daemon = True
resolver_thread.start()
result = None # default return value, used if none of threads says True/"OK", so no connect on any IP address
# start reading from the Queue for message from the threads:
for _ in range(len(allinfo)):
connect_result = result_queue.get() # get a response
if connect_result[1]:
result = connect_result[0]
break # the first True/"OK" is enough, so break out of for loop
except:
result = None
logging.info("Quickest IP address for %s (port %s, preferipv6 %s) is %s", host, port, preferipv6, result)
delay = int(1000 * (time.perf_counter() - start))
logging.debug("Happy Eyeballs lookup and port connect took %s ms", delay)
# We're done. Store and return the result
if result:
happyeyeballs.happylist[hostkey] = (result, timecurrent)
return result
happyeyeballs.happylist = {} # The cached results. This static variable must be after the def happyeyeballs()
if __name__ == "__main__":
# plain HTTP/HTTPS sites:
print((happyeyeballs("www.google.com")))
print((happyeyeballs("www.google.com", port=443)))
print((happyeyeballs("www.nu.nl")))
# newsservers:
print((happyeyeballs("newszilla6.xs4all.nl", port=119)))
print((happyeyeballs("newszilla.xs4all.nl", port=119)))
print((happyeyeballs("block.cheapnews.eu", port=119)))
print((happyeyeballs("block.cheapnews.eu", port=443)))
print((happyeyeballs("sslreader.eweka.nl", port=563)))
print((happyeyeballs("news.thundernews.com", port=119)))
print((happyeyeballs("news.thundernews.com", port=119, preferipv6=False)))
print((happyeyeballs("secure.eu.thundernews.com", port=563)))
print((happyeyeballs("bonus.frugalusenet.com", port=563)))
# Strange cases
print((happyeyeballs("does.not.resolve", port=443)))
print((happyeyeballs("www.google.com", port=119)))
print((happyeyeballs("216.58.211.164")))

View File

@@ -86,6 +86,7 @@ def test_nntp_server_dict(kwargs):
return False, T("Invalid server details")
try:
s.request_addrinfo_blocking()
nw = NewsWrapper(server=s, thrdnum=-1, block=True)
nw.init_connect()
while not nw.connected:

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.0Alpha1"
__version__ = "4.2.0Alpha2"
__baseline__ = "unknown"

View File

@@ -1,5 +1,5 @@
# Testing requirements
pytest==7.4.2
pytest==7.4.3
selenium
requests
pyfakefs

View File

@@ -18,8 +18,8 @@
"""
tests.test_cfg - Testing functions in cfg.py
"""
import sabnzbd.cfg as cfg
import socket
class TestValidators:
@@ -93,12 +93,23 @@ class TestValidators:
assert cfg.validate_single_tag(["alt.bin", "alt.tv"]) == (None, ["alt.bin", "alt.tv"])
assert cfg.validate_single_tag(["alt.group"]) == (None, ["alt.group"])
def test_all_lowercase(self):
assert cfg.all_lowercase("") == (None, "")
assert cfg.all_lowercase("Bla") == (None, "bla")
assert cfg.all_lowercase(["foo", "bar"]) == (None, ["foo", "bar"])
assert cfg.all_lowercase(["foo ", " bar"]) == (None, ["foo", "bar"])
def test_lower_case_ext(self):
assert cfg.lower_case_ext("") == (None, "")
assert cfg.lower_case_ext(".Bla") == (None, "bla")
assert cfg.lower_case_ext([".foo", ".bar"]) == (None, ["foo", "bar"])
assert cfg.lower_case_ext([".foo ", " .bar"]) == (None, ["foo", "bar"])
def test_validate_safedir(self):
assert cfg.validate_safedir("", "", "def") == (None, "def")
assert cfg.validate_safedir("", "C:\\", "") == (None, "C:\\")
assert "UNC path" in cfg.validate_safedir("", "\\\\NAS\\foo", "")[0]
def test_validate_host(self):
# valid input
assert cfg.validate_host("127.0.0.1") == (None, "127.0.0.1")

View File

@@ -309,56 +309,58 @@ class TestSanitizeFiles(ffs.TestCase):
assert not os.path.exists(file)
class TestSameFile:
class TestSameDirectory:
def test_nothing_in_common_win_paths(self):
assert 0 == filesystem.same_file("C:\\", "D:\\")
assert 0 == filesystem.same_file("C:\\", "/home/test")
assert 0 == filesystem.same_directory("C:\\", "D:\\")
assert 0 == filesystem.same_directory("C:\\", "/home/test")
def test_nothing_in_common_unix_paths(self):
assert 0 == filesystem.same_file("/home/", "/data/test")
assert 0 == filesystem.same_file("/test/home/test", "/home/")
assert 0 == filesystem.same_file("/test/../home", "/test")
assert 0 == filesystem.same_file("/test/./test", "/test")
assert 0 == filesystem.same_directory("/home/", "/data/test")
assert 0 == filesystem.same_directory("/test/home/test", "/home/")
assert 0 == filesystem.same_directory("/test/../home", "/test")
assert 0 == filesystem.same_directory("/test/./test", "/test")
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Non-Windows tests")
@set_platform("linux")
def test_posix_fun(self):
assert 1 == filesystem.same_file("/test", "/test")
assert 1 == filesystem.same_directory("/test", "/test")
# IEEE 1003.1-2017 par. 4.13 for details
assert 0 == filesystem.same_file("/test", "//test")
assert 1 == filesystem.same_file("/test", "///test")
assert 1 == filesystem.same_file("/test", "/test/")
assert 1 == filesystem.same_file("/test", "/test//")
assert 1 == filesystem.same_file("/test", "/test///")
assert 0 == filesystem.same_directory("/test", "//test")
assert 1 == filesystem.same_directory("/test", "///test")
assert 1 == filesystem.same_directory("/test", "/test/")
assert 1 == filesystem.same_directory("/test", "/test//")
assert 1 == filesystem.same_directory("/test", "/test///")
def test_same(self):
assert 1 == filesystem.same_file("/home/123", "/home/123")
assert 1 == filesystem.same_file("D:\\", "D:\\")
assert 1 == filesystem.same_file("/test/../test", "/test")
assert 1 == filesystem.same_file("test/../test", "test")
assert 1 == filesystem.same_file("/test/./test", "/test/test")
assert 1 == filesystem.same_file("./test", "test")
assert 1 == filesystem.same_directory("/home/123", "/home/123")
assert 1 == filesystem.same_directory("/test/../test", "/test")
assert 1 == filesystem.same_directory("test/../test", "test")
assert 1 == filesystem.same_directory("/test/./test", "/test/test")
assert 1 == filesystem.same_directory("./test", "test")
def test_subfolder(self):
assert 2 == filesystem.same_file("\\\\?\\C:\\", "\\\\?\\C:\\Users\\")
assert 2 == filesystem.same_file("/home/test123", "/home/test123/sub")
assert 2 == filesystem.same_file("/test", "/test/./test")
assert 2 == filesystem.same_file("/home/../test", "/test/./test")
assert 2 == filesystem.same_directory("/home/test123", "/home/test123/sub")
assert 2 == filesystem.same_directory("/test", "/test/./test")
assert 2 == filesystem.same_directory("/home/../test", "/test/./test")
@set_platform("win32")
def test_capitalization(self):
# Only matters on Windows/macOS
assert 1 == filesystem.same_file("/HOME/123", "/home/123")
assert 1 == filesystem.same_file("D:\\", "d:\\")
assert 2 == filesystem.same_file("\\\\?\\c:\\", "\\\\?\\C:\\Users\\")
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Relies on os.sep so should only run on Windows")
def test_windows(self):
assert 1 == filesystem.same_directory("D:\\", "D:\\")
assert 2 == filesystem.same_directory("\\\\?\\C:\\", "\\\\?\\C:\\Users\\")
assert 1 == filesystem.same_directory("/HOME/123", "/home/123")
assert 1 == filesystem.same_directory("D:\\", "d:\\")
assert 2 == filesystem.same_directory("\\\\?\\c:\\", "\\\\?\\C:\\Users\\")
def test_looks_likesubfolder_but_isnt(self):
assert 0 == filesystem.same_directory("/mnt/sabnzbd", "/mnt/sabnzbd-data")
@pytest.mark.skipif(sys.platform.startswith(("win", "darwin")), reason="Requires a case-sensitive filesystem")
@set_platform("linux")
def test_capitalization_linux(self):
assert 2 == filesystem.same_file("/home/test123", "/home/test123/sub")
assert 0 == filesystem.same_file("/test", "/Test")
assert 0 == filesystem.same_file("tesT", "Test")
assert 0 == filesystem.same_file("/test/../Home", "/home")
assert 2 == filesystem.same_directory("/home/test123", "/home/test123/sub")
assert 0 == filesystem.same_directory("/test", "/Test")
assert 0 == filesystem.same_directory("tesT", "Test")
assert 0 == filesystem.same_directory("/test/../Home", "/home")
class TestClipLongPath:

View File

@@ -347,7 +347,6 @@ class TestOtherApi(ApiTestFunctions):
assert self._get_api_json("pause_pp")["status"] is True
assert self._get_api_text("resume_pp").startswith("ok")
@pytest.mark.xfail(reason="See #2685")
@pytest.mark.parametrize("set_watched_dir", [False, True])
def test_api_watched_now(self, set_watched_dir):
value = SAB_CACHE_DIR if set_watched_dir else ""

View File

@@ -16,12 +16,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
tests.test_utils.test_happyeyeballs - Testing SABnzbd happyeyeballs
tests.test_happyeyeballs - Testing SABnzbd happyeyeballs
"""
import os
import pytest
from flaky import flaky
from sabnzbd.utils.happyeyeballs import happyeyeballs
from sabnzbd.happyeyeballs import happyeyeballs
@flaky
@@ -32,25 +33,27 @@ class TestHappyEyeballs:
"""
def test_google_http(self):
ip = happyeyeballs("www.google.com")
assert "." in ip or ":" in ip
addrinfo = happyeyeballs("www.google.com", port=80)
assert "." in addrinfo.ipaddress or ":" in addrinfo.ipaddress
assert "google" in addrinfo.canonname
def test_google_https(self):
ip = happyeyeballs("www.google.com", port=443)
assert "." in ip or ":" in ip
addrinfo = happyeyeballs("www.google.com", port=443)
assert "." in addrinfo.ipaddress or ":" in addrinfo.ipaddress
assert "google" in addrinfo.canonname
def test_not_resolvable(self):
ip = happyeyeballs("not.resolvable.invalid")
assert ip is None
assert happyeyeballs("not.resolvable.invalid", port=80) is None
def test_ipv6_only(self):
ip = happyeyeballs("ipv6.google.com")
assert ip is None or ":" in ip
if addrinfo := happyeyeballs("ipv6.google.com", port=443):
assert ":" in addrinfo.ipaddress
assert "google" in addrinfo.canonname
def test_google_unreachable_port(self):
ip = happyeyeballs("www.google.com", port=33333)
assert ip is None
assert happyeyeballs("www.google.com", port=33333) is None
def test_newszilla_nntp(self):
ip = happyeyeballs("newszilla.xs4all.nl", port=119)
@pytest.mark.xfail(reason="CI sometimes blocks this")
def test_nntp(self):
ip = happyeyeballs("news.newshosting.com", port=119).ipaddress
assert "." in ip or ":" in ip

View File

@@ -32,6 +32,7 @@ from flaky import flaky
from tests.testhelper import *
from sabnzbd import misc
from sabnzbd import newswrapper
from sabnzbd.happyeyeballs import AddrInfo
TEST_HOST = "127.0.0.1"
TEST_PORT = portend.find_available_local_port()
@@ -107,7 +108,7 @@ class TestNewsWrapper:
nw.server = mock.Mock()
nw.server.host = TEST_HOST
nw.server.port = TEST_PORT
nw.server.info = socket.getaddrinfo(TEST_HOST, TEST_PORT, 0, socket.SOCK_STREAM)
nw.server.info = AddrInfo(*socket.getaddrinfo(TEST_HOST, TEST_PORT, 0, socket.SOCK_STREAM)[0])
nw.server.timeout = 10
nw.server.ssl = True
nw.server.ssl_context = None
@@ -117,9 +118,9 @@ class TestNewsWrapper:
# Do we expect failure to connect?
if not can_connect:
with pytest.raises(OSError):
newswrapper.NNTP(nw, TEST_HOST)
newswrapper.NNTP(nw, nw.server.info)
else:
nntp = newswrapper.NNTP(nw, TEST_HOST)
nntp = newswrapper.NNTP(nw, nw.server.info)
assert nntp.sock.recv(len(TEST_DATA)) == TEST_DATA
# Assert SSL data

View File

@@ -14,7 +14,7 @@ import shutil
from unittest import mock
from sabnzbd import postproc
from sabnzbd.config import ConfigSorter, ConfigCat, read_config
from sabnzbd.config import ConfigSorter, ConfigCat
from sabnzbd.filesystem import globber_full, clip_path
from sabnzbd.misc import sort_to_opts