Compare commits

...

64 Commits

Author SHA1 Message Date
Safihre
8bd39e4c12 Refactor pre-queue script
[skip ci]
2023-11-22 16:17:19 +01:00
SABnzbd Automation
987032b384 Update translatable texts
[skip ci]
2023-11-22 15:14:06 +00:00
Safihre
d516cbf363 Correct tests and improvements for new Duplicate handling 2023-11-22 16:13:22 +01:00
Safihre
824274ac5e Trigger duplicate handling when job is removed from the queue 2023-11-22 16:13:22 +01:00
Safihre
82b1c784f4 No longer warn for duplicates by default 2023-11-22 16:13:22 +01:00
Safihre
232512b860 Let main duplicate handling handle RSS duplicates 2023-11-22 16:13:22 +01:00
Safihre
223fa421c7 Implement more sophisticated duplicate handling
[skip ci]
2023-11-22 16:13:22 +01:00
Safihre
2e5e72bfcf Label in progress bar for URL fetches
Visually more distinctive
2023-11-22 15:37:35 +01:00
Safihre
9bdb986382 Only redirect cherrypy logging to their access log
Closes #2731
2023-11-20 08:49:27 +01:00
SABnzbd Automation
901ff30e11 Update translatable texts
[skip ci]
2023-11-18 20:24:07 +00:00
Safihre
5e04599212 Revert "Simplify handling of nzo.pp"
Closes #2733
2023-11-18 21:22:45 +01:00
Safihre
d3c9b7ead3 Simplify handling of nzo.pp 2023-11-13 12:33:05 +01:00
renovate[bot]
361770c34b Update all dependencies 2023-11-13 01:44:28 +00:00
SABnzbd Automation
5168f3fa97 Update translatable texts
[skip ci]
2023-11-11 22:01:41 +00:00
Safihre
94d307e198 Add simplified Sorter override, to analyse series information 2023-11-11 22:59:58 +01:00
Safihre
eba6236ad2 Make sure we only return successful Happy Eyeballs results 2023-11-10 16:16:46 +01:00
Safihre
d0128bd989 Use sabnzbd.filesystem functions directly 2023-11-10 13:45:56 +01:00
Safihre
fbd7c0ec36 Correct Night display of Sorting page 2023-11-08 16:33:40 +01:00
SABnzbd Automation
55abac97ea Update translatable texts
[skip ci]
2023-11-08 11:38:17 +00:00
Safihre
740b94170e Prevent looping over files for unwanted extension detection 2023-11-08 12:36:57 +01:00
SABnzbd Automation
c6a1a09213 Update translatable texts
[skip ci]
2023-11-07 15:33:21 +00:00
Safihre
cd84d52398 End of queue script to be moved to it's own configuration menu item
Closes #2385
Setting is not copied since it's such an exotic function.
Made pre-queue script an Advanced Setting.
2023-11-07 16:32:39 +01:00
Safihre
cdbad1b397 Add password as option to NzbObject creation
And another refactor of filename/work_name/final_name
2023-11-07 16:24:31 +01:00
Safihre
67e227008a Revert "Remove undocumented detection of password=XX from job name"
This reverts commit 62a057dbfb.

It is listed here: https://sabnzbd.org/wiki/advanced/password-protected-rars
Oops
2023-11-07 15:47:41 +01:00
Safihre
23cf43cac5 Replace uses of os.path.splitext with helper functions 2023-11-06 15:05:50 +01:00
Safihre
62a057dbfb Remove undocumented detection of password=XX from job name 2023-11-06 14:35:17 +01:00
renovate[bot]
f2ff9ae557 Update dependency jaraco.functools to v4 2023-11-06 00:42:28 +00:00
Safihre
9ed4e46919 Update macOS workflow for new GitHub runner 2023-11-03 20:17:52 +01:00
Safihre
faa71bae40 Log traceback in case of exception in __finish_connect_nw 2023-11-03 20:06:41 +01:00
Safihre
bbd5d2cd6d Prevent duplicate IP's in Happy Eyeballs 2023-11-03 12:03:14 +01:00
Safihre
221e135c07 Optimize Happy Eyeballs for our use
Reduced time between connection attempts to prevent slow hosts that happened to be the first in the list to win from faster second-in-list.
Add test for our IPv6 mapping
2023-11-02 21:12:32 +01:00
Safihre
956904c0b3 Correctly implement RFC 6555/8305 (Happy Eyeballs) 2023-11-01 15:16:10 +01:00
Safihre
8590481022 Add IPv6 alternative hostname for common providers
Closes #2721
2023-11-01 09:07:42 +01:00
SABnzbd Automation
2171d0139e Update translatable texts
[skip ci]
2023-10-30 13:45:23 +00:00
Safihre
71d6aca9f8 Remove unused exceptions in servertest 2023-10-30 14:44:31 +01:00
Safihre
0125e279c0 Prevent PyWin32 warning by returning True instead of nothing 2023-10-30 12:33:57 +01:00
SABnzbd Automation
b8e46ccf10 Update translatable texts
[skip ci]
2023-10-30 01:02:52 +00:00
renovate[bot]
787fef1c03 Update dependency orjson to v3.9.10 2023-10-30 01:02:09 +00:00
SABnzbd Automation
98b7a6171f Update translatable texts
[skip ci]
2023-10-27 12:41:14 +00:00
Safihre
210f254f63 Update text files for 4.2.0Alpha2 2023-10-27 14:40:22 +02:00
Safihre
ecdccda1ce Remove support to upgrade from 2.3.9 and older 2023-10-27 14:40:22 +02:00
Safihre
ed66ac91e0 Remove old nzo.md5packs attribute 2023-10-27 14:40:22 +02:00
SABnzbd Automation
e571165c15 Update translatable texts
[skip ci]
2023-10-27 10:20:00 +00:00
Safihre
1513664b5f Lock all config dict operations
Closes #2685
2023-10-27 12:19:12 +02:00
SABnzbd Automation
0132d81c43 Update translatable texts
[skip ci]
2023-10-25 14:19:40 +00:00
Safihre
8d32da8b27 Refactor of some parts of Config saving 2023-10-25 16:06:28 +02:00
Safihre
b5fbc8af86 Refactor handling of Complete vs Incomplete check
Closes #2717
2023-10-25 16:06:28 +02:00
SABnzbd Automation
d0166b5a5c Update translatable texts
[skip ci]
2023-10-25 10:01:25 +00:00
renovate[bot]
ada77d6970 Update all dependencies (#2716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-25 12:00:42 +02:00
Safihre
9f8758b242 Mark newshosting Happy EyeBalls tests as xfail 2023-10-25 11:18:41 +02:00
SABnzbd Automation
5ca629ebea Update translatable texts
[skip ci]
2023-10-24 18:30:36 +00:00
Safihre
f9f3820652 Refactor the way we getaddrinfo and use Happy Eyeballs (#2713)
* Refactor the way we getaddrinfo and use  Happy Eyeballs

* Move tests to right directory

* Do not run Happy Eyeballs for only 1 address

* Process feedback

* Make sure we always have a canonname

* Show IP and resolved name in Status Window

* Simplify Status server updates

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

View File

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

View File

@@ -63,8 +63,8 @@ jobs:
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt
pip install --upgrade -r builder/requirements.txt
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary (32bit and legacy)
run: python builder/package.py binary
- name: Upload Windows standalone binary (32bit and legacy)
@@ -98,10 +98,7 @@ jobs:
if: steps.cache-python-download.outputs.cache-hit != 'true'
run: curl https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macos11.pkg -o ~/python.pkg
- name: Install Python
run: |
sudo installer -pkg ~/python.pkg -target /
unlink /usr/local/bin/python
ln -s /usr/local/bin/python3 /usr/local/bin/python
run: sudo installer -pkg ~/python.pkg -target /
- name: Cache Python virtualenv
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv
@@ -112,19 +109,18 @@ jobs:
# We have to manually take a few steps:
# 1. Because building cryptography is hard, and we cannot force pip to fetch universal2 version we
# first install the x86 version (and it's dependencies) and then manually fetch the universal2 build
# https://github.com/pyca/cryptography/issues/5918
# 2. Due to PyObjC we cannot run pip on the main requirements without installing dependencies
# 3. We need to build the PyInstaller bootloader:
# https://github.com/pypa/pip/issues/5453
# 2. We need to build the PyInstaller bootloader:
# https://github.com/pyinstaller/pyinstaller/issues/6235
if: steps.cache-virtualenv.outputs.cache-hit != 'true'
run: |
python3 --version
pip3 install --upgrade pip wheel
pip3 install --upgrade -r requirements.txt --no-binary cffi
pip3 install --upgrade -r requirements.txt --no-binary cffi --no-dependencies
pip3 uninstall cryptography -y
pip3 download -r builder/osx/requirements.txt --platform macosx_10_12_universal2 --only-binary :all: --no-deps --dest .
pip3 download -r builder/osx/requirements.txt --platform macosx_10_12_universal2 --only-binary :all: --no-dependencies --dest .
pip3 install -r builder/osx/requirements.txt --no-cache-dir --no-index --find-links .
PYINSTALLER_COMPILE_BOOTLOADER=1 pip3 install --upgrade -r builder/requirements.txt --no-binary pyinstaller --no-dependencies

View File

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

View File

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

View File

@@ -860,7 +860,7 @@ def main():
ipv6_hosting = None
inet_exposure = None
service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
_service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
for opt, arg in sab_opts:
if opt == "--servicecall":
@@ -949,8 +949,9 @@ def main():
sabnzbd.DIR_LANGUAGE = real_path(sabnzbd.DIR_PROG, DEF_LANGUAGE)
org_dir = os.getcwd()
# Need console logging if requested or just running as script
console_logging = (console_logging or not hasattr(sys, "frozen")) and not sabnzbd.DAEMON
# Need console logging if requested, for SABnzbd.py and SABnzbd-console.exe
console_logging = console_logging or sys.executable.endswith("console.exe") or not hasattr(sys, "frozen")
console_logging = console_logging and not sabnzbd.DAEMON
LOGLEVELS = (logging.FATAL, logging.WARNING, logging.INFO, logging.DEBUG)
@@ -1348,7 +1349,6 @@ def main():
"server.socket_host": cherryhost,
"server.socket_port": cherryport,
"server.shutdown_timeout": 0,
"log.screen": False,
"engine.autoreload.on": False,
"tools.encode.on": True,
"tools.gzip.on": True,
@@ -1360,13 +1360,11 @@ def main():
)
# Do we want CherryPy Logging? Cannot be done via the config
cherrypy.log.screen = False
cherrypy.log.access_log.propagate = False
if cherrypylogging:
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
cherrypy.log.screen = True
cherrypy.log.access_log.propagate = True
cherrypy.log.access_file = str(sabnzbd.WEBLOGFILE)
else:
cherrypy.log.access_log.propagate = False
# Force mimetypes (OS might overwrite them)
forced_mime_types = {"css": "text/css", "js": "application/javascript"}
@@ -1639,6 +1637,12 @@ if sabnzbd.WIN32:
"access to network shares."
)
# Only SABnzbd-console.exe can print to the console, so the service is installed
# from there. But we run SABnzbd.exe so nothing is logged. Logging can cause the
# Windows Service to stop because the output buffers are full.
if hasattr(sys, "frozen"):
_exe_name_ = "SABnzbd.exe"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,20 @@
# Basic build requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.1.0
pyinstaller==6.2.0
packaging==23.2
pyinstaller-hooks-contrib==2023.9
pyinstaller-hooks-contrib==2023.10
altgraph==0.17.4
wrapt==1.15.0
wrapt==1.16.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.1; python_version < '3.10'
zipp==3.17.0; python_version < '3.10'
# orjson does not support 32bit Windows, also exclude based on Python-version
orjson==3.9.10; python_version > '3.8'
# For the Windows build
pefile==2023.2.7; sys_platform == 'win32'

View File

@@ -1,12 +1,11 @@
<!--#set global $pane="Sorting"#-->
<!--#set global $help_uri = $confighelpuri + "sorting"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="padTable section explain-sorting">
<a class="main-helplink" href="$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
$T('explain-sorting')
</div>
<div class="colmask">
<div class="padTable section">
<a class="main-helplink" href="$help_uri" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a>
$T('explain-sorting')
</div>
<div class="padding alt section">
<button type="button" class="btn btn-default addSorter"><span class="glyphicon glyphicon-plus"></span> $T('add-sorter')</button>
<label for="advanced-settings-button" class="form-control advanced-button ">
@@ -345,7 +344,7 @@
</div>
<!--#if len($slotinfo) == 1 and ("tv" in $categories or "movies" in $categories)#-->
<div class="section align-center alt sorting-quick-setup">
<div class="section align-center sorting-quick-setup">
<h3>$T('sort-quick-add'):</h3>
<!--#if "tv" in $categories#-->
<form action="save_sorter" method="post" autocomplete="off">

View File

@@ -41,7 +41,7 @@
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<div class="field-pair">
<div class="field-pair advanced-settings">
<label class="config" for="pre_script">$T('opt-pre_script')</label>
<select name="pre_script" id="pre_script">
<!--#for $sc in $scripts#-->
@@ -54,6 +54,19 @@
</select>
<span class="desc">$T('explain-pre_script')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="end_queue_script">$T('opt-end_queue_script')</label>
<select name="end_queue_script" id="end_queue_script">
<!--#for $sc in $scripts#-->
<!--#if $sc.lower() == $end_queue_script.lower()#-->
<option value="$sc" selected="selected">$Tspec($sc)</option>
<!--#else#-->
<option value="$sc">$Tspec($sc)</option>
<!--#end if#-->
<!--#end for#-->
</select>
<span class="desc">$T('explain-end_queue_script')</span>
</div>
<div class="field-pair">
<label class="config" for="propagation_delay">$T('opt-propagation_delay')</label>
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" /> <i>$T('minutes')</i>

View File

@@ -107,7 +107,6 @@ select.form-control,
.navbar-default,
.search-box input,
.select,
.Sorting .explain-sorting,
.table-striped>tbody>tr:nth-child(even),
.table>tbody>tr:nth-child(even),
.tab-pane tr:nth-child(odd),

View File

@@ -359,10 +359,6 @@ tr.separator {
margin: 5px;
}
.Sorting .explain-sorting {
background-color: #fff;
color: #000;
}
.Sorting .explain-pattern {
border: none;
width: 100%;

View File

@@ -102,26 +102,15 @@
<li class="divider"></li>
<li class="dropdown-header"><span class="glyphicon glyphicon-off"></span> $T('Glitter-onFinish'):</li>
<li>
<!-- ko if: queue.scriptsListLoaded -->
<select data-bind="value: finishaction, event: { change: setOnQueueFinish }" class="form-control">
<option value=""></option>
<optgroup label="$T('eoq-actions')">
<option value="shutdown_program">$T('shutdownSab')</option>
<!--#if $power_options#-->
<option value="shutdown_pc">$T('shutdownPc')</option>
<option value="standby_pc">$T('standbyPc')</option>
<option value="hibernate_pc">$T('hibernatePc')</option>
<!--#end if#-->
</optgroup>
<optgroup label="$T('eoq-scripts')" data-bind="visible: queue.scriptsList().length > 1">
<!-- ko foreach: queue.scriptsList -->
<!-- ko if: \$data.scriptValue != 'None' -->
<option data-bind="text: \$data.scriptText, attr: { value: 'script_'+\$data.scriptValue } " ></option>
<!-- /ko -->
<!-- /ko -->
</optgroup>
<option value="shutdown_program">$T('shutdownSab')</option>
<!--#if $power_options#-->
<option value="shutdown_pc">$T('shutdownPc')</option>
<option value="standby_pc">$T('standbyPc')</option>
<option value="hibernate_pc">$T('hibernatePc')</option>
<!--#end if#-->
</select>
<!-- /ko -->
</li>
</ul>
</li>

View File

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

View File

@@ -67,6 +67,7 @@
glitterTranslate.fetch = "$T('Glitter-fetch')";
glitterTranslate.checking = "$T('post-Checking')";
glitterTranslate.misingArt = "$T('missingArt')";
glitterTranslate.fetchingURL = "$T('Glitter-addFromURL')"
glitterTranslate.chooseFile = "$T('Glitter-chooseFile')";
glitterTranslate.orphanedJobsMsg = "$T('explain-orphans')";
glitterTranslate.useCache = "$T('explain-cache_limitstr').replace("64M", "256M").replace("128M", "512M")";

View File

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

View File

@@ -37,7 +37,6 @@ function QueueListModel(parent) {
self.multiEditItems = ko.observableArray([]);
self.categoriesList = ko.observableArray([]);
self.scriptsList = ko.observableArray([]);
self.scriptsListLoaded = ko.observable(false);
self.searchTerm = ko.observable('').extend({ rateLimit: { timeout: 400, method: "notifyWhenChangesStop" } });
self.paginationLimit = ko.observable(20).extend({ persist: 'queuePaginationLimit' });
self.pagination = new paginationModel(self);
@@ -507,6 +506,9 @@ function QueueModel(parent, data) {
// MB's
self.progressText = ko.pureComputed(function() {
if(self.isGrabbing()) {
return glitterTranslate.fetchingURL
}
return (self.totalMB() - self.remainingMB()).toFixed(0) + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
})

View File

@@ -30,6 +30,7 @@
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="4.2.0" date="2023-11-26" type="stable"/>
<release version="4.1.0" date="2023-09-26" type="stable"/>
<release version="4.0.3" date="2023-06-16" type="stable"/>
<release version="4.0.2" date="2023-06-09" type="stable"/>

View File

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

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -149,7 +149,7 @@ msgid "Test Notification"
msgstr ""
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgid "Resolving address"
msgstr ""
#. No value, used in dropdown menus
@@ -233,10 +233,6 @@ msgstr ""
msgid "Incorrect parameter"
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr ""
@@ -245,7 +241,7 @@ msgstr ""
msgid "Server address required"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 ""
@@ -1083,6 +1082,18 @@ msgstr ""
msgid "NZB added to queue"
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1097,26 +1108,6 @@ msgstr ""
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1135,6 +1126,10 @@ msgstr ""
msgid "DUPLICATE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr ""
@@ -1180,6 +1175,10 @@ msgstr ""
msgid "%s articles had non-matching duplicates"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr ""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2134,11 +2133,6 @@ msgstr ""
msgid "Retry"
msgstr ""
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3009,6 +3003,14 @@ msgstr ""
msgid "Used before an NZB enters the queue."
msgstr ""
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr ""
@@ -4463,10 +4465,6 @@ msgstr ""
msgid "Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr ""

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# Pavel C <quoing_transifex@mess.cz>, 2023
# Safihre <safihre@sabnzbd.org>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2023\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -166,7 +166,7 @@ msgid "Test Notification"
msgstr "Otestovat notifikace"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgid "Resolving address"
msgstr "Překládám adresu"
#. No value, used in dropdown menus
@@ -258,10 +258,6 @@ msgstr "Kvóta přesažena, pozastavuji stahování"
msgid "Incorrect parameter"
msgstr "Nesprávný parametr"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC cesta \"%s\" zde není povolena"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s není validní emailová adresa"
@@ -270,7 +266,7 @@ msgstr "%s není validní emailová adresa"
msgid "Server address required"
msgstr "Adresa serveru je vyžadována"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Fronta nené prázdná, nelze změnit složku."
#: 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 ""
@@ -1150,6 +1149,18 @@ msgstr "Nelze nahrát %s, detekován porušený soubor"
msgid "NZB added to queue"
msgstr "NZB přidáno do fronty"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignoruji duplikátní NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Nezdařilo se duplikovat NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Duplikátní NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1164,26 +1175,6 @@ msgstr "Prázdný NZB soubor %s"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignoruji duplikátní NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Nezdařilo se duplikovat NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Duplikátní NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pozastavuji duplikátní NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1202,6 +1193,10 @@ msgstr "Chyba při importu %s"
msgid "DUPLICATE"
msgstr "DUPLIKÁT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ŠIFROVANÉ"
@@ -1247,6 +1242,10 @@ msgstr ""
msgid "%s articles had non-matching duplicates"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pozastavuji duplikátní NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2209,11 +2208,6 @@ msgstr "Jméno"
msgid "Retry"
msgstr "Opakovat"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Akce"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3164,6 +3158,14 @@ msgstr ""
msgid "Used before an NZB enters the queue."
msgstr ""
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr ""
@@ -4672,10 +4674,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr ""

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
@@ -166,8 +166,8 @@ msgid "Test Notification"
msgstr "Afprøv notifikation"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp; Server løsning"
msgid "Resolving address"
msgstr "Server løsning"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -258,10 +258,6 @@ msgstr "Kvote brugt, pause downloading"
msgid "Incorrect parameter"
msgstr "Fejl parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC søgning \"%s\" er ikke tilladt her"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s er ikke en godkendt e-mail adresse"
@@ -270,7 +266,7 @@ msgstr "%s er ikke en godkendt e-mail adresse"
msgid "Server address required"
msgstr "Kræver serveradresse"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Køen er ikke tom, kan ikke skifte mappe."
#: 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]"
@@ -1154,6 +1153,18 @@ msgstr "Downloadnings fejl %s, ødelagt fil fundet"
msgid "NZB added to queue"
msgstr "NZB tilføjet i køen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerer identiske NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Fejler dublet NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Dublet NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1168,26 +1179,6 @@ msgstr "Tom NZB fil %s"
msgid "Pre-queue script marked job as failed"
msgstr "Før-kø script job markeret som mislykkedet"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerer identiske NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Fejler dublet NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Dublet NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pause duplikeret NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1206,6 +1197,10 @@ msgstr "Det lykkedes ikke at importere %s"
msgid "DUPLICATE"
msgstr "DUPLIKERE"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "KRYPTEREDE"
@@ -1251,6 +1246,10 @@ msgstr "%s artikler manglede"
msgid "%s articles had non-matching duplicates"
msgstr "%s artikler havde ikke-matchende dubletter"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pause duplikeret NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2241,11 +2240,6 @@ msgstr "Navn"
msgid "Retry"
msgstr "Forsøg igen"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Handlinger"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3240,6 +3234,14 @@ msgstr "Før kø bruger script"
msgid "Used before an NZB enters the queue."
msgstr "Brugt før, en NZB kommer ind i køen."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Ekstra PAR2 parameter"
@@ -4784,10 +4786,6 @@ msgstr ""
"Ukendt SSL protokol: Prøv at deaktivere SSL eller forbinder på en anden "
"port."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Server afslut under login-sekvens."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Serveren kræver brugernavn og adgangskode."

View File

@@ -15,7 +15,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
@@ -182,8 +182,8 @@ msgid "Test Notification"
msgstr "Benachrichtigungen testen"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Adresse wird aufgelöst …"
msgid "Resolving address"
msgstr "Adresse wird aufgelöst …"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -277,10 +277,6 @@ msgstr "Kontingent aufgebraucht, Downloads werden angehalten"
msgid "Incorrect parameter"
msgstr "Fehlerhafter Parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-Pfad \"%s\" ist hier nicht erlaubt"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s ist keine gültige E-Mail-Adresse"
@@ -289,7 +285,7 @@ msgstr "%s ist keine gültige E-Mail-Adresse"
msgid "Server address required"
msgstr "Server-Adresse wird benötigt"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
msgid "Invalid server address."
msgstr "Ungültige Server-Adresse."
@@ -311,10 +307,21 @@ 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."
"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 +449,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 +713,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 +848,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]"
@@ -1205,6 +1203,18 @@ msgstr "Fehler beim Laden von %s. Beschädigte Datei gefunden."
msgid "NZB added to queue"
msgstr "NZB zur Warteschlange hinzugefügt"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Doppelte NZB \"%s\" wird ignoriert"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "kopieren der NZB \"%s\" fehlgeschlagen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Doppelte NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1221,26 +1231,6 @@ msgstr ""
"Das Vorwarteschlangen (pre-queue) Skript hat die Downloadaufgabe als "
"gescheitert markiert"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Doppelte NZB \"%s\" wird ignoriert"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "kopieren der NZB \"%s\" fehlgeschlagen"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Doppelte NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Doppelt vorhandene NZB \"%s\" angehalten"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1259,6 +1249,10 @@ msgstr "Fehler beim Importieren von %s"
msgid "DUPLICATE"
msgstr "DUPLIKAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "VERSCHLÜSSELT"
@@ -1305,6 +1299,10 @@ msgstr "%s Artikel fehlten"
msgid "%s articles had non-matching duplicates"
msgstr "%s Artikel hatten nicht übereinstimmende Duplikate"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Doppelt vorhandene NZB \"%s\" angehalten"
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2307,11 +2305,6 @@ msgstr "Name"
msgid "Retry"
msgstr "Erneut versuchen"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Aktionen"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3365,6 +3358,14 @@ msgid "Used before an NZB enters the queue."
msgstr ""
"Wird verwendet, bevor eine NZB-Datei zur Warteschlange hinzugefügt wird."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Zusätzliche PAR2-Parameter"
@@ -4959,10 +4960,6 @@ msgstr ""
"Unbekanntes SSL-Protokoll: SSL deaktivieren oder alternativen Port "
"versuchen."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Sever beendet beim Anmeldeverlauf."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Server benötigt ein Benutzername und ein Passwort."

View File

@@ -124,4 +124,7 @@ msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 is highest priority, 99 is the lowest priority"
msgid "Filter out sample files (e.g. video samples)."
msgstr "Filter out sample files (e.g. video samples/proofs)."
msgstr "Filter out sample files (e.g. video samples/proofs)."
msgid "Pre-queue user script"
msgstr "Pre-queue script"

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
@@ -175,8 +175,8 @@ msgid "Test Notification"
msgstr "Notificación de prueba"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Resolviendo sitio"
msgid "Resolving address"
msgstr "Resolviendo sitio"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -273,10 +273,6 @@ msgstr "Quota gastado, pausando cola"
msgid "Incorrect parameter"
msgstr "Parámetro incorrecto"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Ruta de acceso UNC \"%s\" no permitido aqui"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s no es una dirección de correo electrónico válida."
@@ -285,7 +281,7 @@ msgstr "%s no es una dirección de correo electrónico válida."
msgid "Server address required"
msgstr "Se necesita la dirección del servidor"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Cola no esta vacía, no se puede cambiar el directorio"
#: 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 ""
@@ -1197,6 +1196,18 @@ msgstr "Error al cargar %s, archivo corrupto"
msgid "NZB added to queue"
msgstr "NZB añadido a la cola"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorando NZB Duplicado \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Fallo al duplicar NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Duplicar NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1213,26 +1224,6 @@ msgstr ""
"La secuencia de comandos de la cola preestablecida ha marcado la tarea como "
"fallida"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorando NZB Duplicado \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Fallo al duplicar NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Duplicar NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicados \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1251,6 +1242,10 @@ msgstr "Error importando %s"
msgid "DUPLICATE"
msgstr "DUPLICADO"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ENCRIPTADO"
@@ -1296,6 +1291,10 @@ msgstr "%s artículos no encontrados"
msgid "%s articles had non-matching duplicates"
msgstr "%s artículos contenían duplicados inconexos"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicados \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2297,11 +2296,6 @@ msgstr "Nombre"
msgid "Retry"
msgstr "Reintentar"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Acciones"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3324,6 +3318,14 @@ msgstr "Script de usuario Pre-cola"
msgid "Used before an NZB enters the queue."
msgstr "Se usa precediendo a la entrada de un NZB en la cola del sistema."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Parámetros PAR2 extra"
@@ -4892,10 +4894,6 @@ msgstr ""
"Protocolo SSL desconocido: intente desabilitar el SSL o conectarse a un "
"puerto diferente."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "El servidor se ha cerrado durante el login"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "El servidor necesita usuario y contraseña."

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
@@ -168,8 +168,8 @@ msgid "Test Notification"
msgstr "Testaa ilmoitusta"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Selvitetään osoitetta"
msgid "Resolving address"
msgstr "Selvitetään osoitetta"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -256,10 +256,6 @@ msgstr "Latausrajoitus saavutettu, keskeytetään lataukset"
msgid "Incorrect parameter"
msgstr "Virheellinen parametri"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "TUNT polku \"%s\" ei ole sallittu"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s ei ole kelvollinen sähköpostiosoite"
@@ -268,7 +264,7 @@ msgstr "%s ei ole kelvollinen sähköpostiosoite"
msgid "Server address required"
msgstr "Palvelimen osoite vaaditaan"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Jono ei ole tyhjä, kansiota ei voida vaihtaa."
#: 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]"
@@ -1148,6 +1147,18 @@ msgstr "Virhe ladattaessa %s, korruptoitunut tiedosto havaittu"
msgid "NZB added to queue"
msgstr "NZB lisätty jonoon"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ohitetaan kaksoiskappale NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1162,26 +1173,6 @@ msgstr "Tyhjä NZB tiedosto %s"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ohitetaan kaksoiskappale NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Keskeytetään kaksoiskappale NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1200,6 +1191,10 @@ msgstr "Virhe tuotaessa %s"
msgid "DUPLICATE"
msgstr "KAKSOISKAPPALE"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "SALATTU"
@@ -1245,6 +1240,10 @@ msgstr "%s artikkelia puuttui"
msgid "%s articles had non-matching duplicates"
msgstr "%s artikkelissa oli ei-vastaavia kaksoiskappaleita"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Keskeytetään kaksoiskappale NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2235,11 +2234,6 @@ msgstr "Nimi"
msgid "Retry"
msgstr "Yritä uudelleen"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Toiminnot"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3243,6 +3237,14 @@ msgstr "Esijonon käyttäjän skripti"
msgid "Used before an NZB enters the queue."
msgstr "Käytetään ennen NZB lisäämistä jonoon."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Ylimääräiset PAR2 parametrit"
@@ -4789,10 +4791,6 @@ msgid ""
msgstr ""
"Tuntematon SSL protokolla: Kokeile ottaa SSL käytöstä tai vaihda porttia."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Palvelin lopetettiin kesken kirjautumisen"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Palvelin vaatii käyttäjänimen ja salasanan."

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2023\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
@@ -177,8 +177,8 @@ msgid "Test Notification"
msgstr "Test de Notification"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Résolution de l'adresse"
msgid "Resolving address"
msgstr "Résolution de l'adresse"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -275,10 +275,6 @@ msgstr "Quota atteint, téléchargement mis en pause"
msgid "Incorrect parameter"
msgstr "Paramètre incorrect"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Le chemin UNC \"%s\" n'est pas autorisé ici"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s n'est pas une adresse email valide"
@@ -287,7 +283,7 @@ msgstr "%s n'est pas une adresse email valide"
msgid "Server address required"
msgstr "Adresse du serveur requise"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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]"
@@ -1205,6 +1203,18 @@ msgstr "Erreur lors du chargement de %s, fichier corrompu détecté"
msgid "NZB added to queue"
msgstr "NZB ajouté à la file d'attente"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Doublon NZB ignoré \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Échec de duplication du NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Dupliquer NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1219,26 +1229,6 @@ msgstr "Fichier NZB %s vide"
msgid "Pre-queue script marked job as failed"
msgstr "Le script de pré-file d'attente a marqué la tâche comme échouée"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Doublon NZB ignoré \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Échec de duplication du NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Dupliquer NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Mise en pause du doublon NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1257,6 +1247,10 @@ msgstr "Erreur lors de l'importation de %s"
msgid "DUPLICATE"
msgstr "DOUBLON"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "CHIFFRÉ"
@@ -1302,6 +1296,10 @@ msgstr "%s articles manquants"
msgid "%s articles had non-matching duplicates"
msgstr "%s articles avec doublons sans correspondance"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Mise en pause du doublon NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2305,11 +2303,6 @@ msgstr "Nom"
msgid "Retry"
msgstr "Réessayer"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Actions"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3365,6 +3358,14 @@ msgstr "Script utilisateur de pré-file d'attente"
msgid "Used before an NZB enters the queue."
msgstr "Utilisé avant qu'un NZB n'entre dans la file d'attente."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr "Script de fin de file d'attente"
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr "Exécuté après la fin du téléchargement de la file d'attente."
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Paramètres PAR2 supplémentaires"
@@ -4966,10 +4967,6 @@ msgstr ""
"Protocole SSL inconnu: essayez de désactiver SSL ou de vous connecter sur un"
" autre port."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Le serveur a interrompu l'ouverture de session."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Le serveur requiert un identifiant et un mot de passe."

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# ION, 2023
# Safihre <safihre@sabnzbd.org>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: ION, 2023\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -165,8 +165,8 @@ msgid "Test Notification"
msgstr "בחן התראה"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;פותר כתובת"
msgid "Resolving address"
msgstr "פותר כתובת"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -254,10 +254,6 @@ msgstr "מכסה נוצלה, משהה הורדה"
msgid "Incorrect parameter"
msgstr "פרמטר שגוי"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "נתיב UNC \"%s\" אינו מותר כאן"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s אינה כתובת דוא״ל תקפה"
@@ -266,7 +262,7 @@ msgstr "%s אינה כתובת דוא״ל תקפה"
msgid "Server address required"
msgstr "כתובת שרת דרושה"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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]"
@@ -1151,6 +1150,18 @@ msgstr "שגיאה בטעינת %s, קובץ פגום התגלה"
msgid "NZB added to queue"
msgstr "NZB התווסף לתור"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "מתעלם מן NZB כפול \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "מכשיל NZB כפול \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "NZB כפול"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1165,26 +1176,6 @@ msgstr "קובץ NZB ריק %s"
msgid "Pre-queue script marked job as failed"
msgstr "תסריט קדם־תור סומן כנכשל"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "מתעלם מן NZB כפול \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "מכשיל NZB כפול \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "NZB כפול"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "משהה NZB כפול \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1203,6 +1194,10 @@ msgstr "שגיאה ביבוא %s"
msgid "DUPLICATE"
msgstr "כפול"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "מוצפן"
@@ -1248,6 +1243,10 @@ msgstr "%s מאמרים היו חסרים"
msgid "%s articles had non-matching duplicates"
msgstr "אל %s מאמרים יש כפילויות בלתי תואמות"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "משהה NZB כפול \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2242,11 +2241,6 @@ msgstr "שם"
msgid "Retry"
msgstr "נסה שוב"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "פעולות"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3249,6 +3243,14 @@ msgstr "תסריט משתמש של קדם־תור"
msgid "Used before an NZB enters the queue."
msgstr "בשימוש לפני ש־NZB נכנס לתור."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "פרמטרי PAR2 נוספים"
@@ -4793,10 +4795,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr "פרוטוקול SSL בלתי ידוע: נסה להשבית SSL או להתחבר על פתחה שונה."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "שרת יצא במהלך רצף כניסות."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr ".השרת דורש שם משתמש וסיסמה"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
@@ -164,8 +164,8 @@ msgid "Test Notification"
msgstr "Test varslingen"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Løs adresse"
msgid "Resolving address"
msgstr "Løs adresse"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -252,10 +252,6 @@ msgstr "Kvote oppbrukt, setter nedlasting på pause"
msgid "Incorrect parameter"
msgstr "Feil parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-sti \"%s\" er ikke tillatt her"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s er ikke en godkjent e-post-adresse"
@@ -264,7 +260,7 @@ msgstr "%s er ikke en godkjent e-post-adresse"
msgid "Server address required"
msgstr "Krever server-adresse"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Køen er ikke tom, kan ikke bytte mappe."
#: 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]"
@@ -1144,6 +1143,18 @@ msgstr "Lastingsfeil %s, feilaktig fil oppdaget"
msgid "NZB added to queue"
msgstr "NZB er lagt til i køen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerer duplikatfil \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1158,26 +1169,6 @@ msgstr "Tom NZB-fil %s"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerer duplikatfil \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Stanser duplikatfil \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1196,6 +1187,10 @@ msgstr "Kunne ikke importere %s"
msgid "DUPLICATE"
msgstr "DUPLIKAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "KRYPTERT"
@@ -1241,6 +1236,10 @@ msgstr "%s artikler manglet"
msgid "%s articles had non-matching duplicates"
msgstr "%s artikler hadde ulike duplikater"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Stanser duplikatfil \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2231,11 +2230,6 @@ msgstr "Navn"
msgid "Retry"
msgstr "Prøv igjen"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Hendelser"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3224,6 +3218,14 @@ msgstr "Før-kø bruker skript"
msgid "Used before an NZB enters the queue."
msgstr "Brukes før en NZB blir lagt til kø"
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Ekstra PAR2 parametere"
@@ -4760,10 +4762,6 @@ msgstr ""
"Ukjent SSL-protokoll: Prøv å deaktivere SSL eller koble til på en annen "
"port."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Server avbrøt undet innloggingssekvens"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Server krever brukernavn og passord."

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
@@ -172,8 +172,8 @@ msgid "Test Notification"
msgstr "Test melding"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Adres opzoeken"
msgid "Resolving address"
msgstr "Adres opzoeken"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -268,10 +268,6 @@ msgstr "Quotum verbruikt, download is gestopt"
msgid "Incorrect parameter"
msgstr "Incorrecte parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-pad '%s' hier niet toegestaan."
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s is geen geldig e-mailadres"
@@ -280,7 +276,7 @@ msgstr "%s is geen geldig e-mailadres"
msgid "Server address required"
msgstr "Serveradres verplicht"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Wachtrij is niet leeg, andere map kiezen niet mogelijk."
#: 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]"
@@ -1190,6 +1189,18 @@ msgstr "Fout bij inladen van %s, corrupt bestand gevonden"
msgid "NZB added to queue"
msgstr "Download aan wachtrij toegevoegd"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" overgeslagen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Download '%s' geweigerd omdat het een dubbele is"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Dubbele download"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1204,26 +1215,6 @@ msgstr "NZB-bestand %s is leeg"
msgid "Pre-queue script marked job as failed"
msgstr "Wachtrij filter script heeft de download afgekeurd"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" overgeslagen"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Download '%s' geweigerd omdat het een dubbele is"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "Dubbele download"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" gepauzeerd"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1242,6 +1233,10 @@ msgstr "Fout bij importeren van %s"
msgid "DUPLICATE"
msgstr "DUBBEL"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "VERSLEUTELD"
@@ -1287,6 +1282,10 @@ msgstr "%s artikelen ontbreken"
msgid "%s articles had non-matching duplicates"
msgstr "%s artikelen hadden afwijkende duplicaten"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" gepauzeerd"
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2282,11 +2281,6 @@ msgstr "Naam"
msgid "Retry"
msgstr "Opnieuw"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Acties"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3321,6 +3315,14 @@ msgstr "Wachtrij-filter script"
msgid "Used before an NZB enters the queue."
msgstr "Word uitgevoerd vóór een download aan de wachtrij word toegevoegd"
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Extra PAR2 parameters"
@@ -4897,10 +4899,6 @@ msgid ""
msgstr ""
"Onbekend SSL protocol: probeer het zonder SSL of probeer een andere poort."
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "De server stopte tijdens de login"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Server heeft een gebruikersnaam en een wachtwoord nodig."

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
@@ -160,8 +160,8 @@ msgid "Test Notification"
msgstr "Powiadomienie testowe"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Rozwiązywanie adresu"
msgid "Resolving address"
msgstr "Rozwiązywanie adresu"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -248,10 +248,6 @@ msgstr "Przekroczono limit, wstrzymywanie pobierania"
msgid "Incorrect parameter"
msgstr "Błędny parametr"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Ścieżka UNC \"%s\" niedozwolona"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s nie jest prawidłowym adresem email"
@@ -260,7 +256,7 @@ msgstr "%s nie jest prawidłowym adresem email"
msgid "Server address required"
msgstr "Wymagane jest podanie adresu serwera"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Kolejka nie jest pusta, nie można zmienić katalogu."
#: 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]"
@@ -1146,6 +1145,18 @@ msgstr "Błąd ładowania %s, wykryto uszkodzony plik"
msgid "NZB added to queue"
msgstr "NZB dodany do kolejki"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignoruję zduplikowany NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1160,26 +1171,6 @@ msgstr "Pusty plik NZB %s"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignoruję zduplikowany NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Wstrzymuję zduplikowany NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1198,6 +1189,10 @@ msgstr "Błąd importu %s"
msgid "DUPLICATE"
msgstr "DUPLIKAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ZASZYFROWANY"
@@ -1243,6 +1238,10 @@ msgstr "Brakowało %s artykułów"
msgid "%s articles had non-matching duplicates"
msgstr "%s artykułów posiadało niepasujące duplikaty"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Wstrzymuję zduplikowany NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2237,11 +2236,6 @@ msgstr "Nazwa"
msgid "Retry"
msgstr "Ponów"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Działania"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3232,6 +3226,14 @@ msgstr "Skrypt użytkownika przed zakolejkowaniem"
msgid "Used before an NZB enters the queue."
msgstr "Uruchamiany zanim plik NZB zostanie umieszczony w kolejce"
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Dodatkowe parametry PAR2"
@@ -4769,10 +4771,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Serwer przerwał połączenie w trakcie logowania."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Serwer wymaga podania nazwy użytkownika i hasła."

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
@@ -164,8 +164,8 @@ msgid "Test Notification"
msgstr "Notificação de teste"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Resolvendo endereço"
msgid "Resolving address"
msgstr "Resolvendo endereço"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -252,10 +252,6 @@ msgstr "Quota esgotada, pausando o download"
msgid "Incorrect parameter"
msgstr "Parâmetro incorreto"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "O caminho UNC \"%s\" não é permitido aqui"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s não é um endereço de e-mail válido"
@@ -264,7 +260,7 @@ msgstr "%s não é um endereço de e-mail válido"
msgid "Server address required"
msgstr "Endereço do servidor necessário"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "A fila não está vazia. Não será possível mudar de pasta."
#: 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]"
@@ -1147,6 +1146,18 @@ msgstr "Erro ao carregar %s. Arquivo corrompido detectado"
msgid "NZB added to queue"
msgstr "NZB adicionado à fila"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorando NZB duplicado \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1161,26 +1172,6 @@ msgstr "Arquivo NZB %s vazio"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorando NZB duplicado \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicado \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1199,6 +1190,10 @@ msgstr "Erro ao importar %s"
msgid "DUPLICATE"
msgstr "DUPLICADO"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "CRIPTOGRAFADO"
@@ -1244,6 +1239,10 @@ msgstr "%s artigos estavam faltando"
msgid "%s articles had non-matching duplicates"
msgstr "%s artigos tinham duplicatas não-correspondentes"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicado \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2240,11 +2239,6 @@ msgstr "Nome"
msgid "Retry"
msgstr "Repetir"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Ações"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3234,6 +3228,14 @@ msgstr "Script de usuário de pré-fila"
msgid "Used before an NZB enters the queue."
msgstr "Utilizado antes de um NZB entrar na fila."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Parâmetros Extras PAR2"
@@ -4769,10 +4771,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Servidor parou durante a sequência de login."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Servidor requer usuário e senha."

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
@@ -169,8 +169,8 @@ msgid "Test Notification"
msgstr "Notificări Test"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Reolvare adresă"
msgid "Resolving address"
msgstr "Reolvare adresă"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -263,10 +263,6 @@ msgstr "Cotă epuizată, întrerupem descărcarea"
msgid "Incorrect parameter"
msgstr "Parametru Incorect"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "cale UNC \"%s\" nu este premisă aici"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s nu este o adresă email validă"
@@ -275,7 +271,7 @@ msgstr "%s nu este o adresă email validă"
msgid "Server address required"
msgstr "Adresă server necesară"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Coada nu este goală, nu pot schimba dosar."
#: 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]"
@@ -1172,6 +1171,18 @@ msgstr "Eroare încărcare %s, fişier corupt detectat"
msgid "NZB added to queue"
msgstr "NZB adăugat în coadă"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorăm duplicat NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Eșuare duplicat NZB „%s”"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "NZB duplicat"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1186,26 +1197,6 @@ msgstr "Fişier NZB gol %s"
msgid "Pre-queue script marked job as failed"
msgstr "Scriptul pre-coadă a marcat sarcina ca nereușită"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorăm duplicat NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Eșuare duplicat NZB „%s”"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "NZB duplicat"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Întrerupem duplicat NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1224,6 +1215,10 @@ msgstr "Eroare importare %s"
msgid "DUPLICATE"
msgstr "DUPLICAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ENCRIPTAT"
@@ -1269,6 +1264,10 @@ msgstr "%s articolele au fost lipsă"
msgid "%s articles had non-matching duplicates"
msgstr "%s articolele au avut duplicate diferite"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Întrerupem duplicat NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2267,11 +2266,6 @@ msgstr "Nume"
msgid "Retry"
msgstr "Reîncearcă"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Acțiuni"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3261,6 +3255,14 @@ msgstr "Script utilizator Pre-Coadă"
msgid "Used before an NZB enters the queue."
msgstr "Folosit înainte ca un NZB să intre în coadă."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Parametri Extra PAR2"
@@ -4802,10 +4804,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Serverul a renunţat în timpul logării."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Serverul necesită nume utilizator şi parolă"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
@@ -164,8 +164,8 @@ msgid "Test Notification"
msgstr "Тестовое уведомление"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Разрешение адреса"
msgid "Resolving address"
msgstr "Разрешение адреса"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -252,10 +252,6 @@ msgstr "Квота исчерпана. Загрузка приостановле
msgid "Incorrect parameter"
msgstr "Неправильный параметр"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-путь «%s» здесь не допускается"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s не является допустимым адресом электронной почты"
@@ -264,7 +260,7 @@ msgstr "%s не является допустимым адресом элект
msgid "Server address required"
msgstr "Требуется адрес сервера"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 ""
@@ -1145,6 +1144,18 @@ msgstr "Ошибка загрузки %s: обнаружен повреждён
msgid "NZB added to queue"
msgstr "NZB-файл добавлен в очередь"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Пропущен повторяющийся NZB-файл «%s»"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1159,26 +1170,6 @@ msgstr "Пустой NZB-файл %s"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Пропущен повторяющийся NZB-файл «%s»"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Приостановлен повторяющийся NZB-файл «%s»"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1197,6 +1188,10 @@ msgstr "Ошибка импорта %s"
msgid "DUPLICATE"
msgstr "ПОВТОР"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ЗАШИФРОВАН"
@@ -1242,6 +1237,10 @@ msgstr "%s статей отсутствует"
msgid "%s articles had non-matching duplicates"
msgstr "%s статей содержат несовпадающие повторы"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Приостановлен повторяющийся NZB-файл «%s»"
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2234,11 +2233,6 @@ msgstr "Название"
msgid "Retry"
msgstr "Повторить"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Действия"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3226,6 +3220,14 @@ msgstr "Пользовательский сценарий до помещени
msgid "Used before an NZB enters the queue."
msgstr "Используется до того, как NZB помещается в очередь."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Дополнительные параметры PAR2"
@@ -4765,10 +4767,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Во время входа на сервер был выполнен выход."
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Для сервера требуется имя пользователя и пароль."

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
@@ -162,8 +162,8 @@ msgid "Test Notification"
msgstr "Probno obaveštenje"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Решавање адресе"
msgid "Resolving address"
msgstr "Решавање адресе"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -250,10 +250,6 @@ msgstr "Kvota utrošena, pauziram preuzimanja"
msgid "Incorrect parameter"
msgstr "Погрешан параметар"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC путања \"%s\" није дозвољена"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s nije ispravna email adresa"
@@ -262,7 +258,7 @@ msgstr "%s nije ispravna email adresa"
msgid "Server address required"
msgstr "Потребна је адреса сервера"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Pед није празан, фасцикла се не може променити."
#: 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]"
@@ -1141,6 +1140,18 @@ msgstr "Грешка учитавање %s, покварена датотека
msgid "NZB added to queue"
msgstr "NZB додат у ред"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Игнорисање дуплог NZB-а \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1155,26 +1166,6 @@ msgstr "Празан NZB %s"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Игнорисање дуплог NZB-а \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Паузирам због дуплог NZB-а \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1193,6 +1184,10 @@ msgstr "Грешка увоза %s"
msgid "DUPLICATE"
msgstr "ДУПЛИКАТ"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ШИФРИРАНО"
@@ -1238,6 +1233,10 @@ msgstr "%s артикла недостају"
msgid "%s articles had non-matching duplicates"
msgstr "%s артикла нису дупликате"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Паузирам због дуплог NZB-а \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2228,11 +2227,6 @@ msgstr "Име"
msgid "Retry"
msgstr "Покушај опет"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Акције"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3215,6 +3209,14 @@ msgstr "Кориснички скрипт пре-реда"
msgid "Used before an NZB enters the queue."
msgstr "Коришћено пре него што NZB уђе у ред."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Додатни параметри PAR2"
@@ -4744,10 +4746,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Сервер се затворио при пријављивање"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Серверу су потребни име и лозинка."

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
@@ -162,8 +162,8 @@ msgid "Test Notification"
msgstr "Testa notifikation"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;Lösa adress"
msgid "Resolving address"
msgstr "Lösa adress"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -250,10 +250,6 @@ msgstr "Din kvot är uppnådd, pausar nerladdning"
msgid "Incorrect parameter"
msgstr "Fel parameter"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC sökväg \"%s\" är inte tillåten här"
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s är inte en godkänd e-mail adress"
@@ -262,7 +258,7 @@ msgstr "%s är inte en godkänd e-mail adress"
msgid "Server address required"
msgstr "Kräver serveradress"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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 "Kön är inte tom, kan inte byta mapp."
#: 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]"
@@ -1145,6 +1144,18 @@ msgstr "Laddningsfel %s, felaktig fil detekterad"
msgid "NZB added to queue"
msgstr "NZB tillagd i kön"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerar dubblett för NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1159,26 +1170,6 @@ msgstr "NZB filen %s är tom"
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerar dubblett för NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausar dubblett för NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1197,6 +1188,10 @@ msgstr "Det gick inte att importera %s"
msgid "DUPLICATE"
msgstr "DUBLETT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "KRYPTERAT"
@@ -1242,6 +1237,10 @@ msgstr "%s artiklar saknades"
msgid "%s articles had non-matching duplicates"
msgstr "%s artiklar hade icke-matchande dubletter"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausar dubblett för NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2234,11 +2233,6 @@ msgstr "Namn"
msgid "Retry"
msgstr "Försök igen"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "Åtgärder"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3225,6 +3219,14 @@ msgstr "Kö-specifika användarskript"
msgid "Used before an NZB enters the queue."
msgstr "Används innan en NZB tas in i kön."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "Extra PAR2 parametrar"
@@ -4758,10 +4760,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr ""
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "Servern avslutades under inloggning"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "Servern kräver användarnamn och lösenord."

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
@@ -160,8 +160,8 @@ msgid "Test Notification"
msgstr "测试通知"
#: sabnzbd/api.py
msgid "&nbsp;Resolving address"
msgstr "&nbsp;正在解析地址"
msgid "Resolving address"
msgstr "正在解析地址"
#. No value, used in dropdown menus
#: sabnzbd/api.py, sabnzbd/skintext.py
@@ -248,10 +248,6 @@ msgstr "配额已耗尽,暂停下载"
msgid "Incorrect parameter"
msgstr "参数不正确"
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "此处不允许使用 UNC 路径 \"%s\""
#: sabnzbd/cfg.py
msgid "%s is not a valid email address"
msgstr "%s 不是有效的电子邮箱地址"
@@ -260,7 +256,7 @@ msgstr "%s 不是有效的电子邮箱地址"
msgid "Server address required"
msgstr "服务器地址必填"
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
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]"
@@ -1134,6 +1133,18 @@ msgstr "无法加载 %s侦测到损坏文件"
msgid "NZB added to queue"
msgstr "NZB 已添加到队列"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "正在忽略重复 NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "失败于重复的 NZB 文件 \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "重复的 NZB 文件"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
@@ -1148,26 +1159,6 @@ msgstr "空 NZB 文件 %s"
msgid "Pre-queue script marked job as failed"
msgstr "预队列脚本将任务标记为失败的"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "正在忽略重复 NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Failing duplicate NZB \"%s\""
msgstr "失败于重复的 NZB 文件 \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Duplicate NZB"
msgstr "重复的 NZB 文件"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "正在暂停重复 NZB \"%s\""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
@@ -1186,6 +1177,10 @@ msgstr "导入 %s 出错"
msgid "DUPLICATE"
msgstr "*重复*"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "*加密*"
@@ -1231,6 +1226,10 @@ msgstr "%s 篇文章缺失"
msgid "%s articles had non-matching duplicates"
msgstr "%s 篇文章存在未匹配的重复"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "正在暂停重复 NZB \"%s\""
#. Footer: indicator of warnings
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
msgid "Warnings"
@@ -2221,11 +2220,6 @@ msgstr "名称"
msgid "Retry"
msgstr "重试"
#. Queue end-of-queue selection box
#: sabnzbd/skintext.py
msgid "Actions"
msgstr "操作"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3174,6 +3168,14 @@ msgstr "加入队列前执行的用户脚本"
msgid "Used before an NZB enters the queue."
msgstr "用于在 NZB 进入队列前执行。"
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
msgstr "额外的 PAR2 参数"
@@ -4692,10 +4694,6 @@ msgid ""
"Unknown SSL protocol: Try disabling SSL or connecting on a different port."
msgstr "未知的 SSL 协议:尝试禁用 SSL 或者连接不同的端口。"
#: sabnzbd/utils/servertests.py
msgid "Server quit during login sequence."
msgstr "登录过程中服务器退出。"
#: sabnzbd/utils/servertests.py
msgid "Server requires username and password."
msgstr "服务器需要用户名与密码。"

View File

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

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2022\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"

View File

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

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: reloxx13 <reloxx@interia.pl>, 2022\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Ester Molla Aragones <moarages@gmail.com>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ configobj==5.0.8
cheroot==10.0.0
six==1.16.0
cherrypy==18.8.0
jaraco.functools==3.9.0
jaraco.functools==4.0.0
jaraco.collections==4.3.0
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
jaraco.classes==3.3.0
@@ -30,7 +30,7 @@ rebulk==3.2.0
# Recent cryptography versions require Rust. If you run into issues compiling this
# SABnzbd will also work with older pre-Rust versions such as cryptography==3.3.2
cryptography==41.0.4
cryptography==41.0.5
# We recommend using "orjson" as it is 2x as fast as "ujson". However, it requires
# Rust so SABnzbd works just as well with "ujson" or the Python built in "json" module

View File

@@ -108,12 +108,8 @@ import sabnzbd.articlecache
import sabnzbd.bpsmeter
import sabnzbd.scheduler as scheduler
import sabnzbd.notifier as notifier
import sabnzbd.sorting
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
@@ -143,7 +139,6 @@ DIR_PID = None
QUEUECOMPLETE = None # stores the nice name of the action
QUEUECOMPLETEACTION = None # stores the name of the function to be called
QUEUECOMPLETEARG = None # stores an extra arguments that need to be passed
DAEMON = None
LINUX_POWER = powersup.HAVE_DBUS
@@ -242,11 +237,11 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
if cfg.wait_for_dfolder():
filesystem.wait_for_download_folder()
# Set the folders to be created, then the check_incomplete_vs_complete
# check will create them by calling get_path on them
# Create the folders, now that we waited for them to be available
cfg.download_dir.set_create(True)
cfg.download_dir.create_path()
cfg.complete_dir.set_create(True)
filesystem.check_incomplete_vs_complete()
cfg.complete_dir.create_path()
# Set call backs for Config items
cfg.cache_limit.callback(cfg.new_limit)

View File

@@ -592,9 +592,9 @@ def _api_addurl(name, kwargs):
password = kwargs.get("password", "")
if name:
nzo_id = sabnzbd.urlgrabber.add_url(name, pp, script, cat, priority, nzbname, password)
# Reporting a list of NZO's, for compatibility with other add-methods
return report(keyword="", data={"status": True, "nzo_ids": [nzo_id]})
res, nzo_ids = sabnzbd.urlgrabber.add_url(name, pp, script, cat, priority, nzbname, password)
return report(keyword="", data={"status": res is AddNzbFileResult.OK, "nzo_ids": nzo_ids})
else:
logging.info("API-call addurl: no URLs received")
return report(_MSG_NO_VALUE)
@@ -1292,11 +1292,11 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
info["servers"] = []
# Servers-list could be modified during iteration, so we need a copy
for server in sabnzbd.Downloader.servers[:]:
connected = sum(nw.connected for nw in server.idle_threads.copy())
activeconn = sum(nw.connected for nw in server.idle_threads.copy())
serverconnections = []
for nw in server.busy_threads.copy():
if nw.connected:
connected += 1
activeconn += 1
if nw.article:
serverconnections.append(
{
@@ -1307,25 +1307,31 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
}
)
if server.warning and not (connected or server.errormsg):
connected = server.warning
if server.request and not server.info:
connected = T("&nbsp;Resolving address").replace("&nbsp;", "")
server_info = {
"servername": server.displayname,
"serveractiveconn": connected,
"serveractive": server.active,
"serveractiveconn": activeconn,
"servertotalconn": server.threads,
"serverconnections": serverconnections,
"serverssl": server.ssl,
"serversslinfo": server.ssl_info,
"serveractive": server.active,
"serveripaddress": None,
"servercanonname": None,
"serverwarning": server.warning,
"servererror": server.errormsg,
"serverpriority": server.priority,
"serveroptional": server.optional,
"serverbps": to_units(sabnzbd.BPSMeter.server_bps.get(server.id, 0)),
}
# Only add this information if we are connected
if activeconn and server.addrinfo:
server_info["serveripaddress"] = server.addrinfo.ipaddress
server_info["servercanonname"] = server.addrinfo.canonname
if server.request and not server.addrinfo:
server_info["serverwarning"] = T("Resolving address")
info["servers"].append(server_info)
return info
@@ -1507,7 +1513,7 @@ def retry_job(job, new_nzb=None, password=None):
history_db = sabnzbd.get_db_connection()
futuretype, url, pp, script, cat = history_db.get_other(job)
if futuretype:
nzo_id = sabnzbd.urlgrabber.add_url(url, pp, script, cat)
nzo_id = sabnzbd.urlgrabber.add_url(url, pp, script, cat, dup_check=False)
else:
path = history_db.get_incomplete_path(job)
nzo_id = sabnzbd.NzbQueue.repair_job(path, new_nzb, password)

View File

@@ -36,6 +36,7 @@ from sabnzbd.filesystem import (
diskspace,
get_filename,
has_unwanted_extension,
get_basename,
)
from sabnzbd.constants import Status, GIGI, MAX_ASSEMBLER_QUEUE
import sabnzbd.cfg as cfg
@@ -249,7 +250,7 @@ SAFE_EXTS = (".mkv", ".mp4", ".avi", ".wmv", ".mpg", ".webm")
def is_cloaked(nzo: NzbObject, path: str, names: List[str]) -> bool:
"""Return True if this is likely to be a cloaked encrypted post"""
fname = os.path.splitext(get_filename(path.lower()))[0]
fname = get_basename(get_filename(path.lower()))
for name in names:
name = get_filename(name.lower())
name, ext = os.path.splitext(name)

View File

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

View File

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

View File

@@ -107,7 +107,6 @@ NORMAL_PRIORITY = 0
LOW_PRIORITY = -1
DEFAULT_PRIORITY = -100
PAUSED_PRIORITY = -2
DUP_PRIORITY = -3
STOP_PRIORITY = -4
PP_LOOKUP = {0: "", 1: "R", 2: "U", 3: "D"}
@@ -166,6 +165,13 @@ class Status:
PROP = "Propagating" # Q: Delayed download
class DuplicateStatus:
DUPLICATE = "Duplicate" # Simple duplicate
DUPLICATE_ALTERNATIVE = "Duplicate Alternative" # Alternative duplicate for a queued job
SERIES_DUPLICATE = "Series Duplicate" # Simple Series duplicate
SERIES_DUPLICATE_ALTERNATIVE = "Series Duplicate Alternative" # Alternative duplicate for a queued job
class AddNzbFileResult:
RETRY = "Retry" # File could not be read
ERROR = "Error" # Rejected as duplicate, by pre-queue script or other failure to process file

View File

@@ -321,15 +321,13 @@ class HistoryDB:
return items, total_items
def have_episode(self, series: str, season: str, episode: str) -> bool:
def have_episode(self, series_key: str) -> bool:
"""Check whether History contains this series episode"""
total = 0
if series and season and episode:
pattern = "%s/%s/%s" % (series.lower(), season, episode)
if self.execute(
"""SELECT COUNT(*) FROM History WHERE series = ? AND STATUS != ?""", (pattern, Status.FAILED)
):
total = self.cursor.fetchone()["COUNT(*)"]
if self.execute(
"""SELECT COUNT(*) FROM History WHERE series = ? AND STATUS != ?""", (series_key, Status.FAILED)
):
total = self.cursor.fetchone()["COUNT(*)"]
return total > 0
def have_name_or_md5sum(self, name: str, md5sum: str) -> bool:
@@ -467,13 +465,8 @@ def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_ou
# Reuse the old 'report' column to indicate a URL-fetch
report = "future" if nzo.futuretype else ""
# Analyze series info only when job is finished
series = ""
show_analysis = sabnzbd.newsunpack.analyse_show(nzo.final_name)
if show_analysis["job_type"] == "tv":
seriesname, season, episode = (show_analysis[key] for key in ("title", "season", "episode"))
if seriesname and season and episode:
series = "%s/%s/%s" % (seriesname.lower(), season, episode)
# Make sure we have the duplicate key
nzo.set_duplicate_series_key()
return (
completed,
@@ -497,7 +490,7 @@ def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_ou
nzo.fail_msg,
url_info,
nzo.bytes_downloaded,
series,
nzo.duplicate_series_key,
nzo.md5sum,
nzo.correct_password,
)

View File

@@ -34,7 +34,7 @@ import os
import re
import sabnzbd
from sabnzbd.filesystem import get_unique_filename, renamer, get_ext
from sabnzbd.filesystem import get_unique_filename, renamer, get_ext, get_basename
from sabnzbd.par2file import is_parfile, parse_par2_file
import sabnzbd.utils.file_extension as file_extension
from sabnzbd.misc import match_str
@@ -299,7 +299,7 @@ def deobfuscate(nzo, filelist: List[str], usefulname: str):
nr_files_renamed += 1
# Now find other files with the same basename in filelist, and rename them in the same way:
basedirfile, _ = os.path.splitext(biggest_file) # something like "/home/this/myiso"
basedirfile = get_basename(biggest_file) # something like "/home/this/myiso"
for otherfile in filelist:
if otherfile.startswith(basedirfile) and os.path.isfile(otherfile):
# yes, same basedirfile, only different ending

View File

@@ -30,7 +30,7 @@ from typing import Optional, Dict, List, Tuple
import sabnzbd
import sabnzbd.cfg as cfg
from sabnzbd.misc import int_conv, format_time_string, build_and_run_command
from sabnzbd.filesystem import long_path, remove_all, real_path, remove_file
from sabnzbd.filesystem import long_path, remove_all, real_path, remove_file, get_basename
from sabnzbd.nzbstuff import NzbObject, NzbFile
from sabnzbd.encoding import platform_btou
from sabnzbd.decorators import synchronized
@@ -552,7 +552,7 @@ def analyze_rar_filename(filename):
else:
# Detect if first of "rxx" set
if filename.endswith(".rar"):
return os.path.splitext(filename)[0], 1
return get_basename(filename), 1
return None, None

View File

@@ -23,7 +23,7 @@ import time
import select
import logging
from math import ceil
from threading import Thread, RLock
from threading import Thread, RLock, current_thread
import socket
import sys
import ssl
@@ -34,8 +34,8 @@ from sabnzbd.decorators import synchronized, NzbQueueLocker, DOWNLOADER_CV, DOWN
from sabnzbd.newswrapper import NewsWrapper, NNTPPermanentError
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.misc import from_units, get_server_addrinfo, helpful_warning, int_conv, MultiAddQueue
from sabnzbd.utils.happyeyeballs import happyeyeballs
from sabnzbd.misc import from_units, helpful_warning, int_conv, MultiAddQueue
from sabnzbd.happyeyeballs import happyeyeballs, AddrInfo
from sabnzbd.constants import SOFT_QUEUE_LIMIT
@@ -91,7 +91,7 @@ class Server:
"bad_cons",
"errormsg",
"warning",
"info",
"addrinfo",
"ssl_info",
"request",
"have_body",
@@ -136,6 +136,13 @@ class Server:
self.retention: int = retention
self.send_group: bool = send_group
# TODO: Remove for final release
if send_group:
helpful_warning(
"You have 'Send Group' enabled for %s. Could you let us know why? https://github.com/sabnzbd/sabnzbd/discussions/2715",
self.displayname,
)
self.username: Optional[str] = username
self.password: Optional[str] = password
@@ -147,7 +154,7 @@ class Server:
self.bad_cons: int = 0
self.errormsg: str = ""
self.warning: str = ""
self.info: Optional[List] = None # Will hold getaddrinfo() list
self.addrinfo: Union[AddrInfo, None, bool] = None # Will hold fasted address information
self.ssl_info: str = "" # Will hold the type and cipher of SSL connection
self.request: bool = False # True if a getaddrinfo() request is pending
self.have_body: bool = True # Assume server has "BODY", until proven otherwise
@@ -163,35 +170,6 @@ class Server:
# Tell the BPSMeter about this server
sabnzbd.BPSMeter.init_server_stats(self.id)
@property
def hostip(self) -> str:
"""In case a server still has active connections, we use the same IP again.
If new connection then use happyeyeballs if there are multiple options.
In case of problems: return the host name itself
"""
# Check if already a successful ongoing connection
if self.busy_threads:
active_thread = next(iter(self.busy_threads))
if active_thread.nntp:
# Re-use that IP
logging.debug("%s: Re-using address %s", self.host, active_thread.nntp.host)
return active_thread.nntp.host
# Determine IP
ip = None
if self.info:
# RFC6555 / Happy Eyeballs in case of multiple options
if len(self.info) > 1:
ip = happyeyeballs(self.host, port=self.port)
# Just 1 IP found, or problem with happyeyeballs, return first one
if not ip:
ip = self.info[0][4][0]
else:
ip = self.host
logging.debug("%s: Connecting to address %s", self.host, ip)
return ip
def deactivate(self):
"""Deactivate server and reset queued articles"""
self.active = False
@@ -234,22 +212,22 @@ class Server:
sabnzbd.NzbQueue.reset_try_lists(article, remove_fetcher_from_trylist=False)
self.article_queue = []
def request_info(self):
"""Launch async request to resolve server address.
getaddrinfo() can be very slow. In some situations this can lead
to delayed starts and timeouts on connections.
def request_addrinfo(self):
"""Launch async request to resolve server address and perform Happy Eyeballs.
In some situations this can be slow and result in delayed starts and timeouts on connections.
Because of this, the results will be cached in the server object."""
if not self.request:
self.request = True
Thread(target=self._request_info_internal).start()
Thread(target=self.request_addrinfo_blocking).start()
def _request_info_internal(self):
"""Async attempt to run getaddrinfo() for specified server"""
def request_addrinfo_blocking(self):
"""Blocking attempt to run getaddrinfo() and Happy Eyeballs for specified server"""
logging.debug("Retrieving server address information for %s", self.host)
self.info = get_server_addrinfo(self.host, self.port)
if not self.info:
self.addrinfo = happyeyeballs(self.host, self.port)
if not self.addrinfo:
self.bad_cons += self.threads
self.info = False
# Notify next call to maybe_block_server
self.addrinfo = False
else:
self.bad_cons = 0
self.request = False
@@ -497,7 +475,7 @@ class Downloader(Thread):
def maybe_block_server(self, server: Server):
# Was it resolving problem?
if server.info is False:
if server.addrinfo is False:
# Warn about resolving issues
errormsg = T("Cannot connect to server %s [%s]") % (server.host, T("Server name does not resolve"))
if server.errormsg != errormsg:
@@ -525,10 +503,10 @@ class Downloader(Thread):
# Remove all connections to server
for nw in server.idle_threads | server.busy_threads:
self.__reset_nw(nw, "forcing disconnect", warn=False, wait=False, retry_article=False)
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:
@@ -592,7 +570,7 @@ class Downloader(Thread):
# Already showed error
self.__reset_nw(nw)
else:
self.__reset_nw(nw, "timed out", warn=True)
self.__reset_nw(nw, "Timed out", warn=True)
server.bad_cons += 1
self.maybe_block_server(server)
@@ -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()
@@ -652,16 +630,16 @@ class Downloader(Thread):
server.host,
sys.exc_info()[1],
)
self.__reset_nw(nw, "failed to initialize", warn=True)
self.__reset_nw(nw, "Failed to initialize", warn=True)
if self.force_disconnect or self.shutdown:
for server in self.servers:
for nw in server.idle_threads | server.busy_threads:
# Send goodbye if we have open socket
if nw.nntp:
self.__reset_nw(nw, "forcing disconnect", wait=False, count_article_try=False)
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()])
@@ -737,7 +715,7 @@ class Downloader(Thread):
except ssl.SSLWantReadError:
return
except:
self.__reset_nw(nw, "server closed connection", wait=False)
self.__reset_nw(nw, "Server closed connection", wait=False)
return
article = nw.article
@@ -928,13 +906,14 @@ class Downloader(Thread):
server.errormsg = errormsg
logging.warning(errormsg)
return False
except:
except Exception as err:
logging.error(
T("Connecting %s@%s failed, message=%s"),
nw.thrdnum,
nw.server.host,
nw.nntp_msg,
err,
)
logging.info("Traceback: ", exc_info=True)
# No reset-warning needed, above logging is sufficient
self.__reset_nw(nw, retry_article=False)
return True
@@ -1005,11 +984,11 @@ class Downloader(Thread):
self.add_socket(nw.nntp.fileno, nw)
except socket.error as err:
logging.info("Looks like server closed connection: %s", err)
self.__reset_nw(nw, "server broke off connection", warn=True)
self.__reset_nw(nw, "Server broke off connection", warn=True)
except:
logging.error(T("Suspect error in downloader"))
logging.info("Traceback: ", exc_info=True)
self.__reset_nw(nw, "server broke off connection", warn=True)
self.__reset_nw(nw, "Server broke off connection", warn=True)
# ------------------------------------------------------------------------------
# Timed restart of servers admin.

View File

@@ -63,6 +63,11 @@ def get_ext(filename: str) -> str:
return ""
def get_basename(filename: str) -> str:
"""Shorthand for getting the basename of a filename"""
return os.path.splitext(filename)[0]
def is_listed_ext(ext: str, ext_list: list) -> bool:
"""Check if the extension is listed. In case of a regexp the entire extension must be matched;
partial matches aren't accepted (e.g. 'r[0-9]{2}' will be treated the same as '^r[0-9]{2}$' and
@@ -111,7 +116,7 @@ def get_filename(path: str) -> str:
def setname_from_path(path: str) -> str:
"""Get the setname from a path"""
return os.path.splitext(os.path.basename(path))[0]
return get_basename(os.path.basename(path))
def is_writable(path: str) -> bool:
@@ -234,21 +239,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 +416,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 +428,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):
@@ -563,7 +579,7 @@ def list_scripts(default: bool = False, none: bool = True) -> List[str]:
if (
(
sabnzbd.WIN32
and os.path.splitext(script)[1].lower() in PATHEXT
and get_ext(script) in PATHEXT
and not win32api.GetFileAttributes(script) & win32file.FILE_ATTRIBUTE_HIDDEN
)
or script.endswith(".py")
@@ -865,11 +881,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 +1209,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():
@@ -1216,8 +1218,7 @@ def wait_for_download_folder():
def backup_exists(filename: str) -> bool:
"""Return True if backup exists and no_dupes is set"""
path = sabnzbd.cfg.nzb_backup_dir.get_path()
return path and os.path.exists(os.path.join(path, filename + ".gz"))
return os.path.exists(os.path.join(sabnzbd.cfg.nzb_backup_dir.get_path(), filename + ".gz"))
def backup_nzb(nzb_path: str):

190
sabnzbd/happyeyeballs.py Normal file
View File

@@ -0,0 +1,190 @@
#!/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/8305 (Happy Eyeballs): find the quickest IPv4/IPv6 connection
# See https://tools.ietf.org/html/rfc6555
# See https://tools.ietf.org/html/rfc8305
import socket
import threading
import time
import logging
import queue
from dataclasses import dataclass
from typing import Tuple, Union, Optional
from more_itertools import roundrobin
from sabnzbd import cfg as cfg
# How long to delay between connection attempts? The RFC suggests 250ms, but this is
# quite long and might give us a slow host that just happened to be on top of the list.
# The absolute minium specified in RFC 8305 is 10ms, so we use that.
CONNECTION_ATTEMPT_DELAY = 0.01
# The total time we want to wait for any result
MAXIMUM_RESOLUTION_TIME = 3
# While providers are afraid to add IPv6 to their standard hostnames
# we map a number of well known hostnames to their IPv6 alternatives.
# WARNING: Only add if the SSL-certificate allows both hostnames!
IPV6_MAPPING = {
"news.eweka.nl": "news6.eweka.nl",
"news.xlned.com": "news6.xlned.com",
"news.usenet.farm": "news6.usenet.farm",
"news.easynews.com": "news6.easynews.com",
"news.tweaknews.nl": "news6.tweaknews.nl",
"news.tweaknews.eu": "news6.tweaknews.eu",
"news.astraweb.com": "news6.astraweb.com",
"news.pureusenet.nl": "news6.pureusenet.nl",
"news.sunnyusenet.com": "news6.sunnyusenet.com",
"news.newshosting.com": "news6.newshosting.com",
"news.usenetserver.com": "news6.usenetserver.com",
"news.frugalusenet.com": "news-v6.frugalusenet.com",
"eunews.frugalusenet.com": "eunews-v6.frugalusenet.com",
}
# 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):
"""Connect to the ip, and put the result into the queue"""
try:
start = time.time()
s = socket.socket(addrinfo.family, addrinfo.type)
s.settimeout(MAXIMUM_RESOLUTION_TIME)
try:
s.connect(addrinfo.sockaddr)
result_queue.put(addrinfo)
logging.debug(
"Happy Eyeballs connected to %s (%s) in %dms",
addrinfo.ipaddress,
addrinfo.canonname,
1000 * (time.time() - start),
)
except socket.error:
logging.debug(
"Happy Eyeballs failed to connect to %s (%s) in %dms",
addrinfo.ipaddress,
addrinfo.canonname,
1000 * (time.time() - start),
)
finally:
s.close()
except:
pass
def happyeyeballs(host: str, port: int) -> Optional[AddrInfo]:
"""Return the fastest result of getaddrinfo() based on RFC 6555/8305 (Happy Eyeballs),
including IPv6 addresses if desired. Returns None in case no addresses were returned
by getaddrinfo or if no connection could be made to any of the addresses"""
try:
# Get address information, by default both IPV4 and IPV6
check_hosts = [host]
family = socket.AF_UNSPEC
if not cfg.ipv6_servers():
family = socket.AF_INET
elif host in IPV6_MAPPING:
# See if we can add a IPv6 alternative
check_hosts.append(IPV6_MAPPING[host])
logging.info("Added alternative IPv6 address: %s", IPV6_MAPPING[host])
ipv4_addrinfo = []
ipv6_addrinfo = []
last_canonname = ""
for check_host in check_hosts:
try:
for addrinfo in socket.getaddrinfo(
check_host, port, family, socket.SOCK_STREAM, flags=socket.AI_CANONNAME
):
# Convert to AddrInfo
addrinfo = AddrInfo(*addrinfo)
# The canonname is only reported once per alias
if addrinfo.canonname:
last_canonname = addrinfo.canonname
elif last_canonname:
addrinfo.canonname = last_canonname
# Put it in the right list for further processing
# But prevent adding duplicate items to the lists
if addrinfo not in ipv6_addrinfo and addrinfo not in ipv4_addrinfo:
if addrinfo.family == socket.AddressFamily.AF_INET6:
ipv6_addrinfo.append(addrinfo)
else:
ipv4_addrinfo.append(addrinfo)
except:
# Did we fail on the first getaddrinfo already?
# Otherwise, we failed on the IPv6 alternative address, and those failures can be ignored
if not ipv4_addrinfo and not ipv6_addrinfo:
raise
logging.debug(
"Available addresses for %s (port=%d): %d IPv4 and %d IPv6",
host,
port,
len(ipv4_addrinfo),
len(ipv6_addrinfo),
)
# To optimize success, the RFC states to alternate between trying the
# IPv6 and IPv4 results, starting with IPv6 since it is the preferred method.
result_queue: queue.Queue[AddrInfo] = queue.Queue()
addr_tried = 0
result: Optional[AddrInfo] = None
for addrinfo in roundrobin(ipv6_addrinfo, ipv4_addrinfo):
threading.Thread(target=do_socket_connect, args=(result_queue, addrinfo), daemon=True).start()
addr_tried += 1
try:
result = result_queue.get(timeout=CONNECTION_ATTEMPT_DELAY)
break
except queue.Empty:
# Start a thread for the next address in the list if the previous
# connection attempt did not complete in time or if it wasn't a success
continue
# If we had no results, we might just need to give it more time
if not result:
try:
# Reduce waiting time by time already spent
result = result_queue.get(timeout=MAXIMUM_RESOLUTION_TIME - addr_tried * CONNECTION_ATTEMPT_DELAY)
except queue.Empty:
raise ConnectionError("No addresses could be resolved")
logging.info("Quickest IP address for %s (port=%d): %s (%s)", host, port, result.ipaddress, result.canonname)
return result
except Exception as e:
logging.debug("Failed Happy Eyeballs lookup: %s", e)
return None

View File

@@ -47,20 +47,19 @@ from sabnzbd.misc import (
get_base_url,
is_ipv4_addr,
is_ipv6_addr,
get_server_addrinfo,
is_lan_addr,
is_local_addr,
is_loopback_addr,
ip_in_subnet,
helpful_warning,
recursive_html_escape,
)
from sabnzbd.happyeyeballs import happyeyeballs
from sabnzbd.filesystem import (
real_path,
globber,
globber_full,
clip_path,
same_file,
same_directory,
setname_from_path,
)
from sabnzbd.encoding import xml_name, utob
@@ -180,8 +179,7 @@ def secured_expose(
# Some pages need correct API key
if check_api_key:
msg = check_apikey(kwargs)
if msg:
if msg := check_apikey(kwargs):
cherrypy.response.status = 403
if cfg.api_warnings():
return msg
@@ -738,21 +736,9 @@ class ConfigFolders:
@secured_expose(check_api_key=True, check_configlock=True)
def saveDirectories(self, **kwargs):
for kw in LIST_DIRPAGE + LIST_BOOL_DIRPAGE:
value = kwargs.get(kw)
if value is not None or kw in LIST_BOOL_DIRPAGE:
if kw in ("complete_dir", "dirscan_dir", "backup_dir"):
msg = config.get_config("misc", kw).set(value, create=True)
else:
msg = config.get_config("misc", kw).set(value)
if msg:
# return sabnzbd.api.report('json', error=msg)
return badParameterResponse(msg, kwargs.get("ajax"))
if msg := config.get_config("misc", kw).set(kwargs.get(kw)):
return badParameterResponse(msg, kwargs.get("ajax"))
if not sabnzbd.filesystem.check_incomplete_vs_complete():
return badParameterResponse(
T("The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"),
kwargs.get("ajax"),
)
config.save_config()
if kwargs.get("ajax"):
return sabnzbd.api.report()
@@ -780,6 +766,7 @@ SWITCH_LIST = (
"nice",
"ionice",
"pre_script",
"end_queue_script",
"pause_on_pwrar",
"sfv_check",
"deobfuscate_final_filenames",
@@ -832,8 +819,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 +919,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 +943,8 @@ GENERAL_LIST = (
"socks5_proxy_url",
"auto_browser",
"check_new_rel",
"bandwidth_max",
"bandwidth_perc",
)
@@ -994,8 +979,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 +991,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 +1000,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 +1151,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 +1772,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 +2138,8 @@ class ConfigNotify:
def saveNotify(self, **kwargs):
for section in NOTIFY_OPTIONS:
for option in NOTIFY_OPTIONS[section]:
config.get_config(section, option).set(kwargs.get(option))
if msg := config.get_config(section, option).set(kwargs.get(option)):
return badParameterResponse(msg, kwargs.get("ajax"))
config.save_config()
if kwargs.get("ajax"):
return sabnzbd.api.report()

View File

@@ -52,7 +52,7 @@ from sabnzbd.constants import (
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.encoding import ubtou
from sabnzbd.filesystem import userxbit, make_script_path, remove_file, is_valid_script
from sabnzbd.filesystem import userxbit, make_script_path, remove_file
if sabnzbd.WIN32:
try:
@@ -85,7 +85,14 @@ HAVE_AMPM = bool(time.strftime("%p"))
def helpful_warning(*args, **kwargs):
"""Wrapper to ignore helpful warnings if desired"""
if sabnzbd.cfg.helpful_warnings():
if cfg.helpful_warnings():
return logging.warning(*args, **kwargs)
return logging.info(*args, **kwargs)
def duplicate_warning(*args, **kwargs):
"""Wrapper to ignore duplicate warnings if desired"""
if cfg.warn_dupl_jobs():
return logging.warning(*args, **kwargs)
return logging.info(*args, **kwargs)
@@ -228,7 +235,7 @@ def cat_to_opts(cat, pp=None, script=None, priority=None) -> Tuple[str, int, str
return cat, pp, script, priority
def pp_to_opts(pp: int) -> Tuple[bool, bool, bool]:
def pp_to_opts(pp: Optional[int]) -> Tuple[bool, bool, bool]:
"""Convert numeric processing options to (repair, unpack, delete)"""
# Convert the pp to an int
pp = sabnzbd.interface.int_conv(pp)
@@ -791,12 +798,13 @@ def format_time_string(seconds: float) -> str:
return " ".join(completestr)
def int_conv(value: Any) -> int:
"""Safe conversion to int (can handle None)"""
def int_conv(value: Any, default: Any = 0) -> int:
"""Safe conversion to int (can handle None)
Returns 0 or requested default value"""
try:
return int(value)
except:
return 0
return default
def create_https_certificates(ssl_cert, ssl_key):
@@ -1027,19 +1035,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
@@ -1174,15 +1169,14 @@ def run_command(cmd: List[str], **kwargs):
return txt
def run_script(script):
"""Run a user script (queue complete only)"""
script_path = make_script_path(script)
if script_path:
def run_script(script: str):
"""Run a user script"""
if script_path := make_script_path(script):
try:
script_output = run_command([script_path])
logging.info("Output of queue-complete script %s: \n%s", script, script_output)
script_output = run_command([script_path], env=sabnzbd.newsunpack.create_env())
logging.info("Output of script %s: \n%s", script, script_output)
except:
logging.info("Failed queue-complete script %s, Traceback: ", script, exc_info=True)
logging.info("Failed script %s, Traceback: ", script, exc_info=True)
def set_socks5_proxy():
@@ -1301,24 +1295,17 @@ def system_standby():
def change_queue_complete_action(action: str, new: bool = True):
"""Action or script to be performed once the queue has been completed
Scripts are prefixed with 'script_'
"""
_action = None
_argument = None
"""Action or script to be performed once the queue has been completed"""
function = None
if new or cfg.queue_complete_pers():
if action.startswith("script_") and is_valid_script(action.replace("script_", "", 1)):
# all scripts are labeled script_xxx
_action = sabnzbd.misc.run_script
_argument = action.replace("script_", "", 1)
elif action == "shutdown_pc":
_action = system_shutdown
if action == "shutdown_pc":
function = system_shutdown
elif action == "hibernate_pc":
_action = system_hibernate
function = system_hibernate
elif action == "standby_pc":
_action = system_standby
function = system_standby
elif action == "shutdown_program":
_action = sabnzbd.shutdown_program
function = sabnzbd.shutdown_program
else:
action = None
else:
@@ -1329,8 +1316,7 @@ def change_queue_complete_action(action: str, new: bool = True):
config.save_config()
sabnzbd.QUEUECOMPLETE = action
sabnzbd.QUEUECOMPLETEACTION = _action
sabnzbd.QUEUECOMPLETEARG = _argument
sabnzbd.QUEUECOMPLETEACTION = function
def keep_awake():

View File

@@ -36,7 +36,6 @@ import sabnzbd.utils.rarfile as rarfile
from sabnzbd.misc import (
format_time_string,
find_on_path,
int_conv,
get_all_passwords,
calc_age,
cmp,
@@ -45,7 +44,6 @@ from sabnzbd.misc import (
format_time_left,
)
from sabnzbd.filesystem import (
make_script_path,
real_path,
globber,
globber_full,
@@ -61,11 +59,12 @@ from sabnzbd.filesystem import (
get_filename,
SEVENMULTI_RE,
is_size,
get_basename,
)
from sabnzbd.nzbstuff import NzbObject
import sabnzbd.cfg as cfg
from sabnzbd.constants import Status, JOB_ADMIN
from sabnzbd.sorting import Sorter
# Regex globals
RAR_V3_RE = re.compile(r"\.(?P<ext>part\d*)$", re.I)
@@ -274,8 +273,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
@@ -509,7 +510,7 @@ def rar_unpack(nzo: NzbObject, workdir_complete: str, one_folder: bool, rars: Li
rar_set = setname_from_path(rar)
if RAR_V3_RE.search(rar_set):
# Remove the ".partXX" part
rar_set = os.path.splitext(rar_set)[0]
rar_set = get_basename(rar_set)
if rar_set not in rar_sets:
rar_sets[rar_set] = []
rar_sets[rar_set].append(rar)
@@ -876,7 +877,7 @@ def unseven(nzo: NzbObject, workdir_complete: str, one_folder: bool, sevens: Lis
setname = setname_from_path(seven)
if SEVENMULTI_RE.search(setname):
# Remove the ".001" part
setname = os.path.splitext(setname)[0]
setname = get_basename(setname)
if setname not in seven_sets:
seven_sets[setname] = []
seven_sets[setname].append(seven)
@@ -1088,7 +1089,7 @@ def par2_repair(nzo: NzbObject, setname: str) -> Tuple[bool, bool]:
# Remove extra files created during repair and par2 base files
for path in new_dir_content:
if os.path.splitext(path)[1] == ".1" and path not in old_dir_content:
if get_ext(path) == ".1" and path not in old_dir_content:
deletables.append(os.path.join(nzo.download_path, path))
deletables.append(os.path.join(nzo.download_path, setname + ".par2"))
deletables.append(os.path.join(nzo.download_path, setname + ".PAR2"))
@@ -2131,124 +2132,6 @@ def add_time_left(perc: float, start_time: Optional[float] = None, time_used: Op
return " - %s %s" % (format_time_left(int((100 - perc) / (perc / time_used)), short_format=True), T("left"))
return ""
def analyse_show(name: str) -> Dict[str, str]:
"""Use the Sorter to collect some basic info on series"""
job = Sorter(
None,
name,
None,
None,
force=True,
sorter_config={
"name": "newsunpack__analyse_show",
"order": 0,
"min_size": -1,
"multipart_label": "",
"sort_string": "",
"sort_cats": [], # Categories and types are ignored when using the force
"sort_type": [],
"is_active": 1,
},
)
job.get_values()
return {
"title": job.info.get("title", ""),
"season": job.info.get("season_num", ""),
"episode": job.info.get("episode_num", ""),
"episode_name": job.info.get("ep_name", ""),
"is_proper": job.is_proper(),
"resolution": job.info.get("resolution", ""),
"decade": job.info.get("decade", ""),
"year": job.info.get("year", ""),
"month": job.info.get("month", ""),
"day": job.info.get("day", ""),
"job_type": job.type,
}
def pre_queue(nzo: NzbObject, pp, cat):
"""Run pre-queue script (if any) and process results.
pp and cat are supplied separate since they can change.
"""
def fix(p):
# If added via API, some items can still be "None" (as a string)
if not p or str(p).lower() == "none":
return ""
return str(p)
values = [1, nzo.final_name_with_password, pp, cat, nzo.script, nzo.priority, None]
script_path = make_script_path(cfg.pre_script())
if script_path:
# Basic command-line parameters
command = [
script_path,
nzo.final_name_with_password,
pp,
cat,
nzo.script,
nzo.priority,
str(nzo.bytes),
" ".join(nzo.groups),
]
command.extend(list(analyse_show(nzo.final_name_with_password).values()))
command = [fix(arg) for arg in command]
# Fields not in the NZO directly
extra_env_fields = {
"groups": " ".join(nzo.groups),
"show_name": command[8],
"show_season": command[9],
"show_episode": command[10],
"show_episode_name": command[11],
"proper": command[12],
"resolution": command[13],
"decade": command[14],
"year": command[15],
"month": command[16],
"day": command[17],
"type": command[18],
}
try:
p = build_and_run_command(command, env=create_env(nzo, extra_env_fields))
except:
logging.debug("Failed script %s, Traceback: ", script_path, exc_info=True)
return values
output = p.stdout.read()
ret = p.wait()
logging.info("Pre-queue script returned %s and output=\n%s", ret, output)
if ret == 0:
split_output = output.splitlines()
try:
# Extract category line from pre-queue output
pre_queue_category = split_output[3].strip(" '\"")
except IndexError:
pre_queue_category = None
for index, line in enumerate(split_output):
line = line.strip(" '\"")
if index < len(values):
if line:
values[index] = line
elif pre_queue_category and index in (2, 4, 5):
# Preserve empty pp, script, and priority lines to prevent
# pre-existing values from overriding category-based settings
values[index] = ""
accept = int_conv(values[0])
if accept < 1:
logging.info("Pre-Q refuses %s", nzo.final_name)
elif accept == 2:
logging.info("Pre-Q accepts&fails %s", nzo.final_name)
else:
logging.info("Pre-Q accepts %s", nzo.final_name)
return values
def is_sevenfile(path: str) -> bool:
"""Return True if path has 7Zip-signature and 7Zip is detected"""
with open(path, "rb") as sevenzip:

View File

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

View File

@@ -214,20 +214,20 @@ def process_nzb_archive_file(
cat=cat,
url=url,
priority=priority,
password=password,
nzbname=nzbname,
nzo_info=nzo_info,
reuse=reuse,
nzo_id=nzo_id,
dup_check=dup_check,
)
if not nzo.password:
nzo.password = password
except (sabnzbd.nzbstuff.NzbEmpty, sabnzbd.nzbstuff.NzbRejected):
# Empty or fully rejected
pass
except sabnzbd.nzbstuff.NzbRejectedToHistory as err:
# Duplicate or unwanted extension that was failed to history
nzo_ids.append(err.nzo_id)
except sabnzbd.nzbstuff.NzbRejectToHistory as err:
# Duplicate or unwanted extension directed to history
sabnzbd.NzbQueue.fail_to_history(err.nzo)
nzo_ids.append(err.nzo.nzo_id)
except:
# Something else is wrong, show error
logging.error(T("Error while adding %s, removing"), name, exc_info=True)
@@ -321,23 +321,23 @@ def process_single_nzb(
cat=cat,
url=url,
priority=priority,
password=password,
nzbname=nzbname,
nzo_info=nzo_info,
reuse=reuse,
nzo_id=nzo_id,
dup_check=dup_check,
)
if not nzo.password:
nzo.password = password
except sabnzbd.nzbstuff.NzbEmpty:
# Malformed or might not be an NZB file
result = AddNzbFileResult.NO_FILES_FOUND
except sabnzbd.nzbstuff.NzbRejected:
# Rejected as duplicate or by pre-queue script
result = AddNzbFileResult.ERROR
except sabnzbd.nzbstuff.NzbRejectedToHistory as err:
# Duplicate or unwanted extension that was failed to history
nzo_ids.append(err.nzo_id)
except sabnzbd.nzbstuff.NzbRejectToHistory as err:
# Duplicate or unwanted extension directed to history
sabnzbd.NzbQueue.fail_to_history(err.nzo)
nzo_ids.append(err.nzo.nzo_id)
except:
# Something else is wrong, show error
logging.error(T("Error while adding %s, removing"), filename, exc_info=True)

View File

@@ -27,7 +27,7 @@ from typing import List, Dict, Union, Tuple, Optional
import sabnzbd
from sabnzbd.nzbstuff import NzbObject, Article
from sabnzbd.misc import exit_sab, cat_to_opts, int_conv, caller_name, safe_lower
from sabnzbd.misc import exit_sab, cat_to_opts, int_conv, caller_name, safe_lower, duplicate_warning
from sabnzbd.filesystem import get_admin_path, remove_all, globber_full, remove_file, is_valid_script
from sabnzbd.nzbparser import process_single_nzb
from sabnzbd.panic import panic_queue
@@ -37,7 +37,6 @@ from sabnzbd.constants import (
QUEUE_VERSION,
FUTURE_Q_FOLDER,
JOB_ADMIN,
DEFAULT_PRIORITY,
LOW_PRIORITY,
HIGH_PRIORITY,
FORCE_PRIORITY,
@@ -45,6 +44,7 @@ from sabnzbd.constants import (
VERIFIED_FILE,
Status,
IGNORED_FILES_AND_FOLDERS,
DuplicateStatus,
)
import sabnzbd.cfg as cfg
@@ -189,8 +189,7 @@ class NzbQueue:
else:
try:
logging.debug("Repair job %s without stored NZB", name)
nzo = NzbObject(name, nzbname=name, reuse=repair_folder)
nzo.password = password
nzo = NzbObject(name, password=password, nzbname=name, reuse=repair_folder)
self.add(nzo)
nzo_ids = [nzo.nzo_id]
except:
@@ -247,25 +246,6 @@ class NzbQueue:
def set_top_only(self, value):
self.__top_only = value
def generate_future(
self, msg, pp=None, script=None, cat=None, url=None, priority=DEFAULT_PRIORITY, nzbname=None
) -> NzbObject:
"""Create and return a placeholder nzo object"""
logging.debug("Creating placeholder NZO")
future_nzo = NzbObject(
filename=msg,
pp=pp,
script=script,
futuretype=True,
cat=cat,
url=url,
priority=priority,
nzbname=nzbname,
status=Status.GRABBING,
)
self.add(future_nzo)
return future_nzo
def change_opts(self, nzo_ids: str, pp: int) -> int:
result = 0
for nzo_id in [item.strip() for item in nzo_ids.split(",")]:
@@ -380,7 +360,7 @@ class NzbQueue:
return nzo.nzo_id
@NzbQueueLocker
def remove(self, nzo_id: str, cleanup: bool = True, delete_all_data: bool = True) -> Optional[str]:
def remove(self, nzo_id: str, cleanup: bool = True, delete_all_data: bool = True) -> Optional[NzbObject]:
"""Remove NZO from queue.
It can be added to history directly.
Or, we do some clean-up, sometimes leaving some data.
@@ -396,18 +376,21 @@ class NzbQueue:
nzo.status = Status.DELETED
nzo.purge_data(delete_all_data=delete_all_data)
self.save(False)
return nzo_id
return None
return nzo
@NzbQueueLocker
def remove_multiple(self, nzo_ids: List[str], delete_all_data=True) -> List[str]:
"""Remove multiple jobs from the queue. Also triggers duplicate handling
and downloader-disconnect, so intended for external use only!"""
removed = []
for nzo_id in nzo_ids:
if self.remove(nzo_id, delete_all_data=delete_all_data):
if nzo := self.remove(nzo_id, delete_all_data=delete_all_data):
removed.append(nzo_id)
# Start an alternative, if available
self.handle_duplicate_alternatives(nzo, success=False)
# Any files left? Otherwise let's disconnect
if self.actives(grabs=False) == 0 and cfg.autodisconnect():
if not self.actives(grabs=False) and cfg.autodisconnect():
# This was the last job, close server connections
sabnzbd.Downloader.disconnect()
@@ -799,6 +782,13 @@ class NzbQueue:
pass
sabnzbd.Assembler.process(nzo)
def fail_to_history(self, nzo: NzbObject):
"""Fail to history, with all the steps in between"""
if not nzo.nzo_id:
self.add(nzo, quiet=True)
self.remove(nzo.nzo_id, cleanup=False)
sabnzbd.PostProcessor.process(nzo)
def actives(self, grabs: bool = True) -> int:
"""Return amount of non-paused jobs, optionally with 'grabbing' items
Not locked for performance, only reads the queue
@@ -956,5 +946,81 @@ class NzbQueue:
lst.append((url, nzo))
return lst
@NzbQueueLocker
def have_name_or_md5sum(self, name: str, md5sum: str) -> bool:
"""Check whether this name or md5sum is already
in the queue or the post-processing queue"""
lname = name.lower()
for nzo in self.__nzo_list + sabnzbd.PostProcessor.get_queue():
# Skip any jobs already marked as duplicate, to prevent double-triggers
if not nzo.duplicate:
# URL's do not have an MD5!
if nzo.final_name.lower() == lname or (nzo.md5sum and md5sum and nzo.md5sum == md5sum):
return True
return False
@NzbQueueLocker
def have_episode(self, series_key: str) -> bool:
"""Check whether this episode of the series is already
in the queue or the post-processing queue"""
for nzo in self.__nzo_list:
# Skip any jobs already marked as duplicate, to prevent double-triggers
if not nzo.duplicate:
if nzo.duplicate_series_key == series_key:
return True
return False
@NzbQueueLocker
def handle_duplicate_alternatives(self, finished_nzo: NzbObject, success: bool):
"""Remove matching duplicates if the first job succeeded,
or start the next alternative if the job failed"""
if not cfg.no_dupes() and not cfg.no_series_dupes():
return
# Unfortunately we need a copy, since we might remove items from the list
for nzo in self.__nzo_list[:]:
if not nzo.duplicate:
continue
# URL's do not have an MD5!
if (
nzo.final_name.lower() == finished_nzo.final_name.lower()
or (nzo.md5sum and finished_nzo.md5sum and nzo.md5sum == finished_nzo.md5sum)
) or (
nzo.duplicate_series_key
and finished_nzo.duplicate_series_key
and nzo.duplicate_series_key == finished_nzo.duplicate_series_key
):
# Start the next alternative
if not success:
logging.info("Resuming duplicate alternative %s for ", nzo.final_name, finished_nzo.final_name)
nzo.duplicate = None
nzo.resume()
return
# Take action on the alternatives to the duplicate
# 1 = Discard
# 2 = Pause
# 3 = Fail (move to History)
# 4 = Tag
series_duplicate = nzo.duplicate == DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE
if (not series_duplicate and cfg.no_dupes() == 1) or (series_duplicate and cfg.no_series_dupes() == 1):
duplicate_warning(T('Ignoring duplicate NZB "%s"'), nzo.final_name)
self.remove(nzo.nzo_id)
elif (not series_duplicate and cfg.no_dupes() == 3) or (
series_duplicate and cfg.no_series_dupes() == 3
):
duplicate_warning(T('Failing duplicate NZB "%s"'), nzo.final_name)
nzo.fail_msg = T("Duplicate NZB")
self.fail_to_history(nzo)
else:
# Action set to Pause or Tag, so only adjust the label on the first matching job
logging.info("Re-tagging duplicate alternative %s for %s", nzo.final_name, finished_nzo.final_name)
if nzo.duplicate == DuplicateStatus.DUPLICATE_ALTERNATIVE:
nzo.duplicate = DuplicateStatus.DUPLICATE
else:
nzo.duplicate = DuplicateStatus.SERIES_DUPLICATE
return
def __repr__(self):
return "<NzbQueue>"

View File

@@ -42,11 +42,11 @@ from sabnzbd.constants import (
LOW_PRIORITY,
DEFAULT_PRIORITY,
PAUSED_PRIORITY,
DUP_PRIORITY,
STOP_PRIORITY,
RENAMES_FILE,
MAX_BAD_ARTICLES,
Status,
DuplicateStatus,
)
from sabnzbd.misc import (
to_units,
@@ -59,6 +59,7 @@ from sabnzbd.misc import (
caller_name,
opts_to_pp,
pp_to_opts,
duplicate_warning,
)
from sabnzbd.filesystem import (
sanitize_foldername,
@@ -79,6 +80,14 @@ from sabnzbd.filesystem import (
is_valid_script,
has_unwanted_extension,
create_all_dirs,
get_basename,
backup_exists,
get_new_id,
save_data,
load_data,
save_compressed,
backup_nzb,
remove_data,
)
from sabnzbd.par2file import FilePar2Info
from sabnzbd.decorators import synchronized
@@ -228,7 +237,7 @@ class Article(TryList):
def get_art_id(self):
"""Return unique article storage name, create if needed"""
if not self.art_id:
self.art_id = sabnzbd.filesystem.get_new_id("article", self.nzf.nzo.admin_path)
self.art_id = get_new_id("article", self.nzf.nzo.admin_path)
return self.art_id
def search_new_server(self):
@@ -342,7 +351,7 @@ class NzbFile(TryList):
self.bytes_left: int = file_bytes
self.nzo: NzbObject = nzo
self.nzf_id: str = sabnzbd.filesystem.get_new_id("nzf", nzo.admin_path)
self.nzf_id: str = get_new_id("nzf", nzo.admin_path)
self.deleted = False
self.import_finished = False
@@ -366,7 +375,7 @@ class NzbFile(TryList):
# Any articles left?
if raw_article_db:
# Save the rest
sabnzbd.filesystem.save_data(raw_article_db, self.nzf_id, nzo.admin_path)
save_data(raw_article_db, self.nzf_id, nzo.admin_path)
else:
# All imported
self.import_finished = True
@@ -374,12 +383,7 @@ class NzbFile(TryList):
def finish_import(self):
"""Load the article objects from disk"""
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)]
if raw_article_db := load_data(self.nzf_id, self.nzo.admin_path, remove=False):
for raw_article in raw_article_db:
self.add_article(raw_article)
@@ -475,10 +479,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.
@@ -513,9 +513,10 @@ class NzbRejected(Exception):
pass
class NzbRejectedToHistory(Exception):
def __init__(self, nzo_id: str):
self.nzo_id = nzo_id
class NzbRejectToHistory(Exception):
def __init__(self, nzo, fail_msg):
self.nzo: NzbObject = nzo
self.nzo.fail_msg = fail_msg
super().__init__()
@@ -538,7 +539,6 @@ NzbObjectSaver = (
"avg_date",
"md5of16k",
"extrapars",
"md5packs",
"par2packs",
"files",
"files_table",
@@ -563,6 +563,7 @@ NzbObjectSaver = (
"encrypted",
"bad_articles",
"duplicate",
"duplicate_series_key",
"oversized",
"precheck",
"incomplete",
@@ -594,6 +595,7 @@ class NzbObject(TryList):
cat: Optional[str] = None,
url: Optional[str] = None,
priority: Optional[Union[int, str]] = DEFAULT_PRIORITY,
password: Optional[str] = None,
nzbname: Optional[str] = None,
status: str = Status.QUEUED,
nzo_info: Optional[Dict[str, Any]] = None,
@@ -602,30 +604,29 @@ class NzbObject(TryList):
dup_check: bool = True,
):
super().__init__()
# Use original filename as basis
self.work_name = self.filename = filename
self.filename = filename # Original filename
if nzbname and nzb_fp:
self.work_name = nzbname # Use nzbname if set and only for non-future slot
else:
self.work_name = filename
# User defined job name
if nzbname:
self.final_name = self.work_name = nzbname
# For future-slots we keep the name given by URLGrabber
if nzb_fp is None:
self.final_name = self.work_name = filename
else:
# Remove trailing .nzb and .par(2)
self.work_name = create_work_name(self.work_name)
# Extract password if not explicitly set, also on URL-fetches which might have a custom name with password
self.password = password
if not self.password:
# Extract before create_work_name, as it would escape the "/" on Windows
self.work_name, self.password = scan_password(self.work_name)
# Extract password
self.work_name, self.password = scan_password(self.work_name)
if not self.work_name:
# In case only /password was entered for nzbname
self.work_name = filename
# Check for password also in filename
if not self.password:
_, self.password = scan_password(get_basename(filename))
# Remove trailing .nzb/.par(2) and sanitize
self.work_name = create_work_name(self.work_name)
self.final_name = self.work_name
# Check for password also in filename
if not self.password:
_, self.password = scan_password(os.path.splitext(filename)[0])
# Temporary store for custom job name for after URL-fetching
self.custom_name = nzbname
# Create a record of the input for pp, script, and priority
input_pp = pp
@@ -668,7 +669,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)
@@ -681,19 +681,21 @@ class NzbObject(TryList):
# The current status of the nzo eg:
# Queued, Downloading, Repairing, Unpacking, Failed, Complete
self.status: str = status
self.avg_bps_freq = 0
self.avg_bps_total = 0
self.first_articles: List[Article] = []
self.first_articles_count = 0
self.saved_articles: Set[Article] = set()
self.nzo_id: Optional[str] = None
self.duplicate: Optional[str] = None
self.duplicate_series_key: Optional[str] = None
self.futuretype = futuretype
self.removed_from_queue = False
self.to_be_removed = False
self.duplicate = False
self.oversized = False
self.precheck = False
self.incomplete = False
@@ -713,9 +715,6 @@ class NzbObject(TryList):
# Stores various info about the nzo to be
self.nzo_info: Dict[str, Any] = nzo_info or {}
# Temporary store for custom foldername - needs to be stored because of url fetching
self.custom_name = nzbname
self.next_save = None
self.save_timeout = None
self.encrypted = 0
@@ -727,9 +726,14 @@ class NzbObject(TryList):
# Path is empty in case of a future NZB
self.download_path = ""
# This is a slot for a future NZB, ready now
# It can also be a retry of a failed job with no extra NZB-file
if nzb_fp is None and not reuse:
# This is a slot for a future NZB, ready now
# It can also be a retry of a failed job with no extra NZB-file
# For future NZB, check if we don't already have this in the queue or history
# based on the custom name supplied by the user or the RSS feed
if self.custom_name and dup_check:
self.duplicate_check()
self.handle_duplicate_action()
return
# Re-use existing nzo_id, when a "future" job gets it payload
@@ -737,9 +741,6 @@ class NzbObject(TryList):
self.nzo_id = nzo_id
sabnzbd.NzbQueue.remove(nzo_id, delete_all_data=False)
# To be updated later if it's a duplicate
duplicate = series_duplicate = False
# Apply conversion option to final folder
if cfg.replace_spaces():
logging.info("Replacing spaces with underscores in %s", self.final_name)
@@ -775,7 +776,7 @@ class NzbObject(TryList):
remove_all(admin_dir, "SABnzbd_article_*", keep_folder=True)
if nzb_fp:
full_nzb_path = sabnzbd.filesystem.save_compressed(admin_dir, filename, nzb_fp)
full_nzb_path = save_compressed(admin_dir, filename, nzb_fp)
try:
sabnzbd.nzbparser.nzbfile_parser(full_nzb_path, self)
except Exception as err:
@@ -793,11 +794,11 @@ class NzbObject(TryList):
# Check against identical checksum or series/season/episode if not repair
# Have to check for duplicate before saving the backup, as it will
# trigger the duplicate-detection based on the backup
if not reuse and dup_check and self.priority != REPAIR_PRIORITY:
duplicate, series_duplicate = self.has_duplicates()
if not reuse and dup_check and not self.duplicate and self.priority != REPAIR_PRIORITY:
self.duplicate_check()
# Copy to backup
sabnzbd.filesystem.backup_nzb(full_nzb_path)
backup_nzb(full_nzb_path)
if not self.files and not reuse:
self.purge_data()
@@ -825,61 +826,15 @@ class NzbObject(TryList):
# Determine category and find pp/script values
self.cat, pp_tmp, self.script, priority = cat_to_opts(cat, pp, script, self.priority)
self.set_priority(priority)
self.repair, self.unpack, self.delete = pp_to_opts(pp_tmp)
self.set_pp(pp_tmp)
# Show first meta-password (if any), when there's no explicit password
if not self.password and self.meta.get("password"):
self.password = self.meta.get("password", [None])[0]
# Run user pre-queue script if set and valid
if not reuse and make_script_path(cfg.pre_script()):
# Call the script
accept, name, pp, cat_pp, script_pp, priority, group = sabnzbd.newsunpack.pre_queue(self, pp, cat)
if cat_pp:
# An explicit pp/script/priority set upon adding the job takes precedence
# over an implicit setting based on the category set by pre-queue
if input_priority and not priority:
priority = input_priority
if input_pp and not pp:
pp = input_pp
if input_script and not script_pp:
script_pp = input_script
# Accept or reject
accept = int_conv(accept)
if accept < 1:
self.purge_data()
raise NzbRejected
if accept == 2:
self.fail_msg = T("Pre-queue script marked job as failed")
# Process all options, only over-write if set by script
# Beware that cannot do "if priority/pp", because those can
# also have a valid value of 0, which shouldn't be ignored
if name:
self.set_final_name_and_scan_password(name)
try:
pp = int(pp)
except:
pp = None
if cat_pp:
cat = cat_pp
try:
priority = int(priority)
except:
priority = DEFAULT_PRIORITY
if script_pp and is_valid_script(script_pp):
script = script_pp
if group:
self.groups = [str(group)]
# Re-evaluate results from pre-queue script
self.cat, pp, self.script, priority = cat_to_opts(cat, pp, script, priority)
self.set_priority(priority)
self.repair, self.unpack, self.delete = pp_to_opts(pp)
else:
accept = 1
# Run user pre-queue script
if not reuse:
self.run_pre_queue(input_priority, input_pp, input_script)
# Pause if requested by the NZB-adding or the pre-queue script
if self.priority == PAUSED_PRIORITY:
@@ -894,57 +849,22 @@ class NzbObject(TryList):
self.oversized = True
self.priority = LOW_PRIORITY
# If the job is forced in any way, skip duplicate check
if self.priority == FORCE_PRIORITY:
duplicate = series_duplicate = False
# Handle duplicates
if duplicate and (
(not series_duplicate and cfg.no_dupes() == 1) or (series_duplicate and cfg.no_series_dupes() == 1)
):
if cfg.warn_dupl_jobs():
logging.warning(T('Ignoring duplicate NZB "%s"'), filename)
self.purge_data()
raise NzbRejected
if duplicate and (
(not series_duplicate and cfg.no_dupes() == 3) or (series_duplicate and cfg.no_series_dupes() == 3)
):
if cfg.warn_dupl_jobs():
logging.warning(T('Failing duplicate NZB "%s"'), filename)
# Move to history, utilizing the same code as accept&fail from pre-queue script
self.fail_msg = T("Duplicate NZB")
accept = 2
duplicate = False
if duplicate or self.priority == DUP_PRIORITY:
self.duplicate = True
if cfg.no_dupes() == 4 or cfg.no_series_dupes() == 4:
if cfg.warn_dupl_jobs():
logging.warning('%s: "%s"', T("Duplicate NZB"), filename)
else:
if cfg.warn_dupl_jobs():
logging.warning(T('Pausing duplicate NZB "%s"'), filename)
self.pause()
# Only change priority if it's currently set to duplicate, otherwise keep original one
if self.priority == DUP_PRIORITY:
self.set_stateless_priority(self.cat)
# Take action on the duplicate status
self.handle_duplicate_action()
# Check if there is any unwanted extension in plain sight in the NZB itself
for nzf in self.files:
if cfg.action_on_unwanted_extensions() and has_unwanted_extension(nzf.filename):
# ... we found an unwanted extension
logging.warning(T("Unwanted Extension in file %s (%s)"), nzf.filename, self.final_name)
# Pause, or Abort:
if cfg.action_on_unwanted_extensions() == 1:
logging.debug("Unwanted extension ... pausing")
self.unwanted_ext = 1
self.pause()
if cfg.action_on_unwanted_extensions() == 2:
logging.debug("Unwanted extension ... aborting")
self.fail_msg = T("Aborted, unwanted extension detected")
accept = 2
if cfg.action_on_unwanted_extensions():
for nzf in self.files:
if has_unwanted_extension(nzf.filename):
logging.warning(T("Unwanted Extension in file %s (%s)"), nzf.filename, self.final_name)
# Pause, or Abort:
if cfg.action_on_unwanted_extensions() == 1:
logging.debug("Unwanted extension ... pausing")
self.unwanted_ext = 1
self.pause()
if cfg.action_on_unwanted_extensions() == 2:
logging.debug("Unwanted extension ... aborting")
raise NzbRejectToHistory(self, T("Aborted, unwanted extension detected"))
if reuse:
self.check_existing_files(self.download_path)
@@ -961,14 +881,6 @@ class NzbObject(TryList):
# Set nzo save-delay to minimum 120 seconds
self.save_timeout = max(120, min(6.0 * self.bytes / GIGI, 300.0))
# In case pre-queue script or duplicate check want to move
# to history we first need a nzo_id by entering the NzbQueue
if accept == 2:
sabnzbd.NzbQueue.add(self, quiet=True)
sabnzbd.NzbQueue.end_job(self)
# Raise error, so it's not added
raise NzbRejectedToHistory(nzo_id=self.nzo_id)
def update_download_stats(self, bps, serverid, bytes_received):
if bps:
self.avg_bps_total += bps / 1024
@@ -1265,7 +1177,7 @@ class NzbObject(TryList):
existing_files = globber(wdir, "*.*")
# Substitute renamed files
if renames := sabnzbd.filesystem.load_data(RENAMES_FILE, self.admin_path, remove=True):
if renames := load_data(RENAMES_FILE, self.admin_path, remove=True):
for name in renames:
if name in existing_files or renames[name] in existing_files:
if name in existing_files:
@@ -1359,7 +1271,6 @@ class NzbObject(TryList):
LOW_PRIORITY,
DEFAULT_PRIORITY,
PAUSED_PRIORITY,
DUP_PRIORITY,
STOP_PRIORITY,
):
self.priority = value
@@ -1379,7 +1290,7 @@ class NzbObject(TryList):
for cat in cat_options:
prio = cat_to_opts(cat)[3]
if prio not in (DUP_PRIORITY, PAUSED_PRIORITY, FORCE_PRIORITY):
if prio not in (PAUSED_PRIORITY, FORCE_PRIORITY):
self.priority = prio
break
else:
@@ -1389,8 +1300,10 @@ class NzbObject(TryList):
def labels(self):
"""Return (translated) labels of job"""
labels = []
if self.duplicate:
if self.duplicate in (DuplicateStatus.DUPLICATE, DuplicateStatus.SERIES_DUPLICATE):
labels.append(T("DUPLICATE"))
if self.duplicate in (DuplicateStatus.DUPLICATE_ALTERNATIVE, DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE):
labels.append(T("ALTERNATIVE"))
if self.encrypted > 0:
labels.append(T("ENCRYPTED"))
if self.oversized:
@@ -1456,7 +1369,7 @@ class NzbObject(TryList):
# If user resumes after encryption warning, no more auto-pauses
self.encrypted = 2
# If user resumes after warning, reset duplicate/oversized/incomplete/unwanted indicators
self.duplicate = False
self.duplicate = None
self.oversized = False
self.incomplete = False
if self.unwanted_ext:
@@ -1516,7 +1429,8 @@ class NzbObject(TryList):
def abort_direct_unpacker(self):
"""Abort any running DirectUnpackers"""
if self.direct_unpacker:
# During nzo creation the property doesn't exist yet
if hasattr(self, "direct_unpacker") and self.direct_unpacker:
self.direct_unpacker.abort()
def check_availability_ratio(self):
@@ -1833,14 +1747,14 @@ class NzbObject(TryList):
# Delete all, or just basic files
if self.futuretype:
# Remove temporary file left from URL-fetches
sabnzbd.filesystem.remove_data(self.nzo_id, self.admin_path)
remove_data(self.nzo_id, self.admin_path)
elif delete_all_data:
remove_all(self.download_path, recursive=True)
else:
# We remove any saved articles and save the renames file
remove_all(self.download_path, "SABnzbd_nz?_*", keep_folder=True)
remove_all(self.download_path, "SABnzbd_article_*", keep_folder=True)
sabnzbd.filesystem.save_data(self.renames, RENAMES_FILE, self.admin_path, silent=True)
save_data(self.renames, RENAMES_FILE, self.admin_path, silent=True)
def get_nzf_by_id(self, nzf_id: str) -> NzbFile:
if nzf_id in self.files_table:
@@ -1879,7 +1793,7 @@ class NzbObject(TryList):
"""Save job's admin to disk"""
self.save_attribs()
if self.nzo_id and not self.removed_from_queue:
sabnzbd.filesystem.save_data(self, self.nzo_id, self.admin_path)
save_data(self, self.nzo_id, self.admin_path)
def save_attribs(self):
"""Save specific attributes for Retry"""
@@ -1887,11 +1801,11 @@ class NzbObject(TryList):
for attrib in NzoAttributeSaver:
attribs[attrib] = getattr(self, attrib)
logging.debug("Saving attributes %s for %s", attribs, self.final_name)
sabnzbd.filesystem.save_data(attribs, ATTRIB_FILE, self.admin_path, silent=True)
save_data(attribs, ATTRIB_FILE, self.admin_path, silent=True)
def load_attribs(self) -> Tuple[Optional[str], Optional[int], Optional[str]]:
"""Load saved attributes and return them to be parsed"""
attribs = sabnzbd.filesystem.load_data(ATTRIB_FILE, self.admin_path, remove=False)
attribs = load_data(ATTRIB_FILE, self.admin_path, remove=False)
logging.debug("Loaded attributes %s for %s", attribs, self.final_name)
# If attributes file somehow does not exist
@@ -1927,56 +1841,191 @@ class NzbObject(TryList):
else:
nzf_ids.remove(nzf_id)
def has_duplicates(self) -> Tuple[bool, bool]:
"""Return (res, series)
where "res" is True when this is a duplicate
where "series" is True when this is an episode
"""
no_dupes = cfg.no_dupes()
no_series_dupes = cfg.no_series_dupes()
series_propercheck = cfg.series_propercheck()
# Abort if dupe check is off for both nzb and series
if not no_dupes and not no_series_dupes:
return False, False
series = False
res = False
with HistoryDB() as history_db:
# Dupe check off nzb contents
if no_dupes:
res = history_db.have_name_or_md5sum(self.final_name, self.md5sum)
logging.debug(
"Duplicate checked NZB in history: filename=%s, md5sum=%s, result=%s",
self.filename,
self.md5sum,
res,
)
if not res and cfg.backup_for_duplicates():
res = sabnzbd.filesystem.backup_exists(self.filename)
logging.debug("Duplicate checked NZB against backup: filename=%s, result=%s", self.filename, res)
# Dupe check off nzb filename
if not res and no_series_dupes:
show_analysis = sabnzbd.newsunpack.analyse_show(self.final_name)
def set_duplicate_series_key(self):
"""Shorthand to set the key once"""
if not self.duplicate_series_key:
show_analysis = sabnzbd.sorting.analyse_show(self.final_name)
if show_analysis["job_type"] == "tv":
series, season, episode, is_proper = (
show_analysis[key] for key in ("title", "season", "episode", "is_proper")
)
if is_proper and series_propercheck:
logging.debug("Dupe checking series+season+ep in history aborted due to PROPER/REAL/REPACK found")
else:
res = history_db.have_episode(series, season, episode)
logging.debug(
"Dupe checking series+season+ep in history: series=%s, season=%s, episode=%s, result=%s",
series,
season,
episode,
res,
)
# Ignore proper results if not desired
if not cfg.series_propercheck():
is_proper = False
return res, series
# We allow 1 proper result to bypass duplicate detection
self.duplicate_series_key = f"{series.lower()}/{season}/{episode}{f'/{is_proper}' if is_proper else ''}"
def duplicate_check(self):
"""Set the correct duplicate status"""
if not cfg.no_dupes() and not cfg.no_series_dupes():
return
duplicate_in_history = series_duplicate_in_history = False
duplicate_in_queue = series_duplicate_in_queue = False
with HistoryDB() as history_db:
# Dupe check off just name or nzb contents
if cfg.no_dupes():
logging.debug("Duplicate checking NZB %s (md5sum=%s)", self.final_name, self.md5sum)
duplicate_in_history = history_db.have_name_or_md5sum(self.final_name, self.md5sum)
logging.debug("Duplicate in history: %s", duplicate_in_history)
if not duplicate_in_history and cfg.backup_for_duplicates():
duplicate_in_history = backup_exists(self.filename)
logging.debug("Duplicate in backup: %s", duplicate_in_history)
duplicate_in_queue = sabnzbd.NzbQueue.have_name_or_md5sum(self.final_name, self.md5sum)
logging.debug("Duplicate in queue: %s", duplicate_in_queue)
# Dupe check off nzb filename
if not duplicate_in_history and not duplicate_in_queue and cfg.no_series_dupes():
logging.debug("Duplicate episode checking (%s): %s", self.final_name, self.duplicate_series_key)
self.set_duplicate_series_key()
if self.duplicate_series_key:
series_duplicate_in_history = history_db.have_episode(self.duplicate_series_key)
logging.debug("Duplicate episode in history: %s", series_duplicate_in_history)
series_duplicate_in_queue = sabnzbd.NzbQueue.have_episode(self.duplicate_series_key)
logging.debug("Duplicate episode in queue: %s", series_duplicate_in_queue)
else:
logging.debug("Not an episode, skipping duplicate episode check")
# Set the correct status
if series_duplicate_in_queue:
self.duplicate = DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE
elif duplicate_in_queue:
self.duplicate = DuplicateStatus.DUPLICATE_ALTERNATIVE
elif series_duplicate_in_history:
self.duplicate = DuplicateStatus.SERIES_DUPLICATE
elif duplicate_in_history:
self.duplicate = DuplicateStatus.DUPLICATE
def handle_duplicate_action(self):
"""Handle duplicate detection action"""
# If the job is set Force in any way, ignore results of duplicate check
if self.priority == FORCE_PRIORITY:
self.duplicate = None
# Take a direct action
# 1 = Discard
# 2 = Pause
# 3 = Fail (move to History)
# 4 = Tag
if self.duplicate in (DuplicateStatus.DUPLICATE, DuplicateStatus.SERIES_DUPLICATE):
series_duplicate = self.duplicate == DuplicateStatus.SERIES_DUPLICATE
if (not series_duplicate and cfg.no_dupes() == 1) or (series_duplicate and cfg.no_series_dupes() == 1):
# Discard
duplicate_warning(T('Ignoring duplicate NZB "%s"'), self.final_name)
self.purge_data()
raise NzbRejected
elif (not series_duplicate and cfg.no_dupes() == 3) or (series_duplicate and cfg.no_series_dupes() == 3):
# Fail (move to History)
duplicate_warning(T('Failing duplicate NZB "%s"'), self.final_name)
raise NzbRejectToHistory(self, T("Duplicate NZB"))
elif (not series_duplicate and cfg.no_dupes() == 2) or (series_duplicate and cfg.no_series_dupes() == 2):
# Pause
duplicate_warning(T('Pausing duplicate NZB "%s"'), self.final_name)
self.pause()
else:
# Tag job
duplicate_warning('%s: "%s"', T("Duplicate NZB"), self.final_name)
# In case of alternative, just pause
if self.duplicate in (DuplicateStatus.DUPLICATE_ALTERNATIVE, DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE):
logging.info("Pausing duplicate alternative %s", self.final_name)
self.pause()
def run_pre_queue(
self,
input_priority: Optional[Union[int, str]],
input_pp: Optional[int],
input_script: Optional[str],
):
"""Run pre-queue script (if any) and process results."""
if script_path := make_script_path(cfg.pre_script()):
def fix_parameter(parameter: Any) -> str:
# If added via API, some items can still be "None" (as a string)
if not parameter or str(parameter).lower() == "none":
return ""
return str(parameter)
# Basic parameters
command = [script_path, self.final_name_with_password, self.cat, self.priority, self.pp, self.script]
command = [fix_parameter(arg) for arg in command]
# Fields not in the NZO directly
extra_env_fields = sabnzbd.newsunpack.analyse_show(self.final_name_with_password)
extra_env_fields["groups"] = " ".join(self.groups)
try:
p = sabnzbd.newsunpack.build_and_run_command(
command, env=sabnzbd.newsunpack.create_env(self, extra_env_fields)
)
except:
logging.debug("Failed script %s, Traceback: ", script_path, exc_info=True)
return
output = p.stdout.read()
ret = p.wait()
logging.info("Pre-queue script returned %s and output=\n%s", ret, output)
if ret == 0:
# Base values
pq_cat = pq_pp = pq_script = pq_priority = None
for index, line in enumerate(output.splitlines(), start=1):
# Make sure to keep this in line with the documentation!
# 1: Accept
# 2: Name
# 3: Category
# 4: Priority
# 5: Post-processing
# 6: Script
# 7: Duplicate
# 8: Duplicate key
if line := line.strip(" '\""):
if index == 1:
# Accept or reject
accept = int_conv(line)
if accept < 1:
logging.info("Pre-queue script refuses %s", self.final_name)
self.purge_data()
raise NzbRejected
if accept == 2:
logging.info("Pre-queue marking as failed %s", self.final_name)
raise NzbRejectToHistory(self, T("Pre-queue script marked job as failed"))
logging.info("Pre-queue accepts %s", self.final_name)
elif index == 2:
self.set_final_name_and_scan_password(line)
elif index == 3:
pq_cat = line
elif index == 4:
pq_priority = int_conv(line, default=DEFAULT_PRIORITY)
elif index == 5:
pq_pp = int_conv(line, default=None)
elif index == 6:
if is_valid_script(line):
pq_script = line
elif index == 7:
self.duplicate = line
elif index == 8:
self.duplicate_series_key = line
if pq_cat:
# An explicit pp/script/priority set upon adding the job takes precedence
# over an implicit setting based on the category set by pre-queue
if input_priority and pq_priority is None:
pq_priority = input_priority
if input_pp and pq_pp is None:
pq_pp = input_pp
if input_script and not pq_script:
pq_script = input_script
# Re-evaluate results from pre-queue script
self.cat, pp, self.script, priority = cat_to_opts(pq_cat, pq_pp, pq_script, pq_priority)
self.set_priority(priority)
self.set_pp(pp)
def __getstate__(self):
"""Save to pickle file, selecting attributes"""
@@ -2003,27 +2052,8 @@ class NzbObject(TryList):
self.url_tries = 0
self.to_be_removed = False
self.direct_unpacker = None
if self.meta is None:
self.meta = {}
if self.servercount is None:
self.servercount = {}
if self.md5of16k is None:
self.md5of16k = {}
if self.renames is None:
self.renames = {}
if self.bad_articles is None:
self.bad_articles = 0
self.first_articles_count = 0
if self.bytes_missing is None:
self.bytes_missing = 0
if self.bytes_tried is None:
# Fill with old info
self.bytes_tried = 0
for nzf in self.finished_files:
# Emulate behavior of 1.0.x
self.bytes_tried += nzf.bytes
for nzf in self.files:
self.bytes_tried += nzf.bytes - nzf.bytes_left
# Attributes added since 3.0.0
if self.bytes_par2 is None:
self.bytes_par2 = 0
for nzf in self.files + self.finished_files:

View File

@@ -29,6 +29,7 @@ from typing import Dict, Optional, Tuple
from sabnzbd.constants import MEBI
from sabnzbd.encoding import correct_unknown_encoding
from sabnzbd.filesystem import get_basename
PROBABLY_PAR2_RE = re.compile(r"(.*)\.vol(\d*)[+\-](\d*)\.par2", re.I)
SCAN_LIMIT = 10 * MEBI
@@ -80,7 +81,7 @@ def analyse_par2(name: str, filepath: Optional[str] = None) -> Tuple[str, int, i
block = m.group(3)
else:
# Base-par2 file
setname = os.path.splitext(name)[0].strip()
setname = get_basename(name).strip()
# Could not parse the filename, need deep inspection
# We already know it's a par2 from the is_parfile
if filepath:

View File

@@ -39,7 +39,14 @@ from sabnzbd.newsunpack import (
is_sfv_file,
)
from threading import Thread
from sabnzbd.misc import on_cleanup_list, is_sample, helpful_warning
from sabnzbd.misc import (
on_cleanup_list,
is_sample,
helpful_warning,
history_updated,
change_queue_complete_action,
run_script,
)
from sabnzbd.filesystem import (
real_path,
get_unique_dir,
@@ -172,7 +179,7 @@ class PostProcessor(Thread):
else:
self.slow_queue.put(nzo)
self.save()
sabnzbd.misc.history_updated()
history_updated()
def remove(self, nzo: NzbObject):
"""Remove given nzo from the queue"""
@@ -181,7 +188,7 @@ class PostProcessor(Thread):
except:
pass
self.save()
sabnzbd.misc.history_updated()
history_updated()
def stop(self):
"""Stop thread after finishing running job"""
@@ -398,7 +405,7 @@ def process_job(nzo: NzbObject) -> bool:
return False
# If we don't need extra par2, we can disconnect
if sabnzbd.NzbQueue.actives(grabs=False) == 0 and cfg.autodisconnect():
if not sabnzbd.NzbQueue.actives(grabs=False) and cfg.autodisconnect():
# This was the last job, close server connections
sabnzbd.Downloader.disconnect()
@@ -593,6 +600,9 @@ def process_job(nzo: NzbObject) -> bool:
# Force error for empty result
all_ok = all_ok and not empty
# See if we need to start an alternative or remove the duplicates
sabnzbd.NzbQueue.handle_duplicate_alternatives(nzo, all_ok)
except:
logging.error(T("Post Processing Failed for %s (%s)"), filename, T("see logfile"))
logging.info("Traceback: ", exc_info=True)
@@ -658,7 +668,7 @@ def process_job(nzo: NzbObject) -> bool:
# Purge items
history_db.auto_history_purge()
sabnzbd.misc.history_updated()
history_updated()
return True
@@ -718,7 +728,8 @@ def prepare_extraction_path(nzo: NzbObject) -> Tuple[str, str, Sorter, bool, Opt
# Is the unique path different? Then we also need to modify the final path
if prefixed_path != tmp_workdir_complete:
workdir_complete = workdir_complete + os.path.splitext(tmp_workdir_complete)[1]
# The unique path adds an "extension"
workdir_complete = workdir_complete + get_ext(tmp_workdir_complete)
else:
tmp_workdir_complete = workdir_complete
@@ -1040,20 +1051,20 @@ def rar_renamer(nzo: NzbObject) -> int:
def handle_empty_queue():
"""Check if empty queue calls for action"""
if sabnzbd.NzbQueue.actives() == 0:
if not sabnzbd.NzbQueue.actives():
sabnzbd.save_state()
notifier.send_notification("SABnzbd", T("Queue finished"), "queue_done")
# Perform end-of-queue script
if cfg.end_queue_script():
logging.info("Queue has finished, launching script: %s ", cfg.end_queue_script())
run_script(cfg.end_queue_script())
# Perform end-of-queue action when one is set
if sabnzbd.QUEUECOMPLETEACTION:
logging.info(
"Queue has finished, launching: %s (%s)", sabnzbd.QUEUECOMPLETEACTION, sabnzbd.QUEUECOMPLETEARG
)
if sabnzbd.QUEUECOMPLETEARG:
sabnzbd.QUEUECOMPLETEACTION(sabnzbd.QUEUECOMPLETEARG)
else:
Thread(target=sabnzbd.QUEUECOMPLETEACTION).start()
sabnzbd.misc.change_queue_complete_action(cfg.queue_complete(), new=False)
logging.info("Queue has finished, launching action: %s ", sabnzbd.QUEUECOMPLETEACTION)
Thread(target=sabnzbd.QUEUECOMPLETEACTION).start()
change_queue_complete_action(cfg.queue_complete(), new=False)
# Trigger garbage collection and release of memory
logging.debug("Triggering garbage collection and release of memory")

View File

@@ -27,7 +27,7 @@ import threading
import urllib.parse
import sabnzbd
from sabnzbd.constants import RSS_FILE_NAME, DEFAULT_PRIORITY, DUP_PRIORITY
from sabnzbd.constants import RSS_FILE_NAME, DEFAULT_PRIORITY
from sabnzbd.decorators import synchronized
import sabnzbd.config as config
import sabnzbd.cfg as cfg
@@ -308,7 +308,7 @@ class RSSReader:
myPrio = defPrio
n = 0
if ("F" in reTypes or "S" in reTypes) and (not season or not episode):
show_analysis = sabnzbd.newsunpack.analyse_show(title)
show_analysis = sabnzbd.sorting.analyse_show(title)
season, episode = show_analysis["season"], show_analysis["episode"]
# Match against all filters until an positive or negative match
@@ -391,19 +391,6 @@ class RSSReader:
elif not ((rePrios[n] != str(DEFAULT_PRIORITY)) or category):
myPrio = catPrio
if cfg.no_dupes() and self.check_duplicate(title):
if cfg.no_dupes() == 1:
# Dupe-detection: Discard
logging.info("Ignoring duplicate job %s", title)
continue
elif cfg.no_dupes() == 3:
# Dupe-detection: Fail
# We accept it so the Queue can send it to the History
logging.info("Found duplicate job %s", title)
else:
# Dupe-detection: Pause
myPrio = DUP_PRIORITY
act = download and not first
if link in jobs:
act = act and not jobs[link].get("status", "").endswith("*")
@@ -546,18 +533,6 @@ class RSSReader:
if self.jobs[feed][item]["status"] == "D":
self.jobs[feed][item]["status"] = "D-"
def check_duplicate(self, title):
"""Check if this title was in this or other feeds
Return matching feed name
"""
title = title.lower()
for fd in self.jobs:
for lk in self.jobs[fd]:
item = self.jobs[fd][lk]
if item.get("status", " ")[0] == "D" and item.get("title", "").lower() == title:
return fd
return ""
def patch_feedparser():
"""Apply options that work for SABnzbd

View File

@@ -169,7 +169,6 @@ SKIN_TEXT = {
"mode": TT("Processing"), #: Queue page table column header
"name": TT("Name"), #: Queue page table column header
"button-retry": TT("Retry"), #: Queue page button
"eoq-actions": TT("Actions"), #: Queue end-of-queue selection box
"eoq-scripts": TT("Scripts"), #: Queue page table, script selection menu
"purgeQueue": TT("Purge Queue"), #: Queue page button
"purgeQueueConf": TT("Delete all items from the queue?"), #: Confirmation popup
@@ -453,6 +452,8 @@ SKIN_TEXT = {
),
"opt-pre_script": TT("Pre-queue user script"),
"explain-pre_script": TT("Used before an NZB enters the queue."),
"opt-end_queue_script": TT("On queue finish script"),
"explain-end_queue_script": TT("Executed after the queue finishes downloading."),
"opt-par_option": TT("Extra PAR2 Parameters"),
"explain-par_option": TT("Read the Wiki Help on this!"),
"opt-nice": TT("Nice Parameters"),

View File

@@ -73,8 +73,8 @@ class Sorter:
self,
nzo: Optional[NzbObject],
job_name: str,
path: str,
cat: str,
path: Optional[str] = None,
cat: Optional[str] = None,
force: Optional[bool] = False,
sorter_config: Optional[dict] = None,
):
@@ -96,6 +96,9 @@ class Sorter:
self.is_season_pack = False
self.season_pack_setname = ""
self.match_sorters()
def match_sorters(self):
# If a sorter configuration is passed as an argument, only use that one
sorters = [self.sorter_config] if self.sorter_config else config.get_ordered_sorters()
@@ -260,7 +263,7 @@ class Sorter:
except TypeError:
pass
def is_proper(self):
def is_proper(self) -> bool:
"""Determine if the release is tagged 'Proper'. Note that guessit also sets this for similar
tags such as 'Real' and 'Repack', saving us the trouble of checking for additional keywords."""
if not self.guess:
@@ -582,6 +585,40 @@ class Sorter:
return move_to_parent_directory(base_path)
class SeriesAnalyzer(Sorter):
def __init__(self, job_name: str):
"""Very basic sorter that doesn't require a config"""
super().__init__(nzo=None, job_name=job_name)
def match_sorters(self):
"""Much more basic matching"""
self.guess = guess_what(self.original_job_name)
# Set the detected job type
self.type = self.guess["type"]
if self.guess["type"] == "episode":
self.type = "date" if self.guess.get("date") else "tv"
def analyse_show(job_name: str) -> Dict[str, str]:
"""Use the Sorter to collect some basic info on series"""
job = SeriesAnalyzer(job_name)
job.get_values()
return {
"title": job.info.get("title", ""),
"season": job.info.get("season_num", ""),
"episode": job.info.get("episode_num", ""),
"episode_name": job.info.get("ep_name", ""),
"is_proper": job.is_proper(),
"resolution": job.info.get("resolution", ""),
"decade": job.info.get("decade", ""),
"year": job.info.get("year", ""),
"month": job.info.get("month", ""),
"day": job.info.get("day", ""),
"job_type": job.type,
}
def ends_in_file(path: str) -> bool:
"""Return True when path ends with '.%ext' or '%fn' while allowing for a lowercase marker"""
return bool(RE_ENDEXT.search(path) or RE_ENDFN.search(path))

View File

@@ -31,7 +31,7 @@ from http.client import IncompleteRead, HTTPResponse
from mailbox import Message
from threading import Thread
import base64
from typing import Tuple, Optional, Union
from typing import Tuple, Optional, Union, List
import sabnzbd
from sabnzbd.constants import DEF_TIMEOUT, FUTURE_Q_FOLDER, VALID_NZB_FILES, Status, VALID_ARCHIVES, DEFAULT_PRIORITY
@@ -42,7 +42,7 @@ import sabnzbd.emailer as emailer
import sabnzbd.notifier as notifier
from sabnzbd.encoding import ubtou, utob
from sabnzbd.nzbparser import AddNzbFileResult
from sabnzbd.nzbstuff import NzbObject
from sabnzbd.nzbstuff import NzbObject, NzbRejected, NzbRejectToHistory
class URLGrabber(Thread):
@@ -207,14 +207,6 @@ class URLGrabber(Thread):
# Sometimes the filename contains the full URL, duh!
filename = filename[filename.find("&nzbname=") + 9 :]
pp = future_nzo.pp
script = future_nzo.script
cat = future_nzo.cat
if (cat is None or cat == "*") and category:
cat = misc.cat_convert(category)
priority = future_nzo.priority
nzbname = future_nzo.custom_name
# process data
if not data:
try:
@@ -246,11 +238,11 @@ class URLGrabber(Thread):
if sabnzbd.filesystem.get_ext(filename) in VALID_ARCHIVES + VALID_NZB_FILES:
res, _ = sabnzbd.nzbparser.add_nzbfile(
path,
pp=pp,
script=script,
cat=cat,
priority=priority,
nzbname=nzbname,
pp=future_nzo.pp,
script=future_nzo.script,
cat=future_nzo.cat,
priority=future_nzo.priority,
nzbname=future_nzo.custom_name,
nzo_info=nzo_info,
url=future_nzo.url,
keep=False,
@@ -313,8 +305,7 @@ class URLGrabber(Thread):
nzo.cat, _, nzo.script, _ = misc.cat_to_opts(nzo.cat, script=nzo.script)
# Add to history and run script if desired
sabnzbd.NzbQueue.remove(nzo.nzo_id)
sabnzbd.PostProcessor.process(nzo)
sabnzbd.NzbQueue.fail_to_history(nzo)
def _build_request(url: str) -> HTTPResponse:
@@ -385,33 +376,46 @@ def add_url(
pp: Optional[Union[int, str]] = None,
script: Optional[str] = None,
cat: Optional[str] = None,
priority: Optional[Union[int, str]] = DEFAULT_PRIORITY,
priority: Optional[Union[int, str]] = None,
nzbname: Optional[str] = None,
password: Optional[str] = None,
):
dup_check: bool = True,
) -> Tuple[AddNzbFileResult, List[str]]:
"""Add NZB based on a URL, attributes optional"""
if not url.lower().startswith("http"):
return
if not pp or pp == "-1":
pp = None
if script and script.lower() == "default":
script = None
if cat and cat.lower() == "default":
cat = None
logging.info("Fetching %s", url)
# Add feed name if it came from RSS
msg = T("Trying to fetch NZB from %s") % url
if nzbname:
msg = "%s - %s" % (nzbname, msg)
return AddNzbFileResult.NO_FILES_FOUND, []
# Generate the placeholder
future_nzo = sabnzbd.NzbQueue.generate_future(msg, pp, script, cat, url=url, priority=priority, nzbname=nzbname)
logging.debug("Creating placeholder NZO for %s", url)
msg = T("Trying to fetch NZB from %s") % url
result: AddNzbFileResult = AddNzbFileResult.OK
future_nzo = None
nzo_ids = []
try:
future_nzo = NzbObject(
filename=msg,
pp=pp,
script=script,
futuretype=True,
cat=cat,
url=url,
priority=priority,
password=password,
nzbname=nzbname,
status=Status.GRABBING,
dup_check=dup_check,
)
except NzbRejected:
# Rejected as duplicate
result = AddNzbFileResult.ERROR
except NzbRejectToHistory as err:
# Duplicate directed to history
sabnzbd.NzbQueue.fail_to_history(err.nzo)
nzo_ids.append(err.nzo.nzo_id)
# Set password
if not future_nzo.password:
future_nzo.password = password
# Success
if future_nzo:
nzo_ids.append(sabnzbd.NzbQueue.add(future_nzo))
sabnzbd.URLGrabber.add(url, future_nzo)
# Get it!
sabnzbd.URLGrabber.add(url, future_nzo)
return future_nzo.nzo_id
return result, nzo_ids

View File

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

View File

@@ -86,6 +86,7 @@ def test_nntp_server_dict(kwargs):
return False, T("Invalid server details")
try:
s.request_addrinfo_blocking()
nw = NewsWrapper(server=s, thrdnum=-1, block=True)
nw.init_connect()
while not nw.connected:
@@ -102,16 +103,8 @@ def test_nntp_server_dict(kwargs):
# Trying SSL on non-SSL port?
if match_str(str(err), ("unknown protocol", "wrong version number")):
return False, T("Unknown SSL protocol: Try disabling SSL or connecting on a different port.")
return False, str(err)
except TypeError:
return False, T("Invalid server address.")
except IndexError:
# No data was received in recv_chunk() call
return False, T("Server quit during login sequence.")
except NNTPPermanentError:
# Handled by the code below
pass

Some files were not shown because too many files have changed in this diff Show More