mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-01-07 23:18:26 -05:00
Compare commits
25 Commits
4.2.0Alpha
...
4.2.0Alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
210f254f63 | ||
|
|
ecdccda1ce | ||
|
|
ed66ac91e0 | ||
|
|
e571165c15 | ||
|
|
1513664b5f | ||
|
|
0132d81c43 | ||
|
|
8d32da8b27 | ||
|
|
b5fbc8af86 | ||
|
|
d0166b5a5c | ||
|
|
ada77d6970 | ||
|
|
9f8758b242 | ||
|
|
5ca629ebea | ||
|
|
f9f3820652 | ||
|
|
08e61ecf19 | ||
|
|
d15f0cafce | ||
|
|
1b85253940 | ||
|
|
b329ff007e | ||
|
|
f6918d598a | ||
|
|
0cdfdd82d4 | ||
|
|
de3649dba4 | ||
|
|
9ba975ac44 | ||
|
|
2b0ea92da8 | ||
|
|
b79a1e973d | ||
|
|
1be4cf986d | ||
|
|
18c4226b90 |
4
.github/renovate.json
vendored
4
.github/renovate.json
vendored
@@ -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": [
|
||||
{
|
||||
|
||||
15
.github/workflows/build_release.yml
vendored
15
.github/workflows/build_release.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@@ -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: |
|
||||
|
||||
14
README.mkd
14
README.mkd
@@ -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"
|
||||
|
||||
|
||||
13
SABnzbd.py
13
SABnzbd.py
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -19,7 +19,6 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import github
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' })
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 " 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 ""
|
||||
|
||||
@@ -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 " 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 ""
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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 ""
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " פותר כתובת"
|
||||
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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Разрешение адреса"
|
||||
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 ""
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Решавање адресе"
|
||||
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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 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]"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 正在解析地址"
|
||||
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]"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(" Resolving address").replace(" ", "")
|
||||
|
||||
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
|
||||
|
||||
101
sabnzbd/cfg.py
101
sabnzbd/cfg.py
@@ -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)
|
||||
|
||||
|
||||
##############################################################################
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()])
|
||||
|
||||
@@ -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
126
sabnzbd/happyeyeballs.py
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")))
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Testing requirements
|
||||
pytest==7.4.2
|
||||
pytest==7.4.3
|
||||
selenium
|
||||
requests
|
||||
pyfakefs
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user