mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-31 03:30:03 -05:00
Compare commits
64 Commits
4.2.0Alpha
...
feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bd39e4c12 | ||
|
|
987032b384 | ||
|
|
d516cbf363 | ||
|
|
824274ac5e | ||
|
|
82b1c784f4 | ||
|
|
232512b860 | ||
|
|
223fa421c7 | ||
|
|
2e5e72bfcf | ||
|
|
9bdb986382 | ||
|
|
901ff30e11 | ||
|
|
5e04599212 | ||
|
|
d3c9b7ead3 | ||
|
|
361770c34b | ||
|
|
5168f3fa97 | ||
|
|
94d307e198 | ||
|
|
eba6236ad2 | ||
|
|
d0128bd989 | ||
|
|
fbd7c0ec36 | ||
|
|
55abac97ea | ||
|
|
740b94170e | ||
|
|
c6a1a09213 | ||
|
|
cd84d52398 | ||
|
|
cdbad1b397 | ||
|
|
67e227008a | ||
|
|
23cf43cac5 | ||
|
|
62a057dbfb | ||
|
|
f2ff9ae557 | ||
|
|
9ed4e46919 | ||
|
|
faa71bae40 | ||
|
|
bbd5d2cd6d | ||
|
|
221e135c07 | ||
|
|
956904c0b3 | ||
|
|
8590481022 | ||
|
|
2171d0139e | ||
|
|
71d6aca9f8 | ||
|
|
0125e279c0 | ||
|
|
b8e46ccf10 | ||
|
|
787fef1c03 | ||
|
|
98b7a6171f | ||
|
|
210f254f63 | ||
|
|
ecdccda1ce | ||
|
|
ed66ac91e0 | ||
|
|
e571165c15 | ||
|
|
1513664b5f | ||
|
|
0132d81c43 | ||
|
|
8d32da8b27 | ||
|
|
b5fbc8af86 | ||
|
|
d0166b5a5c | ||
|
|
ada77d6970 | ||
|
|
9f8758b242 | ||
|
|
5ca629ebea | ||
|
|
f9f3820652 | ||
|
|
08e61ecf19 | ||
|
|
d15f0cafce | ||
|
|
1b85253940 | ||
|
|
b329ff007e | ||
|
|
f6918d598a | ||
|
|
0cdfdd82d4 | ||
|
|
de3649dba4 | ||
|
|
9ba975ac44 | ||
|
|
2b0ea92da8 | ||
|
|
b79a1e973d | ||
|
|
1be4cf986d | ||
|
|
18c4226b90 |
4
.github/renovate.json
vendored
4
.github/renovate.json
vendored
@@ -8,7 +8,6 @@
|
||||
"before 8am on Monday"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"tests/**",
|
||||
".github/workflows/**"
|
||||
],
|
||||
"pip_requirements": {
|
||||
@@ -22,7 +21,8 @@
|
||||
},
|
||||
"ignoreDeps": [
|
||||
"jaraco.text",
|
||||
"sabctools"
|
||||
"sabctools",
|
||||
"werkzeug"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
|
||||
20
.github/workflows/build_release.yml
vendored
20
.github/workflows/build_release.yml
vendored
@@ -63,8 +63,8 @@ jobs:
|
||||
run: |
|
||||
python --version
|
||||
python -m pip install --upgrade pip wheel
|
||||
pip install --upgrade -r requirements.txt
|
||||
pip install --upgrade -r builder/requirements.txt
|
||||
pip install --upgrade -r requirements.txt --no-dependencies
|
||||
pip install --upgrade -r builder/requirements.txt --no-dependencies
|
||||
- name: Build Windows standalone binary (32bit and legacy)
|
||||
run: python builder/package.py binary
|
||||
- name: Upload Windows standalone binary (32bit and legacy)
|
||||
@@ -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
|
||||
|
||||
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@@ -21,10 +21,10 @@ jobs:
|
||||
- name: Push/pull Transifex translations
|
||||
if: env.TX_TOKEN
|
||||
# Add --translation to the push command in order to update Transifex using local translation edits
|
||||
# However, this prevents modifying existing translations in Transifex as they will be overwritten by the push!
|
||||
# However, this prevents modifying existing translations in Transifex as they will be overwritten by the push!
|
||||
run: |
|
||||
curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash
|
||||
./tx push --source
|
||||
./tx push --source
|
||||
./tx pull --all --force
|
||||
- name: Compile translations to validate them
|
||||
run: |
|
||||
|
||||
14
README.mkd
14
README.mkd
@@ -1,17 +1,25 @@
|
||||
Release Notes - SABnzbd 4.2.0 Alpha 1
|
||||
Release Notes - SABnzbd 4.2.0 Alpha 2
|
||||
=========================================================
|
||||
|
||||
## Changes since 4.1.0
|
||||
- Numerous smaller performance improvements were made.
|
||||
- Windows: Removed `SABnzbd-console.exe`.
|
||||
If you need console logging, run `SABnzbd.exe --console`.
|
||||
- Reduced recursive unpacking to 2 levels, instead of 5.
|
||||
- IPv6 addresses are preferred during server address selection.
|
||||
- Stricter check if `Complete Folder` is inside `Download Folder`.
|
||||
- Windows: Reduced size of installer.
|
||||
- Windows/macOS: Updated to Python 3.12.
|
||||
|
||||
## Bugfixes since 4.1.0
|
||||
- Multi-select in the queue was broken for some users.
|
||||
- Prevent crash during saving of configuration.
|
||||
- Removing a failed download from the history could break active downloads.
|
||||
|
||||
## Upgrade notices
|
||||
- Direct upgrade is possible from version 3.0.0 and newer.
|
||||
Upgrading from older versions will require `Queue repair`.
|
||||
- Downgrading from version 4.2.0 or newer to 3.7.2 or older will
|
||||
require `Queue repair` due to changes in the internal data format.
|
||||
|
||||
## Known problems and solutions
|
||||
- Read the file "ISSUES.txt"
|
||||
|
||||
|
||||
20
SABnzbd.py
20
SABnzbd.py
@@ -860,7 +860,7 @@ def main():
|
||||
ipv6_hosting = None
|
||||
inet_exposure = None
|
||||
|
||||
service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
|
||||
_service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
|
||||
|
||||
for opt, arg in sab_opts:
|
||||
if opt == "--servicecall":
|
||||
@@ -949,8 +949,9 @@ def main():
|
||||
sabnzbd.DIR_LANGUAGE = real_path(sabnzbd.DIR_PROG, DEF_LANGUAGE)
|
||||
org_dir = os.getcwd()
|
||||
|
||||
# Need console logging if requested or just running as script
|
||||
console_logging = (console_logging or not hasattr(sys, "frozen")) and not sabnzbd.DAEMON
|
||||
# Need console logging if requested, for SABnzbd.py and SABnzbd-console.exe
|
||||
console_logging = console_logging or sys.executable.endswith("console.exe") or not hasattr(sys, "frozen")
|
||||
console_logging = console_logging and not sabnzbd.DAEMON
|
||||
|
||||
LOGLEVELS = (logging.FATAL, logging.WARNING, logging.INFO, logging.DEBUG)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -113,8 +113,7 @@ exe = EXE(
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name="SABnzbd",
|
||||
console=True,
|
||||
hide_console="hide-early",
|
||||
console=False,
|
||||
append_pkg=False,
|
||||
icon="icons/sabnzbd.ico",
|
||||
contents_directory=".",
|
||||
@@ -126,6 +125,29 @@ exe = EXE(
|
||||
|
||||
coll = COLLECT(exe, pyi_analysis.binaries, pyi_analysis.zipfiles, pyi_analysis.datas, name="SABnzbd")
|
||||
|
||||
# We need to run again for the console-app
|
||||
if sys.platform == "win32":
|
||||
# Enable console=True for this one
|
||||
console_exe = EXE(
|
||||
pyz,
|
||||
pyi_analysis.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name="SABnzbd-console",
|
||||
append_pkg=False,
|
||||
icon="icons/sabnzbd.ico",
|
||||
contents_directory=".",
|
||||
version=version_info,
|
||||
)
|
||||
|
||||
console_coll = COLLECT(
|
||||
console_exe,
|
||||
pyi_analysis.binaries,
|
||||
pyi_analysis.zipfiles,
|
||||
pyi_analysis.datas,
|
||||
name="SABnzbd-console",
|
||||
)
|
||||
|
||||
# Build the APP on macOS
|
||||
if sys.platform == "darwin":
|
||||
info_plist = {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Special requirements for macOS universal2 binary release
|
||||
# This way dependabot can auto-update them
|
||||
cryptography==41.0.4
|
||||
cryptography==41.0.5
|
||||
@@ -227,6 +227,9 @@ if __name__ == "__main__":
|
||||
# Run PyInstaller and check output
|
||||
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
|
||||
|
||||
shutil.copytree("dist/SABnzbd-console", "dist/SABnzbd", dirs_exist_ok=True)
|
||||
safe_remove("dist/SABnzbd-console")
|
||||
|
||||
# Remove unwanted DLL's
|
||||
shutil.rmtree("dist/SABnzbd/Pythonwin")
|
||||
if BUILDING_64BIT:
|
||||
|
||||
@@ -19,7 +19,6 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import github
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -359,10 +359,6 @@ tr.separator {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.Sorting .explain-sorting {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
.Sorting .explain-pattern {
|
||||
border: none;
|
||||
width: 100%;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")";
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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";
|
||||
})
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<url type="faq">https://sabnzbd.org/wiki/faq</url>
|
||||
<url type="contact">https://sabnzbd.org/live-chat.html</url>
|
||||
<releases>
|
||||
<release version="4.2.0" date="2023-11-26" type="stable"/>
|
||||
<release version="4.1.0" date="2023-09-26" type="stable"/>
|
||||
<release version="4.0.3" date="2023-06-16" type="stable"/>
|
||||
<release version="4.0.2" date="2023-06-09" type="stable"/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-4.2.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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 " 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 ""
|
||||
|
||||
@@ -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 " 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 ""
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Server løsning"
|
||||
msgid "Resolving address"
|
||||
msgstr "Server løsning"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -258,10 +258,6 @@ msgstr "Kvote brugt, pause downloading"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Fejl parameter"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC søgning \"%s\" er ikke tilladt her"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s er ikke en godkendt e-mail adresse"
|
||||
@@ -270,7 +266,7 @@ msgstr "%s er ikke en godkendt e-mail adresse"
|
||||
msgid "Server address required"
|
||||
msgstr "Kræver serveradresse"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Adresse wird aufgelöst …"
|
||||
msgid "Resolving address"
|
||||
msgstr "Adresse wird aufgelöst …"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -277,10 +277,6 @@ msgstr "Kontingent aufgebraucht, Downloads werden angehalten"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Fehlerhafter Parameter"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-Pfad \"%s\" ist hier nicht erlaubt"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s ist keine gültige E-Mail-Adresse"
|
||||
@@ -289,7 +285,7 @@ msgstr "%s ist keine gültige E-Mail-Adresse"
|
||||
msgid "Server address required"
|
||||
msgstr "Server-Adresse wird benötigt"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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"
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Resolviendo sitio"
|
||||
msgid "Resolving address"
|
||||
msgstr "Resolviendo sitio"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -273,10 +273,6 @@ msgstr "Quota gastado, pausando cola"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parámetro incorrecto"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "Ruta de acceso UNC \"%s\" no permitido aqui"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s no es una dirección de correo electrónico válida."
|
||||
@@ -285,7 +281,7 @@ msgstr "%s no es una dirección de correo electrónico válida."
|
||||
msgid "Server address required"
|
||||
msgstr "Se necesita la dirección del servidor"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Selvitetään osoitetta"
|
||||
msgid "Resolving address"
|
||||
msgstr "Selvitetään osoitetta"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -256,10 +256,6 @@ msgstr "Latausrajoitus saavutettu, keskeytetään lataukset"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Virheellinen parametri"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "TUNT polku \"%s\" ei ole sallittu"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s ei ole kelvollinen sähköpostiosoite"
|
||||
@@ -268,7 +264,7 @@ msgstr "%s ei ole kelvollinen sähköpostiosoite"
|
||||
msgid "Server address required"
|
||||
msgstr "Palvelimen osoite vaaditaan"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Résolution de l'adresse"
|
||||
msgid "Resolving address"
|
||||
msgstr "Résolution de l'adresse"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -275,10 +275,6 @@ msgstr "Quota atteint, téléchargement mis en pause"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Paramètre incorrect"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "Le chemin UNC \"%s\" n'est pas autorisé ici"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s n'est pas une adresse email valide"
|
||||
@@ -287,7 +283,7 @@ msgstr "%s n'est pas une adresse email valide"
|
||||
msgid "Server address required"
|
||||
msgstr "Adresse du serveur requise"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
100
po/main/he.po
100
po/main/he.po
@@ -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 " Resolving address"
|
||||
msgstr " פותר כתובת"
|
||||
msgid "Resolving address"
|
||||
msgstr "פותר כתובת"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -254,10 +254,6 @@ msgstr "מכסה נוצלה, משהה הורדה"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "פרמטר שגוי"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "נתיב UNC \"%s\" אינו מותר כאן"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s אינה כתובת דוא״ל תקפה"
|
||||
@@ -266,7 +262,7 @@ msgstr "%s אינה כתובת דוא״ל תקפה"
|
||||
msgid "Server address required"
|
||||
msgstr "כתובת שרת דרושה"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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 ".השרת דורש שם משתמש וסיסמה"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Løs adresse"
|
||||
msgid "Resolving address"
|
||||
msgstr "Løs adresse"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -252,10 +252,6 @@ msgstr "Kvote oppbrukt, setter nedlasting på pause"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Feil parameter"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-sti \"%s\" er ikke tillatt her"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s er ikke en godkjent e-post-adresse"
|
||||
@@ -264,7 +260,7 @@ msgstr "%s er ikke en godkjent e-post-adresse"
|
||||
msgid "Server address required"
|
||||
msgstr "Krever server-adresse"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Adres opzoeken"
|
||||
msgid "Resolving address"
|
||||
msgstr "Adres opzoeken"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -268,10 +268,6 @@ msgstr "Quotum verbruikt, download is gestopt"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Incorrecte parameter"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-pad '%s' hier niet toegestaan."
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s is geen geldig e-mailadres"
|
||||
@@ -280,7 +276,7 @@ msgstr "%s is geen geldig e-mailadres"
|
||||
msgid "Server address required"
|
||||
msgstr "Serveradres verplicht"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Rozwiązywanie adresu"
|
||||
msgid "Resolving address"
|
||||
msgstr "Rozwiązywanie adresu"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -248,10 +248,6 @@ msgstr "Przekroczono limit, wstrzymywanie pobierania"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Błędny parametr"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "Ścieżka UNC \"%s\" niedozwolona"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s nie jest prawidłowym adresem email"
|
||||
@@ -260,7 +256,7 @@ msgstr "%s nie jest prawidłowym adresem email"
|
||||
msgid "Server address required"
|
||||
msgstr "Wymagane jest podanie adresu serwera"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Resolvendo endereço"
|
||||
msgid "Resolving address"
|
||||
msgstr "Resolvendo endereço"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -252,10 +252,6 @@ msgstr "Quota esgotada, pausando o download"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parâmetro incorreto"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "O caminho UNC \"%s\" não é permitido aqui"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s não é um endereço de e-mail válido"
|
||||
@@ -264,7 +260,7 @@ msgstr "%s não é um endereço de e-mail válido"
|
||||
msgid "Server address required"
|
||||
msgstr "Endereço do servidor necessário"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Reolvare adresă"
|
||||
msgid "Resolving address"
|
||||
msgstr "Reolvare adresă"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -263,10 +263,6 @@ msgstr "Cotă epuizată, întrerupem descărcarea"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parametru Incorect"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "cale UNC \"%s\" nu este premisă aici"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s nu este o adresă email validă"
|
||||
@@ -275,7 +271,7 @@ msgstr "%s nu este o adresă email validă"
|
||||
msgid "Server address required"
|
||||
msgstr "Adresă server necesară"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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ă"
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Разрешение адреса"
|
||||
msgid "Resolving address"
|
||||
msgstr "Разрешение адреса"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -252,10 +252,6 @@ msgstr "Квота исчерпана. Загрузка приостановле
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Неправильный параметр"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-путь «%s» здесь не допускается"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s не является допустимым адресом электронной почты"
|
||||
@@ -264,7 +260,7 @@ msgstr "%s не является допустимым адресом элект
|
||||
msgid "Server address required"
|
||||
msgstr "Требуется адрес сервера"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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 "Для сервера требуется имя пользователя и пароль."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Решавање адресе"
|
||||
msgid "Resolving address"
|
||||
msgstr "Решавање адресе"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -250,10 +250,6 @@ msgstr "Kvota utrošena, pauziram preuzimanja"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Погрешан параметар"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC путања \"%s\" није дозвољена"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s nije ispravna email adresa"
|
||||
@@ -262,7 +258,7 @@ msgstr "%s nije ispravna email adresa"
|
||||
msgid "Server address required"
|
||||
msgstr "Потребна је адреса сервера"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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 "Серверу су потребни име и лозинка."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " Lösa adress"
|
||||
msgid "Resolving address"
|
||||
msgstr "Lösa adress"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -250,10 +250,6 @@ msgstr "Din kvot är uppnådd, pausar nerladdning"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Fel parameter"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC sökväg \"%s\" är inte tillåten här"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s är inte en godkänd e-mail adress"
|
||||
@@ -262,7 +258,7 @@ msgstr "%s är inte en godkänd e-mail adress"
|
||||
msgid "Server address required"
|
||||
msgstr "Kräver serveradress"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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."
|
||||
|
||||
@@ -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 " Resolving address"
|
||||
msgstr " 正在解析地址"
|
||||
msgid "Resolving address"
|
||||
msgstr "正在解析地址"
|
||||
|
||||
#. No value, used in dropdown menus
|
||||
#: sabnzbd/api.py, sabnzbd/skintext.py
|
||||
@@ -248,10 +248,6 @@ msgstr "配额已耗尽,暂停下载"
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "参数不正确"
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "此处不允许使用 UNC 路径 \"%s\""
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "%s is not a valid email address"
|
||||
msgstr "%s 不是有效的电子邮箱地址"
|
||||
@@ -260,7 +256,7 @@ msgstr "%s 不是有效的电子邮箱地址"
|
||||
msgid "Server address required"
|
||||
msgstr "服务器地址必填"
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/utils/servertests.py
|
||||
#: sabnzbd/cfg.py, sabnzbd/newswrapper.py
|
||||
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 "服务器需要用户名与密码。"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(" Resolving address").replace(" ", "")
|
||||
|
||||
server_info = {
|
||||
"servername": server.displayname,
|
||||
"serveractiveconn": connected,
|
||||
"serveractive": server.active,
|
||||
"serveractiveconn": activeconn,
|
||||
"servertotalconn": server.threads,
|
||||
"serverconnections": serverconnections,
|
||||
"serverssl": server.ssl,
|
||||
"serversslinfo": server.ssl_info,
|
||||
"serveractive": server.active,
|
||||
"serveripaddress": None,
|
||||
"servercanonname": None,
|
||||
"serverwarning": server.warning,
|
||||
"servererror": server.errormsg,
|
||||
"serverpriority": server.priority,
|
||||
"serveroptional": server.optional,
|
||||
"serverbps": to_units(sabnzbd.BPSMeter.server_bps.get(server.id, 0)),
|
||||
}
|
||||
|
||||
# Only add this information if we are connected
|
||||
if activeconn and server.addrinfo:
|
||||
server_info["serveripaddress"] = server.addrinfo.ipaddress
|
||||
server_info["servercanonname"] = server.addrinfo.canonname
|
||||
|
||||
if server.request and not server.addrinfo:
|
||||
server_info["serverwarning"] = T("Resolving address")
|
||||
|
||||
info["servers"].append(server_info)
|
||||
|
||||
return info
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
107
sabnzbd/cfg.py
107
sabnzbd/cfg.py
@@ -25,7 +25,7 @@ import re
|
||||
import argparse
|
||||
import socket
|
||||
import ipaddress
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.config import (
|
||||
@@ -51,7 +51,11 @@ from sabnzbd.constants import (
|
||||
DEF_HTTPS_CERT_FILE,
|
||||
DEF_HTTPS_KEY_FILE,
|
||||
)
|
||||
from sabnzbd.filesystem import long_path
|
||||
from sabnzbd.filesystem import same_directory, real_path
|
||||
|
||||
# Validators currently only are made for string/list-of-strings
|
||||
# and return those on success or an error message.
|
||||
ValidateResult = Union[Tuple[None, str], Tuple[None, List[str]], Tuple[str, None]]
|
||||
|
||||
|
||||
##############################################################################
|
||||
@@ -64,7 +68,7 @@ class ErrorCatchingArgumentParser(argparse.ArgumentParser):
|
||||
raise ValueError
|
||||
|
||||
|
||||
def clean_nice_ionice_parameters(value):
|
||||
def clean_nice_ionice_parameters(value: str) -> ValidateResult:
|
||||
"""Verify that the passed parameters are not exploits"""
|
||||
if value:
|
||||
parser = ErrorCatchingArgumentParser()
|
||||
@@ -87,30 +91,20 @@ def clean_nice_ionice_parameters(value):
|
||||
return None, value
|
||||
|
||||
|
||||
def all_lowercase(value):
|
||||
"""Lowercase everything!"""
|
||||
def all_lowercase(value: Union[str, List]) -> Tuple[None, Union[str, List]]:
|
||||
"""Lowercase and strip everything!"""
|
||||
if isinstance(value, list):
|
||||
# If list, for each item
|
||||
return None, [item.lower() for item in value]
|
||||
return None, value.lower()
|
||||
return None, [item.lower().strip() for item in value]
|
||||
return None, value.lower().strip()
|
||||
|
||||
|
||||
def lower_case_ext(value):
|
||||
def lower_case_ext(value: Union[str, List]) -> Tuple[None, Union[str, List]]:
|
||||
"""Generate lower case extension(s), without dot"""
|
||||
if isinstance(value, list):
|
||||
return None, [item.lower().strip(" .") for item in value]
|
||||
return None, value.lower().strip(" .")
|
||||
|
||||
|
||||
def validate_no_unc(root, value, default):
|
||||
"""Check if path isn't a UNC path"""
|
||||
# Only need to check the 'value' part
|
||||
if value and not value.startswith(r"\\"):
|
||||
return validate_notempty(root, value, default)
|
||||
else:
|
||||
return T('UNC path "%s" not allowed here') % value, None
|
||||
|
||||
|
||||
def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
|
||||
"""Don't split single indexer tags like "TV > HD"
|
||||
into ['TV', '>', 'HD']
|
||||
@@ -121,7 +115,7 @@ def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
|
||||
return None, value
|
||||
|
||||
|
||||
def validate_strip_right_slash(value):
|
||||
def validate_strip_right_slash(value: str) -> Tuple[None, str]:
|
||||
"""Strips the right slash"""
|
||||
if value:
|
||||
return None, value.rstrip("/")
|
||||
@@ -131,8 +125,7 @@ def validate_strip_right_slash(value):
|
||||
RE_VAL = re.compile(r"[^@ ]+@[^.@ ]+\.[^.@ ]")
|
||||
|
||||
|
||||
def validate_email(value):
|
||||
global email_endjob, email_full, email_rss
|
||||
def validate_email(value: Union[List, str]) -> ValidateResult:
|
||||
if email_endjob() or email_full() or email_rss():
|
||||
if isinstance(value, list):
|
||||
values = value
|
||||
@@ -144,18 +137,16 @@ def validate_email(value):
|
||||
return None, value
|
||||
|
||||
|
||||
def validate_server(value):
|
||||
def validate_server(value: str) -> ValidateResult:
|
||||
"""Check if server non-empty"""
|
||||
global email_endjob, email_full, email_rss
|
||||
if value == "" and (email_endjob() or email_full() or email_rss()):
|
||||
return T("Server address required"), None
|
||||
else:
|
||||
return None, value
|
||||
|
||||
|
||||
def validate_host(value):
|
||||
def validate_host(value: str) -> ValidateResult:
|
||||
"""Check if host is valid: an IP address, or a name/FQDN that resolves to an IP address"""
|
||||
|
||||
# easy: value is a plain IPv4 or IPv6 address:
|
||||
try:
|
||||
ipaddress.ip_address(value)
|
||||
@@ -195,7 +186,7 @@ def validate_host(value):
|
||||
return T("Invalid server address."), None
|
||||
|
||||
|
||||
def validate_script(value):
|
||||
def validate_script(value: str) -> ValidateResult:
|
||||
"""Check if value is a valid script"""
|
||||
if not sabnzbd.__INITIALIZED__ or (value and sabnzbd.filesystem.is_valid_script(value)):
|
||||
return None, value
|
||||
@@ -204,7 +195,7 @@ def validate_script(value):
|
||||
return T("%s is not a valid script") % value, None
|
||||
|
||||
|
||||
def validate_permissions(value: str):
|
||||
def validate_permissions(value: str) -> ValidateResult:
|
||||
"""Check the permissions for correct input"""
|
||||
# Octal verification
|
||||
if not value:
|
||||
@@ -225,18 +216,46 @@ def validate_permissions(value: str):
|
||||
return None, value
|
||||
|
||||
|
||||
def validate_safedir(root, value, default):
|
||||
def validate_safedir(root: str, value: str, default: str) -> ValidateResult:
|
||||
"""Allow only when queues are empty and no UNC"""
|
||||
if not sabnzbd.__INITIALIZED__ or (sabnzbd.PostProcessor.empty() and sabnzbd.NzbQueue.is_empty()):
|
||||
return validate_no_unc(root, value, default)
|
||||
if value.startswith(r"\\"):
|
||||
return T('UNC path "%s" not allowed here') % value, None
|
||||
else:
|
||||
return validate_default_if_empty(root, value, default)
|
||||
else:
|
||||
return T("Error: Queue not empty, cannot change folder."), None
|
||||
return T("Queue not empty, cannot change folder."), None
|
||||
|
||||
|
||||
def validate_scriptdir_not_appdir(root, value, default):
|
||||
def validate_download_vs_complete_dir(root: str, value: str, default: str):
|
||||
"""Make sure download_dir and complete_dir are not identical
|
||||
or that download_dir is not a subfolder of complete_dir"""
|
||||
# Check what new value we are trying to set
|
||||
if default == DEF_COMPLETE_DIR:
|
||||
check_download_dir = download_dir.get_path()
|
||||
check_complete_dir = real_path(root, value)
|
||||
elif default == DEF_DOWNLOAD_DIR:
|
||||
check_download_dir = real_path(root, value)
|
||||
check_complete_dir = complete_dir.get_path()
|
||||
else:
|
||||
raise ValueError("Validator can only be used for download_dir/complete_dir")
|
||||
|
||||
if same_directory(check_download_dir, check_complete_dir):
|
||||
return (
|
||||
T("The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"),
|
||||
None,
|
||||
)
|
||||
elif default == DEF_COMPLETE_DIR:
|
||||
# The complete_dir allows UNC
|
||||
return validate_default_if_empty(root, value, default)
|
||||
else:
|
||||
return validate_safedir(root, value, default)
|
||||
|
||||
|
||||
def validate_scriptdir_not_appdir(root: str, value: str, default: str) -> Tuple[None, str]:
|
||||
"""Warn users to not use the Program Files folder for their scripts"""
|
||||
# Need to add seperator so /mnt/sabnzbd and /mnt/sabnzbd-data are not detected as equal
|
||||
if value and long_path(os.path.join(root, value)).startswith(long_path(sabnzbd.DIR_PROG) + os.pathsep):
|
||||
if value and same_directory(sabnzbd.DIR_PROG, os.path.join(root, value)):
|
||||
# Warn, but do not block
|
||||
sabnzbd.misc.helpful_warning(
|
||||
T(
|
||||
@@ -246,7 +265,7 @@ def validate_scriptdir_not_appdir(root, value, default):
|
||||
return None, value
|
||||
|
||||
|
||||
def validate_notempty(root, value, default):
|
||||
def validate_default_if_empty(root: str, value: str, default: str) -> Tuple[None, str]:
|
||||
"""If value is empty, return default"""
|
||||
if value:
|
||||
return None, value
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
190
sabnzbd/happyeyeballs.py
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# Python implementation of RFC 6555 / Happy Eyeballs: find the quickest IPv4/IPv6 connection
|
||||
# See https://tools.ietf.org/html/rfc6555
|
||||
# Method: Start parallel sessions using threads, and only wait for the quickest successful socket connect
|
||||
# See https://tools.ietf.org/html/rfc6555#section-4.1
|
||||
|
||||
# You can run this as a standalone program, or as a module:
|
||||
"""
|
||||
from happyeyeballs import happyeyeballs
|
||||
print happyeyeballs('newszilla.xs4all.nl', port=119)
|
||||
"""
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import queue
|
||||
|
||||
|
||||
# Called by each thread
|
||||
def do_socket_connect(result_queue: queue.Queue, ip: str, port: int, ipv4delay: int):
|
||||
"""Connect to the ip, and put the result into the queue"""
|
||||
try:
|
||||
# Create socket
|
||||
if ip.find(":") >= 0:
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
if ip.find(".") >= 0:
|
||||
time.sleep(ipv4delay) # IPv4 ... so a delay for IPv4 if we prefer IPv6. Note: ipv4delay could be 0
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
s.settimeout(3)
|
||||
|
||||
try:
|
||||
# Connect ...
|
||||
s.connect((ip, port))
|
||||
finally:
|
||||
# always close
|
||||
s.close()
|
||||
|
||||
result_queue.put((ip, True))
|
||||
except:
|
||||
# We got an exception, so no successful connect on IP & port:
|
||||
result_queue.put((ip, False))
|
||||
|
||||
|
||||
def happyeyeballs(host: str, port: int = 80, preferipv6: bool = False) -> str:
|
||||
"""Happyeyeballs function, with caching of the results"""
|
||||
|
||||
# Find out if a cached result is available, and recent enough:
|
||||
timecurrent = int(time.time()) # current time in seconds since epoch
|
||||
retentionseconds = 100
|
||||
hostkey = (host, port, preferipv6) # Example key: ('ssl.astraweb.com', 563, True)
|
||||
|
||||
try:
|
||||
# Let's check the time:
|
||||
timecached = happyeyeballs.happylist[hostkey][1]
|
||||
if timecurrent - timecached <= retentionseconds:
|
||||
return happyeyeballs.happylist[hostkey][0]
|
||||
except:
|
||||
# Exception, so entry not there, so we have to fill it out
|
||||
pass
|
||||
|
||||
# we only arrive here if the entry has to be determined. So let's do that:
|
||||
# We have to determine the (new) best IP address
|
||||
start = time.perf_counter()
|
||||
ipv4delay = 0
|
||||
try:
|
||||
# Check if there is an AAAA / IPv6 result for this host:
|
||||
socket.getaddrinfo(host, port, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME)
|
||||
# preferipv6, AND at least one IPv6 found, so give IPv4 (!) a delay so IPv6 has a head start and is preferred
|
||||
if preferipv6:
|
||||
ipv4delay = 0.1
|
||||
except:
|
||||
pass
|
||||
|
||||
result_queue = queue.Queue() # queue used for threads giving back the results
|
||||
|
||||
try:
|
||||
# Get all IP (IPv4 and IPv6) addresses:
|
||||
allinfo = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)
|
||||
for info in allinfo:
|
||||
address = info[4][0]
|
||||
resolver_thread = threading.Thread(target=do_socket_connect, args=(result_queue, address, port, ipv4delay))
|
||||
resolver_thread.daemon = True
|
||||
resolver_thread.start()
|
||||
|
||||
result = None # default return value, used if none of threads says True/"OK", so no connect on any IP address
|
||||
# start reading from the Queue for message from the threads:
|
||||
for _ in range(len(allinfo)):
|
||||
connect_result = result_queue.get() # get a response
|
||||
if connect_result[1]:
|
||||
result = connect_result[0]
|
||||
break # the first True/"OK" is enough, so break out of for loop
|
||||
except:
|
||||
result = None
|
||||
|
||||
logging.info("Quickest IP address for %s (port %s, preferipv6 %s) is %s", host, port, preferipv6, result)
|
||||
delay = int(1000 * (time.perf_counter() - start))
|
||||
logging.debug("Happy Eyeballs lookup and port connect took %s ms", delay)
|
||||
|
||||
# We're done. Store and return the result
|
||||
if result:
|
||||
happyeyeballs.happylist[hostkey] = (result, timecurrent)
|
||||
return result
|
||||
|
||||
|
||||
happyeyeballs.happylist = {} # The cached results. This static variable must be after the def happyeyeballs()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# plain HTTP/HTTPS sites:
|
||||
print((happyeyeballs("www.google.com")))
|
||||
print((happyeyeballs("www.google.com", port=443)))
|
||||
print((happyeyeballs("www.nu.nl")))
|
||||
|
||||
# newsservers:
|
||||
print((happyeyeballs("newszilla6.xs4all.nl", port=119)))
|
||||
print((happyeyeballs("newszilla.xs4all.nl", port=119)))
|
||||
print((happyeyeballs("block.cheapnews.eu", port=119)))
|
||||
print((happyeyeballs("block.cheapnews.eu", port=443)))
|
||||
print((happyeyeballs("sslreader.eweka.nl", port=563)))
|
||||
print((happyeyeballs("news.thundernews.com", port=119)))
|
||||
print((happyeyeballs("news.thundernews.com", port=119, preferipv6=False)))
|
||||
print((happyeyeballs("secure.eu.thundernews.com", port=563)))
|
||||
print((happyeyeballs("bonus.frugalusenet.com", port=563)))
|
||||
|
||||
# Strange cases
|
||||
print((happyeyeballs("does.not.resolve", port=443)))
|
||||
print((happyeyeballs("www.google.com", port=119)))
|
||||
print((happyeyeballs("216.58.211.164")))
|
||||
@@ -86,6 +86,7 @@ def test_nntp_server_dict(kwargs):
|
||||
return False, T("Invalid server details")
|
||||
|
||||
try:
|
||||
s.request_addrinfo_blocking()
|
||||
nw = NewsWrapper(server=s, thrdnum=-1, block=True)
|
||||
nw.init_connect()
|
||||
while not nw.connected:
|
||||
@@ -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
Reference in New Issue
Block a user