Compare commits

...

73 Commits

Author SHA1 Message Date
Safihre
b922274b61 Update text files for 3.5.0 Beta 2 2021-11-30 16:49:50 +01:00
Safihre
863c0e5eb7 Add unittests for the NNTP SSL/cipher settings 2021-11-30 16:19:16 +01:00
Safihre
46f9956791 Force TLSv1.2 when setting custom ciphers 2021-11-30 16:19:16 +01:00
Safihre
879a6f2552 Always set SSL ciphers to allow TLSv1.2 connections 2021-11-30 16:19:16 +01:00
Safihre
5e3d237c99 HappyEyeBalls used SSL even though it wasn't needed 2021-11-30 16:19:16 +01:00
puzzledsab
1eb83e4eb0 Print low level Windows status error on IOError (#1986)
* Print low level Windows status error on assembler IOError

* Include error code in renamer OSError message
2021-11-30 08:06:11 +01:00
puzzledsab
194c0e6708 Add all passwords to list every time (#1985) 2021-11-28 08:28:10 +01:00
Safihre
a87d38c61f Full Apple M1 compatibility
Closes #1711 (see for more details!)
2021-11-27 16:28:08 +01:00
Safihre
ed0e5bbf9b Add placeholder for SOCKS5 Proxy input 2021-11-22 16:54:19 +01:00
Safihre
2249b623e6 Restore CI functional tests
Use build-in Chrome and ChromeDriver, we shouldn't try to be smarter than GitHub Actions 😉
2021-11-21 13:58:00 +01:00
SABnzbd Automation
854ca5f5d4 Update translatable texts 2021-11-21 08:06:35 +00:00
Safihre
a0a8029c36 Set the minimum supported TLS version the new way 2021-11-21 09:06:00 +01:00
Safihre
e7eec8e4f1 Check for PySocks at start-up to give a nice error 2021-11-21 08:54:21 +01:00
Safihre
a354c1984b Update text files for 3.5.0 Beta 1 2021-11-20 09:49:11 +01:00
Safihre
34799c397c Force compile all Python modules on macOS 2021-11-20 09:31:56 +01:00
Safihre
2c88dddc1e Set selenium-requirement to fix tests 2021-11-20 09:31:41 +01:00
SABnzbd Automation
64b0216ba9 Update translatable texts 2021-11-20 06:06:10 +00:00
puzzledsab
f950520475 Compare variable with int, not string (#1981) 2021-11-20 07:05:34 +01:00
SABnzbd Automation
60f3de2a91 Update translatable texts 2021-11-19 13:39:58 +00:00
puzzledsab
06e13483bd SOCKS 5 support (#1894)
* Add testable socks support

* Messed up merge

* Use proxy for urllib.request.urlopen

* Reset socket before trying to get public IP

* Add socks requirement

* PySocks, not socks?

* Use only PySocks

* Clean up and reduce number of new translations

* Move configuration to special variable socks5_proxy_url

* Remove useless dereferencing

* Catch OSError on proxy preconnect

* Try only setting socks proxy once

* Missed some spots

* Catch all errors for IPv6 checks

* Move proxy initialization up before threads are started

* No special `sock.connect` required for Socks5

* Revert "No special `sock.connect` required for Socks5"

This reverts commit 5e901f8b58.

* Remove callback and ORIGINAL_SOCKET variable

* Move all `sock.connect` code

* Create SSLContext only once for each server

It is re-created if server-settings are updated.

* Add SOCKS5 proxy configuration to General page

* Show if proxy is active in Status-window

Co-authored-by: Safihre <safihre@sabnzbd.org>
2021-11-19 14:39:23 +01:00
Safihre
f00cbe89bc Refactor HappyEyeBalls 2021-11-18 20:27:49 +01:00
SABnzbd Automation
e450f5744b Update translatable texts 2021-11-18 14:27:05 +00:00
jcfp
255242eca5 disable randomisation of directory listings in pyfakefs (#1978) 2021-11-18 15:26:26 +01:00
Safihre
cbeab4dd55 Force rebuild for binary pip-packages 2021-11-16 21:56:10 +01:00
Safihre
6b8506c986 Set target-arch to universal2 for macOS 2021-11-16 21:40:42 +01:00
Safihre
d5d5647b7c Add hidden import of guessit.data 2021-11-16 21:24:35 +01:00
Safihre
1a76de1ca3 Build PyInstaller bootloader from sources to support macOS 10.9 2021-11-16 20:40:24 +01:00
Safihre
1913109623 Add freeze support for multiprocessing
See https://github.com/pyinstaller/pyinstaller/issues/6368
2021-11-16 13:58:12 +01:00
Safihre
e76b4395a7 Remove specific macOS version check
Old macOS versions just have to deal with it
2021-11-15 21:41:31 +01:00
Safihre
6670156397 Correct Scheduler Wiki-URL 2021-11-08 13:15:13 +01:00
Safihre
37b7a77b70 Update snapcraft build 2021-11-07 09:08:43 +01:00
Safihre
ddb5a007a5 Update snapcraft base to Ubuntu 20.04 2021-11-07 08:50:06 +01:00
SABnzbd Automation
8568df4552 Update translatable texts 2021-11-05 06:57:19 +00:00
jcfp
493a5f715c Reintroduce uu support (#1969)
* reintroduce uu support

* housekeeping, bug fixes

* add tests for decode_uu()

* simplify, fix typo

* remove leftover debugging; housekeeping

* oops

* add non-ascii input for test_broken_uu
2021-11-05 07:56:39 +01:00
Safihre
a61b27992e Update code-format to 3.7
No changes really
2021-11-05 07:50:55 +01:00
Safihre
798eec7aa8 Drop (official) Python 3.6 support and run fully on 3.10
Probably still runs, we just don't test it.
2021-11-05 07:45:24 +01:00
Safihre
0d29603e2b portable.cmd was no longer included in Windows release
Closes sabnzbd/sabnzbd.github.io/176
2021-11-03 21:06:28 +01:00
SABnzbd Automation
48882220d6 Update translatable texts 2021-11-02 10:32:30 +00:00
Safihre
b4ad292ec5 Build release using 3.10
Closes #1958
2021-11-02 11:31:43 +01:00
Safihre
b59a14f6b7 Correct "Remove even more unused imports"
My fault.
2021-10-22 10:04:35 +02:00
SABnzbd Automation
80ed385a41 Update translatable texts 2021-10-22 07:52:02 +00:00
Safihre
04cd67b98b Remove even more unused imports 2021-10-22 09:51:24 +02:00
jcfp
68eded2c0c remove unused imports (#1966) 2021-10-19 19:29:54 +02:00
Safihre
389a0d3afa Revert to using regex based sample detection
Closes #1964
2021-10-13 18:24:55 +02:00
Safihre
5a3e4a28fe Solve deprecation warnings 2021-10-12 11:20:33 +02:00
SABnzbd Automation
66b5629a31 Update translatable texts 2021-10-12 07:30:17 +00:00
Safihre
eae77eb236 Prevent double guessit parsing 2021-10-12 09:29:38 +02:00
Safihre
5f44ec8a0d Correct behavior of Sorter when no filename and/or extension is supplied
Closes #1962, #1957
2021-10-08 10:35:06 +02:00
Safihre
9d8c62de6b Only fail jobs if the sorter should have renamed 2021-10-08 10:35:06 +02:00
Safihre
3229fd8d28 Use general detection of RAR-files in file-extension correction
Correct file_extension test
2021-10-08 10:35:06 +02:00
SABnzbd Automation
fdee789637 Update translatable texts 2021-10-08 08:34:20 +00:00
Safihre
c762dda1b1 Fix tavern for Python 3.6 and run tests on Python 3.10 (Linux-only) 2021-10-08 10:33:37 +02:00
SABnzbd Automation
c5c8b902c4 Update translatable texts 2021-10-06 12:36:55 +00:00
Safihre
ee255a5042 Require at least 1 category to be set for Sorting and warn if not set
Before 3.4.0, only for TV sorting we allowed to set 0 categories. But for Movies and Date Sorting we did require at least 1 category to be set. This was harmonized in 3.4.0, breaking existing setups. Added warning for those users.
The Sorting behavior is different from Notifications: in Notifications selecting Default only(!) means to apply it to all categories.
However, that has never been the case for Sorting. So for now added a bit more help texts to the Affected categories box on both pages.
2021-10-06 14:34:15 +02:00
Sander
3952965632 make .cbz a well-known extension, so that no extension is added (#1960) 2021-10-05 22:00:30 +02:00
Safihre
85db706bbe Always require TLS1.2 or higher for NNTP connections 2021-10-05 17:32:39 +02:00
Safihre
ea570442c6 Update Wiki-URL to 3.5 2021-10-05 17:21:29 +02:00
Safihre
9c109b803d Do not fail all jobs if one job fails in order to locate the failing job 2021-10-05 12:29:37 +02:00
Safihre
86f77f8064 Only run Transifex if the TX_TOKEN is set 2021-10-05 12:26:34 +02:00
Sander
81c33d65e4 make .cbr a well-known extension, so that no extension (".rar") is added (#1959) 2021-10-05 10:11:52 +02:00
SABnzbd Automation
a1cf822141 Update translatable texts 2021-10-03 08:06:24 +00:00
Safihre
ce48a9697a Check for puremagic and guessit first and add comments about cherrypy 2021-10-03 10:05:39 +02:00
Safihre
9b22c4b23c Always show number of MB missing
https://forums.sabnzbd.org/viewtopic.php?f=2&t=25573
2021-10-03 09:59:26 +02:00
Safihre
6283b0460a Job failure due to Sorting-problems was not shown in the interface 2021-10-01 15:43:13 +02:00
Safihre
4fe977fa47 rXX files are popular extensions and don't need renames
Closes #1955
2021-09-29 09:17:19 +02:00
SABnzbd Automation
f188e55692 Update translatable texts 2021-09-29 06:37:30 +00:00
Safihre
f5487ed932 Do not search whole file when checking if txt or nzb file 2021-09-29 08:36:48 +02:00
Safihre
c69b25ff0d Only run process_unpacked_par2 when cleanup happened
Relates to https://forums.sabnzbd.org/viewtopic.php?f=1&t=25552
2021-09-29 08:24:27 +02:00
SABnzbd Automation
b608af640f Update translatable texts 2021-09-27 18:22:43 +00:00
puzzledsab
315f1ff3bc Add required server option (#1948)
* Add required server option

* Use plan_resume() instead of the resume_task system

* Retry articles on required servers after connection failure

* Update comment to match new code

* Remove unnecessary try
2021-09-27 20:22:02 +02:00
Safihre
b6c2ac194b Set version information to 3.5.0-develop 2021-09-24 13:41:12 +02:00
Safihre
00570d2089 Make add_parfile return if it could actually add the file
Maybe it was long finished, which could result in crashes.
Closes #1953
2021-09-24 08:57:53 +02:00
Safihre
56375b16fe Do not rename in decode_par2 if the filename didn't change
Closes #1952
2021-09-23 10:00:19 +02:00
86 changed files with 1778 additions and 731 deletions

View File

@@ -17,6 +17,6 @@ jobs:
builder
tests
--line-length=120
--target-version=py36
--target-version=py37
--check
--diff

View File

@@ -10,11 +10,11 @@ jobs:
AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9 (64bit)
- name: Set up Python 3.10 (64bit)
uses: actions/setup-python@v2
with:
python-version: 3.9
architecture: x64
python-version: "3.10"
architecture: "x64"
- name: Install Python dependencies (64bit)
run: |
python --version
@@ -43,8 +43,8 @@ jobs:
- name: Set up Python 3.8 (32bit and legacy)
uses: actions/setup-python@v2
with:
python-version: 3.8
architecture: x86
python-version: "3.8"
architecture: "x86"
- name: Install Python dependencies (32bit and legacy)
run: |
python --version
@@ -64,7 +64,7 @@ jobs:
build_macos:
name: Build macOS binary
runs-on: macos-latest
runs-on: macos-11
env:
SIGNING_AUTH: ${{ secrets.SIGNING_AUTH }}
NOTARIZATION_USER: ${{ secrets.NOTARIZATION_USER }}
@@ -73,8 +73,10 @@ jobs:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: 3.9.7
MACOSX_DEPLOYMENT_TARGET: 10.9
PYTHON_VERSION: "3.10.0"
MACOSX_DEPLOYMENT_TARGET: "10.9"
# We need to force compile for universal2 support
CFLAGS: -arch arm64 -arch x86_64
steps:
- uses: actions/checkout@v2
- name: Cache Python download
@@ -85,15 +87,43 @@ jobs:
key: macOS-Python-${{ env.PYTHON_VERSION }}
- name: Get Python
if: steps.cache-python-download.outputs.cache-hit != 'true'
run: curl https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macosx10.9.pkg -o ~/python.pkg
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 /
- name: Install Python dependencies
# cffi will pick up the single-arch libffi from Homebrew, while Apple provides universal2-version
# of libffi with XCode. So we forcefully have to remove the Homebrew one.
# 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
run: |
brew uninstall libffi --ignore-dependencies
python3 --version
pip3 install --upgrade pip wheel
pip3 install --upgrade -r requirements.txt
ARCHFLAGS="-arch x86_64 -arch arm64" pip3 install --upgrade cffi --no-binary cffi
pip3 install --upgrade -r requirements.txt --no-binary sabyenc3
pip3 uninstall cryptography -y
pip3 download cryptography --platform macosx_10_10_universal2 --only-binary :all: --no-deps --dest .
pip3 install cryptography --no-cache-dir --no-index --find-links .
pip3 install --upgrade -r builder/requirements.txt
- name: Checkout PyInstaller
# We need to build the PyInstaller bootloader from sources:
# https://github.com/pyinstaller/pyinstaller/issues/6235
uses: actions/checkout@v2
with:
repository: pyinstaller/pyinstaller
path: pyinstaller
- name: Build PyInstaller bootloader for macOS 10.9 support
# Make sure we checkout the latest stable release
run: |
python3 --version
cd pyinstaller/bootloader
git fetch --tags
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
python3 ./waf all
cd ..
python3 -m pip install .
cd ..
- name: Import macOS codesign certificates
uses: apple-actions/import-codesign-certs@v1
if: env.SIGNING_AUTH

View File

@@ -7,16 +7,17 @@ jobs:
name: Test ${{ matrix.name }} - Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.7", "3.8", "3.9", "3.10"]
os: [ubuntu-20.04]
include:
- name: macOS
os: macos-latest
python-version: 3.9
python-version: "3.10"
- name: Windows
os: windows-latest
python-version: 3.9
python-version: "3.10"
steps:
- uses: actions/checkout@v2
@@ -26,11 +27,11 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies
if: runner.os == 'Linux'
run: sudo apt-get install unrar p7zip-full par2 chromium-chromedriver
run: sudo apt-get install unrar p7zip-full par2
- name: Install Python dependencies
run: |
python --version
pip install --upgrade pip
pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt
pip install --upgrade -r tests/requirements.txt
- name: Test SABnzbd

View File

@@ -8,27 +8,30 @@ on:
jobs:
translations:
runs-on: ubuntu-latest
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
steps:
- uses: actions/checkout@v2
- name: Generate translatable texts
run: |
python3 tools/extract_pot.py
- name: Install Transifex client
if: env.TX_TOKEN
# Sudo is needed to link the "tx"-command
run: |
sudo -H python3 -m pip install setuptools wheel
sudo -H python3 -m pip install transifex-client
- name: Push/pull Transifex translations
if: env.TX_TOKEN
run: |
tx push --source --parallel
tx pull --all --force --parallel
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
- name: Compile translations to validate them
run: |
python3 tools/make_mo.py
- name: Push translatable and translated texts back to repo
uses: stefanzweifel/git-auto-commit-action@v4.5.1
if: env.TX_TOKEN
with:
commit_message: Update translatable texts
commit_user_name: SABnzbd Automation

View File

@@ -52,7 +52,7 @@ Specific guides to install from source are available for Windows and macOS:
https://sabnzbd.org/wiki/installation/install-macos
https://sabnzbd.org/wiki/installation/install-from-source-windows
Only Python 3.6 and above is supported.
Only Python 3.7 and above is supported.
On Linux systems you need to install:
par2 unrar unzip python3-setuptools python3-pip

View File

@@ -1,7 +1,7 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 3.4.1
Summary: SABnzbd-3.4.1
Version: 3.5.0Beta2
Summary: SABnzbd-3.5.0Beta2
Home-page: https://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org

View File

@@ -18,7 +18,7 @@ If you want to know more you can head over to our website: https://sabnzbd.org.
SABnzbd has a few dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages, then you likely already have all the needed dependencies. If not, here's what you're looking for:
- `python` (Python 3.6 and higher, often called `python3`)
- `python` (Python 3.7 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt -U`
- `par2` (Multi-threaded par2 installation guide can be found [here](https://sabnzbd.org/wiki/installation/multicore-par2))
- `unrar` (make sure you get the "official" non-free version of unrar)

View File

@@ -1,28 +1,26 @@
Release Notes - SABnzbd 3.4.1
Release Notes - SABnzbd 3.5.0 Beta 2
=========================================================
## Bugfixes since 3.4.0
- macOS: Failed to run on M1 systems or older macOS versions.
## Changes since 3.5.0 Beta 1
- Updated news server SSL setup due to changes in Python 3.10.
- Setting custom ciphers forces the maximum TLS version to 1.2.
- Handle multiple passwords stored in NZB-file.
- Print low-level Windows status error on `IOError`.
- macOS: full native support for M1 systems.
## Changes since 3.3.1
- Extended `Deobfuscate final filenames` to attempt to set the correct
file extension based on the file signature if the file extension is
not present or meaningless.
- Added additional pattern keys that can be used in the `Sort String`
for Sorting, by using the `guessit` package internally for parsing.
- If unpacked files contain `.par2` files they will always be read and
used to rename any matching files.
- Regular expressions can be used to specify `Unwanted extensions`.
- Not all passwords will be tried if a matching one was found.
- Some interface-only options were added as API-call.
- The Plush skin has been removed.
## Changes since 3.4.2
- Removed Python 3.6 support.
- SOCKS5 proxy support for all outgoing connections.
- Restored support for UUencoded jobs.
- `Required` server option: in case of connection failures, the queue
will be paused for a few minutes instead of skipping the server.
- Require TLSv1.2 or higher for SSL news server connections.
- macOS/Windows: updated to Python 3.10.
- macOS: native support for M1 systems.
## Bugfixes since 3.3.1
- Duplicate check based on `.nzb` MD5 was performed before it was calculated.
- Enforce `local_ranges` for broadcasts (Bonjour/SSDP).
- Correctly parse the filename in `Content-Disposition` header.
- `Warning` instead of `Info` when there is a restart due to crashed thread.
- Only run Direct Unpack if `enable_unrar` is enabled.
## Bugfixes since 3.4.2
- Email notification setting was not shown correctly.
- Windows: `portable.cmd` was not included in the release.
## Upgrade notices
- The download statistics file `totals10.sab` is updated in 3.2.x

View File

@@ -17,8 +17,8 @@
import sys
if sys.hexversion < 0x03060000:
print("Sorry, requires Python 3.6 or above")
if sys.hexversion < 0x03070000:
print("Sorry, requires Python 3.7 or above")
print("You can read more at: https://sabnzbd.org/wiki/installation/install-off-modules")
sys.exit(1)
@@ -32,6 +32,7 @@ import signal
import socket
import platform
import subprocess
import multiprocessing
import ssl
import time
import re
@@ -46,6 +47,9 @@ try:
import portend
import cryptography
import chardet
import guessit
import puremagic
import socks
except ImportError as e:
print("Not all required Python modules are available, please check requirements.txt")
print("Missing module:", e.name)
@@ -92,7 +96,6 @@ from sabnzbd.filesystem import get_ext, real_path, long_path, globber_full, remo
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic, launch_a_browser
import sabnzbd.config as config
import sabnzbd.cfg
import sabnzbd.downloader
import sabnzbd.notifier as notifier
import sabnzbd.zconfig
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6
@@ -106,11 +109,10 @@ try:
import win32event
import win32service
import win32ts
import pywintypes
import servicemanager
from win32com.shell import shell, shellcon
from sabnzbd.utils.apireg import get_connection_info, set_connection_info, del_connection_info
from sabnzbd.utils.apireg import get_connection_info, set_connection_info
import sabnzbd.sabtray
win32api.SetConsoleCtrlHandler(sabnzbd.sig_handler, True)
@@ -1439,12 +1441,11 @@ def main():
try:
cherrypy.engine.start()
except:
# Since the webserver is started by cherrypy in a separate thread, we can't really catch any
# start-up errors. This try/except only catches very few errors, the rest is only shown in the console.
logging.error(T("Failed to start web-interface: "), exc_info=True)
abort_and_show_error(browserhost, cherryport)
# Wait for server to become ready
cherrypy.engine.wait(cherrypy.process.wspbus.states.STARTED)
if sabnzbd.WIN32:
if enable_https:
mode = "s"
@@ -1468,6 +1469,9 @@ def main():
if sabnzbd.NO_DOWNLOADING:
return
# Apply proxy, if configured, before main requests are made
sabnzbd.set_socks5_proxy()
# Start all SABnzbd tasks
logging.info("Starting %s-%s", sabnzbd.MY_NAME, sabnzbd.__version__)
try:
@@ -1739,6 +1743,9 @@ def handle_windows_service():
if __name__ == "__main__":
# Require for freezing
multiprocessing.freeze_support()
# We can only register these in the main thread
signal.signal(signal.SIGINT, sabnzbd.sig_handler)
signal.signal(signal.SIGTERM, sabnzbd.sig_handler)

View File

@@ -41,6 +41,7 @@ RELEASE_VERSION = pkginfo.Develop(".").version
# Add hidden imports
extra_hiddenimports = ["Cheetah.DummyTransaction", "cheroot.ssl.builtin", "certifi"]
extra_hiddenimports.extend(collect_submodules("babelfish.converters"))
extra_hiddenimports.extend(collect_submodules("guessit.data"))
# Add platform specific stuff
if sys.platform == "darwin":
@@ -66,6 +67,7 @@ else:
# Windows
extra_hiddenimports.append("win32timezone")
extra_folders += ["win/multipar/", "win/unrar/", "win/7zip/"]
extra_files += ["portable.cmd"]
# Parse the version info
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
@@ -135,6 +137,7 @@ exe = EXE(
append_pkg=False,
icon="icons/sabnzbd.ico",
version=version_info,
target_arch="universal2",
)
coll = COLLECT(exe, pyi_analysis.binaries, pyi_analysis.zipfiles, pyi_analysis.datas, name="SABnzbd")

View File

@@ -22,11 +22,11 @@ import sys
import os
import time
import shutil
import shlex
import subprocess
import tarfile
import pkginfo
import github
from distutils.dir_util import copy_tree
VERSION_FILE = "sabnzbd/version.py"
@@ -185,8 +185,7 @@ if __name__ == "__main__":
# Run PyInstaller and check output
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
# Use special distutils function to merge the main and console directories
copy_tree("dist/SABnzbd-console", "dist/SABnzbd")
shutil.copytree("dist/SABnzbd-console", "dist/SABnzbd", dirs_exist_ok=True)
safe_remove("dist/SABnzbd-console")
# Remove unwanted DLL's
@@ -245,6 +244,14 @@ if __name__ == "__main__":
# Run PyInstaller and check output
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
# Make sure we created a fully universal2 release
for bin_to_check in glob.glob("dist/SABnzbd.app/Contents/MacOS/**/*.so", recursive=True):
print("Checking if binary is universal2: %s" % bin_to_check)
file_output = run_external_command(["file", bin_to_check])
# Make sure we have both arm64 and x86
if not ("x86_64" in file_output and "arm64" in file_output):
raise RuntimeError("Non-universal2 binary found!")
# Only continue if we can sign
if authority:
files_to_sign = [
@@ -361,7 +368,7 @@ if __name__ == "__main__":
# Copy all folders and files to the new folder
for source_folder in extra_folders:
copy_tree(source_folder, os.path.join(src_folder, source_folder))
shutil.copytree(source_folder, os.path.join(src_folder, source_folder), dirs_exist_ok=True)
# Copy all files
for source_file in extra_files:

View File

@@ -1,5 +1,5 @@
# Basic build requirements
pyinstaller==4.2
pyinstaller
setuptools
pkginfo
certifi

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Config"#-->
<!--#set global $help_uri="configuration/3.4/configure"#-->
<!--#set global $help_uri="configuration/3.5/configure"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#from sabnzbd.encoding import CODEPAGE#-->

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Categories"#-->
<!--#set global $help_uri="configuration/3.4/categories"#-->
<!--#set global $help_uri="configuration/3.5/categories"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<div class="section">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Folders"#-->
<!--#set global $help_uri="configuration/3.4/folders"#-->
<!--#set global $help_uri="configuration/3.5/folders"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="General"#-->
<!--#set global $help_uri="configuration/3.4/general"#-->
<!--#set global $help_uri="configuration/3.5/general"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -179,6 +179,11 @@
<input type="checkbox" name="enable_https_verification" id="enable_https_verification" value="1" <!--#if int($enable_https_verification) > 0 then 'checked="checked"' else ""#--> <!--#if int($certificate_validation) == 0 then "disabled=\"disabled\"" else ""#--> />
<span class="desc">$T('explain-enable_https_verification')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="socks5_proxy_url">$T('opt-socks5_proxy_url')</label>
<input type="text" name="socks5_proxy_url" id="socks5_proxy_url" value="$socks5_proxy_url" placeholder="socks5://username:password@hostname:port" />
<span class="desc">$T('explain-socks5_proxy_url') <br/>$T('readwiki')</span>
</div>
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
</div>

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Email"#-->
<!--#set global $help_uri="configuration/3.4/notifications"#-->
<!--#set global $help_uri="configuration/3.5/notifications"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#def show_notify_checkboxes($section_label)#-->
@@ -22,6 +22,7 @@
<option value="$ct" <!--#if $ct in $getVar($section_label + '_cats') then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
<p>$T('defaultNotifiesAll')</p>
</div>
<!--#end def#-->
@@ -40,6 +41,7 @@
<option value="$ct" <!--#if $ct in $email_cats then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
<p>$T('defaultNotifiesAll')</p>
</div>
</div>
<div class="col1">
@@ -47,9 +49,9 @@
<div class="field-pair">
<label class="config" for="email_endjob">$T('opt-email_endjob')</label>
<select name="email_endjob" id="email_endjob">
<option value="0" <!--#if int($email_endjob) == "0" then 'selected="selected"' else ""#--> >$T('email-never')</option>
<option value="1" <!--#if int($email_endjob) == "1" then 'selected="selected"' else ""#--> >$T('email-always')</option>
<option value="2" <!--#if int($email_endjob) == "2" then 'selected="selected"' else ""#--> >$T('email-errorOnly')</option>
<option value="0" <!--#if int($email_endjob) == 0 then 'selected="selected"' else ""#--> >$T('email-never')</option>
<option value="1" <!--#if int($email_endjob) == 1 then 'selected="selected"' else ""#--> >$T('email-always')</option>
<option value="2" <!--#if int($email_endjob) == 2 then 'selected="selected"' else ""#--> >$T('email-errorOnly')</option>
</select>
</div>
<div class="field-pair">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="RSS"#-->
<!--#set global $help_uri="configuration/3.4/rss"#-->
<!--#set global $help_uri="configuration/3.5/rss"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#import html#-->
<div class="colmask">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Scheduling"#-->
<!--#set global $help_uri="configuration/3.4s/scheduling"#-->
<!--#set global $help_uri="configuration/3.5/scheduling"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<%

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Servers"#-->
<!--#set global $help_uri="configuration/3.4/servers"#-->
<!--#set global $help_uri="configuration/3.5/servers"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#import json#-->
@@ -103,6 +103,11 @@
<input type="checkbox" name="send_group" id="send_group" value="1" />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="required">$T('srv-required')</label>
<input type="checkbox" name="required" id="required" value="1" />
<span class="desc">$T('explain-required')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="optional">$T('srv-optional')</label>
<input type="checkbox" name="optional" id="optional" value="1" />
@@ -225,6 +230,11 @@
<span class="desc">$T('explain-ssl_ciphers') <br>$T('readwiki')
<a href="${helpuri}advanced/ssl-ciphers" target="_blank">${helpuri}advanced/ssl-ciphers</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="required$cur">$T('srv-required')</label>
<input type="checkbox" name="required" id="required$cur" value="1" <!--#if int($server['required']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-required')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="optional$cur">$T('srv-optional')</label>
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Sorting"#-->
<!--#set global $help_uri="configuration/3.4/sorting"#-->
<!--#set global $help_uri="configuration/3.5/sorting"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -11,12 +11,13 @@
<h3>$T('seriesSorting') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<p>
<b>$T('affectedCat')</b><br/>
<select name="tv_categories" multiple="multiple" class="multiple_cats">
<select name="tv_categories" multiple="multiple" class="multiple_cats" required="required">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $tv_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
<!--#end for#-->
</select>
</p>
<p>$T('selectOneCat')</p>
</div>
<!-- /col2 -->
<div class="col1">
@@ -223,12 +224,13 @@
<h3>$T('movieSort') <a href="$helpuri$help_uri#toc6" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<p>
<b>$T('affectedCat')</b><br/>
<select name="movie_categories" multiple="multiple" class="multiple_cats">
<select name="movie_categories" multiple="multiple" class="multiple_cats" required="required">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $movie_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
<!--#end for#-->
</select>
</p>
<p>$T('selectOneCat')</p>
</div>
<!-- /col2 -->
<div class="col1">
@@ -419,12 +421,13 @@
<h3>$T('dateSorting') <a href="$helpuri$help_uri#toc9" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<p>
<b>$T('affectedCat')</b><br/>
<select name="date_categories" multiple="multiple" class="multiple_cats">
<select name="date_categories" multiple="multiple" class="multiple_cats" required="required">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $date_categories then 'selected="selected"' else ""#--> >$Tspec($ct)</option>
<!--#end for#-->
</select>
</p>
<p>$T('selectOneCat')</p>
</div>
<!-- /col2 -->
<div class="col1">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Special"#-->
<!--#set global $help_uri="configuration/3.4/special"#-->
<!--#set global $help_uri="configuration/3.5/special"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Switches"#-->
<!--#set global $help_uri="configuration/3.4/switches"#-->
<!--#set global $help_uri="configuration/3.5/switches"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">

View File

@@ -85,6 +85,11 @@
</ul>
<div class="tab-content">
<div class="tab-pane fade in active" id="options-status">
<div class="row" data-bind="visible: statusInfo.active_socks5_proxy">
<div class="col-sm-6">$T('opt-socks5_proxy_url') &nbsp </div>
<div class="col-sm-6" data-bind="visible: hasStatusInfo, text: statusInfo.active_socks5_proxy"></div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasStatusInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
</div>
<div class="row">
<div class="col-sm-6">$T('dashboard-localIP4') &nbsp; </div>
<div class="col-sm-6" data-bind="visible: hasStatusInfo, text: !statusInfo.localipv4() ? '$T('dashboard-connectionError')' : statusInfo.localipv4(), css: { 'options-bad-status' : !statusInfo.localipv4() }"></div>

View File

@@ -55,6 +55,7 @@ function ViewModel() {
self.statusInfo = {};
self.statusInfo.folders = ko.observableArray([]);
self.statusInfo.servers = ko.observableArray([]);
self.statusInfo.active_socks5_proxy = ko.observable();
self.statusInfo.localipv4 = ko.observable();
self.statusInfo.publicipv4 = ko.observable();
self.statusInfo.ipv6 = ko.observable();
@@ -773,6 +774,7 @@ function ViewModel() {
self.statusInfo.completedirspeed(data.status.completedirspeed)
self.statusInfo.internetbandwidth(data.status.internetbandwidth)
self.statusInfo.dnslookup(data.status.dnslookup)
self.statusInfo.active_socks5_proxy(data.status.active_socks5_proxy)
self.statusInfo.localipv4(data.status.localipv4)
self.statusInfo.publicipv4(data.status.publicipv4)
self.statusInfo.ipv6(data.status.ipv6 || glitterTranslate.noneText)
@@ -1116,6 +1118,9 @@ function ViewModel() {
// Save servers (for reporting functionality of OZnzb)
self.servers = response.config.servers;
// Already set if we are using a proxy
if(response.config.misc.socks5_proxy_url) self.statusInfo.active_socks5_proxy(true)
// Update message
if(newRelease) {
self.allMessages.push({

View File

@@ -531,11 +531,10 @@ function QueueModel(parent, data) {
return self.name()
})
self.missingText = ko.pureComputed(function() {
// Check for missing data, the value is arbitrary! (1%)
if(self.missingMB()/self.totalMB() > 0.01) {
// Check for missing data, can show 0 if article-size is smaller than 500K, but we accept that
if(self.missingMB()) {
return self.missingMB().toFixed(0) + ' MB ' + glitterTranslate.misingArt
}
return;
})
self.statusText = ko.computed(function() {
// Checking

View File

@@ -387,10 +387,6 @@ msgstr ""
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3454,6 +3450,14 @@ msgstr ""
msgid "Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr ""
@@ -3686,6 +3690,15 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr ""
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid "In case of connection failures, the download queue will be paused for a few minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -3862,6 +3875,10 @@ msgstr ""
msgid "Read All Feeds Now"
msgstr ""
#: sabnzbd/skintext.py
msgid "If only the <em>Default</em> category is selected, notifications are enabled for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr ""
@@ -4132,7 +4149,13 @@ msgstr ""
msgid "X"
msgstr ""
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr ""
@@ -4160,7 +4183,8 @@ msgstr ""
msgid "Example"
msgstr ""
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4312,7 +4336,8 @@ msgstr ""
msgid "No folders"
msgstr ""
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr ""

View File

@@ -36,7 +36,7 @@ msgstr "Nezdařilo se spustit webové rozhraní"
#. Warning message
#: SABnzbd.py
msgid "Cannot find web template: %s, trying standard template"
msgstr ""
msgstr "Šablona pro web nebyla nalezena: %s, zkouším standardní šablonu"
#. Error message
#: SABnzbd.py
@@ -49,6 +49,7 @@ msgstr ""
msgid ""
"SABYenc module... NOT found! Expecting v%s - https://sabnzbd.org/sabyenc"
msgstr ""
"Modul SABYenc... nebyl nalezen! Očekávána v%s - https://sabnzbd.org/sabyenc"
#. Error message
#: SABnzbd.py
@@ -84,6 +85,8 @@ msgid ""
"Please be aware the 0.0.0.0 hostname will need an IPv6 address for external "
"access"
msgstr ""
"Pozor, hostitelská adresa 0.0.0.0 bude vyžadovat IPv6 adresu pro externí "
"přístup"
#. Error message
#: SABnzbd.py
@@ -169,22 +172,22 @@ msgstr "Načítání %s selhalo"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed postprocessor"
msgstr ""
msgstr "Restartuji protože postprocessor selhal"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed downloader"
msgstr ""
msgstr "Restartuji protože selhal downloader"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed decoder"
msgstr ""
msgstr "Restartuji protože selhal decoder"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed assembler"
msgstr ""
msgstr "Restartuji protože selhal assembler"
#. Warning message
#: sabnzbd/__init__.py
@@ -340,7 +343,7 @@ msgstr "Adresa serveru je vyžadována"
#: sabnzbd/cfg.py
msgid "%s is not a valid script"
msgstr ""
msgstr "%s neni validní skript"
#. Warning message
#: sabnzbd/config.py
@@ -408,10 +411,6 @@ msgstr ""
msgid "Decoder failure: Out of memory"
msgstr "Chyba dekodéru: nedostatek paměti"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "Detekováno UUencode, pouze yEnc je podporováno [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -516,12 +515,12 @@ msgstr "Vypínání"
#. Warning message
#: sabnzbd/downloader.py
msgid "Server %s is expiring in %s day(s)"
msgstr ""
msgstr "Server %s vyprší za %s dnů"
#. Warning message
#: sabnzbd/downloader.py
msgid "Server %s has used the specified quota"
msgstr ""
msgstr "Server %s vyčerpal nastavenou kvótu"
#: sabnzbd/emailer.py
msgid "Failed to connect to mail server"
@@ -618,11 +617,11 @@ msgstr "Chyba přesunu %s do %s"
#. Error message
#: sabnzbd/filesystem.py
msgid "Blocked attempt to create directory %s"
msgstr ""
msgstr "Zablokován pokus vytvořit adresář %s"
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgstr ""
msgstr "Odmítnuto spojení z:"
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
@@ -791,7 +790,7 @@ msgstr ""
#. Error message
#: sabnzbd/misc.py
msgid "Python script \"%s\" does not have execute (+x) permission set"
msgstr ""
msgstr "Python skript \"%s\" nemá nastaveno právo spuštění (+x)"
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
msgid "Running script"
@@ -808,11 +807,11 @@ msgstr ""
#: sabnzbd/newsunpack.py
msgid "Incomplete sequence of joinable files"
msgstr ""
msgstr "Nekompletní sekvence spojovaných souborů"
#: sabnzbd/newsunpack.py
msgid "File join of %s failed"
msgstr ""
msgstr "Spojování souboru %s selhalo"
#: sabnzbd/newsunpack.py
msgid "[%s] Error \"%s\" while joining files"
@@ -847,12 +846,12 @@ msgstr "Mazání %s selhalo!"
#: sabnzbd/newsunpack.py
msgid "Trying unrar with password \"%s\""
msgstr ""
msgstr "Zkouším unrar s heslem \"%s\""
#. Error message
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr ""
msgstr "Rozbalení selhalo, archiv vyžaduje heslo"
#: sabnzbd/newsunpack.py
msgid "Unpacking"
@@ -860,7 +859,7 @@ msgstr "Rozbaluji"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, unable to find %s"
msgstr ""
msgstr "Rozbalování selhalo, nezdařilo se najít %s"
#. Warning message
#: sabnzbd/newsunpack.py
@@ -924,11 +923,11 @@ msgstr "%s souborů v %s"
#. Error message
#: sabnzbd/newsunpack.py
msgid "Error \"%s\" while running unzip() on %s"
msgstr ""
msgstr "Chyba \"%s\" během běhu unzip() na %s"
#: sabnzbd/newsunpack.py
msgid "No 7za binary found, cannot unpack \"%s\""
msgstr ""
msgstr "7za nebylo nalezeno, nemohu rozbalit \"%s\""
#: sabnzbd/newsunpack.py
msgid "Trying 7zip with password \"%s\""
@@ -936,7 +935,7 @@ msgstr "Zkouším 7zip s heslem \"%s\""
#: sabnzbd/newsunpack.py
msgid "7ZIP set \"%s\" is incomplete, cannot unpack"
msgstr ""
msgstr "Sada 7zip \"%s\" je nekompletní, nelze rozbalit"
#: sabnzbd/newsunpack.py
msgid "Could not unpack %s"
@@ -1180,7 +1179,7 @@ msgstr "Nekompatibilní soubor fronty, nelze pokračovat"
#. Error message
#: sabnzbd/nzbqueue.py
msgid "Error loading %s, corrupt file detected"
msgstr ""
msgstr "Nelze nahrát %s, detekován porušený soubor"
#: sabnzbd/nzbqueue.py
msgid "NZB added to queue"
@@ -1333,15 +1332,15 @@ msgstr "Zkontrolovat sledovanou složku"
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Read all RSS feeds"
msgstr ""
msgstr "Číst všechny RSS kanály"
#: sabnzbd/osxmenu.py
msgid "Complete Folder"
msgstr ""
msgstr "Složka dokončených"
#: sabnzbd/osxmenu.py
msgid "Incomplete Folder"
msgstr ""
msgstr "Složka nedokončených"
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py
msgid "Troubleshoot"
@@ -1359,11 +1358,11 @@ msgstr "Restart bez přihlášení"
#: sabnzbd/osxmenu.py
msgid "Quit"
msgstr ""
msgstr "Vypnout"
#: sabnzbd/osxmenu.py
msgid "Queue First 10 Items"
msgstr ""
msgstr "Fronta prvních 10 položek"
#: sabnzbd/osxmenu.py
msgid "Empty"
@@ -1371,7 +1370,7 @@ msgstr "Prázdný"
#: sabnzbd/osxmenu.py
msgid "History Last 10 Items"
msgstr ""
msgstr "Historie posledních 10 položek"
#: sabnzbd/osxmenu.py
msgid "Go to wizard"
@@ -1383,7 +1382,7 @@ msgstr "Zastavuji..."
#: sabnzbd/panic.py
msgid "Problem with"
msgstr ""
msgstr "Problém s"
#: sabnzbd/panic.py
msgid ""
@@ -1431,7 +1430,7 @@ msgstr ""
#: sabnzbd/panic.py
msgid "SABnzbd detected a fatal error:"
msgstr ""
msgstr "SABnzbd detekovalo závažnou chybu:"
#: sabnzbd/panic.py
msgid ""
@@ -1452,7 +1451,7 @@ msgstr ""
#: sabnzbd/panic.py
msgid "Program did not start!"
msgstr ""
msgstr "Program se nespustil!"
#: sabnzbd/panic.py
msgid "Fatal error"
@@ -1467,7 +1466,7 @@ msgstr ""
#. Warning message
#: sabnzbd/panic.py
msgid "Cannot launch the browser, probably not found"
msgstr ""
msgstr "Nelze spustit webový prohlížeč, nejspíš nenalezen"
#: sabnzbd/panic.py
msgid "Access denied"
@@ -1480,7 +1479,7 @@ msgstr ""
#. Warning message
#: sabnzbd/postproc.py
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr ""
msgstr "Stará fronta nalezena, použijte Status->Repair pro konverzi fronty"
#. Warning message
#: sabnzbd/postproc.py
@@ -1555,7 +1554,7 @@ msgstr "Stahování selhalo"
#. Error message
#: sabnzbd/postproc.py
msgid "Cleanup of %s failed."
msgstr ""
msgstr "Čištění %s selhalo."
#: sabnzbd/postproc.py
msgid "Download Completed"
@@ -1564,7 +1563,7 @@ msgstr "Stahování dokončeno"
#. Error message
#: sabnzbd/postproc.py
msgid "Cannot create final folder %s"
msgstr ""
msgstr "Nelze vytvořit složku dokončených souborů %s"
#: sabnzbd/postproc.py
msgid "[%s] No par2 sets"
@@ -1576,7 +1575,7 @@ msgstr ""
#: sabnzbd/postproc.py
msgid "Verified successfully using SFV files"
msgstr ""
msgstr "Úspěšně ověřeno s využitím SFV souborů"
#: sabnzbd/postproc.py
msgid "Trying RAR-based verification"
@@ -1588,7 +1587,7 @@ msgstr ""
#: sabnzbd/postproc.py, sabnzbd/skintext.py
msgid "Passworded"
msgstr ""
msgstr "Zaheslováno"
#: sabnzbd/postproc.py
msgid "RAR files verified successfully"
@@ -1639,7 +1638,7 @@ msgstr ""
#: sabnzbd/rating.py
msgid "Server address"
msgstr ""
msgstr "Adresa serveru"
#: sabnzbd/rating.py, sabnzbd/skintext.py
msgid "API Key"
@@ -1654,7 +1653,7 @@ msgstr ""
#. Error message
#: sabnzbd/rss.py
msgid "Incorrect RSS feed description \"%s\""
msgstr ""
msgstr "Špatný popis RSS kanálu \"%s\""
#: sabnzbd/rss.py
msgid "Do not have valid authentication for feed %s"
@@ -1666,7 +1665,7 @@ msgstr ""
#: sabnzbd/rss.py
msgid "Failed to retrieve RSS from %s: %s"
msgstr ""
msgstr "Nezdařilo se stáhnout RSS z %s:%s"
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
msgid "Server %s uses an untrusted HTTPS certificate"
@@ -1675,24 +1674,24 @@ msgstr ""
#. Warning message
#: sabnzbd/rss.py
msgid "RSS Feed %s was empty"
msgstr ""
msgstr "RSS kanál %s byl prázdný"
#: sabnzbd/rss.py
msgid "Incompatible feed"
msgstr ""
msgstr "Nekompatibilní kanál"
#. Warning message
#: sabnzbd/rss.py
msgid "Empty RSS entry found (%s)"
msgstr ""
msgstr "Prázdný RSS záznam nalezen (%s)"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Show interface"
msgstr ""
msgstr "Zobrazit rozhraní"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr ""
msgstr "Otevřít složku s kompletními soubory"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
@@ -1726,7 +1725,7 @@ msgstr "Pozastavit na 6 hodin"
#. Shutdown SABnzbd - Config->Scheduling
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
msgid "Shutdown"
msgstr ""
msgstr "Vypnout"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Remaining"
@@ -1744,7 +1743,7 @@ msgstr ""
#. Warning message
#: sabnzbd/scheduler.py
msgid "Unknown action: %s"
msgstr ""
msgstr "Neznámá akce: %s"
#. Warning message
#: sabnzbd/scheduler.py
@@ -1755,17 +1754,17 @@ msgstr ""
#. "download item"
#: sabnzbd/skintext.py
msgid "Download"
msgstr ""
msgstr "Stahování"
#. PP phase "filejoin"
#: sabnzbd/skintext.py
msgid "Join files"
msgstr ""
msgstr "Spojit soubory"
#. PP phase "unpack"
#: sabnzbd/skintext.py
msgid "Unpack"
msgstr ""
msgstr "Rozbalit"
#. PP phase "script" - Notification Script settings
#: sabnzbd/skintext.py
@@ -1775,67 +1774,67 @@ msgstr "Skript"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
msgstr ""
msgstr "Zdroj"
#. PP Distribution over servers - Main menu item
#: sabnzbd/skintext.py
msgid "Servers"
msgstr ""
msgstr "Servery"
#. PP Failure message
#: sabnzbd/skintext.py
msgid "Failure"
msgstr ""
msgstr "Chyba"
#. PP status
#: sabnzbd/skintext.py
msgid "Failed"
msgstr ""
msgstr "Selhalo"
#. Queue and PP status
#: sabnzbd/skintext.py
msgid "Waiting"
msgstr ""
msgstr "Čekání"
#. PP status
#: sabnzbd/skintext.py
msgid "Repairing..."
msgstr ""
msgstr "Opravuji..."
#. PP status
#: sabnzbd/skintext.py
msgid "Extracting..."
msgstr ""
msgstr "Rozbaluji..."
#. PP status
#: sabnzbd/skintext.py
msgid "Moving..."
msgstr ""
msgstr "Přesouvám..."
#. PP status
#: sabnzbd/skintext.py
msgid "Running script..."
msgstr ""
msgstr "Spouštím skript..."
#. PP status
#: sabnzbd/skintext.py
msgid "Fetching extra blocks..."
msgstr ""
msgstr "Stahuji extra bloky..."
#. PP status
#: sabnzbd/skintext.py
msgid "Quick Check..."
msgstr ""
msgstr "Rychlá kontrola..."
#. PP status
#: sabnzbd/skintext.py
msgid "Verifying..."
msgstr ""
msgstr "Kontroluji..."
#. Pseudo-PP status, in reality used for Queue-status
#: sabnzbd/skintext.py
msgid "Downloading"
msgstr ""
msgstr "Stahování"
#: sabnzbd/skintext.py
msgid "Propagation delay"
@@ -1844,22 +1843,22 @@ msgstr ""
#. #: Config->Scheduler
#: sabnzbd/skintext.py
msgid "Task"
msgstr ""
msgstr "Úkol"
#. #: Config->Scheduler
#: sabnzbd/skintext.py
msgid "disable server"
msgstr ""
msgstr "deaktivovat server"
#. #: Config->Scheduler
#: sabnzbd/skintext.py
msgid "enable server"
msgstr ""
msgstr "povolit server"
#. #: Config->Scheduler
#: sabnzbd/skintext.py
msgid "Speedlimit"
msgstr ""
msgstr "Omezení rychlosti"
#. #: Config->Scheduler
#: sabnzbd/skintext.py
@@ -1879,17 +1878,17 @@ msgstr ""
#. #: Config->Scheduler
#: sabnzbd/skintext.py
msgid "Read RSS feeds"
msgstr ""
msgstr "Číst RSS kanály"
#. Config->Scheduler
#: sabnzbd/skintext.py
msgid "Remove failed jobs"
msgstr ""
msgstr "Odstranit neúspěšné úkoly"
#. Config->Scheduler
#: sabnzbd/skintext.py
msgid "Remove completed jobs"
msgstr ""
msgstr "Odstranit dokončené úkoly"
#. Config->Scheduler
#: sabnzbd/skintext.py
@@ -1919,17 +1918,17 @@ msgstr ""
#. Config->Scheduler
#: sabnzbd/skintext.py
msgid "Resume high prioirty jobs"
msgstr ""
msgstr "Obnovit úkoly s vysokou prioritou"
#. Config->Scheduler
#: sabnzbd/skintext.py
msgid "Enable quota management"
msgstr ""
msgstr "Povolit správu kvót"
#. Config->Scheduler
#: sabnzbd/skintext.py
msgid "Disable quota management"
msgstr ""
msgstr "Vypnout správu kvót"
#. Config->Scheduler
#: sabnzbd/skintext.py
@@ -1939,17 +1938,17 @@ msgstr "Pozastavit úkoly v kategorii"
#. Config->Scheduler
#: sabnzbd/skintext.py
msgid "Resume jobs with category"
msgstr ""
msgstr "Onovit úkoly v kategorii"
#. Prowl priority - Three way switch for duplicates
#: sabnzbd/skintext.py
msgid "Off"
msgstr ""
msgstr "Vypnuto"
#. Prowl priority
#: sabnzbd/skintext.py
msgid "Very Low"
msgstr ""
msgstr "Velmi nízká"
#. Prowl priority
#: sabnzbd/skintext.py
@@ -2114,11 +2113,11 @@ msgstr "Vlastní"
#: sabnzbd/skintext.py
msgid "Speed"
msgstr ""
msgstr "Rychlost"
#: sabnzbd/skintext.py
msgid "on"
msgstr ""
msgstr "zapnuto"
#. Config: startup parameters of SABnzbd - Notification Script settings
#: sabnzbd/skintext.py
@@ -2146,7 +2145,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Comment"
msgstr ""
msgstr "Komentář"
#: sabnzbd/skintext.py
msgid "Send"
@@ -2158,11 +2157,11 @@ msgstr "Zrušit"
#: sabnzbd/skintext.py
msgid "Other"
msgstr ""
msgstr "Ostatní"
#: sabnzbd/skintext.py
msgid "Report"
msgstr ""
msgstr "Výkaz"
#: sabnzbd/skintext.py
msgid "Video"
@@ -2174,11 +2173,11 @@ msgstr "Audio"
#: sabnzbd/skintext.py
msgid "Not used"
msgstr ""
msgstr "Nepoužito"
#: sabnzbd/skintext.py
msgid "or less"
msgstr ""
msgstr "nebo méně"
#: sabnzbd/skintext.py
msgid "Log in"
@@ -2204,12 +2203,12 @@ msgstr "Uložit"
#: sabnzbd/skintext.py
msgid "Saving.."
msgstr ""
msgstr "Ukládám..."
#. Used in confirmation popups
#: sabnzbd/skintext.py
msgid "Are you sure?"
msgstr ""
msgstr "Jste si jistí?"
#. Used in confirmation popups
#: sabnzbd/skintext.py
@@ -2219,7 +2218,7 @@ msgstr "Smazat všechny stažené soubory?"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Home"
msgstr ""
msgstr "Domů"
#. Main menu item
#: sabnzbd/skintext.py
@@ -2234,7 +2233,7 @@ msgstr "Stav"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Help"
msgstr ""
msgstr "Nápověda"
#. Main menu item
#: sabnzbd/skintext.py
@@ -2249,12 +2248,12 @@ msgstr "IRC"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Issues"
msgstr ""
msgstr "Problémy"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Support the project, Donate!"
msgstr ""
msgstr "Podpořte projekt!"
#. Main menu item
#: sabnzbd/skintext.py
@@ -2274,7 +2273,7 @@ msgstr "Přepínače"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Scheduling"
msgstr ""
msgstr "Plánování"
#. Main menu item
#: sabnzbd/skintext.py
@@ -2304,7 +2303,7 @@ msgstr "Řazení"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Special"
msgstr ""
msgstr "Speciální"
#. Main menu item
#: sabnzbd/skintext.py
@@ -2408,7 +2407,7 @@ msgstr "Zadejte URL"
#. Queue page selection menu
#: sabnzbd/skintext.py
msgid "On queue finish"
msgstr ""
msgstr "Při dokončení fronty"
#. Queue page end-of-queue action
#: sabnzbd/skintext.py
@@ -2438,7 +2437,7 @@ msgstr "Limit rychlosti"
#. Queue page table column header - Config->RSS table column header
#: sabnzbd/skintext.py
msgid "Order"
msgstr ""
msgstr "Pořadí"
#. Queue page table column header - Job details page
#: sabnzbd/skintext.py
@@ -2478,7 +2477,7 @@ msgstr "Skripty"
#. Confirmation popup
#: sabnzbd/skintext.py
msgid "Delete all items from the queue?"
msgstr ""
msgstr "Smazat všechny položky z fronty?"
#. Queue page button
#: sabnzbd/skintext.py
@@ -2508,22 +2507,22 @@ msgstr "Odstranit NZB a smazat soubory"
#. Queue page, as in "4G *of* 10G"
#: sabnzbd/skintext.py
msgid "of"
msgstr ""
msgstr "z"
#. Caption for missing articles in Queue
#: sabnzbd/skintext.py
msgid "Missing articles"
msgstr ""
msgstr "Chybějící části"
#. Remaining quota (displayed in Queue)
#: sabnzbd/skintext.py
msgid "Quota left"
msgstr ""
msgstr "Zbývající kvóta"
#. Manual reset of quota
#: sabnzbd/skintext.py
msgid "manual"
msgstr ""
msgstr "ručně"
#: sabnzbd/skintext.py
msgid "Reset Quota now"
@@ -2532,7 +2531,7 @@ msgstr "Vynulovat kvótu"
#. Confirmation popup
#: sabnzbd/skintext.py
msgid "Delete all completed items from History?"
msgstr ""
msgstr "Smazat věchny dokončené položky z historie?"
#. Button/link hiding History job details
#: sabnzbd/skintext.py
@@ -2613,7 +2612,7 @@ msgstr ""
#. Status page button
#: sabnzbd/skintext.py
msgid "Force Disconnect"
msgstr ""
msgstr "Vynucené odpojení"
#: sabnzbd/skintext.py
msgid ""
@@ -2694,7 +2693,7 @@ msgstr ""
#. column header
#: sabnzbd/skintext.py
msgid "Type"
msgstr ""
msgstr "Typ"
#. Status page, indicator that server is enabled
#: sabnzbd/skintext.py
@@ -2707,7 +2706,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Connection failed!"
msgstr ""
msgstr "Spojení selhalo!"
#: sabnzbd/skintext.py
msgid "Local IPv4 address"
@@ -2744,7 +2743,7 @@ msgstr "Rychlost složko pro dokončené"
#: sabnzbd/skintext.py
msgid "Writing speed"
msgstr ""
msgstr "Rychlost zápisu"
#: sabnzbd/skintext.py
msgid "Could not write. Check that the directory is writable."
@@ -2794,7 +2793,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Advanced"
msgstr ""
msgstr "Pokročilé"
#: sabnzbd/skintext.py
msgid ""
@@ -2865,15 +2864,15 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Restarting SABnzbd..."
msgstr ""
msgstr "Restartuji SABnzbd..."
#: sabnzbd/skintext.py
msgid "Changes will require a SABnzbd restart!"
msgstr ""
msgstr "Změny vyžadují restart SABnzbd!"
#: sabnzbd/skintext.py
msgid "SABnzbd Web Server"
msgstr ""
msgstr "SABnzbd webový server"
#: sabnzbd/skintext.py
msgid "SABnzbd Host"
@@ -2986,7 +2985,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "RSS Checking Interval"
msgstr ""
msgstr "Interval konctroly RSS"
#: sabnzbd/skintext.py
msgid ""
@@ -3028,7 +3027,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "History Retention"
msgstr ""
msgstr "Retence historie"
#: sabnzbd/skintext.py
msgid ""
@@ -3038,19 +3037,19 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Keep all jobs"
msgstr ""
msgstr "Zachovat všechny úkoly"
#: sabnzbd/skintext.py
msgid "Keep maximum number of completed jobs"
msgstr ""
msgstr "Maximální počet dokončených úkolů"
#: sabnzbd/skintext.py
msgid "Keep completed jobs maximum number of days"
msgstr ""
msgstr "Počet dnů pro zachování dokončených ukolů "
#: sabnzbd/skintext.py
msgid "Do not keep any completed jobs"
msgstr ""
msgstr "Nauchovávat dokončené úkoly"
#: sabnzbd/skintext.py
msgid "Jobs"
@@ -3115,7 +3114,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "No access"
msgstr ""
msgstr "Bez přístupu"
#: sabnzbd/skintext.py
msgid "Add NZB files "
@@ -3611,6 +3610,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr ""
@@ -3854,6 +3861,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr ""
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4034,6 +4052,12 @@ msgstr ""
msgid "Read All Feeds Now"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr ""
@@ -4063,11 +4087,11 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Send RSS notifications"
msgstr ""
msgstr "Odeslat RSS upozornění"
#: sabnzbd/skintext.py
msgid "Send email when an RSS feed adds jobs to the queue."
msgstr ""
msgstr "Odeslat email když RSS kanál přidá úkol do fronty."
#: sabnzbd/skintext.py
msgid "SMTP Server"
@@ -4310,7 +4334,13 @@ msgstr ""
msgid "X"
msgstr ""
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr ""
@@ -4338,7 +4368,8 @@ msgstr ""
msgid "Example"
msgstr ""
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4490,7 +4521,8 @@ msgstr ""
msgid "No folders"
msgstr ""
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr ""

View File

@@ -411,10 +411,6 @@ msgstr "Forkert logning i historiken av %s"
msgid "Decoder failure: Out of memory"
msgstr "Dekoder fejl: Ikke mere hukommelse"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3701,6 +3697,14 @@ msgstr ""
"Kontroller certifikater, når du opretter forbindelse til indeksører og RSS-"
"kilder ved hjælp HTTPS."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Server"
@@ -3949,6 +3953,17 @@ msgstr "Streng"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 er højeste prioritet, 100 er den laveste prioritet"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4134,6 +4149,12 @@ msgstr "Hentet"
msgid "Read All Feeds Now"
msgstr "Læs alle Feeds nu"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "E-mail påmindelse når job er fuldført"
@@ -4416,7 +4437,13 @@ msgstr "Indekseringen kategorier/grupper"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Serie sortering"
@@ -4444,7 +4471,8 @@ msgstr "Forudindstillinger"
msgid "Example"
msgstr "Eksempel"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "Film sortering"
@@ -4596,7 +4624,8 @@ msgstr "I mappe"
msgid "No folders"
msgstr "Ingen mappe"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Dato sortering"

View File

@@ -9,12 +9,13 @@
# Ben Hecht <benjamin.hecht@me.com>, 2021
# Safihre <safihre@sabnzbd.org>, 2021
# Manuel C. Senn, 2021
# Andreas Kames, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-develop\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Manuel C. Senn, 2021\n"
"Last-Translator: Andreas Kames, 2021\n"
"Language-Team: German (https://www.transifex.com/sabnzbd/teams/111101/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -429,10 +430,6 @@ msgstr "Ungültiges Stufen-Protokoll im Verlauf für %s"
msgid "Decoder failure: Out of memory"
msgstr "Decoder Fehler: Nicht genügend Speicher"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "UUencode erkannt, aber nur yEnc encoding ist untertützt [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -538,7 +535,7 @@ msgstr "Wird beendet …"
#. Warning message
#: sabnzbd/downloader.py
msgid "Server %s is expiring in %s day(s)"
msgstr "Server %s läuft in %s tag(en) ab"
msgstr "Server %s läuft in %s Tag(en) ab"
#. Warning message
#: sabnzbd/downloader.py
@@ -1688,7 +1685,7 @@ msgstr "RAR-Datei konnten nicht überprüft werden"
#: sabnzbd/postproc.py
msgid "Trying RAR renamer"
msgstr "Versuche RAR Umbenenner"
msgstr "Versuche RAR-Umbenenner"
#. Warning message
#: sabnzbd/postproc.py
@@ -3818,6 +3815,14 @@ msgstr ""
"Überprüfe Zertifikate bei Verbindungen zu Indexern und RSS-Quellen über "
"HTTPS."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Server"
@@ -4077,6 +4082,19 @@ msgstr "Strikt"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 ist die höchste, 99 die niedrigste Priorität"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr "Benötigt"
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
"Im Fall von Verbindungsausfällen wird die Downloadwarteschlange für ein paar"
" Minuten pausiert, statt diesen Server zu überspringen"
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4194,7 +4212,7 @@ msgstr "Bearbeiten"
#. Config->RSS when will be the next RSS scan
#: sabnzbd/skintext.py
msgid "Next scan at"
msgstr "Nächster scan um"
msgstr "Nächster Scan um"
#. Config->RSS table column header
#: sabnzbd/skintext.py
@@ -4261,6 +4279,12 @@ msgstr "Heruntergeladen"
msgid "Read All Feeds Now"
msgstr "Jetzt alle Feeds lesen"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Email-Benachrichtigung beim Fertigstellen von Aufträgen"
@@ -4547,7 +4571,13 @@ msgstr "Indexer Kategorien/Gruppen"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr "Wähle mindestens 1 Kategorie"
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Sortieren von TV-Serien"
@@ -4575,7 +4605,8 @@ msgstr "Voreinstellungen"
msgid "Example"
msgstr "Beispiel"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "Film Sortierung"
@@ -4727,7 +4758,8 @@ msgstr "In Ordnern"
msgid "No folders"
msgstr "Keine Ordner"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Sortieren nach Datum"
@@ -4925,7 +4957,7 @@ msgstr "Weitere Warteschlangen Spalten"
#: sabnzbd/skintext.py
msgid "Extra history columns"
msgstr "Weitere Verlauf Spalten"
msgstr "Weitere Verlaufspalten"
#: sabnzbd/skintext.py
msgid "page"

View File

@@ -428,10 +428,6 @@ msgstr "Registro de etapa invalido para transferencia terminada %s"
msgid "Decoder failure: Out of memory"
msgstr "Fallo del decodificador: no hay memoria"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "Se ha detectado UUencode, solo se permite codificación yEnc [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3802,6 +3798,14 @@ msgstr ""
"Verificar certificados al conectarse a indexadores y fuentes RSS usando "
"HTTPS."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Servidor"
@@ -4059,6 +4063,17 @@ msgstr "Estricto"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 El prioridad más alta, 99 es la prioridad más baja"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4245,6 +4260,12 @@ msgstr "Descargado"
msgid "Read All Feeds Now"
msgstr "Leer todas las fuentes ahora"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Notificación por email al terminar"
@@ -4531,7 +4552,13 @@ msgstr "Categorías Indexer / Grupos"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Ordenación de Series"
@@ -4559,7 +4586,8 @@ msgstr "Preajustes"
msgid "Example"
msgstr "Ejemplo"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "Clasificación de películas"
@@ -4711,7 +4739,8 @@ msgstr "En directorios"
msgid "No folders"
msgstr "Sin Directorios"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Ordenar por fecha"

View File

@@ -408,10 +408,6 @@ msgstr "Virheellinen tila lokihistoriassa kohteelle %s"
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3703,6 +3699,14 @@ msgstr ""
"Varmenna sertifikaatit yhdistettäessä indeksoijiin ja RSS-lähteisiin HTTPS "
"protokollan avulla."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Palvelin"
@@ -3950,6 +3954,17 @@ msgstr "Tiukka"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 on suurin prioriteetti, 99 on pienin prioriteetti"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4134,6 +4149,12 @@ msgstr "Ladattu"
msgid "Read All Feeds Now"
msgstr "Lue kaikki syötteet nyt"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Sähköposti-ilmoitus onnistuneesta latauksesta"
@@ -4416,7 +4437,13 @@ msgstr "Indeksoijan kategoriat / ryhmä"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Sarjojen lajittelu"
@@ -4444,7 +4471,8 @@ msgstr "Esiasetukset"
msgid "Example"
msgstr "Esimerkki"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4596,7 +4624,8 @@ msgstr "Kansioissa"
msgid "No folders"
msgstr "Ei kansioita"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Päivämäärän lajittelu"

View File

@@ -429,10 +429,6 @@ msgstr "Étape de journalisation invalide dans l'historique pour %s"
msgid "Decoder failure: Out of memory"
msgstr "Échec du décodeur : mémoire insuffisante"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "UUencode détecté, seul l'encodage yEnc est pris en charge [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3821,6 +3817,15 @@ msgstr ""
"Vérifier les certificats lors de la connexion aux indexeurs et sources RSS "
"utilisant HTTPS."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr "Proxy SOCKS5"
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
"Utiliser le proxy SOCKS5 spécifié pour toutes les connexions sortantes."
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Serveur"
@@ -4080,6 +4085,19 @@ msgstr "Strict"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 est la priorité la plus élevée, 99 est la priorité la plus faible"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr "Obligatoire"
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
"En cas d'échecs de connexion, la file d'attente des téléchargements sera "
"mise en pause pendant quelques minutes au lieu de passer au serveur suivant."
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4267,6 +4285,14 @@ msgstr "Téléchargé"
msgid "Read All Feeds Now"
msgstr "Lire tous les flux maintenant"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
"Si seule la catégorie <em>Défaut</em> est sélectionnée, les notifications "
"seront activées pour les tâches de toutes les catégories."
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Notification par email lorsque des téléchargements sont terminés"
@@ -4555,7 +4581,13 @@ msgstr "Catégories de l'indexeur / Groupes"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr "Sélectionnez au moins une catégorie."
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Tri des séries"
@@ -4583,7 +4615,8 @@ msgstr "Modèles prédéfinis"
msgid "Example"
msgstr "Exemple"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "Tri des films"
@@ -4736,7 +4769,8 @@ msgstr "Dans les dossiers"
msgid "No folders"
msgstr "Pas de dossiers"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Tri par date"

View File

@@ -404,10 +404,6 @@ msgstr "רישום של אירוע בלתי תקף בהיסטוריה עבור %
msgid "Decoder failure: Out of memory"
msgstr "כישלון מפענח: אין זיכרון"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "UUencode התגלה, רק קידוד yEnc נתמך [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3699,6 +3695,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr "וודא תעודות בעת התחברות אל מדדנים ומקורות RSS ע״י שימוש ב־HTTPS."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "שרת"
@@ -3950,6 +3954,19 @@ msgstr "קפדני"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 הוא העדיפות הגבוהה ביותר, 100 הוא העדיפות הנמוכה ביותר"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr "דרוש"
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
"במקרה של כישלונות חיבור, תור ההורדות יושהה למשך כמה דקות במקום דילוג על השרת"
" הזה"
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4133,6 +4150,14 @@ msgstr "הוּרד"
msgid "Read All Feeds Now"
msgstr "קרא את כל ההזנות כעת"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
"אם רק קטגורית <em>ברירת המחדל</em> נבחרת, התראות מאופשרות עבור עבודות בכל "
"הקטגוריות."
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "התראת דוא״ל בעת השלמת עבודה"
@@ -4413,7 +4438,13 @@ msgstr "קטגוריות / קבוצות של מדדן"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr "בחר קטגוריה 1 לפחות."
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "מיון סדרות"
@@ -4441,7 +4472,8 @@ msgstr "קדם־קביעות"
msgid "Example"
msgstr "דוגמה"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "מיון סרטים"
@@ -4593,7 +4625,8 @@ msgstr "בתיקיות"
msgid "No folders"
msgstr "אין תיקיות"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "מיון תאריכים"

View File

@@ -405,10 +405,6 @@ msgstr "Ugyldig scenen logging i historien for %s"
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3679,6 +3675,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Server"
@@ -3927,6 +3931,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 er høyeste prioritet, 99 er laveste prioritet"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4111,6 +4126,12 @@ msgstr "Nedlastet"
msgid "Read All Feeds Now"
msgstr "Les alle kilder nå"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "E-Post varsling når nedlasting er ferdig"
@@ -4389,7 +4410,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Seriesortering"
@@ -4417,7 +4444,8 @@ msgstr "For innstillinger"
msgid "Example"
msgstr "Eksempel"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4569,7 +4597,8 @@ msgstr "I mappe"
msgid "No folders"
msgstr "Ingen mappe"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Dato sortering"

View File

@@ -420,10 +420,6 @@ msgstr "Ongeldig loggen van fase in geschiedenis voor %s"
msgid "Decoder failure: Out of memory"
msgstr "Decoder fout: onvoldoende geheugen"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "UUencode gevonden, alleen yEnc codering wordt ondersteund [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3771,6 +3767,14 @@ msgstr ""
"Controleer certificaten bij beveiligde verbindingen met indexers en RSS-"
"feeds."
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr "SOCKS5 Proxy"
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr "Gebruik een SOCKS5 proxy server voor alle uitgaande verbindingen."
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Server"
@@ -4033,6 +4037,19 @@ msgstr "Strikt"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 is de hoogste en 99 de laagste prioriteit"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr "Vereist"
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
"Het downloaden zal een aantal minuten gepauzeerd worden wanneer deze server "
"niet beschikbaar is."
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4219,6 +4236,14 @@ msgstr "Gedownload"
msgid "Read All Feeds Now"
msgstr "Alle feeds nu uitlezen"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
"Notificaties zullen worden verstuurd voor alle downloads als de "
"<em>Standaard</em> categorie geselecteerd is."
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Stuur een e-mail na het voltooien van elke download"
@@ -4505,7 +4530,13 @@ msgstr "Indexer Categorieën / Groepen"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr "Selecteer minstens 1 categorie."
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Serie sorteren"
@@ -4533,7 +4564,8 @@ msgstr "Standaardinstellingen"
msgid "Example"
msgstr "Voorbeeld"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "Film sorteren"
@@ -4685,7 +4717,8 @@ msgstr "In mappen"
msgid "No folders"
msgstr "Geen mappen"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Datum sorteren"
@@ -4724,7 +4757,7 @@ msgstr "eigenschap"
#: sabnzbd/skintext.py
msgid "GuessIt Property"
msgstr "GuessIt eigenschap"
msgstr "GuessIt Eigenschap"
#: sabnzbd/skintext.py
msgid "GuessIt.Property"

View File

@@ -400,10 +400,6 @@ msgstr "Nieprawidłowy log etapu w historii dla %s"
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3686,6 +3682,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Serwer"
@@ -3935,6 +3939,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 oznacza najwyższy priorytet, 99 - najniższy"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4119,6 +4134,12 @@ msgstr "Pobrane"
msgid "Read All Feeds Now"
msgstr "Czytaj teraz wszystkie kanały"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Powiadomienia email po zakończeniu zadania"
@@ -4397,7 +4418,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Sortowanie seriali"
@@ -4425,7 +4452,8 @@ msgstr "Predefiniowane"
msgid "Example"
msgstr "Przykład"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4577,7 +4605,8 @@ msgstr "W katalogach"
msgid "No folders"
msgstr "Brak katalogów"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Sortowanie według daty"

View File

@@ -406,10 +406,6 @@ msgstr "Registro inválido de etapa no histórico para %s"
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3691,6 +3687,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Servidor"
@@ -3939,6 +3943,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 é a prioridade mais alta, 99 é a prioridade mais baixa"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4123,6 +4138,12 @@ msgstr "Baixados"
msgid "Read All Feeds Now"
msgstr "Ler Todos os Feeds Agora"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Notificar por e-mail na conclusão da tarefa"
@@ -4401,7 +4422,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Ordenação de Séries"
@@ -4429,7 +4456,8 @@ msgstr "Predefinições"
msgid "Example"
msgstr "Exemplo"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4581,7 +4609,8 @@ msgstr "Em pastas"
msgid "No folders"
msgstr "Sem pastas"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Ordenação por data"

View File

@@ -417,10 +417,6 @@ msgstr "Jurnal istoric stagii invalid pentru %s"
msgid "Decoder failure: Out of memory"
msgstr "Eroare decodare: lipsă memorie"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr "UUencode detectat, este suportată doar codarea yEnc [%s]"
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3717,6 +3713,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Server"
@@ -3967,6 +3971,17 @@ msgid "0 is highest priority, 100 is the lowest priority"
msgstr ""
"0 este prioritatea cea mai ridicată, 99 este prioritatea cea mai scăzută"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4151,6 +4166,12 @@ msgstr "Descărcate"
msgid "Read All Feeds Now"
msgstr "Citeşte Toate Fluxurile Acum"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Notificări Email Sarcină Terminată"
@@ -4428,7 +4449,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Sortare Seriale"
@@ -4456,7 +4483,8 @@ msgstr "Presetări"
msgid "Example"
msgstr "Exemplu"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4608,7 +4636,8 @@ msgstr "În dosare"
msgid "No folders"
msgstr "Fără dosare"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Sortare Dată"

View File

@@ -404,10 +404,6 @@ msgstr "Недопустимый этап ведения журнала для %
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3681,6 +3677,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Сервер"
@@ -3928,6 +3932,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr ""
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4112,6 +4127,12 @@ msgstr "Загружено"
msgid "Read All Feeds Now"
msgstr "Прочитать все ленты"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Уведомление по электронной почте после завершения задания"
@@ -4396,7 +4417,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Сортировка сериалов"
@@ -4424,7 +4451,8 @@ msgstr "Готовые шаблоны"
msgid "Example"
msgstr "Пример"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4576,7 +4604,8 @@ msgstr "В папках"
msgid "No folders"
msgstr "Без папок"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Сортировка даты"

View File

@@ -402,10 +402,6 @@ msgstr "Погрешне етапе извештаја можете наћи у
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3666,6 +3662,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Сервер"
@@ -3914,6 +3918,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 je najveći prioritet, 99 je najniži prioritet"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4097,6 +4112,12 @@ msgstr "Преузето"
msgid "Read All Feeds Now"
msgstr "Сада читај све фидове"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "Нотификација е-поштом при завршетку рада"
@@ -4374,7 +4395,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Сортирање серије"
@@ -4402,7 +4429,8 @@ msgstr "Предподешавања"
msgid "Example"
msgstr "Примери"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4554,7 +4582,8 @@ msgstr "У фасциклама"
msgid "No folders"
msgstr "Нема фасцикле"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Сређивање датумом"

View File

@@ -403,10 +403,6 @@ msgstr "Felaktig loggning i historiken av %s"
msgid "Decoder failure: Out of memory"
msgstr ""
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3678,6 +3674,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr ""
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "Server"
@@ -3926,6 +3930,17 @@ msgstr ""
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 är högst prioritet, 99 är lägst prioritet"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4110,6 +4125,12 @@ msgstr "Nedladdad"
msgid "Read All Feeds Now"
msgstr "Läsa Alla Flöden Nu"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "E-post notifiering när jobb är slutfört"
@@ -4387,7 +4408,13 @@ msgstr ""
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "Seriesortering"
@@ -4415,7 +4442,8 @@ msgstr "Förinställningar"
msgid "Example"
msgstr "Exempel"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr ""
@@ -4567,7 +4595,8 @@ msgstr "In mapp"
msgid "No folders"
msgstr "Ingen mapp"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "Datum sortering"

View File

@@ -400,10 +400,6 @@ msgstr "%s 历史信息中 stage 日志无效"
msgid "Decoder failure: Out of memory"
msgstr "解码器失败:内存不足"
#: sabnzbd/decoder.py
msgid "UUencode detected, only yEnc encoding is supported [%s]"
msgstr ""
#. Warning message
#: sabnzbd/decoder.py
msgid "Unknown Error while decoding %s"
@@ -3621,6 +3617,14 @@ msgid ""
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
msgstr "当用 HTTPS 方式连接索引和RSS源时验证证书。"
#: sabnzbd/skintext.py
msgid "SOCKS5 Proxy"
msgstr ""
#: sabnzbd/skintext.py
msgid "Use the specified SOCKS5 proxy for all outgoing connections."
msgstr ""
#: sabnzbd/skintext.py
msgid "Server"
msgstr "服务器"
@@ -3864,6 +3868,17 @@ msgstr "严格"
msgid "0 is highest priority, 100 is the lowest priority"
msgstr "0 为最高优先级100 为最低优先级"
#. Server required tickbox
#: sabnzbd/skintext.py
msgid "Required"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"In case of connection failures, the download queue will be paused for a few "
"minutes instead of skipping this server"
msgstr ""
#. Server optional tickbox
#: sabnzbd/skintext.py
msgid "Optional"
@@ -4046,6 +4061,12 @@ msgstr "已下载"
msgid "Read All Feeds Now"
msgstr "立即读取全部 Feed"
#: sabnzbd/skintext.py
msgid ""
"If only the <em>Default</em> category is selected, notifications are enabled"
" for jobs in all categories."
msgstr ""
#: sabnzbd/skintext.py
msgid "Email Notification On Job Completion"
msgstr "任务完成 Email 通知"
@@ -4324,7 +4345,13 @@ msgstr "索引 Categories / Groups"
msgid "X"
msgstr "X"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Select at least 1 category."
msgstr ""
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Series Sorting"
msgstr "TV 排序"
@@ -4352,7 +4379,8 @@ msgstr "预设"
msgid "Example"
msgstr "示例"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Movie Sorting"
msgstr "电影排序"
@@ -4504,7 +4532,8 @@ msgstr "分文件夹"
msgid "No folders"
msgstr "不分文件夹"
#: sabnzbd/skintext.py
#. Warning message
#: sabnzbd/skintext.py, sabnzbd/sorting.py
msgid "Date Sorting"
msgstr "日期排序"

View File

@@ -8,6 +8,7 @@ cherrypy
portend
chardet
notify2
PySocks
puremagic
guessit>=3.1.0

View File

@@ -24,11 +24,13 @@ import ctypes.util
import gzip
import time
import socket
import socks
import cherrypy
import cherrypy._cpreqbody
import platform
import sys
import ssl
import urllib.parse
from threading import Lock, Thread, Condition
from typing import Any, AnyStr, Optional, Union
@@ -69,8 +71,6 @@ elif os.name == "posix":
# Parse macOS version numbers
if platform.system().lower() == "darwin":
DARWIN = True
# 12 = Sierra, 11 = ElCaptain, 10 = Yosemite, 9 = Mavericks, 8 = MountainLion
DARWIN_VERSION = int(platform.mac_ver()[0].split(".")[1])
MACOSLIBC = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) # the MacOS C library
try:
import Foundation
@@ -121,10 +121,6 @@ from sabnzbd.constants import (
DEFAULT_PRIORITY,
VALID_ARCHIVES,
REPAIR_REQUEST,
QUEUE_FILE_NAME,
QUEUE_VERSION,
QUEUE_FILE_TMPL,
Status,
)
import sabnzbd.utils.ssdp
@@ -201,7 +197,6 @@ CMDLINE = " ".join(['"%s"' % arg for arg in sys.argv])
__INITIALIZED__ = False
__SHUTTING_DOWN__ = False
##############################################################################
# Signal Handler
##############################################################################
@@ -274,6 +269,7 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
cfg.https_cert.callback(guard_restart)
cfg.https_key.callback(guard_restart)
cfg.enable_https.callback(guard_restart)
cfg.socks5_proxy_url.callback(guard_restart)
cfg.top_only.callback(guard_top_only)
cfg.pause_on_post_processing.callback(guard_pause_on_pp)
cfg.quota_size.callback(guard_quota_size)
@@ -1082,6 +1078,21 @@ def wait_for_download_folder():
time.sleep(2.0)
def set_socks5_proxy():
if cfg.socks5_proxy_url():
proxy = urllib.parse.urlparse(cfg.socks5_proxy_url())
logging.info("Using Socks5 proxy %s:%s", proxy.hostname, proxy.port)
socks.set_default_proxy(
socks.SOCKS5,
proxy.hostname,
proxy.port,
True, # use remote DNS, default
proxy.username,
proxy.password,
)
socket.socket = socks.socksocket
def test_ipv6():
"""Check if external IPv6 addresses are reachable"""
if not cfg.selftest_host():

View File

@@ -63,11 +63,10 @@ from sabnzbd.misc import (
from sabnzbd.filesystem import diskspace, get_ext, clip_path, remove_all, list_scripts
from sabnzbd.encoding import xml_name, utob
from sabnzbd.utils.servertests import test_nntp_server_dict
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6, addresslookup
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6, addresslookup, active_socks5_proxy
from sabnzbd.database import build_history_info, unpack_history_info, HistoryDB
from sabnzbd.lang import is_rtl
import sabnzbd.notifier
import sabnzbd.rss
import sabnzbd.emailer
import sabnzbd.sorting
@@ -1336,6 +1335,7 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
# Dashboard: Connection information
if not int_conv(skip_dashboard):
info["active_socks5_proxy"] = active_socks5_proxy()
info["localipv4"] = localipv4()
info["publicipv4"] = publicipv4()
info["ipv6"] = ipv6()

View File

@@ -24,8 +24,8 @@ import queue
import logging
import re
from threading import Thread
from time import sleep
import hashlib
import ctypes
from typing import Tuple, Optional, List
import sabnzbd
@@ -41,7 +41,6 @@ from sabnzbd.filesystem import (
from sabnzbd.constants import Status, GIGI, MAX_ASSEMBLER_QUEUE
import sabnzbd.cfg as cfg
from sabnzbd.nzbstuff import NzbObject, NzbFile
import sabnzbd.downloader
import sabnzbd.par2file as par2file
import sabnzbd.utils.rarfile as rarfile
@@ -119,6 +118,8 @@ class Assembler(Thread):
logging.error(T("Disk error on creating file %s"), clip_path(filepath))
# Log traceback
logging.info("Traceback: ", exc_info=True)
if sabnzbd.WIN32:
logging.info("Winerror: %s", hex(ctypes.windll.ntdll.RtlGetLastNtStatus() + 2 ** 32))
# Pause without saving
sabnzbd.Downloader.pause()
continue

View File

@@ -233,7 +233,7 @@ rating_filter_pause_keywords = OptionStr("misc", "rating_filter_pause_keywords")
##############################################################################
enable_tv_sorting = OptionBool("misc", "enable_tv_sorting", False)
tv_sort_string = OptionStr("misc", "tv_sort_string")
tv_categories = OptionList("misc", "tv_categories", "")
tv_categories = OptionList("misc", "tv_categories", ["tv"])
enable_movie_sorting = OptionBool("misc", "enable_movie_sorting", False)
movie_sort_string = OptionStr("misc", "movie_sort_string")
@@ -289,7 +289,7 @@ api_warnings = OptionBool("misc", "api_warnings", True, protect=True)
disable_key = OptionBool("misc", "disable_api_key", False, protect=True)
no_penalties = OptionBool("misc", "no_penalties", False)
x_frame_options = OptionBool("misc", "x_frame_options", True)
require_modern_tls = OptionBool("misc", "require_modern_tls", False)
allow_old_ssl_tls = OptionBool("misc", "allow_old_ssl_tls", False)
num_decoders = OptionNumber("misc", "num_decoders", 3)
# Text values
@@ -311,6 +311,7 @@ local_ranges = OptionList("misc", "local_ranges", protect=True)
max_url_retries = OptionNumber("misc", "max_url_retries", 10, 1)
downloader_sleep_time = OptionNumber("misc", "downloader_sleep_time", 10, 0)
ssdp_broadcast_interval = OptionNumber("misc", "ssdp_broadcast_interval", 15, 1, 600)
socks5_proxy_url = OptionStr("misc", "socks5_proxy_url")
##############################################################################

View File

@@ -409,6 +409,7 @@ class ConfigServer:
self.ssl_verify = OptionNumber(name, "ssl_verify", 2, add=False)
self.ssl_ciphers = OptionStr(name, "ssl_ciphers", add=False)
self.enable = OptionBool(name, "enable", True, add=False)
self.required = OptionBool(name, "required", False, add=False)
self.optional = OptionBool(name, "optional", False, add=False)
self.retention = OptionNumber(name, "retention", 0, add=False)
self.expire_date = OptionStr(name, "expire_date", add=False)
@@ -442,6 +443,7 @@ class ConfigServer:
"ssl_ciphers",
"send_group",
"enable",
"required",
"optional",
"retention",
"expire_date",
@@ -476,6 +478,7 @@ class ConfigServer:
output_dict["ssl_verify"] = self.ssl_verify()
output_dict["ssl_ciphers"] = self.ssl_ciphers()
output_dict["enable"] = self.enable()
output_dict["required"] = self.required()
output_dict["optional"] = self.optional()
output_dict["retention"] = self.retention()
output_dict["expire_date"] = self.expire_date()

View File

@@ -17,7 +17,6 @@
import os
from collections import namedtuple
from re import compile
CONFIG_VERSION = 19
@@ -122,6 +121,7 @@ VALID_NZB_FILES = (".nzb", ".gz", ".bz2")
CHEETAH_DIRECTIVES = {"directiveStartToken": "<!--#", "directiveEndToken": "#-->", "prioritizeSearchListOverSelf": True}
IGNORED_FOLDERS = ("@eaDir", ".appleDouble")
IGNORED_MOVIE_FOLDERS = ("video_ts", "audio_ts", "bdmv")
EXCLUDED_GUESSIT_PROPERTIES = [
"part",

View File

@@ -22,12 +22,15 @@ sabnzbd.decoder - article decoder
import logging
import hashlib
import queue
import binascii
from io import BytesIO
from threading import Thread
from typing import Tuple, List, Optional
import sabnzbd
import sabnzbd.cfg as cfg
from sabnzbd.constants import SABYENC_VERSION_REQUIRED
from sabnzbd.encoding import ubtou
from sabnzbd.nzbstuff import Article
from sabnzbd.misc import match_str
@@ -45,17 +48,18 @@ except ImportError:
SABYENC_ENABLED = False
class CrcError(Exception):
def __init__(self, needcrc: int, gotcrc: int, data: bytes):
class BadData(Exception):
def __init__(self, data: bytes):
super().__init__()
self.needcrc = needcrc
self.gotcrc = gotcrc
self.data = data
class BadYenc(Exception):
def __init__(self):
super().__init__()
pass
class BadUu(Exception):
pass
class Decoder:
@@ -140,7 +144,11 @@ class DecoderWorker(Thread):
if sabnzbd.LOG_ALL:
logging.debug("Decoding %s", art_id)
decoded_data = decode(article, raw_data)
if article.nzf.type == "uu":
decoded_data = decode_uu(article, raw_data)
else:
decoded_data = decode_yenc(article, raw_data)
article_success = True
except MemoryError:
@@ -154,15 +162,20 @@ class DecoderWorker(Thread):
sabnzbd.NzbQueue.reset_try_lists(article)
continue
except CrcError as crc_error:
logging.info("CRC Error in %s" % art_id)
except BadData as error:
# Continue to the next one if we found new server
if search_new_server(article):
continue
# Store data, maybe par2 can still fix it
decoded_data = crc_error.data
decoded_data = error.data
except BadUu:
logging.info("Badly formed uu article in %s", art_id)
# Try the next server
if search_new_server(article):
continue
except (BadYenc, ValueError):
# Handles precheck and badly formed articles
@@ -170,25 +183,29 @@ class DecoderWorker(Thread):
# STAT was used, so we only get a status code
article_success = True
else:
# Examine headers (for precheck) or body (for download)
# Look for DMCA clues (while skipping "X-" headers)
# Detect potential UUencode
for line in raw_data:
lline = line.lower()
if b"message-id:" in lline:
# Try uu-decoding
if (not nzo.precheck) and article.nzf.type != "yenc":
try:
decoded_data = decode_uu(article, raw_data)
logging.debug("Found uu-encoded article %s in job %s", art_id, nzo.final_name)
article_success = True
if not lline.startswith(b"x-") and match_str(
lline, (b"dmca", b"removed", b"cancel", b"blocked")
):
article_success = False
logging.info("Article removed from server (%s)", art_id)
break
if lline.find(b"\nbegin ") >= 0:
logme = T("UUencode detected, only yEnc encoding is supported [%s]") % nzo.final_name
logging.error(logme)
nzo.fail_msg = logme
sabnzbd.NzbQueue.end_job(nzo)
break
except Exception:
pass
# Only bother with further checks if uu-decoding didn't work out
if not article_success:
# Convert the initial chunks of raw socket data to article lines,
# and examine the headers (for precheck) or body (for download).
for line in b"".join(raw_data[:2]).split(b"\r\n"):
lline = line.lower()
if lline.startswith(b"message-id:"):
article_success = True
# Look for DMCA clues (while skipping "X-" headers)
if not lline.startswith(b"x-") and match_str(
lline, (b"dmca", b"removed", b"cancel", b"blocked")
):
article_success = False
logging.info("Article removed from server (%s)", art_id)
break
# Pre-check, proper article found so just register
if nzo.precheck and article_success and sabnzbd.LOG_ALL:
@@ -218,7 +235,7 @@ class DecoderWorker(Thread):
sabnzbd.NzbQueue.register_article(article, article_success)
def decode(article: Article, raw_data: List[bytes]) -> bytes:
def decode_yenc(article: Article, raw_data: List[bytes]) -> bytes:
# Let SABYenc do all the heavy lifting
decoded_data, yenc_filename, crc, crc_expected, crc_correct = sabyenc3.decode_usenet_chunks(raw_data, article.bytes)
@@ -240,11 +257,135 @@ def decode(article: Article, raw_data: List[bytes]) -> bytes:
# CRC check
if not crc_correct:
raise CrcError(crc_expected, crc, decoded_data)
logging.info("CRC Error in %s", article.article)
raise BadData(decoded_data)
return decoded_data
def decode_uu(article: Article, raw_data: List[bytes]) -> bytes:
"""Try to uu-decode an article. The raw_data may or may not contain headers.
If there are headers, they will be separated from the body by at least one
empty line. In case of no headers, the first line seems to always be the nntp
response code (222) directly followed by the msg body."""
if not raw_data:
logging.debug("No data to decode")
raise BadUu
# Line up the raw_data
with BytesIO() as encoded_data:
for data in raw_data:
encoded_data.write(data)
raw_data = encoded_data.getvalue().split(b"\r\n")
# Index of the uu payload start in raw_data
uu_start = 0
# Limit the number of lines to check for the onset of uu data
limit = min(len(raw_data), 32) - 1
if limit < 3:
logging.debug("Article too short to contain valid uu-encoded data")
raise BadUu
# Try to find an empty line separating the body from headers or response
# code and set the expected payload start to the next line.
try:
uu_start = raw_data[:limit].index(b"") + 1
except ValueError:
# No empty line, look for a response code instead
if raw_data[0].startswith(b"222 "):
uu_start = 1
else:
# Invalid data?
logging.debug("Failed to locate start of uu payload")
raise BadUu
def is_uu_junk(line: bytes) -> bool:
"""Determine if the line is empty or contains known junk data"""
return (not line) or line == b"-- " or line.startswith(b"Posted via ")
# Check the uu 'begin' line
if article.lowest_partnum:
try:
# Make sure the line after the uu_start one isn't empty as well or
# detection of the 'begin' line won't work. For articles other than
# lowest_partnum, filtering out empty lines (and other junk) can
# wait until the actual decoding step.
for index in range(uu_start, limit):
if is_uu_junk(raw_data[index]):
uu_start = index + 1
else:
# Bingo
break
else:
# Search reached the limit
raise IndexError
uu_begin_data = raw_data[uu_start].split(b" ")
# Filename may contain spaces
uu_filename = ubtou(b" ".join(uu_begin_data[2:]).strip())
# Sanity check the 'begin' line
if (
len(uu_begin_data) < 3
or uu_begin_data[0].lower() != b"begin"
or (not int(uu_begin_data[1], 8))
or (not uu_filename)
):
raise ValueError
# Consider this enough proof to set the type, avoiding further
# futile attempts at decoding articles in this nzf as yenc.
article.nzf.type = "uu"
# Bump the pointer for the payload to the next line
uu_start += 1
except Exception:
logging.debug("Missing or invalid uu 'begin' line: %s", raw_data[uu_start] if uu_start < limit else None)
raise BadUu
# Do the actual decoding
with BytesIO() as decoded_data:
for line in raw_data[uu_start:]:
# Ignore junk
if is_uu_junk(line):
continue
# End of the article
if line in (b"`", b"end", b"."):
break
try:
decoded_line = binascii.a2b_uu(line)
except binascii.Error as msg:
try:
# Workaround for broken uuencoders by Fredrik Lundh
nbytes = (((ord(line[0]) - 32) & 63) * 4 + 5) / 3
decoded_line = binascii.a2b_uu(line[:nbytes])
except Exception as msg2:
logging.info(
"Error while uu-decoding %s: %s (line: %s; workaround: %s)", article.article, msg, line, msg2
)
raise BadData(decoded_data.getvalue())
# Store the decoded data
decoded_data.write(decoded_line)
# Mark as decoded and set the type to uu; the latter is still needed in
# case the lowest_partnum article was damaged or slow to download.
article.decoded = True
article.nzf.type = "uu"
if article.lowest_partnum:
decoded_data.seek(0)
article.nzf.md5of16k = hashlib.md5(decoded_data.read(16384)).digest()
# Handle the filename
if not article.nzf.filename_checked and uu_filename:
article.nzf.nzo.verify_nzf_filename(article.nzf, uu_filename)
return decoded_data.getvalue()
def search_new_server(article: Article) -> bool:
"""Shorthand for searching new server or else increasing bad_articles"""
# Continue to the next one if we found new server

View File

@@ -64,9 +64,9 @@ def decode_par2(parfile: str) -> List[str]:
with open(filepath, "rb") as fileToMatch:
first16k_data = fileToMatch.read(16384)
# Check if we have this hash
# Check if we have this hash and the filename is different
file_md5of16k = hashlib.md5(first16k_data).digest()
if file_md5of16k in md5of16k:
if file_md5of16k in md5of16k and fn != md5of16k[file_md5of16k]:
new_path = os.path.join(dirname, md5of16k[file_md5of16k])
# Make sure it's a unique name
unique_filename = get_unique_filename(new_path)
@@ -166,7 +166,7 @@ def deobfuscate_list(filelist: List[str], usefulname: str):
# 2. if no meaningful extension, add it
# 3. based on detecting obfuscated filenames
# to be sure, only keep really exsiting files:
# to be sure, only keep really existing files:
filelist = [f for f in filelist if os.path.isfile(f)]
# let's see if there are files with uncommon/unpopular (so: obfuscated) extensions
@@ -176,7 +176,7 @@ def deobfuscate_list(filelist: List[str], usefulname: str):
for file in filelist:
if file_extension.has_popular_extension(file):
# common extension, like .doc or .iso, so assume OK and change nothing
logging.debug("extension of %s looks common", file)
logging.debug("Extension of %s looks common", file)
newlist.append(file)
else:
# uncommon (so: obfuscated) extension
@@ -220,6 +220,7 @@ def deobfuscate_list(filelist: List[str], usefulname: str):
# check that file is still there (and not renamed by the secondary renaming process below)
if not os.path.isfile(filename):
continue
logging.debug("Deobfuscate inspecting %s", filename)
# Do we need to rename this file?
# Criteria: big, not-excluded extension, obfuscated (in that order)

View File

@@ -28,6 +28,7 @@ from nntplib import NNTPPermanentError
import socket
import random
import sys
import ssl
from typing import List, Dict, Optional, Union
import sabnzbd
@@ -73,6 +74,8 @@ class Server:
"ssl",
"ssl_verify",
"ssl_ciphers",
"ssl_context",
"required",
"optional",
"retention",
"send_group",
@@ -103,12 +106,13 @@ class Server:
timeout,
threads,
priority,
ssl,
use_ssl,
ssl_verify,
ssl_ciphers,
send_group,
username=None,
password=None,
required=False,
optional=False,
retention=0,
):
@@ -122,9 +126,11 @@ class Server:
self.timeout: int = timeout
self.threads: int = threads
self.priority: int = priority
self.ssl: bool = ssl
self.ssl: bool = use_ssl
self.ssl_verify: int = ssl_verify
self.ssl_ciphers: str = ssl_ciphers
self.ssl_context: Optional[ssl.SSLContext] = None
self.required: bool = required
self.optional: bool = optional
self.retention: int = retention
self.send_group: bool = send_group
@@ -183,7 +189,7 @@ class Server:
logging.debug("%s: Connecting to address %s", self.host, ip)
elif cfg.load_balancing() == 2:
# RFC6555 / Happy Eyeballs:
ip = happyeyeballs(self.host, port=self.port, ssl=self.ssl)
ip = happyeyeballs(self.host, port=self.port)
if ip:
logging.debug("%s: Connecting to address %s", self.host, ip)
else:
@@ -312,6 +318,7 @@ class Downloader(Thread):
ssl_ciphers = srv.ssl_ciphers()
username = srv.username()
password = srv.password()
required = srv.required()
optional = srv.optional()
retention = int(srv.retention() * 24 * 3600) # days ==> seconds
send_group = srv.send_group()
@@ -344,6 +351,7 @@ class Downloader(Thread):
send_group,
username,
password,
required,
optional,
retention,
)
@@ -473,12 +481,16 @@ class Downloader(Thread):
if server.errormsg != errormsg:
server.errormsg = errormsg
logging.warning(errormsg)
logging.warning(T("Server %s will be ignored for %s minutes"), server.host, _PENALTY_TIMEOUT)
if not server.required:
logging.warning(T("Server %s will be ignored for %s minutes"), server.host, _PENALTY_TIMEOUT)
# Not fully the same as the code below for optional servers
server.bad_cons = 0
server.deactivate()
self.plan_server(server, _PENALTY_TIMEOUT)
if server.required:
sabnzbd.Scheduler.plan_required_server_resume()
else:
server.deactivate()
self.plan_server(server, _PENALTY_TIMEOUT)
# Optional and active server had too many problems.
# Disable it now and send a re-enable plan to the scheduler
@@ -835,12 +847,17 @@ class Downloader(Thread):
penalty = _PENALTY_UNKNOWN
block = True
if block or (penalty and server.optional):
retry_article = False
if server.active:
server.deactivate()
if penalty and (block or server.optional):
self.plan_server(server, penalty)
# Note that the article is discard for this server
self.__reset_nw(nw, retry_article=False, send_quit=True)
if server.required:
sabnzbd.Scheduler.plan_required_server_resume()
retry_article = True
else:
server.deactivate()
if penalty and (block or server.optional):
self.plan_server(server, penalty)
# Note that the article is discard for this server if the server is not required
self.__reset_nw(nw, retry_article=retry_article, send_quit=True)
continue
except:
logging.error(

View File

@@ -480,6 +480,61 @@ def check_mount(path: str) -> bool:
return not m
RAR_RE = re.compile(r"\.(?P<ext>part\d*\.rar|rar|r\d\d|s\d\d|t\d\d|u\d\d|v\d\d|\d\d\d?\d)$", re.I)
SPLITFILE_RE = re.compile(r"\.(\d\d\d?\d$)", re.I)
ZIP_RE = re.compile(r"\.(zip$)", re.I)
SEVENZIP_RE = re.compile(r"\.7z$", re.I)
SEVENMULTI_RE = re.compile(r"\.7z\.\d+$", re.I)
TS_RE = re.compile(r"\.(\d+)\.(ts$)", re.I)
def build_filelists(
workdir: Optional[str], workdir_complete: Optional[str] = None, check_both: bool = False, check_rar: bool = True
) -> Tuple[List[str], List[str], List[str], List[str], List[str]]:
"""Build filelists, if workdir_complete has files, ignore workdir.
Optionally scan both directories.
Optionally test content to establish RAR-ness
"""
sevens, joinables, zips, rars, ts, filelist = ([], [], [], [], [], [])
if workdir_complete:
filelist.extend(listdir_full(workdir_complete))
if workdir and (not filelist or check_both):
filelist.extend(listdir_full(workdir, recursive=False))
for file in filelist:
# Extra check for rar (takes CPU/disk)
file_is_rar = False
if check_rar:
file_is_rar = rarfile.is_rarfile(file)
# Run through all the checks
if SEVENZIP_RE.search(file) or SEVENMULTI_RE.search(file):
# 7zip
sevens.append(file)
elif SPLITFILE_RE.search(file) and not file_is_rar:
# Joinables, optional with RAR check
joinables.append(file)
elif ZIP_RE.search(file):
# ZIP files
zips.append(file)
elif RAR_RE.search(file):
# RAR files
rars.append(file)
elif TS_RE.search(file):
# TS split files
ts.append(file)
logging.debug("build_filelists(): joinables: %s", joinables)
logging.debug("build_filelists(): zips: %s", zips)
logging.debug("build_filelists(): rars: %s", rars)
logging.debug("build_filelists(): 7zips: %s", sevens)
logging.debug("build_filelists(): ts: %s", ts)
return joinables, zips, rars, sevens, ts
def safe_fnmatch(f: str, pattern: str) -> bool:
"""fnmatch will fail if the pattern contains any of it's
key characters, like [, ] or !.
@@ -902,7 +957,7 @@ def renamer(old: str, new: str, create_local_directories: bool = False) -> str:
time.sleep(2)
else:
raise
raise OSError("Failed to rename")
raise OSError("Failed to rename (Winerr %s)" % hex(ctypes.windll.ntdll.RtlGetLastNtStatus() + 2 ** 32))
else:
shutil.move(old, new)
return new

View File

@@ -24,6 +24,7 @@ import multiprocessing.pool
import functools
import urllib.request
import urllib.error
import socks
import sabnzbd
import sabnzbd.cfg
@@ -64,6 +65,13 @@ def addresslookup6(myhost):
return socket.getaddrinfo(myhost, 80, socket.AF_INET6)
def active_socks5_proxy():
"""Return the active proxy"""
if socket.socket == socks.socksocket:
return "%s:%s" % socks.socksocket.default_proxy[1:3]
return None
def localipv4():
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
@@ -124,6 +132,6 @@ def ipv6():
# IPv6 prefix for documentation purpose
s_ipv6.connect(("2001:db8::8080", 80))
ipv6_address = s_ipv6.getsockname()[0]
except socket.error:
except:
ipv6_address = None
return ipv6_address

View File

@@ -24,7 +24,7 @@ import time
from datetime import datetime
import cherrypy
import logging
import urllib.request, urllib.parse, urllib.error
import urllib.parse
import re
import hashlib
import socket
@@ -908,7 +908,7 @@ SPECIAL_BOOL_LIST = (
"disable_api_key",
"api_logging",
"x_frame_options",
"require_modern_tls",
"allow_old_ssl_tls",
)
SPECIAL_VALUE_LIST = (
"downloader_sleep_time",
@@ -992,6 +992,7 @@ GENERAL_LIST = (
"https_key",
"https_chain",
"enable_https_verification",
"socks5_proxy_url",
"auto_browser",
"check_new_rel",
)
@@ -1234,7 +1235,7 @@ def handle_server(kwargs, root=None, new_svr=False):
if new_svr:
server = unique_svr_name(server)
for kw in ("ssl", "send_group", "enable", "optional"):
for kw in ("ssl", "send_group", "enable", "required", "optional"):
if kw not in kwargs.keys():
kwargs[kw] = None
if svr and not new_svr:

View File

@@ -43,6 +43,7 @@ from sabnzbd.filesystem import userxbit
TAB_UNITS = ("", "K", "M", "G", "T", "P")
RE_UNITS = re.compile(r"(\d+\.*\d*)\s*([KMGTP]?)", re.I)
RE_VERSION = re.compile(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)")
RE_SAMPLE = re.compile(r"((^|[\W_])(sample|proof))", re.I) # something-sample or something-proof
RE_IP4 = re.compile(r"inet\s+(addr:\s*)?(\d+\.\d+\.\d+\.\d+)")
RE_IP6 = re.compile(r"inet6\s+(addr:\s*)?([0-9a-f:]+)", re.I)
@@ -751,16 +752,15 @@ def create_https_certificates(ssl_cert, ssl_key):
def get_all_passwords(nzo) -> List[str]:
"""Get all passwords, from the NZB, meta and password file. In case the correct password is
already known, only that password is returned."""
"""Get all passwords, from the NZB, meta and password file. In case a working password is
already known, try it first."""
passwords = []
if nzo.correct_password:
return [nzo.correct_password]
passwords.append(nzo.correct_password)
if nzo.password:
logging.info("Found a password that was set by the user: %s", nzo.password)
passwords = [nzo.password.strip()]
else:
passwords = []
passwords.append(nzo.password.strip())
meta_passwords = nzo.meta.get("password", [])
pw = nzo.nzo_info.get("password")
@@ -808,6 +808,11 @@ def get_all_passwords(nzo) -> List[str]:
return unique_passwords
def is_sample(filename: str) -> bool:
"""Try to determine if filename is (most likely) a sample"""
return bool(re.search(RE_SAMPLE, filename))
def find_on_path(targets):
"""Search the PATH for a program and return full path"""
if sabnzbd.WIN32:

View File

@@ -56,6 +56,8 @@ from sabnzbd.filesystem import (
setname_from_path,
get_ext,
get_filename,
TS_RE,
build_filelists,
)
from sabnzbd.nzbstuff import NzbObject, NzbFile
from sabnzbd.sorting import SeriesSorter
@@ -63,18 +65,12 @@ import sabnzbd.cfg as cfg
from sabnzbd.constants import Status
# Regex globals
RAR_RE = re.compile(r"\.(?P<ext>part\d*\.rar|rar|r\d\d|s\d\d|t\d\d|u\d\d|v\d\d|\d\d\d?\d)$", re.I)
RAR_RE_V3 = re.compile(r"\.(?P<ext>part\d*)$", re.I)
TARGET_RE = re.compile(r'^(?:File|Target): "(.+)" -')
EXTRACTFROM_RE = re.compile(r"^Extracting\sfrom\s(.+)")
EXTRACTED_RE = re.compile(r"^(Extracting|Creating|...)\s+(.*?)\s+OK\s*$")
SPLITFILE_RE = re.compile(r"\.(\d\d\d?\d$)", re.I)
ZIP_RE = re.compile(r"\.(zip$)", re.I)
SEVENZIP_RE = re.compile(r"\.7z$", re.I)
SEVENMULTI_RE = re.compile(r"\.7z\.\d+$", re.I)
TS_RE = re.compile(r"\.(\d+)\.(ts$)", re.I)
# Constants
PAR2_COMMAND = None
MULTIPAR_COMMAND = None
RAR_COMMAND = None
@@ -1118,8 +1114,7 @@ def par2_repair(parfile_nzf: NzbFile, nzo: NzbObject, workdir, setname, single):
readd = False
for extrapar in nzo.extrapars[setname][:]:
# Make sure we only get new par2 files
if extrapar not in nzo.finished_files and extrapar not in nzo.files:
nzo.add_parfile(extrapar)
if nzo.add_parfile(extrapar):
readd = True
if readd:
return readd, result
@@ -1995,51 +1990,6 @@ def rar_sort(a, b):
return cmp(a, b)
def build_filelists(workdir, workdir_complete=None, check_both=False, check_rar=True):
"""Build filelists, if workdir_complete has files, ignore workdir.
Optionally scan both directories.
Optionally test content to establish RAR-ness
"""
sevens, joinables, zips, rars, ts, filelist = ([], [], [], [], [], [])
if workdir_complete:
filelist.extend(listdir_full(workdir_complete))
if workdir and (not filelist or check_both):
filelist.extend(listdir_full(workdir, recursive=False))
for file in filelist:
# Extra check for rar (takes CPU/disk)
file_is_rar = False
if check_rar:
file_is_rar = rarfile.is_rarfile(file)
# Run through all the checks
if SEVENZIP_RE.search(file) or SEVENMULTI_RE.search(file):
# 7zip
sevens.append(file)
elif SPLITFILE_RE.search(file) and not file_is_rar:
# Joinables, optional with RAR check
joinables.append(file)
elif ZIP_RE.search(file):
# ZIP files
zips.append(file)
elif RAR_RE.search(file):
# RAR files
rars.append(file)
elif TS_RE.search(file):
# TS split files
ts.append(file)
logging.debug("build_filelists(): joinables: %s", joinables)
logging.debug("build_filelists(): zips: %s", zips)
logging.debug("build_filelists(): rars: %s", rars)
logging.debug("build_filelists(): 7zips: %s", sevens)
logging.debug("build_filelists(): ts: %s", ts)
return joinables, zips, rars, sevens, ts
def quick_check_set(set, nzo):
"""Check all on-the-fly md5sums of a set"""
md5pack = nzo.md5packs.get(set)

View File

@@ -268,7 +268,7 @@ class NNTP:
if not self.nw.server.info:
raise socket.error(errno.EADDRNOTAVAIL, "Address not available - Check for internet or DNS problems")
af, socktype, proto, canonname, sa = self.nw.server.info[0]
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):
@@ -276,38 +276,42 @@ class NNTP:
if is_ipv6_addr(self.host):
af = socket.AF_INET6
# Secured or unsecured?
if not self.nw.server.ssl:
# Basic connection
self.sock = socket.socket(af, socktype, proto)
else:
# Use context or just wrapper
if sabnzbd.CERTIFICATE_VALIDATION:
# Setup the SSL socket
ctx = ssl.create_default_context()
# 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
self.nw.server.ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
if sabnzbd.cfg.require_modern_tls():
# We want a modern TLS (1.2 or higher), so we disallow older protocol versions (<= TLS 1.1)
ctx.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
# Only verify hostname when we're strict
if self.nw.server.ssl_verify < 2:
ctx.check_hostname = False
# Only verify hostname when we're strict
if self.nw.server.ssl_verify < 2:
self.nw.server.ssl_context.check_hostname = False
# Certificates optional
if self.nw.server.ssl_verify == 0:
ctx.verify_mode = ssl.CERT_NONE
self.nw.server.ssl_context.verify_mode = ssl.CERT_NONE
# Did the user set a custom cipher-string?
if self.nw.server.ssl_ciphers:
# At their own risk, socket will error out in case it was invalid
ctx.set_ciphers(self.nw.server.ssl_ciphers)
self.sock = ctx.wrap_socket(socket.socket(af, socktype, proto), server_hostname=self.nw.server.host)
# Did the user set a custom cipher-string?
if self.nw.server.ssl_ciphers:
# At their own risk, socket will error out in case it was invalid
self.nw.server.ssl_context.set_ciphers(self.nw.server.ssl_ciphers)
# Python does not allow setting ciphers on TLSv1.3, so have to force TLSv1.2 as the maximum
self.nw.server.ssl_context.maximum_version = ssl.TLSVersion.TLSv1_2
else:
# Use a regular wrapper, no certificate validation
self.sock = ssl.wrap_socket(socket.socket(af, socktype, proto))
# Support at least TLSv1.2+ ciphers, as some essential ones are removed by default in Python 3.10
self.nw.server.ssl_context.set_ciphers("HIGH")
# Store fileno of the socket
if sabnzbd.cfg.allow_old_ssl_tls():
# Allow anything that the system has
self.nw.server.ssl_context.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED
else:
# We want a modern TLS (1.2 or higher), so we disallow older protocol versions (<= TLS 1.1)
self.nw.server.ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
# Disable any verification if the setup is bad
if not sabnzbd.CERTIFICATE_VALIDATION:
self.nw.server.ssl_context.check_hostname = False
self.nw.server.ssl_context.verify_mode = ssl.CERT_NONE
# Create socket and store fileno of the socket
self.sock = socket.socket(af, socktype, proto)
self.fileno: int = self.sock.fileno()
# Open the connection in a separate thread due to avoid blocking
@@ -326,10 +330,11 @@ class NNTP:
# Connect
self.sock.connect((self.host, self.nw.server.port))
self.sock.setblocking(self.nw.blocking)
# Log SSL/TLS info
# Secured or unsecured?
if self.nw.server.ssl:
# Wrap socket and log SSL/TLS diagnostic info
self.sock = self.nw.server.ssl_context.wrap_socket(self.sock, server_hostname=self.nw.server.host)
logging.info(
"%s@%s: Connected using %s (%s)",
self.nw.thrdnum,
@@ -339,6 +344,9 @@ class NNTP:
)
self.nw.server.ssl_info = "%s (%s)" % (self.sock.version(), self.sock.cipher()[0])
# Set blocking mode
self.sock.setblocking(self.nw.blocking)
# Now it's safe to add the socket to the list of active sockets
# Skip this step during server test
if not self.nw.blocking:

View File

@@ -23,7 +23,8 @@ sabnzbd.notifier - Send notifications to any notification services
import os.path
import logging
import urllib.request, urllib.error, urllib.parse
import urllib.request
import urllib.parse
import http.client
import json
from threading import Thread

View File

@@ -30,13 +30,15 @@ from typing import Optional, Dict, Any, Union
import sabnzbd
from sabnzbd import filesystem, nzbstuff
from sabnzbd.constants import Status
from sabnzbd.encoding import utob, correct_unknown_encoding
from sabnzbd.filesystem import is_archive, get_filename
from sabnzbd.misc import name_to_cat
def nzbfile_parser(raw_data, nzo):
def nzbfile_parser(raw_data: str, nzo):
# For type-hinting
nzo: sabnzbd.nzbstuff.NzbObject
# Load data as file-object
raw_data = re.sub(r"""\s(xmlns="[^"]+"|xmlns='[^']+')""", "", raw_data, count=1)
nzb_tree = xml.etree.ElementTree.fromstring(raw_data)

View File

@@ -1105,8 +1105,7 @@ class NzbObject(TryList):
self.postpone_pars(nzf, setname)
# Get the next one
for new_nzf in self.extrapars[setname]:
if not new_nzf.completed:
self.add_parfile(new_nzf)
if self.add_parfile(new_nzf):
# Add it to the top
self.files.remove(new_nzf)
self.files.insert(0, new_nzf)
@@ -1143,8 +1142,8 @@ class NzbObject(TryList):
added_blocks = 0
while added_blocks < needed_blocks:
new_nzf = block_list.pop()
self.add_parfile(new_nzf)
added_blocks += new_nzf.blocks
if self.add_parfile(new_nzf):
added_blocks += new_nzf.blocks
logging.info("Added %s blocks to %s", added_blocks, self.final_name)
return added_blocks
@@ -1433,15 +1432,18 @@ class NzbObject(TryList):
self.unwanted_ext = 2
@synchronized(NZO_LOCK)
def add_parfile(self, parfile: NzbFile):
def add_parfile(self, parfile: NzbFile) -> bool:
"""Add parfile to the files to be downloaded
Resets trylist just to be sure
Adjust download-size accordingly
Returns False when the file couldn't be added
"""
if not parfile.completed and parfile not in self.files and parfile not in self.finished_files:
parfile.reset_try_list()
self.files.append(parfile)
self.bytes_tried -= parfile.bytes_left
return True
return False
@synchronized(NZO_LOCK)
def remove_parset(self, setname: str):
@@ -1468,12 +1470,12 @@ class NzbObject(TryList):
# from all the sets. This probably means we get too much par2, but it's worth it.
blocks_new = 0
for new_nzf in self.extrapars[parset]:
self.add_parfile(new_nzf)
blocks_new += new_nzf.blocks
# Enough now?
if blocks_new >= self.bad_articles:
logging.info("Prospectively added %s repair blocks to %s", blocks_new, self.final_name)
break
if self.add_parfile(new_nzf):
blocks_new += new_nzf.blocks
# Enough now?
if blocks_new >= self.bad_articles:
logging.info("Prospectively added %s repair blocks to %s", blocks_new, self.final_name)
break
# Reset NZO TryList
self.reset_try_list()
@@ -1742,7 +1744,7 @@ class NzbObject(TryList):
and not is_probably_obfuscated(yenc_filename)
and not nzf.filename.endswith(".par2")
):
logging.info("Detected filename from yenc: %s -> %s", nzf.filename, yenc_filename)
logging.info("Detected filename from yenc or uu: %s -> %s", nzf.filename, yenc_filename)
self.renamed_file(yenc_filename, nzf.filename)
nzf.filename = yenc_filename

View File

@@ -19,10 +19,8 @@
sabnzbd.osxmenu - macOS Top Menu
"""
import objc
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
from objc import YES, NO
import os
@@ -40,7 +38,6 @@ from sabnzbd.panic import launch_a_browser
from sabnzbd.api import fast_queue
import sabnzbd.config as config
import sabnzbd.downloader
status_icons = {
"idle": "icons/sabnzbd_osx_idle.tiff",
@@ -80,9 +77,7 @@ class SABnzbdDelegate(NSObject):
# Path is modified for the binary
icon_path = os.path.join(os.path.dirname(sys.executable), "..", "Resources", status_icons[icon])
self.icons[icon] = NSImage.alloc().initByReferencingFile_(icon_path)
if sabnzbd.DARWIN_VERSION > 9:
# Support for Yosemite Dark Mode
self.icons[icon].setTemplate_(YES)
self.icons[icon].setTemplate_(YES)
self.status_item.setImage_(self.icons["idle"])
self.status_item.setAlternateImage_(self.icons["clicked"])
@@ -435,13 +430,8 @@ class SABnzbdDelegate(NSObject):
style.setMaximumLineHeight_(9.0)
style.setParagraphSpacing_(-3.0)
# In Big Sur the offset was changed
baseline_offset = 5.0
if sabnzbd.DARWIN_VERSION >= 16:
baseline_offset = baseline_offset * -1
titleAttributes = {
NSBaselineOffsetAttributeName: baseline_offset,
NSBaselineOffsetAttributeName: -5.0,
NSFontAttributeName: NSFont.menuFontOfSize_(9.0),
NSParagraphStyleAttributeName: style,
}

View File

@@ -39,7 +39,7 @@ from sabnzbd.newsunpack import (
is_sfv_file,
)
from threading import Thread
from sabnzbd.misc import on_cleanup_list
from sabnzbd.misc import on_cleanup_list, is_sample
from sabnzbd.filesystem import (
real_path,
get_unique_path,
@@ -65,7 +65,7 @@ from sabnzbd.filesystem import (
get_filename,
)
from sabnzbd.nzbstuff import NzbObject
from sabnzbd.sorting import Sorter, is_sample
from sabnzbd.sorting import Sorter
from sabnzbd.constants import (
REPAIR_PRIORITY,
FORCE_PRIORITY,
@@ -74,13 +74,12 @@ from sabnzbd.constants import (
JOB_ADMIN,
Status,
VERIFIED_FILE,
IGNORED_MOVIE_FOLDERS,
)
from sabnzbd.nzbparser import process_single_nzb
import sabnzbd.emailer as emailer
import sabnzbd.downloader
import sabnzbd.config as config
import sabnzbd.cfg as cfg
import sabnzbd.nzbqueue
import sabnzbd.database as database
import sabnzbd.notifier as notifier
import sabnzbd.utils.rarfile as rarfile
@@ -499,7 +498,7 @@ def process_job(nzo: NzbObject):
)
logging.info("Traceback: ", exc_info=True)
# Better disable sorting because filenames are all off now
file_sorter.sort_file = None
file_sorter.sorter_active = None
if empty:
job_result = -1
@@ -510,17 +509,19 @@ def process_job(nzo: NzbObject):
remove_samples(workdir_complete)
# TV/Movie/Date Renaming code part 2 - rename and move files to parent folder
if all_ok and file_sorter.sort_file:
if all_ok and file_sorter.sorter_active:
if newfiles:
workdir_complete, ok = file_sorter.sorter.rename(newfiles, workdir_complete)
if not ok:
nzo.set_unpack_info("Unpack", T("Failed to move files"))
nzo.fail_msg = T("Failed to move files")
all_ok = False
# Run further post-processing
if (all_ok or not cfg.safe_postproc()) and not nzb_list:
# Use par2 files to deobfuscate unpacked file names
if cfg.process_unpacked_par2():
# Only if we also run cleanup, so not to process the "regular" par2 files
if flag_delete and cfg.process_unpacked_par2():
newfiles = deobfuscate.recover_par2_names(newfiles)
if cfg.deobfuscate_final_filenames():
@@ -691,7 +692,7 @@ def prepare_extraction_path(nzo: NzbObject) -> Tuple[str, str, Sorter, bool, Opt
else:
file_sorter = Sorter(None, nzo.cat)
complete_dir = file_sorter.detect(nzo.final_name, complete_dir)
if file_sorter.sort_file:
if file_sorter.sorter_active:
one_folder = False
complete_dir = sanitize_and_trim_path(complete_dir)
@@ -1175,7 +1176,7 @@ def rename_and_collapse_folder(oldpath, newpath, files):
if len(items) == 1:
folder = items[0]
folder_path = os.path.join(oldpath, folder)
if os.path.isdir(folder_path) and folder not in ("VIDEO_TS", "AUDIO_TS"):
if os.path.isdir(folder_path) and folder.lower() not in IGNORED_MOVIE_FOLDERS:
logging.info("Collapsing %s", os.path.join(newpath, folder))
oldpath = folder_path

View File

@@ -25,9 +25,7 @@ import time
from typing import Optional
import sabnzbd.utils.kronos as kronos
import sabnzbd.rss
import sabnzbd.downloader
import sabnzbd.dirscanner
import sabnzbd.misc
import sabnzbd.config as config
import sabnzbd.cfg as cfg
@@ -391,6 +389,11 @@ class Scheduler:
self.__check_diskspace, "check_diskspace", 5 * 60, 9 * 60, "threaded", args=[full_dir, required_space]
)
def plan_required_server_resume(self, interval: int = 5):
"""Create task for resuming downloading"""
if not sabnzbd.Downloader.paused:
self.plan_resume(interval)
def cancel_resume_task(self):
"""Cancel the current auto resume task"""
if self.resume_task:

View File

@@ -569,6 +569,8 @@ SKIN_TEXT = {
"explain-enable_https_verification": TT(
"Verify certificates when connecting to indexers and RSS-sources using HTTPS."
),
"opt-socks5_proxy_url": TT("SOCKS5 Proxy"),
"explain-socks5_proxy_url": TT("Use the specified SOCKS5 proxy for all outgoing connections."),
"swtag-server": TT("Server"),
"swtag-queue": TT("Queue"),
"swtag-pp": TT("Post processing"),
@@ -644,6 +646,10 @@ SKIN_TEXT = {
"ssl_verify-strict": TT("Strict"),
"srv-priority": TT("Priority"), #: Server priority
"explain-svrprio": TT("0 is highest priority, 100 is the lowest priority"), #: Explain server priority
"srv-required": TT("Required"), #: Server required tickbox
"explain-required": TT(
"In case of connection failures, the download queue will be paused for a few minutes instead of skipping this server"
), #: Explain server required tickbox
"srv-optional": TT("Optional"), #: Server optional tickbox
"explain-optional": TT(
"For unreliable servers, will be ignored longer in case of failures"
@@ -701,6 +707,9 @@ SKIN_TEXT = {
"link-download": TT("Download"), #: Config->RSS button "download item"
"button-rssNow": TT("Read All Feeds Now"), #: Config->RSS button
# Config->Notifications
"defaultNotifiesAll": TT(
"If only the <em>Default</em> category is selected, notifications are enabled for jobs in all categories."
),
"opt-email_endjob": TT("Email Notification On Job Completion"),
"email-never": TT("Never"), #: When to send email
"email-always": TT("Always"), #: When to send email
@@ -773,6 +782,7 @@ SKIN_TEXT = {
"catTags": TT("Indexer Categories / Groups"),
"button-delCat": TT("X"), #: Small delete button
# Config->Sorting
"selectOneCat": TT("Select at least 1 category."),
"seriesSorting": TT("Series Sorting"),
"opt-tvsort": TT("Enable TV Sorting"),
"sort-legenda": TT("Pattern Key"),

View File

@@ -38,7 +38,8 @@ from sabnzbd.filesystem import (
clip_path,
)
import sabnzbd.cfg as cfg
from sabnzbd.constants import EXCLUDED_GUESSIT_PROPERTIES
from sabnzbd.constants import EXCLUDED_GUESSIT_PROPERTIES, IGNORED_MOVIE_FOLDERS
from sabnzbd.misc import is_sample
from sabnzbd.nzbstuff import NzbObject, scan_password
# Do not rename .vob files as they are usually DVD's
@@ -76,7 +77,7 @@ class BaseSorter:
self.cat = cat
self.filename_set = ""
self.fname = "" # Value for %fn substitution in folders
self.do_rename = False
self.rename_files = False
self.info = {}
self.type = None
self.guess = guess
@@ -259,7 +260,7 @@ class BaseSorter:
# Split the last part of the path up for the renamer
if extension:
path, self.filename_set = os.path.split(path)
self.do_rename = True
self.rename_files = True
# The normpath function translates "" to "." which results in an incorrect path
return os.path.normpath(path) if path else path
@@ -305,7 +306,7 @@ class BaseSorter:
except:
logging.error(T("Failed to rename: %s to %s"), clip_path(current_path), clip_path(newpath))
logging.info("Traceback: ", exc_info=True)
rename_similar(current_path, ext, self.filename_set, ())
rename_similar(current_path, ext, self.filename_set)
else:
logging.debug("Nothing to rename, %s", files)
@@ -317,7 +318,7 @@ class Sorter:
def __init__(self, nzo: Optional[NzbObject], cat: str):
self.sorter: Optional[BaseSorter] = None
self.sort_file = False
self.sorter_active = False
self.nzo = nzo
self.cat = cat
@@ -334,9 +335,9 @@ class Sorter:
self.sorter = MovieSorter(self.nzo, job_name, complete_dir, self.cat, guess)
if self.sorter and self.sorter.matched:
self.sort_file = True
self.sorter_active = True
return self.sorter.get_final_path() if self.sort_file else complete_dir
return self.sorter.get_final_path() if self.sorter_active else complete_dir
class SeriesSorter(BaseSorter):
@@ -357,12 +358,17 @@ class SeriesSorter(BaseSorter):
def match(self):
"""Try to guess series info if config and category sort out or force is set"""
if self.force or (cfg.enable_tv_sorting() and cfg.tv_sort_string() and self.cat.lower() in self.cats):
self.guess = guess_what(self.original_job_name, sort_type="episode")
if not self.guess:
self.guess = guess_what(self.original_job_name, sort_type="episode")
if self.guess.get("type") == "episode" and "date" not in self.guess:
logging.debug("Using tv sorter for %s", self.original_job_name)
self.matched = True
self.type = "tv"
# Require at least 1 category, this was not enforced before 3.4.0
if cfg.enable_tv_sorting() and not self.cats:
logging.warning("%s: %s", T("Series Sorting"), T("Select at least 1 category."))
def get_values(self):
"""Collect all values needed for path replacement"""
self.get_year()
@@ -394,8 +400,8 @@ class SeriesSorter(BaseSorter):
"""Rename for Series"""
if min_size < 0:
min_size = cfg.episode_rename_limit.get_int()
if not self.do_rename:
return current_path, False
if not self.rename_files:
return move_to_parent_directory(current_path)
else:
logging.debug("Renaming series file(s)")
return super().rename(files, current_path, min_size)
@@ -420,12 +426,17 @@ class MovieSorter(BaseSorter):
def match(self):
"""Try to guess movie info if config and category sort out or force is set"""
if self.force or (cfg.enable_movie_sorting() and self.sort_string and self.cat.lower() in self.cats):
self.guess = guess_what(self.original_job_name, sort_type="movie")
if not self.guess:
self.guess = guess_what(self.original_job_name, sort_type="movie")
if self.guess.get("type") == "movie":
logging.debug("Using movie sorter for %s", self.original_job_name)
self.matched = True
self.type = "movie"
# Require at least 1 category, this was not enforced before 3.4.0
if cfg.enable_movie_sorting() and not self.cats:
logging.warning("%s: %s", T("Movie Sorting"), T("Select at least 1 category."))
def get_values(self):
"""Collect all values needed for path replacement"""
self.get_year()
@@ -437,8 +448,9 @@ class MovieSorter(BaseSorter):
if min_size < 0:
min_size = cfg.movie_rename_limit.get_int()
if not self.do_rename:
return current_path, False
if not self.rename_files:
return move_to_parent_directory(current_path)
logging.debug("Renaming movie file(s)")
def filter_files(f, current_path):
@@ -500,12 +512,17 @@ class DateSorter(BaseSorter):
def match(self):
"""Checks the category for a match, if so set self.matched to true"""
if self.force or (cfg.enable_date_sorting() and self.sort_string and self.cat.lower() in self.cats):
self.guess = guess_what(self.original_job_name, sort_type="episode")
if not self.guess:
self.guess = guess_what(self.original_job_name, sort_type="episode")
if self.guess.get("type") == "episode" and "date" in self.guess:
logging.debug("Using date sorter for %s", self.original_job_name)
self.matched = True
self.type = "date"
# Require at least 1 category, this was not enforced before 3.4.0
if cfg.enable_date_sorting() and not self.cats:
logging.warning("%s: %s", T("Date Sorting"), T("Select at least 1 category."))
def get_date(self):
"""Get month and day"""
self.info["month"] = str(self.guess.get("date").month)
@@ -526,8 +543,8 @@ class DateSorter(BaseSorter):
"""Renaming Date file"""
if min_size < 0:
min_size = cfg.episode_rename_limit.get_int()
if not self.do_rename:
return current_path, False
if not self.rename_files:
return move_to_parent_directory(current_path)
else:
logging.debug("Renaming date file(s)")
return super().rename(files, current_path, min_size)
@@ -546,9 +563,11 @@ def move_to_parent_directory(workdir: str) -> Tuple[str, bool]:
workdir = os.path.abspath(os.path.normpath(workdir))
dest = os.path.abspath(os.path.normpath(os.path.join(workdir, "..")))
logging.debug("Moving all files from %s to %s", workdir, dest)
# Check for DVD folders and bail out if found
for item in os.listdir(workdir):
if item.lower() in ("video_ts", "audio_ts", "bdmv"):
if item.lower() in IGNORED_MOVIE_FOLDERS:
return workdir, True
for root, dirs, files in os.walk(workdir):
@@ -612,40 +631,9 @@ def guess_what(name: str, sort_type: Optional[str] = None) -> MatchesDict:
):
guess["type"] = "unknown"
# Remove sample indicators from groupnames, e.g. 'sample-groupname' or 'groupname-proof'
group = guess.get("release_group", "")
if group.lower().startswith(("sample-", "proof-")) or group.lower().endswith(("-sample", "-proof")):
# Set clean groupname
guess["release_group"] = re.sub("^(sample|proof)-|-(sample|proof)$", "", group, re.I)
# Add 'Sample' property to the guess
other = guess.get("other")
if not other:
guess.setdefault("other", "Sample")
else:
if "Sample" not in guess["other"]:
# Pre-existing 'other' may be a string or a list
try:
guess["other"].append("Sample")
except AttributeError:
guess["other"] = [other, "Sample"]
return guess
def is_sample(filename: str) -> bool:
"""Try to determine if filename belongs to a sample"""
if os.path.splitext(filename)[0].lower().strip() in ("sample", "proof"):
# The entire filename is just 'sample.ext' or similar
return True
# If that didn't work, start guessing
guess = guess_what(filename).get("other", "")
if isinstance(guess, list):
return any(item in ("Sample", "Proof") for item in guess)
else:
return guess in ("Sample", "Proof")
def path_subst(path: str, mapping: List[Tuple[str, str]]) -> str:
"""Replace the sort string elements in the path with the real values provided by the mapping;
non-elements are copied verbatim."""
@@ -794,7 +782,7 @@ def strip_path_elements(path: str) -> str:
return "\\\\" + path if is_unc else path
def rename_similar(folder: str, skip_ext: str, name: str, skipped_files: List[str]):
def rename_similar(folder: str, skip_ext: str, name: str, skipped_files: Optional[List[str]] = None):
"""Rename all other files in the 'folder' hierarchy after 'name'
and move them to the root of 'folder'.
Files having extension 'skip_ext' will be moved, but not renamed.
@@ -807,7 +795,7 @@ def rename_similar(folder: str, skip_ext: str, name: str, skipped_files: List[st
for root, dirs, files in os.walk(folder):
for f in files:
path = os.path.join(root, f)
if path in skipped_files:
if skipped_files and path in skipped_files:
continue
org, ext = os.path.splitext(f)
if ext.lower() == skip_ext:
@@ -861,7 +849,7 @@ def eval_sort(sort_type: str, expression: str, name: str = None, multipart: str
if "%fn" in path:
path = path.replace("%fn", fname + ".ext")
else:
if sorter.do_rename:
if sorter.rename_files:
path = fpath + ".ext"
else:
path += "\\" if sabnzbd.WIN32 else "/"

View File

@@ -25,7 +25,6 @@ import time
import logging
import queue
import urllib.request
import urllib.error
import urllib.parse
from http.client import IncompleteRead, HTTPResponse
from mailbox import Message

View File

@@ -9,8 +9,7 @@ import puremagic
import os
import sys
from typing import List
from pathlib import Path
from sabnzbd.filesystem import get_ext
from sabnzbd.filesystem import get_ext, RAR_RE
# common extension from https://www.computerhope.com/issues/ch001789.htm
POPULAR_EXT = (
@@ -168,6 +167,8 @@ DOWNLOAD_EXT = (
"bdmv",
"bin",
"bup",
"cbr",
"cbz",
"clpi",
"crx",
"db",
@@ -234,16 +235,16 @@ DOWNLOAD_EXT = (
"xpi",
)
# combine to one tuple, with unique entries:
# Combine to one tuple, with unique entries:
ALL_EXT = tuple(set(POPULAR_EXT + DOWNLOAD_EXT))
# prepend a dot to each extension, because we work with a leading dot in extensions
# Prepend a dot to each extension, because we work with a leading dot in extensions
ALL_EXT = tuple(["." + i for i in ALL_EXT])
def has_popular_extension(file_path: str) -> bool:
"""returns boolean if the extension of file_path is a popular, well-known extension"""
file_extension = get_ext(file_path)
return file_extension in ALL_EXT
return file_extension in ALL_EXT or RAR_RE.match(file_extension)
def all_possible_extensions(file_path: str) -> List[str]:
@@ -264,9 +265,12 @@ def what_is_most_likely_extension(file_path: str) -> str:
# Check if text or NZB, as puremagic is not good at that.
try:
txt = Path(file_path).read_text()
# Only read the start, don't need the whole file
with open(file_path, "r") as inp_file:
txt = inp_file.read(200).lower()
# Yes, a text file ... so let's check if it's even an NZB:
if txt.lower().find("<nzb xmlns=") >= 0 or txt.lower().find("!doctype nzb public") >= 0:
if "!doctype nzb public" in txt or "<nzb xmlns=" in txt:
# yes, contains NZB signals:
return ".nzb"
else:

View File

@@ -9,33 +9,19 @@
from happyeyeballs import happyeyeballs
print happyeyeballs('newszilla.xs4all.nl', port=119)
"""
# or with more logging:
"""
from happyeyeballs import happyeyeballs
import logging
logger = logging.getLogger('')
logger.setLevel(logging.DEBUG)
print happyeyeballs('newszilla.xs4all.nl', port=119)
"""
import socket
import ssl
import threading
import time
import logging
import queue
DEBUG = False
# called by each thread
def do_socket_connect(queue, ip, PORT, SSL, ipv4delay):
# connect to the ip, and put the result into the queue
if DEBUG:
logging.debug("Input for thread is %s %s %s", ip, PORT, SSL)
# 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
# Create socket
if ip.find(":") >= 0:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
if ip.find(".") >= 0:
@@ -43,123 +29,78 @@ def do_socket_connect(queue, ip, PORT, SSL, ipv4delay):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
if not SSL:
try:
# Connect ...
s.connect((ip, PORT))
finally:
# always close
s.close()
else:
# SSL, so wrap socket
wrappedSocket = ssl.wrap_socket(s)
try:
# CONNECT
wrappedSocket.connect((ip, PORT))
finally:
# CLOSE SOCKET CONNECTION
wrappedSocket.close()
queue.put((ip, True))
if DEBUG:
logging.debug("connect to %s OK", ip)
try:
# Connect ...
s.connect((ip, port))
finally:
# always close
s.close()
result_queue.put((ip, True))
except:
# We got an exception, so no succesfull connect on IP & port:
queue.put((ip, False))
if DEBUG:
logging.debug("connect to %s not OK", ip)
pass
# We got an exception, so no successful connect on IP & port:
result_queue.put((ip, False))
def happyeyeballs(HOST, **kwargs):
# Happyeyeballs function, with caching of the results
def happyeyeballs(host: str, port: int = 80, preferipv6: bool = False) -> str:
"""Happyeyeballs function, with caching of the results"""
# Fill out the parameters into the variables
try:
PORT = kwargs["port"]
except:
PORT = 80
try:
SSL = kwargs["ssl"]
except:
SSL = False
try:
preferipv6 = kwargs["preferipv6"]
except:
preferipv6 = False # Do not prefer IPv6
# Find out if a cached result is available, and recent enough:
# 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, SSL, preferipv6) # Example key: (u'ssl.astraweb.com', 563, True, True)
hostkey = (host, port, preferipv6) # Example key: ('ssl.astraweb.com', 563, True)
try:
happyeyeballs.happylist[hostkey] # just to check: does it exist?
# No exception, so entry exists, so let's check the time:
# Let's check the time:
timecached = happyeyeballs.happylist[hostkey][1]
if timecurrent - timecached <= retentionseconds:
if DEBUG:
logging.debug("existing cached result recent enough")
return happyeyeballs.happylist[hostkey][0]
else:
if DEBUG:
logging.debug("existing cached result too old. Find a new one")
# Continue a few lines down
except:
# Exception, so entry not there, so we have to fill it out
if DEBUG:
logging.debug("Host not yet in the cache. Find entry")
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
# 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()
if DEBUG:
logging.debug("\n\n%s %s %s %s", HOST, PORT, SSL, preferipv6)
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)
if DEBUG:
logging.debug("IPv6 address found for %s", 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 # preferipv6, AND at least one IPv6 found, so give IPv4 (!) a delay so that IPv6 has a head start and is preferred
ipv4delay = 0.1
except:
if DEBUG:
logging.debug("No IPv6 address found for %s", HOST)
pass
myqueue = queue.Queue() # queue used for threads giving back the results
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)
allinfo = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)
for info in allinfo:
address = info[4][0]
thisthread = threading.Thread(target=do_socket_connect, args=(myqueue, address, PORT, SSL, ipv4delay))
thisthread.daemon = True
thisthread.start()
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 i in range(len(allinfo)):
s = myqueue.get() # get a response
if s[1] == True:
result = s[0]
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:
if DEBUG:
logging.debug("something went wrong in the try block")
result = None
logging.info(
"Quickest IP address for %s (port %s, ssl %s, preferipv6 %s) is %s", HOST, PORT, SSL, preferipv6, result
)
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)
if DEBUG:
logging.debug("Determined new result for %s with result %s", hostkey, happyeyeballs.happylist[hostkey])
return result
@@ -167,29 +108,23 @@ happyeyeballs.happylist = {} # The cached results. This static variable must be
if __name__ == "__main__":
logger = logging.getLogger("")
logger.setLevel(logging.INFO)
if DEBUG:
logger.setLevel(logging.DEBUG)
# plain HTTP/HTTPS sites:
print((happyeyeballs("www.google.com")))
print((happyeyeballs("www.google.com", port=443, ssl=True)))
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, ssl=True)))
print((happyeyeballs("sslreader.eweka.nl", port=563, ssl=True)))
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, ssl=True)))
print((happyeyeballs("bonus.frugalusenet.com", port=563, ssl=True)))
print((happyeyeballs("secure.eu.thundernews.com", port=563)))
print((happyeyeballs("bonus.frugalusenet.com", port=563)))
# Strange cases
print((happyeyeballs("does.not.resolve", port=443, ssl=True)))
print((happyeyeballs("does.not.resolve", port=443)))
print((happyeyeballs("www.google.com", port=119)))
print((happyeyeballs("216.58.211.164")))

View File

@@ -414,7 +414,7 @@ class ThreadedScheduler(Scheduler):
def start(self):
"""Splice off a thread in which the scheduler will run."""
self.thread = threading.Thread(target=self._run)
self.thread.setDaemon(True)
self.thread.deamon = True
self.thread.start()
def stop(self):

View File

@@ -2,7 +2,7 @@ name: sabnzbd
summary: SABnzbd
description: The automated Usenet download tool
confinement: strict
base: core18
base: core20
grade: stable
icon: interfaces/Config/templates/staticcfg/images/logo-small.svg
adopt-info: sabnzbd
@@ -19,8 +19,7 @@ parts:
sabnzbd:
plugin: python
source: .
python-version: python3
requirements: [snap/requirements_snap.txt, requirements.txt]
requirements: [snap/local/requirements_snap.txt, requirements.txt]
stage-packages: [python3-dev, libdb5.3, unrar, p7zip-full, par2]
build-packages: [libffi-dev, python3-dev, libssl-dev, cargo]
override-build: |

View File

@@ -136,11 +136,6 @@ def run_sabnews_and_selenium(request):
if not sys.platform.startswith("win"):
driver_options.add_argument("--single-process")
# On Linux we want to use the PPA Chrome
# This makes sure we always match Chrome and chromedriver
if not sys.platform.startswith(("win", "darwin")):
driver_options.binary_location = "/usr/bin/chromium-browser"
# Start the driver and pass it on to all the classes
driver = webdriver.Chrome(options=driver_options)
for item in request.node.items:

194
tests/test_decoder.py Normal file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/python3 -OO
# Copyright 2007-2021 The SABnzbd-Team <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.
"""
tests.test_decoder- Testing functions in decoder.py
"""
import binascii
import os
import pytest
from random import randint
from unittest import mock
import sabnzbd.decoder as decoder
from sabnzbd.nzbstuff import Article
LINES_DATA = [os.urandom(45) for _ in range(32)]
VALID_UU_LINES = [binascii.b2a_uu(data).rstrip(b"\n") for data in LINES_DATA]
END_DATA = os.urandom(randint(1, 45))
VALID_UU_END = [
binascii.b2a_uu(END_DATA).rstrip(b"\n"),
b"`",
b"end",
]
class TestUuDecoder:
def _generate_msg_part(
self,
part: str,
insert_empty_line: bool = True,
insert_excess_empty_lines: bool = False,
insert_headers: bool = False,
insert_end: bool = True,
begin_line: bytes = b"begin 644 My Favorite Open Source Movie.mkv",
):
"""Generate message parts. Part may be one of 'begin', 'middle', or 'end' for multipart
messages, or 'single' for a singlepart message. All uu payload is taken from VALID_UU_*.
Returns Article with a random id and lowest_partnum correctly set, socket-style raw
data, and the expected result of uu decoding for the generated message.
"""
article_id = "test@host" + os.urandom(8).hex() + ".sab"
article = Article(article_id, randint(4321, 54321), None)
article.lowest_partnum = True if part in ("begin", "single") else False
# Mock an nzf so results from hashing and filename handling can be stored
article.nzf = mock.Mock()
# Store the message data and the expected decoding result
data = []
result = []
# Always start with the response code line
data.append(b"222 0 <" + bytes(article_id, encoding="ascii") + b">")
if insert_empty_line:
# Only insert other headers if there's an empty line
if insert_headers:
data.extend([b"x-hoop: is uitgestelde teleurstelling", b"Another-Header: Sure"])
# Insert the empty line between response code and body
data.append(b"")
if insert_excess_empty_lines:
data.extend([b"", b""])
# Insert uu data into the body
if part in ("begin", "single"):
data.append(begin_line)
if part in ("begin", "middle", "single"):
size = randint(4, len(VALID_UU_LINES) - 1)
data.extend(VALID_UU_LINES[:size])
result.extend(LINES_DATA[:size])
if part in ("end", "single"):
if insert_end:
data.extend(VALID_UU_END)
result.append(END_DATA)
# Signal the end of the message with a dot on a line of its own
data.append(b".")
# Join the data with \r\n line endings, just like we get from socket reads
data = b"\r\n".join(data)
# Concatenate expected result
result = b"".join(result)
return article, data, result
def test_no_data(self):
with pytest.raises(decoder.BadUu):
assert decoder.decode_uu(None, None)
@pytest.mark.parametrize(
"raw_data",
[
[b""],
[b"\r\n\r\n"],
[b"f", b"o", b"o", b"b", b"a", b"r", b"\r\n"], # Plenty of list items, but (too) few actual lines
[b"222 0 <artid@woteva>\r\nX-Too-Short: yup\r\n"],
],
)
def test_short_data(self, raw_data):
with pytest.raises(decoder.BadUu):
assert decoder.decode_uu(None, raw_data)
@pytest.mark.parametrize(
"raw_data",
[
[b"222 0 <foo@bar>\r\n\r\n"], # Missing altogether
[b"222 0 <foo@bar>\r\n\r\nbeing\r\n"], # Typo in 'begin'
[b"222 0 <foo@bar>\r\n\r\nx-header: begin 644 foobar\r\n"], # Not at start of the line
[b"666 0 <foo@bar>\r\nbegin\r\n"], # No empty line + wrong response code
[b"OMG 0 <foo@bar>\r\nbegin\r\n"], # No empty line + invalid response code
[b"222 0 <foo@bar>\r\nbegin\r\n"], # No perms
[b"222 0 <foo@bar>\r\nbegin ABC DEF\r\n"], # Permissions not octal
[b"222 0 <foo@bar>\r\nbegin 755\r\n"], # No filename
[b"222 0 <foo@bar>\r\nbegin 644 \t \t\r\n"], # Filename empty after stripping
],
)
def test_missing_uu_begin(self, raw_data):
article = Article("foo@bar", 1234, None)
article.lowest_partnum = True
filler = b"\r\n" * 4
with pytest.raises(decoder.BadUu):
assert decoder.decode_uu(article, raw_data.append(filler))
@pytest.mark.parametrize("insert_empty_line", [True, False])
@pytest.mark.parametrize("insert_excess_empty_lines", [True, False])
@pytest.mark.parametrize("insert_headers", [True, False])
@pytest.mark.parametrize("insert_end", [True, False])
@pytest.mark.parametrize(
"begin_line",
[
b"begin 644 nospace.bin",
b"begin 444 filename with spaces.txt",
b"BEGIN 644 foobar",
b"begin 0755 shell.sh",
],
)
def test_singlepart(self, insert_empty_line, insert_excess_empty_lines, insert_headers, insert_end, begin_line):
"""Test variations of a sane single part nzf with proper uu-encoded data"""
# Generate a singlepart message
article, raw_data, expected_result = self._generate_msg_part(
"single", insert_empty_line, insert_excess_empty_lines, insert_headers, insert_end, begin_line
)
assert decoder.decode_uu(article, [raw_data]) == expected_result
assert article.nzf.filename_checked
@pytest.mark.parametrize("insert_empty_line", [True, False])
def test_multipart(self, insert_empty_line):
"""Test a simple multipart nzf"""
# Generate and process a multipart msg
decoded_data = expected_data = b""
for part in ("begin", "middle", "middle", "end"):
article, data, result = self._generate_msg_part(part, insert_empty_line, False, False, True)
decoded_data += decoder.decode_uu(article, [data])
expected_data += result
# Verify results
assert decoded_data == expected_data
assert article.nzf.filename_checked
@pytest.mark.parametrize(
"bad_data",
[
b"MI^+0E\"C^364:CQ':]DW++^$F0J)6FDG/!`]0\\(4;EG$UY5RI,3JMBNX\\8+06\r\n$(WAIVBC^", # Trailing junk
VALID_UU_LINES[-1][:10] + bytes("ваше здоровье", encoding="utf8") + VALID_UU_LINES[-1][-10:], # Non-ascii
],
)
def test_broken_uu(self, bad_data):
article = Article("foo@bar", 4321, None)
article.lowest_partnum = False
filler = b"\r\n".join(VALID_UU_LINES[:4]) + b"\r\n"
with pytest.raises(decoder.BadData):
assert decoder.decode_uu(article, [b"222 0 <foo@bar>\r\n" + filler + bad_data + b"\r\n"])

View File

@@ -29,6 +29,10 @@ class Test_File_Extension:
assert file_extension.has_popular_extension("blabla/blabla.mkv")
assert file_extension.has_popular_extension("blabla/blabla.srt")
assert file_extension.has_popular_extension("djjddj/aaaaa.epub")
assert file_extension.has_popular_extension("test/testing.r01")
assert file_extension.has_popular_extension("test/testing.s91")
assert not file_extension.has_popular_extension("test/testing")
assert not file_extension.has_popular_extension("test/testing.rar01")
assert not file_extension.has_popular_extension("98ads098f098fa.a0ds98f098asdf")
def test_what_is_most_likely_extension(self):

View File

@@ -257,6 +257,8 @@ class TestSanitizeFiles(ffs.TestCase):
self.setUpPyfakefs()
self.fs.path_separator = "\\"
self.fs.is_windows_fs = True
# Disable randomisation of directory listings
self.fs.shuffle_listdir_results = False
def test_sanitize_files_input(self):
assert [] == filesystem.sanitize_files(folder=None)

View File

@@ -26,7 +26,6 @@ import sys
from random import choice, randint, sample
from string import ascii_lowercase, digits
import sabnzbd.config
from sabnzbd.constants import (
DUP_PRIORITY,
PAUSED_PRIORITY,

View File

@@ -18,8 +18,6 @@
"""
tests.test_functional_downloads - Test the downloading flow
"""
import sys
import sabnzbd.filesystem as filesystem
from tests.testhelper import *

View File

@@ -214,6 +214,44 @@ class TestMisc:
os.unlink("test.cert")
os.unlink("test.key")
@pytest.mark.parametrize(
"name, result",
[
("Free.Open.Source.Movie.2001.1080p.WEB-DL.DD5.1.H264-FOSS", False), # Not samples
("Setup.exe", False),
("23.123.hdtv-rofl", False),
("Something.1080p.WEB-DL.DD5.1.H264-EMRG-sample", True), # Samples
("Something.1080p.WEB-DL.DD5.1.H264-EMRG-sample.ogg", True),
("Sumtin_Else_1080p_WEB-DL_DD5.1_H264_proof-EMRG", True),
("Wot.Eva.540i.WEB-DL.aac.H264-Groupie sample.mp4", True),
("file-sample.mkv", True),
("PROOF.JPG", True),
("Bla.s01e02.title.1080p.aac-sample proof.mkv", True),
("Bla.s01e02.title.1080p.aac-proof.mkv", True),
("Bla.s01e02.title.1080p.aac sample proof.mkv", True),
("Bla.s01e02.title.1080p.aac proof.mkv", True),
("Lwtn.s08e26.1080p.web.h264-glhf-sample.par2", True),
("Lwtn.s08e26.1080p.web.h264-glhf-sample.vol001-002.par2", True),
("Look at That 2011 540i WEB-DL.H265-NoSample", False),
],
)
def test_is_sample(self, name, result):
assert misc.is_sample(name) == result
@pytest.mark.parametrize(
"name, result",
[
("Not Death Proof (2022) 1080p x264 (DD5.1) BE Subs", False), # Try to trigger some false positives
("Proof.of.Everything.(2042).4320p.x266-4U", False),
("Crime_Scene_S01E13_Free_Sample_For_Sale_480p-OhDear", False),
("Sample That 2011 480p WEB-DL.H265-aMiGo", False),
("NOT A SAMPLE.JPG", False),
],
)
def test_is_sample_known_false_positives(self, name, result):
"""We know these fail, but don't have a better solution for them at the moment."""
assert misc.is_sample(name) != result
@pytest.mark.parametrize(
"test_input, expected_output",
[

View File

@@ -18,9 +18,6 @@
"""
tests.test_newsunpack - Tests of various functions in newspack
"""
import pytest
from sabnzbd.newsunpack import *

121
tests/test_newswrapper.py Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/python3 -OO
# Copyright 2007-2021 The SABnzbd-Team <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.
"""
tests.test_newswrapper - Tests of various functions in newswrapper
"""
import os.path
import socket
import tempfile
import threading
import ssl
from typing import Optional
import portend
from tests.testhelper import *
from sabnzbd import misc
from sabnzbd import newswrapper
TEST_HOST = "127.0.0.1"
TEST_PORT = portend.find_available_local_port()
TEST_DATA = b"connection_test"
def socket_test_server(ssl_context: ssl.SSLContext):
"""Support function that starts a mini-server, as
socket.create_server is not supported on Python 3.7"""
# Allow reuse of the address, because our CI is too fast for the socket closing
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
server_socket.bind((TEST_HOST, TEST_PORT))
server_socket.listen(5)
server_socket.settimeout(5)
conn, _ = server_socket.accept()
with ssl_context.wrap_socket(sock=conn, server_side=True) as wrapped_socket:
wrapped_socket.write(TEST_DATA)
except:
# Skip SSL errors
pass
finally:
# Make sure to close the socket
server_socket.close()
class TestNewsWrapper:
cert_file = os.path.join(tempfile.mkdtemp(), "test.cert")
key_file = os.path.join(tempfile.mkdtemp(), "test.key")
@pytest.mark.parametrize(
"server_tls, expected_client_tls, client_cipher, can_connect",
[
(None, "TLSv1.3", None, True), # Default, highest
(ssl.TLSVersion.TLSv1_2, "TLSv1.2", None, True), # Server with just TLSv1.2
(ssl.TLSVersion.SSLv3, None, None, False), # No connection for old TLS/SSL
(None, None, "RC4-MD5", False), # No connection for old cipher
(None, "TLSv1.2", "AES256-SHA", True), # Forced to TLSv1.2 if ciphers set
(None, None, "TLS_AES_128_CCM_SHA256", False), # Cannot force use of TLSv1.3 cipher
],
)
def test_newswrapper(
self,
server_tls: Optional[ssl.TLSVersion],
expected_client_tls: Optional[str],
client_cipher: Optional[str],
can_connect: bool,
):
# We need at least some certificates for the server to work
if not os.path.exists(self.cert_file) or not os.path.exists(self.key_file):
misc.create_https_certificates(self.cert_file, self.key_file)
# Create the server context
server_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
server_context.load_cert_chain(self.cert_file, self.key_file)
server_context.set_ciphers("HIGH")
# Set the options
if server_tls:
server_context.maximum_version = server_tls
threading.Thread(target=socket_test_server, args=(server_context,), daemon=True).start()
# Create the NNTP, mocking the required values
# We disable certificate validation, as we use self-signed certificates
nw = mock.Mock()
nw.blocking = True
nw.thrdnum = 1
nw.server = mock.Mock()
nw.server.host = TEST_HOST
nw.server.port = TEST_PORT
nw.server.info = socket.getaddrinfo(TEST_HOST, TEST_PORT, 0, socket.SOCK_STREAM)
nw.server.ssl = True
nw.server.ssl_context = None
nw.server.ssl_verify = 0
nw.server.ssl_ciphers = client_cipher
# Do we expect failure to connect?
if not can_connect:
with pytest.raises(OSError):
newswrapper.NNTP(nw, TEST_HOST)
else:
nntp = newswrapper.NNTP(nw, TEST_HOST)
assert nntp.sock.recv(len(TEST_DATA)) == TEST_DATA
# Assert SSL data
assert nntp.sock.version() == expected_client_tls
if client_cipher:
assert nntp.sock.cipher()[0] == client_cipher

View File

@@ -9,7 +9,6 @@ tests.test_postproc- Tests of various functions in newspack, among which rar_ren
"""
import shutil
from distutils.dir_util import copy_tree
from unittest import mock
from sabnzbd.postproc import *
@@ -35,8 +34,7 @@ class TestPostProc:
# create a fresh copy
try:
# shutil.copytree(sourcedir, workingdir) gives problems on AppVeyor, so:
copy_tree(sourcedir, workingdir)
shutil.copytree(sourcedir, workingdir)
except:
pytest.fail("Could not create copy of files for rar_renamer")

View File

@@ -25,6 +25,7 @@ import sys
from random import choice
from sabnzbd import sorting
from sabnzbd.constants import IGNORED_MOVIE_FOLDERS
from tests.testhelper import *
@@ -65,7 +66,7 @@ class TestSortingFunctions:
"country": "US",
},
),
("Test Movie 720p HDTV AAC x265 sample-MYgroup", {"release_group": "MYgroup", "other": "Sample"}),
("Test Movie 720p HDTV AAC x265 MYgroup-Sample", {"release_group": "MYgroup", "other": "Sample"}),
(None, None), # Jobname missing
("", None),
],
@@ -85,33 +86,6 @@ class TestSortingFunctions:
else:
assert guess[key] == value
@pytest.mark.parametrize(
"name, result",
[
("Free.Open.Source.Movie.2001.1080p.WEB-DL.DD5.1.H264-FOSS", False), # Not samples
("Setup.exe", False),
("23.123.hdtv-rofl", False),
("Something.1080p.WEB-DL.DD5.1.H264-EMRG-sample", True), # Samples
("Something.1080p.WEB-DL.DD5.1.H264-EMRG-sample.ogg", True),
("Sumtin_Else_1080p_WEB-DL_DD5.1_H264_proof-EMRG", True),
("Wot.Eva.540i.WEB-DL.aac.H264-Groupie sample.mp4", True),
("file-sample.mkv", True),
("PROOF.JPG", True),
("Bla.s01e02.title.1080p.aac-sample proof.mkv", True),
("Bla.s01e02.title.1080p.aac-proof.mkv", True),
("Bla.s01e02.title.1080p.aac sample proof.mkv", True),
("Bla.s01e02.title.1080p.aac proof.mkv", True),
("Not Death Proof (2022) 1080p x264 (DD5.1) BE Subs", False), # Try to trigger some false positives
("Proof.of.Everything.(2042).4320p.x266-4U", False),
("Crime_Scene_S01E13_Free_Sample_For_Sale_480p-OhDear", False),
("Sample That 2011 480p WEB-DL.H265-aMiGo", False),
("Look at That 2011 540i WEB-DL.H265-NoSample", False),
("NOT A SAMPLE.JPG", False),
],
)
def test_is_sample(self, name, result):
assert sorting.is_sample(name) == result
@pytest.mark.parametrize("platform", ["linux", "darwin", "win32"])
@pytest.mark.parametrize(
"path, result_unix, result_win",
@@ -315,7 +289,7 @@ class TestSortingFunctions:
pyfakefs.fake_filesystem_unittest.set_uid(0)
# Create a fake filesystem in a random base directory, and included a typical DVD directory
base_dir = "/" + os.urandom(4).hex() + "/" + os.urandom(2).hex()
dvd = choice(("video_ts", "audio_ts", "bdmv"))
dvd = choice(IGNORED_MOVIE_FOLDERS)
for test_dir in ["dir/2", "TEST/DIR2"]:
ffs.fs.create_dir(base_dir + "/" + test_dir, perm_bits=755)
assert os.path.exists(base_dir + "/" + test_dir) is True
@@ -373,7 +347,7 @@ class TestSortingFunctions:
pyfakefs.fake_filesystem_unittest.set_uid(0)
# Create a fake filesystem in a random base directory, and included a typical DVD directory
base_dir = "D:\\" + os.urandom(4).hex() + "\\" + os.urandom(2).hex()
dvd = choice(("video_ts", "audio_ts", "bdmv"))
dvd = choice(IGNORED_MOVIE_FOLDERS)
for test_dir in ["dir\\2", "TEST\\DIR2"]:
ffs.fs.create_dir(base_dir + "\\" + test_dir, perm_bits=755)
assert os.path.exists(base_dir + "\\" + test_dir) is True
@@ -553,11 +527,14 @@ class TestSortingSorters:
_func()
@pytest.mark.parametrize(
"s_class, job_tag, sort_string, sort_result", # sort_result without extension
"s_class, job_tag, sort_string, sort_filename_result", # Supply sort_filename_result without extension
[
(sorting.SeriesSorter, "S01E02", "%r/%sn s%0se%0e.%ext", "Simulated Job s01e02"),
(sorting.SeriesSorter, "S01E02", "%r/%sn s%0se%0e", ""),
(sorting.MovieSorter, "2021", "%y_%.title.%r.%ext", "2021_Simulated.Job.2160p"),
(sorting.DateSorter, "2020-02-29", "%y/%0m/%0d/%.t-%GI<release_group>", "Simulated.Job-SAB"),
(sorting.MovieSorter, "2021", "%y_%.title.%r", ""),
(sorting.DateSorter, "2020-02-29", "%y/%0m/%0d/%.t-%GI<release_group>.%ext", "Simulated.Job-SAB"),
(sorting.DateSorter, "2020-02-29", "%y/%0m/%0d/%.t-%GI<release_group>", ""),
],
)
@pytest.mark.parametrize("size_limit, file_size", [(512, 1024), (1024, 512)])
@@ -569,7 +546,7 @@ class TestSortingSorters:
s_class,
job_tag,
sort_string,
sort_result,
sort_filename_result,
size_limit,
file_size,
extension,
@@ -631,8 +608,10 @@ class TestSortingSorters:
# Check the result
try:
# If there's no "%ext" in the sort_string, no filenames should be changed
if (
is_ok
and sort_filename_result
and file_size > size_limit
and extension not in sorting.EXCLUDED_FILE_EXTS
and not (sorter.type == "movie" and number_of_files > 1 and not generate_sequential_filenames)
@@ -642,10 +621,10 @@ class TestSortingSorters:
if number_of_files > 1 and generate_sequential_filenames and sorter.type == "movie":
# Movie sequential file handling
for n in range(1, number_of_files + 1):
expected = os.path.join(sort_dest, sort_result + " CD" + str(n) + extension)
expected = os.path.join(sort_dest, sort_filename_result + " CD" + str(n) + extension)
assert os.path.exists(expected)
else:
expected = os.path.join(sort_dest, sort_result + extension)
expected = os.path.join(sort_dest, sort_filename_result + extension)
assert os.path.exists(expected)
else:
# No renaming should happen
@@ -699,7 +678,7 @@ class TestSortingSorters:
generic = sorting.Sorter(None, "test_cat")
generic.detect(job_name, SAB_CACHE_DIR)
assert generic.sort_file is result_sort_file
assert generic.sorter_active is result_sort_file
if result_sort_file:
assert generic.sorter
assert generic.sorter.__class__ is result_class

View File

@@ -25,7 +25,6 @@ import urllib.parse
import pytest_httpbin
import sabnzbd.urlgrabber as urlgrabber
import sabnzbd.version
from sabnzbd.cfg import selftest_host
from tests.testhelper import *

View File

@@ -36,7 +36,7 @@ class TestHappyEyeballs:
assert "." in ip or ":" in ip
def test_google_https(self):
ip = happyeyeballs("www.google.com", port=443, ssl=True)
ip = happyeyeballs("www.google.com", port=443)
assert "." in ip or ":" in ip
def test_not_resolvable(self):