mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-01-03 04:59:50 -05:00
Compare commits
52 Commits
bugfix/han
...
3.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
971e4fc909 | ||
|
|
51cc765949 | ||
|
|
19c6a4fffa | ||
|
|
105ac32d2f | ||
|
|
57550675d2 | ||
|
|
e674abc5c0 | ||
|
|
f965c96f51 | ||
|
|
c76b8ed9e0 | ||
|
|
4fbd0d8a7b | ||
|
|
2186c0fff6 | ||
|
|
1adca9a9c1 | ||
|
|
9408353f2b | ||
|
|
84f4d453d2 | ||
|
|
d10209f2a1 | ||
|
|
3ae149c72f | ||
|
|
47385acc3b | ||
|
|
814eeaa900 | ||
|
|
5f2ea13aad | ||
|
|
41ca217931 | ||
|
|
b57d36e8dd | ||
|
|
9a4be70734 | ||
|
|
a8443595a6 | ||
|
|
fd0a70ac58 | ||
|
|
8a8685c968 | ||
|
|
9e6cb8da8e | ||
|
|
054ec54d51 | ||
|
|
272ce773cb | ||
|
|
050b925f7b | ||
|
|
0087940898 | ||
|
|
e323c014f9 | ||
|
|
cc465c7554 | ||
|
|
14cb37564f | ||
|
|
094db56c3b | ||
|
|
aabb709b8b | ||
|
|
0833dd2db9 | ||
|
|
cd3f912be4 | ||
|
|
665c516db6 | ||
|
|
b670da9fa0 | ||
|
|
80bee9bffe | ||
|
|
d85a70e8ad | ||
|
|
8f21533e76 | ||
|
|
89996482a1 | ||
|
|
03c10dce91 | ||
|
|
bd5331be05 | ||
|
|
46e1645289 | ||
|
|
4ce3965747 | ||
|
|
9d4af19db3 | ||
|
|
48e034f4be | ||
|
|
f8959baa2f | ||
|
|
8ed5997eae | ||
|
|
daf9f50ac8 | ||
|
|
6b11013c1a |
4
.github/workflows/black.yml
vendored
4
.github/workflows/black.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Black Code Formatter
|
||||
uses: lgeiger/black-action@master
|
||||
uses: lgeiger/black-action@v1.0.1
|
||||
with:
|
||||
args: >
|
||||
SABnzbd.py
|
||||
@@ -16,5 +16,5 @@ jobs:
|
||||
tools
|
||||
tests
|
||||
--line-length=120
|
||||
--target-version=py36
|
||||
--target-version=py35
|
||||
--check
|
||||
|
||||
37
.github/workflows/integration_testing.yml
vendored
37
.github/workflows/integration_testing.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: CI Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test ${{ matrix.os }} - Python ${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
os: [ubuntu-20.04]
|
||||
include:
|
||||
- os: macos-latest
|
||||
python-version: 3.9
|
||||
- os: windows-latest
|
||||
python-version: 3.9
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install system dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get install unrar p7zip-full par2 chromium-chromedriver
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python --version
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade -r requirements.txt
|
||||
pip install --upgrade -r tests/requirements.txt
|
||||
- name: Test SABnzbd
|
||||
run: pytest -s
|
||||
|
||||
|
||||
33
.github/workflows/translations.yml
vendored
33
.github/workflows/translations.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Update translatable texts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
translations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generate translatable texts
|
||||
run: |
|
||||
python3 tools/extract_pot.py
|
||||
- name: Install Transifex client
|
||||
# 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
|
||||
run: |
|
||||
tx push --source --parallel
|
||||
tx pull --all --force --parallel
|
||||
env:
|
||||
TX_TOKEN: ${{ secrets.TX_TOKEN }}
|
||||
- name: Push translatable and translated texts back to repo
|
||||
uses: stefanzweifel/git-auto-commit-action@v4.5.1
|
||||
with:
|
||||
commit_message: Update translatable texts
|
||||
commit_user_name: SABnzbd Automation
|
||||
commit_user_email: bugs@sabnzbd.org
|
||||
commit_author: SABnzbd Automation <bugs@sabnzbd.org>
|
||||
7
.lgtm.yml
Normal file
7
.lgtm.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
path_classifiers:
|
||||
oldinterfaces:
|
||||
- interfaces/smpl
|
||||
- interfaces/Plush
|
||||
library:
|
||||
- "*knockout*"
|
||||
- "**/*min*"
|
||||
46
.travis.yml
Normal file
46
.travis.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
matrix:
|
||||
include:
|
||||
# On Linux we test all supported Python versions
|
||||
# On macOS we only test the semi-recent version that is included
|
||||
- os: linux
|
||||
language: python
|
||||
python: "3.5"
|
||||
- os: linux
|
||||
language: python
|
||||
python: "3.6"
|
||||
- os: linux
|
||||
language: python
|
||||
python: "3.7"
|
||||
- os: linux
|
||||
language: python
|
||||
python: "3.8"
|
||||
- os: osx
|
||||
addons:
|
||||
chrome: stable
|
||||
env:
|
||||
- HOMEBREW_NO_AUTO_UPDATE=1
|
||||
|
||||
install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
LATEST_CHROMEDRIVER=$(curl -s https://chromedriver.storage.googleapis.com/LATEST_RELEASE) &&
|
||||
wget --no-verbose -O /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/$LATEST_CHROMEDRIVER/chromedriver_mac64.zip &&
|
||||
sudo unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/;
|
||||
else
|
||||
sudo add-apt-repository ppa:jcfp -y;
|
||||
sudo apt-get update -q;
|
||||
sudo apt-get install unrar p7zip-full par2 chromium-chromedriver -y;
|
||||
ln -s /usr/lib/chromium-browser/chromedriver ~/bin/chromedriver;
|
||||
fi;
|
||||
- python3 --version
|
||||
- python3 -m pip install --upgrade pip
|
||||
- python3 -m pip install --upgrade wheel
|
||||
- python3 -m pip install --upgrade -r requirements.txt
|
||||
- python3 -m pip install --upgrade -r tests/requirements.txt
|
||||
|
||||
script:
|
||||
- python3 -m pytest -s
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: change
|
||||
24
.tx/config
24
.tx/config
@@ -1,24 +0,0 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[sabnzbd-translations.po-main-sabnzbd-pot--develop]
|
||||
file_filter = po/main/<lang>.po
|
||||
minimum_perc = 0
|
||||
source_file = po/main/SABnzbd.pot
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[sabnzbd-translations.po-email-sabemail-pot--develop]
|
||||
file_filter = po/email/<lang>.po
|
||||
minimum_perc = 0
|
||||
source_file = po/email/SABemail.pot
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[sabnzbd-translations.po-nsis-sabnsis-pot--develop]
|
||||
file_filter = po/nsis/<lang>.po
|
||||
minimum_perc = 0
|
||||
source_file = po/nsis/SABnsis.pot
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
16
ABOUT.txt
Normal file
16
ABOUT.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd ***
|
||||
*******************************************
|
||||
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
thanks to its friendly web-based user interface and advanced
|
||||
built-in post-processing options that automatically verify, repair,
|
||||
extract and clean up posts downloaded from Usenet.
|
||||
SABnzbd also has a fully customizable user interface,
|
||||
and offers a complete API for third-party applications to hook into.
|
||||
|
||||
There is an extensive Wiki on the use of SABnzbd.
|
||||
https://sabnzbd.org/wiki/
|
||||
|
||||
Please also read the file "ISSUES.txt"
|
||||
@@ -1,4 +1,4 @@
|
||||
SABnzbd 3.2.0
|
||||
SABnzbd 3.0.0
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
@@ -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.5 and above is supported.
|
||||
|
||||
On Linux systems you need to install:
|
||||
par2 unrar unzip python3-setuptools python3-pip
|
||||
|
||||
21
ISSUES.txt
21
ISSUES.txt
@@ -14,15 +14,15 @@
|
||||
For these the server blocking method is not very favourable.
|
||||
There is an INI-only option that will limit blocks to 1 minute.
|
||||
no_penalties = 1
|
||||
See: https://sabnzbd.org/wiki/configuration/3.1/special
|
||||
See: https://sabnzbd.org/wiki/configuration/3.0/special
|
||||
|
||||
- Some third-party utilties try to probe SABnzbd API in such a way that you will
|
||||
often see warnings about unauthenticated access.
|
||||
If you are sure these probes are harmless, you can suppress the warnings by
|
||||
setting the option "api_warnings" to 0.
|
||||
See: https://sabnzbd.org/wiki/configuration/3.1/special
|
||||
See: https://sabnzbd.org/wiki/configuration/3.0/special
|
||||
|
||||
- On macOS you may encounter downloaded files with foreign characters.
|
||||
- On OSX you may encounter downloaded files with foreign characters.
|
||||
The par2 repair may fail when the files were created on a Windows system.
|
||||
The problem is caused by the PAR2 utility and we cannot fix this now.
|
||||
This does not apply to files inside RAR files.
|
||||
@@ -33,14 +33,25 @@
|
||||
We cannot solve this problem, because the Operating System (read Windows)
|
||||
prevents the removal.
|
||||
|
||||
- Memory usage can sometimes have high peaks. This makes using SABnzbd on very low
|
||||
memory systems (e.g. a NAS device or a router) a challenge.
|
||||
In particular on Synology (SynoCommunity) the device may report that SABnzbd is using
|
||||
a lot of memory even when idle. In this case the memory is usually not actually used by
|
||||
SABnzbd and will be available if required by other apps or the system. More information
|
||||
can be found in the discussion here: https://github.com/SynoCommunity/spksrc/issues/2856
|
||||
|
||||
- SABnzbd is not compatible with some software firewall versions.
|
||||
The Microsoft Windows Firewall works fine, but remember to tell this
|
||||
firewall that SABnzbd is allowed to talk to other computers.
|
||||
|
||||
- When SABnzbd cannot send notification emails, check your virus scanner,
|
||||
firewall or security suite. It may be blocking outgoing email.
|
||||
|
||||
- When you are using external drives or network shares on macOS or Linux
|
||||
- When you are using external drives or network shares on OSX or Linux
|
||||
make sure that the drives are mounted.
|
||||
The operating system will simply redirect your files to alternative locations.
|
||||
You may have trouble finding the files when mounting the drive later.
|
||||
On macOS, SABnzbd will not create new folders in /Volumes.
|
||||
On OSX, SABnzbd will not create new folders in /Volumes.
|
||||
The result will be a failed job that can be retried once the volume has been mounted.
|
||||
|
||||
- If you use a mounted drive as "temporary download folder", it must be present when SABnzbd
|
||||
|
||||
4
PKG-INFO
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 3.2.0-develop
|
||||
Summary: SABnzbd-3.2.0-develop
|
||||
Version: 3.0.2
|
||||
Summary: SABnzbd-3.0.2
|
||||
Home-page: https://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
@@ -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.5 and higher, often called `python3`)
|
||||
- Python modules listed in `requirements.txt`
|
||||
- `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)
|
||||
|
||||
100
README.mkd
100
README.mkd
@@ -1,38 +1,76 @@
|
||||
Release Notes - SABnzbd 3.1.0 Release Candidate 1
|
||||
Release Notes - SABnzbd 3.0.2
|
||||
=========================================================
|
||||
|
||||
## Changes and bugfixes since 3.1.0 Beta 2
|
||||
- Deobfuscate final filenames can now be used when job folders are disabled.
|
||||
- Deobfuscate final filenames will ignore blu-ray disc files.
|
||||
- Clear error if Complete Folder is set as a subfolder of the Temporary Folder.
|
||||
- Filtering of history by category would not filter jobs in post-processing.
|
||||
## Bugfixes since 3.0.1
|
||||
- Priority was not parsed correctly if supplied as through the API.
|
||||
- API-call `addfile` could fail if `name` and `nzbfile` were used.
|
||||
- Permissions were still not set correctly when creating directories.
|
||||
- Propagation delay label was shown even if no delay was activated.
|
||||
- Reading RSS feed with no categories set could result in crash.
|
||||
- Jobs with numeric names could crash post-processing.
|
||||
- Jobs with missing articles could result in crash.
|
||||
- macOS: changed the power assertion to `NoIdleSleep`.
|
||||
- Windows: end-of-queue-script did not run on Windows.
|
||||
- Windows: crash if the virus scanner removed the certificate bundle.
|
||||
|
||||
## Changes since 3.0.2
|
||||
- Added option to automatically deobfuscate final filenames: after unpacking,
|
||||
detect and rename obfuscated or meaningless filenames to the job name,
|
||||
similar to the Deobfuscate.py post-processing script.
|
||||
- Switched to Transifex as our translations platform:
|
||||
Help us translate SABnzbd in your language! Add untranslated texts or
|
||||
improved existing translations here: https://sabnzbd.org/wiki/translate
|
||||
- Redesigned job availability-check to be more efficient and reliable.
|
||||
- Skip repair on Retry if all sets were previously successfully verified.
|
||||
- Passwords included in the filename no longer have to be at the end.
|
||||
- Restore limit on length of foldernames (`max_foldername_length`).
|
||||
- Added password input box on the Add NZB screen.
|
||||
- Show warning that Pyton 3.5 support will be dropped after 3.1.0.
|
||||
- Windows/macOS: update UnRar to 5.91 and MultiPar to 1.3.1.0.
|
||||
- Windows: retry `Access Denied` when renaming files on Windows.
|
||||
## Bugfixes since 3.0.0
|
||||
- Basic Authentication resulted in crash.
|
||||
- Permissions were not set correctly when creating directories.
|
||||
- Windows: base SSL certificate bundle was not included.
|
||||
|
||||
## Bugfixes since 3.0.2
|
||||
- Assembler crashes could occur due to race condition in `ArticleCache`.
|
||||
- On HTTP-redirects the scheme/hostname/port were ignored when behind a proxy.
|
||||
- Strip slash of the end of `url_base` as it could break other code.
|
||||
- Unpacking with a relative folder set for a category could fail.
|
||||
- Paused priority of pre-queue script was ignored.
|
||||
- Duplicate Detection did not check filenames in History.
|
||||
- Downloaded bytes could show as exceeding the total bytes of a job.
|
||||
- Windows: non-Latin languages were displayed incorrectly in the installer.
|
||||
- Windows: could fail to create folders on some network shares.
|
||||
## About the new major version
|
||||
We have been working for months to upgrade the SABnzbd code from Python 2 to Python 3.
|
||||
Although it might not sound like a big change, we had to rewrite almost every part of
|
||||
the code. We also included a number of new features, listed below.
|
||||
|
||||
## Big changes in 3.0.0
|
||||
- Python 3.5 and above are the only supported versions of Python.
|
||||
- Cache handling is greatly improved, resulting in more stable speeds on some systems.
|
||||
- Articles failing with CRC errors are now retried on other servers.
|
||||
- SFV files, even obfuscated, will be used for renaming when there are no par2 files.
|
||||
- Fully obfuscated RAR-sets with no verification files are detected and extracted.
|
||||
- Built-in internet bandwidth test.
|
||||
- Windows Service support was changed. The service will need to be reinstalled!
|
||||
Documentation: https://sabnzbd.org/wiki/advanced/sabnzbd-as-a-windows-service
|
||||
- The Windows installer is 64-bit only, for 32-bit please use the standalone package.
|
||||
|
||||
## Other changes since 2.3.9
|
||||
- Files inside an NZB that are fully identical are now skipped automatically.
|
||||
- Folders of jobs that failed post-processing are renamed to `_FAILED_`.
|
||||
- Blocking of unwanted extensions that are directly inside an NZB.
|
||||
- In Python 3 OpenSSL 1.1.1 is used for Windows and macOS, as a result
|
||||
newsservers manually set to `RC4-MD5` cipher can no longer connect.
|
||||
Documentation: https://sabnzbd.org/wiki/advanced/ssl-ciphers
|
||||
- TLS1.3 support for newsserver connections.
|
||||
- SABYenc, par2 and unrar are now required to start downloading.
|
||||
- Growl-support was removed.
|
||||
- The `smpl` skin was removed.
|
||||
- Using the API with `output=text` to add NZB's will report the `nzo_ids` instead of `ok`.
|
||||
- Queue-item labels are no longer part of the name but separated in API-property `labels`.
|
||||
- API-calls `tapi` and `qstatus` were removed.
|
||||
- On Windows only Multipar is available for repair.
|
||||
- Linux tray icon support was improved.
|
||||
- On Linux special permission bits are removed from files after download.
|
||||
- macOS features such as the menu and notifications now use native code.
|
||||
|
||||
## Bugfixes since 2.3.9
|
||||
- Resolved potential security issue in FAT-filesystem check and Nice and IONice Parameters.
|
||||
More information: https://github.com/sabnzbd/sabnzbd/security/advisories/GHSA-9x87-96gg-33w2
|
||||
- Sample removal did not work if only 1 sample file was present.
|
||||
- Crash on badly formatted RSS-feeds or readout during editing.
|
||||
- Only really run pre-queue-script when it is set.
|
||||
- Always report API `paused` status as a boolean.
|
||||
- Automatic aborting of jobs that can't be completed would sometimes not trigger.
|
||||
- Windows systems could enter standby state during downloading.
|
||||
- Some errors thrown by unrar were not caught.
|
||||
- Files and sockets were not always closed correctly.
|
||||
- Unwanted extension check was overly aggressively deleting folders
|
||||
|
||||
## Upgrade notices
|
||||
- When upgrading from 2.x.x or older the queue will be converted. Job order,
|
||||
settings and data will be preserved, but if you decide to go back to 2.x.x
|
||||
your queue cannot be downgraded again. But you can restore the jobs by going
|
||||
to the Status page and running Queue Repair.
|
||||
|
||||
## Known problems and solutions
|
||||
- Read the file "ISSUES.txt"
|
||||
|
||||
203
SABnzbd.py
203
SABnzbd.py
@@ -17,8 +17,8 @@
|
||||
|
||||
import sys
|
||||
|
||||
if sys.hexversion < 0x03060000:
|
||||
print("Sorry, requires Python 3.6 or above")
|
||||
if sys.hexversion < 0x03050000:
|
||||
print("Sorry, requires Python 3.5 or above")
|
||||
print("You can read more at: https://sabnzbd.org/python3")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -34,7 +34,6 @@ import subprocess
|
||||
import ssl
|
||||
import time
|
||||
import re
|
||||
from typing import List, Dict, Any
|
||||
|
||||
try:
|
||||
import Cheetah
|
||||
@@ -67,17 +66,15 @@ from sabnzbd.misc import (
|
||||
get_serv_parms,
|
||||
get_from_url,
|
||||
upload_file_to_sabnzbd,
|
||||
probablyipv4,
|
||||
)
|
||||
from sabnzbd.filesystem import get_ext, real_path, long_path, globber_full, remove_file
|
||||
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic, launch_a_browser
|
||||
import sabnzbd.scheduler as scheduler
|
||||
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
|
||||
import sabnzbd.utils.ssdp as ssdp
|
||||
|
||||
try:
|
||||
import win32api
|
||||
@@ -105,49 +102,36 @@ def guard_loglevel():
|
||||
LOG_FLAG = True
|
||||
|
||||
|
||||
def warning_helpful(*args, **kwargs):
|
||||
""" Wrapper to ignore helpfull warnings if desired """
|
||||
if sabnzbd.cfg.helpfull_warnings():
|
||||
return logging.warning(*args, **kwargs)
|
||||
return logging.info(*args, **kwargs)
|
||||
|
||||
|
||||
logging.warning_helpful = warning_helpful
|
||||
|
||||
|
||||
class GUIHandler(logging.Handler):
|
||||
"""Logging handler collects the last warnings/errors/exceptions
|
||||
to be displayed in the web-gui
|
||||
""" Logging handler collects the last warnings/errors/exceptions
|
||||
to be displayed in the web-gui
|
||||
"""
|
||||
|
||||
def __init__(self, size):
|
||||
""" Initializes the handler """
|
||||
logging.Handler.__init__(self)
|
||||
self._size: int = size
|
||||
self.store: List[Dict[str, Any]] = []
|
||||
self.size = size
|
||||
self.store = []
|
||||
|
||||
def emit(self, record: logging.LogRecord):
|
||||
def emit(self, record):
|
||||
""" Emit a record by adding it to our private queue """
|
||||
# If % is part of the msg, this could fail
|
||||
try:
|
||||
parsed_msg = record.msg % record.args
|
||||
except TypeError:
|
||||
parsed_msg = record.msg + str(record.args)
|
||||
|
||||
if record.levelno == logging.WARNING:
|
||||
sabnzbd.notifier.send_notification(T("Warning"), parsed_msg, "warning")
|
||||
if record.levelname == "WARNING":
|
||||
sabnzbd.LAST_WARNING = record.msg % record.args
|
||||
else:
|
||||
sabnzbd.notifier.send_notification(T("Error"), parsed_msg, "error")
|
||||
sabnzbd.LAST_ERROR = record.msg % record.args
|
||||
|
||||
# Append traceback, if available
|
||||
warning = {"type": record.levelname, "text": parsed_msg, "time": int(time.time())}
|
||||
if record.exc_info:
|
||||
warning["text"] = "%s\n%s" % (warning["text"], traceback.format_exc())
|
||||
|
||||
# Loose the oldest record
|
||||
if len(self.store) >= self._size:
|
||||
if len(self.store) >= self.size:
|
||||
# Loose the oldest record
|
||||
self.store.pop(0)
|
||||
self.store.append(warning)
|
||||
try:
|
||||
# Append traceback, if available
|
||||
warning = {"type": record.levelname, "text": record.msg % record.args, "time": int(time.time())}
|
||||
if record.exc_info:
|
||||
warning["text"] = "%s\n%s" % (warning["text"], traceback.format_exc())
|
||||
self.store.append(warning)
|
||||
except UnicodeDecodeError:
|
||||
# Catch elusive Unicode conversion problems
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
self.store = []
|
||||
@@ -249,7 +233,7 @@ def daemonize():
|
||||
|
||||
# Get log file path and remove the log file if it got too large
|
||||
log_path = os.path.join(sabnzbd.cfg.log_dir.get_path(), DEF_LOG_ERRFILE)
|
||||
if os.path.exists(log_path) and os.path.getsize(log_path) > sabnzbd.cfg.log_size():
|
||||
if os.path.exists(log_path) and os.path.getsize(log_path) > sabnzbd.cfg.log_size.get_int():
|
||||
remove_file(log_path)
|
||||
|
||||
# Replace file descriptors for stdin, stdout, and stderr
|
||||
@@ -292,7 +276,7 @@ def identify_web_template(key, defweb, wdir):
|
||||
full_main = real_path(full_dir, DEF_MAIN_TMPL)
|
||||
|
||||
if not os.path.exists(full_main):
|
||||
logging.warning_helpful(T("Cannot find web template: %s, trying standard template"), full_main)
|
||||
logging.warning(T("Cannot find web template: %s, trying standard template"), full_main)
|
||||
full_dir = real_path(sabnzbd.DIR_INTERFACES, DEF_STDINTF)
|
||||
full_main = real_path(full_dir, DEF_MAIN_TMPL)
|
||||
if not os.path.exists(full_main):
|
||||
@@ -336,6 +320,7 @@ def get_user_profile_paths(vista_plus):
|
||||
if sabnzbd.DAEMON:
|
||||
# In daemon mode, do not try to access the user profile
|
||||
# just assume that everything defaults to the program dir
|
||||
sabnzbd.DIR_APPDATA = sabnzbd.DIR_PROG
|
||||
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_PROG
|
||||
sabnzbd.DIR_HOME = sabnzbd.DIR_PROG
|
||||
if sabnzbd.WIN32:
|
||||
@@ -349,6 +334,8 @@ def get_user_profile_paths(vista_plus):
|
||||
try:
|
||||
from win32com.shell import shell, shellcon
|
||||
|
||||
path = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0)
|
||||
sabnzbd.DIR_APPDATA = os.path.join(path, DEF_WORKDIR)
|
||||
path = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0)
|
||||
sabnzbd.DIR_LCLDATA = os.path.join(path, DEF_WORKDIR)
|
||||
sabnzbd.DIR_HOME = os.environ["USERPROFILE"]
|
||||
@@ -357,16 +344,18 @@ def get_user_profile_paths(vista_plus):
|
||||
if vista_plus:
|
||||
root = os.environ["AppData"]
|
||||
user = os.environ["USERPROFILE"]
|
||||
sabnzbd.DIR_LCLDATA = "%s\\%s" % (root.replace("\\Roaming", "\\Local"), DEF_WORKDIR)
|
||||
sabnzbd.DIR_APPDATA = "%s\\%s" % (root.replace("\\Roaming", "\\Local"), DEF_WORKDIR)
|
||||
sabnzbd.DIR_HOME = user
|
||||
else:
|
||||
root = os.environ["USERPROFILE"]
|
||||
sabnzbd.DIR_LCLDATA = "%s\\%s" % (root, DEF_WORKDIR)
|
||||
sabnzbd.DIR_APPDATA = "%s\\%s" % (root, DEF_WORKDIR)
|
||||
sabnzbd.DIR_HOME = root
|
||||
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
|
||||
except:
|
||||
pass
|
||||
|
||||
# Long-path everything
|
||||
sabnzbd.DIR_APPDATA = long_path(sabnzbd.DIR_APPDATA)
|
||||
sabnzbd.DIR_LCLDATA = long_path(sabnzbd.DIR_LCLDATA)
|
||||
sabnzbd.DIR_HOME = long_path(sabnzbd.DIR_HOME)
|
||||
return
|
||||
@@ -374,14 +363,16 @@ def get_user_profile_paths(vista_plus):
|
||||
elif sabnzbd.DARWIN:
|
||||
home = os.environ.get("HOME")
|
||||
if home:
|
||||
sabnzbd.DIR_LCLDATA = "%s/Library/Application Support/SABnzbd" % home
|
||||
sabnzbd.DIR_APPDATA = "%s/Library/Application Support/SABnzbd" % home
|
||||
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
|
||||
sabnzbd.DIR_HOME = home
|
||||
return
|
||||
else:
|
||||
# Unix/Linux
|
||||
home = os.environ.get("HOME")
|
||||
if home:
|
||||
sabnzbd.DIR_LCLDATA = "%s/.%s" % (home, DEF_WORKDIR)
|
||||
sabnzbd.DIR_APPDATA = "%s/.%s" % (home, DEF_WORKDIR)
|
||||
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
|
||||
sabnzbd.DIR_HOME = home
|
||||
return
|
||||
|
||||
@@ -428,12 +419,10 @@ def print_modules():
|
||||
logging.info("UNRAR binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)
|
||||
|
||||
# Report problematic unrar
|
||||
if sabnzbd.newsunpack.RAR_PROBLEM:
|
||||
if sabnzbd.newsunpack.RAR_PROBLEM and not sabnzbd.cfg.ignore_wrong_unrar():
|
||||
have_str = "%.2f" % (float(sabnzbd.newsunpack.RAR_VERSION) / 100)
|
||||
want_str = "%.2f" % (float(sabnzbd.constants.REC_RAR_VERSION) / 100)
|
||||
logging.warning_helpful(
|
||||
T("Your UNRAR version is %s, we recommend version %s or higher.<br />"), have_str, want_str
|
||||
)
|
||||
logging.warning(T("Your UNRAR version is %s, we recommend version %s or higher.<br />"), have_str, want_str)
|
||||
elif not (sabnzbd.WIN32 or sabnzbd.DARWIN):
|
||||
logging.info("UNRAR binary version %.2f", (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
|
||||
else:
|
||||
@@ -506,8 +495,8 @@ def check_resolve(host):
|
||||
|
||||
|
||||
def get_webhost(cherryhost, cherryport, https_port):
|
||||
"""Determine the webhost address and port,
|
||||
return (host, port, browserhost)
|
||||
""" Determine the webhost address and port,
|
||||
return (host, port, browserhost)
|
||||
"""
|
||||
if cherryhost == "0.0.0.0" and not check_resolve("127.0.0.1"):
|
||||
cherryhost = ""
|
||||
@@ -531,7 +520,7 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
# Valid user defined name?
|
||||
info = socket.getaddrinfo(cherryhost, None)
|
||||
except socket.error:
|
||||
if cherryhost not in LOCALHOSTS:
|
||||
if cherryhost not in ("localhost", "127.0.0.1", "::1"):
|
||||
cherryhost = "0.0.0.0"
|
||||
try:
|
||||
info = socket.getaddrinfo(localhost, None)
|
||||
@@ -598,12 +587,12 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
except socket.error:
|
||||
cherryhost = cherryhost.strip("[]")
|
||||
|
||||
if ipv6 and ipv4 and browserhost not in LOCALHOSTS:
|
||||
if ipv6 and ipv4 and (browserhost not in ("localhost", "127.0.0.1", "[::1]", "::1")):
|
||||
sabnzbd.AMBI_LOCALHOST = True
|
||||
logging.info("IPV6 has priority on this system, potential Firefox issue")
|
||||
|
||||
if ipv6 and ipv4 and cherryhost == "" and sabnzbd.WIN32:
|
||||
logging.warning_helpful(T("Please be aware the 0.0.0.0 hostname will need an IPv6 address for external access"))
|
||||
logging.warning(T("Please be aware the 0.0.0.0 hostname will need an IPv6 address for external access"))
|
||||
|
||||
if cherryhost == "localhost" and not sabnzbd.WIN32 and not sabnzbd.DARWIN:
|
||||
# On the Ubuntu family, localhost leads to problems for CherryPy
|
||||
@@ -613,7 +602,7 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
if ips[0] != "127.0.0.1":
|
||||
browserhost = "127.0.0.1"
|
||||
|
||||
# This is to please Chrome on macOS
|
||||
# This is to please Chrome on OSX
|
||||
if cherryhost == "localhost" and sabnzbd.DARWIN:
|
||||
cherryhost = "127.0.0.1"
|
||||
browserhost = "localhost"
|
||||
@@ -678,8 +667,8 @@ def find_free_port(host, currentport):
|
||||
|
||||
|
||||
def check_for_sabnzbd(url, upload_nzbs, allow_browser=True):
|
||||
"""Check for a running instance of sabnzbd on this port
|
||||
allow_browser==True|None will launch the browser, False will not.
|
||||
""" Check for a running instance of sabnzbd on this port
|
||||
allow_browser==True|None will launch the browser, False will not.
|
||||
"""
|
||||
if allow_browser is None:
|
||||
allow_browser = True
|
||||
@@ -701,10 +690,10 @@ def check_for_sabnzbd(url, upload_nzbs, allow_browser=True):
|
||||
|
||||
|
||||
def evaluate_inipath(path):
|
||||
"""Derive INI file path from a partial path.
|
||||
Full file path: if file does not exist the name must contain a dot
|
||||
but not a leading dot.
|
||||
foldername is enough, the standard name will be appended.
|
||||
""" Derive INI file path from a partial path.
|
||||
Full file path: if file does not exist the name must contain a dot
|
||||
but not a leading dot.
|
||||
foldername is enough, the standard name will be appended.
|
||||
"""
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
inipath = os.path.join(path, DEF_INI_FILE)
|
||||
@@ -721,16 +710,16 @@ def evaluate_inipath(path):
|
||||
|
||||
|
||||
def commandline_handler():
|
||||
"""Split win32-service commands are true parameters
|
||||
Returns:
|
||||
service, sab_opts, serv_opts, upload_nzbs
|
||||
""" Split win32-service commands are true parameters
|
||||
Returns:
|
||||
service, sab_opts, serv_opts, upload_nzbs
|
||||
"""
|
||||
service = ""
|
||||
sab_opts = []
|
||||
serv_opts = [os.path.normpath(os.path.abspath(sys.argv[0]))]
|
||||
upload_nzbs = []
|
||||
|
||||
# macOS binary: get rid of the weird -psn_0_123456 parameter
|
||||
# OSX binary: get rid of the weird -psn_0_123456 parameter
|
||||
for arg in sys.argv:
|
||||
if arg.startswith("-psn_"):
|
||||
sys.argv.remove(arg)
|
||||
@@ -854,6 +843,7 @@ def main():
|
||||
pid_path = None
|
||||
pid_file = None
|
||||
new_instance = False
|
||||
osx_console = False
|
||||
ipv6_hosting = None
|
||||
|
||||
_service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()
|
||||
@@ -1116,7 +1106,7 @@ def main():
|
||||
try:
|
||||
if not no_file_log:
|
||||
rollover_log = logging.handlers.RotatingFileHandler(
|
||||
sabnzbd.LOGFILE, "a+", sabnzbd.cfg.log_size(), sabnzbd.cfg.log_backups()
|
||||
sabnzbd.LOGFILE, "a+", sabnzbd.cfg.log_size.get_int(), sabnzbd.cfg.log_backups()
|
||||
)
|
||||
rollover_log.setFormatter(logging.Formatter(logformat))
|
||||
logger.addHandler(rollover_log)
|
||||
@@ -1138,19 +1128,8 @@ def main():
|
||||
if no_file_log:
|
||||
logging.info("Console logging only")
|
||||
|
||||
# Start SABnzbd
|
||||
logging.info("--------------------------------")
|
||||
logging.info("%s-%s", sabnzbd.MY_NAME, sabnzbd.__version__)
|
||||
|
||||
# See if we can get version from git when running an unknown revision
|
||||
if sabnzbd.__baseline__ == "unknown":
|
||||
try:
|
||||
sabnzbd.__baseline__ = sabnzbd.misc.run_command(
|
||||
["git", "rev-parse", "--short", "HEAD"], cwd=sabnzbd.DIR_PROG
|
||||
).strip()
|
||||
except:
|
||||
pass
|
||||
logging.info("Commit: %s", sabnzbd.__baseline__)
|
||||
logging.info("%s-%s (rev=%s)", sabnzbd.MY_NAME, sabnzbd.__version__, sabnzbd.__baseline__)
|
||||
logging.info("Full executable path = %s", sabnzbd.MY_FULLNAME)
|
||||
if sabnzbd.WIN32:
|
||||
suffix = ""
|
||||
@@ -1174,7 +1153,7 @@ def main():
|
||||
|
||||
# On Linux/FreeBSD/Unix "UTF-8" is strongly, strongly adviced:
|
||||
if not sabnzbd.WIN32 and not sabnzbd.DARWIN and not ("utf-8" in sabnzbd.encoding.CODEPAGE.lower()):
|
||||
logging.warning_helpful(
|
||||
logging.warning(
|
||||
T(
|
||||
"SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads."
|
||||
),
|
||||
@@ -1204,6 +1183,9 @@ def main():
|
||||
ctx = ssl.create_default_context()
|
||||
logging.debug("Available certificates: %s", repr(ctx.cert_store_stats()))
|
||||
|
||||
# Show IPv4/IPv6 address
|
||||
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6
|
||||
|
||||
mylocalipv4 = localipv4()
|
||||
if mylocalipv4:
|
||||
logging.debug("My local IPv4 address = %s", mylocalipv4)
|
||||
@@ -1239,7 +1221,7 @@ def main():
|
||||
if autobrowser is not None:
|
||||
sabnzbd.cfg.autobrowser.set(autobrowser)
|
||||
|
||||
sabnzbd.initialize(pause, clean_up, repair=repair)
|
||||
sabnzbd.initialize(pause, clean_up, evalSched=True, repair=repair)
|
||||
|
||||
os.chdir(sabnzbd.DIR_PROG)
|
||||
|
||||
@@ -1415,7 +1397,6 @@ def main():
|
||||
|
||||
# Make available from both URLs
|
||||
main_page = sabnzbd.interface.MainPage()
|
||||
cherrypy.Application.relative_urls = "server"
|
||||
cherrypy.tree.mount(main_page, "/", config=appconfig)
|
||||
cherrypy.tree.mount(main_page, sabnzbd.cfg.url_base(), config=appconfig)
|
||||
|
||||
@@ -1484,37 +1465,25 @@ def main():
|
||||
check_latest_version()
|
||||
autorestarted = False
|
||||
|
||||
# bonjour/zeroconf needs an ip. Lets try to find it.
|
||||
external_host = localipv4() # IPv4 address of the LAN interface. This is the normal use case
|
||||
if not external_host:
|
||||
# None, so no network / default route, so let's set to ...
|
||||
external_host = "127.0.0.1"
|
||||
elif probablyipv4(cherryhost) and cherryhost not in LOCALHOSTS + ("0.0.0.0", "::"):
|
||||
# a hard-configured cherryhost other than the usual, so let's take that (good or wrong)
|
||||
external_host = cherryhost
|
||||
logging.debug("bonjour/zeroconf/SSDP using host: %s", external_host)
|
||||
sabnzbd.zconfig.set_bonjour(external_host, cherryport)
|
||||
|
||||
# Start SSDP if SABnzbd is running exposed
|
||||
if cherryhost not in LOCALHOSTS:
|
||||
# Set URL for browser for external hosts
|
||||
if enable_https:
|
||||
ssdp_url = "https://%s:%s%s" % (external_host, cherryport, sabnzbd.cfg.url_base())
|
||||
else:
|
||||
ssdp_url = "http://%s:%s%s" % (external_host, cherryport, sabnzbd.cfg.url_base())
|
||||
ssdp.start_ssdp(
|
||||
external_host,
|
||||
"SABnzbd",
|
||||
ssdp_url,
|
||||
"SABnzbd %s" % sabnzbd.__version__,
|
||||
"SABnzbd Team",
|
||||
"https://sabnzbd.org/",
|
||||
"SABnzbd %s" % sabnzbd.__version__,
|
||||
)
|
||||
# ZeroConfig/Bonjour needs a ip. Lets try to find it.
|
||||
try:
|
||||
z_host = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
z_host = cherryhost
|
||||
sabnzbd.zconfig.set_bonjour(z_host, cherryport)
|
||||
|
||||
# Have to keep this running, otherwise logging will terminate
|
||||
timer = 0
|
||||
while not sabnzbd.SABSTOP:
|
||||
if sabnzbd.LAST_WARNING:
|
||||
msg = sabnzbd.LAST_WARNING
|
||||
sabnzbd.LAST_WARNING = None
|
||||
sabnzbd.notifier.send_notification(T("Warning"), msg, "warning")
|
||||
if sabnzbd.LAST_ERROR:
|
||||
msg = sabnzbd.LAST_ERROR
|
||||
sabnzbd.LAST_ERROR = None
|
||||
sabnzbd.notifier.send_notification(T("Error"), msg, "error")
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
# Check for loglevel changes
|
||||
@@ -1531,7 +1500,7 @@ def main():
|
||||
# Keep OS awake (if needed)
|
||||
sabnzbd.keep_awake()
|
||||
# Restart scheduler (if needed)
|
||||
sabnzbd.Scheduler.restart(plan_restart=False)
|
||||
scheduler.restart()
|
||||
# Save config (if needed)
|
||||
config.save_config()
|
||||
# Check the threads
|
||||
@@ -1545,18 +1514,17 @@ def main():
|
||||
# Check for auto-restart request
|
||||
# Or special restart cases like Mac and WindowsService
|
||||
if sabnzbd.TRIGGER_RESTART:
|
||||
logging.info("Performing triggered restart")
|
||||
# Shutdown
|
||||
sabnzbd.shutdown_program()
|
||||
|
||||
if sabnzbd.Downloader.paused:
|
||||
if sabnzbd.downloader.Downloader.do.paused:
|
||||
sabnzbd.RESTART_ARGS.append("-p")
|
||||
if autorestarted:
|
||||
sabnzbd.RESTART_ARGS.append("--autorestarted")
|
||||
sys.argv = sabnzbd.RESTART_ARGS
|
||||
|
||||
os.chdir(org_dir)
|
||||
# If macOS frozen restart of app instead of embedded python
|
||||
# If OSX frozen restart of app instead of embedded python
|
||||
if hasattr(sys, "frozen") and sabnzbd.DARWIN:
|
||||
# [[NSProcessInfo processInfo] processIdentifier]]
|
||||
# logging.info("%s" % (NSProcessInfo.processInfo().processIdentifier()))
|
||||
@@ -1564,7 +1532,7 @@ def main():
|
||||
my_name = sabnzbd.MY_FULLNAME.replace("/Contents/MacOS/SABnzbd", "")
|
||||
my_args = " ".join(sys.argv[1:])
|
||||
cmd = 'kill -9 %s && open "%s" --args %s' % (my_pid, my_name, my_args)
|
||||
logging.info("Launching: %s", cmd)
|
||||
logging.info("Launching: ", cmd)
|
||||
os.system(cmd)
|
||||
elif sabnzbd.WIN_SERVICE:
|
||||
# Use external service handler to do the restart
|
||||
@@ -1669,14 +1637,13 @@ https://sabnzbd.org/wiki/advanced/sabnzbd-as-a-windows-service
|
||||
|
||||
|
||||
def handle_windows_service():
|
||||
"""Handle everything for Windows Service
|
||||
Returns True when any service commands were detected or
|
||||
when we have started as a service.
|
||||
""" Handle everything for Windows Service
|
||||
Returns True when any service commands were detected or
|
||||
when we have started as a service.
|
||||
"""
|
||||
# Detect if running as Windows Service (only Vista and above!)
|
||||
# Adapted from https://stackoverflow.com/a/55248281/5235502
|
||||
# Only works when run from the exe-files
|
||||
if hasattr(sys, "frozen") and win32ts.ProcessIdToSessionId(win32api.GetCurrentProcessId()) == 0:
|
||||
if win32ts.ProcessIdToSessionId(win32api.GetCurrentProcessId()) == 0:
|
||||
servicemanager.Initialize()
|
||||
servicemanager.PrepareToHostSingle(SABnzbd)
|
||||
servicemanager.StartServiceCtrlDispatcher()
|
||||
@@ -1725,7 +1692,7 @@ if __name__ == "__main__":
|
||||
|
||||
elif sabnzbd.DARWIN and sabnzbd.FOUNDATION:
|
||||
|
||||
# macOS binary runner
|
||||
# OSX binary runner
|
||||
from threading import Thread
|
||||
from PyObjCTools import AppHelper
|
||||
from AppKit import NSApplication
|
||||
|
||||
15
appveyor.yml
Normal file
15
appveyor.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
environment:
|
||||
# We only test the latest Python version
|
||||
matrix:
|
||||
- PYTHON: "C:\\Python38-x64"
|
||||
|
||||
install:
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
- python --version
|
||||
- python -m pip install --upgrade pip
|
||||
- python -m pip install --upgrade wheel
|
||||
- python -m pip install --upgrade -r requirements.txt
|
||||
- python -m pip install --upgrade -r tests/requirements.txt
|
||||
|
||||
build_script:
|
||||
- python -m pytest -s
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Config"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/configure"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/configure"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<!--#from sabnzbd.encoding import CODEPAGE#-->
|
||||
@@ -9,7 +9,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">$T('version'): </th>
|
||||
<td>$version [<a href="https://github.com/sabnzbd/sabnzbd/commit/$build" target="_blank">$build</a>]</td>
|
||||
<td>$version [$build]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">$T('uptime'): </th>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Categories"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/categories"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/categories"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
<div class="colmask">
|
||||
<div class="section">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Folders"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/folders"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/folders"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="General"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/general"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/general"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--#set global $pane="Email"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/notifications"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/notifications"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<!--#def show_notify_checkboxes($section_label)#-->
|
||||
<!--#for $type in $notify_types#-->
|
||||
<!--#for $type in $notify_keys#-->
|
||||
<div class="field-pair">
|
||||
<label class="config wide" for="${section_label}_prio_$type">
|
||||
$T($notify_types[$type]).replace('/', ' / ')
|
||||
$T($notify_texts[$type]).replace('/', ' / ') <!--#if $type == 'download'#--> / $T('link-pause') / $T('link-resume')<!--#end if#-->
|
||||
</label>
|
||||
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if int($getVar($section_label + '_prio_' + $type)) > 0 then 'checked="checked"' else ""#--> />
|
||||
</div>
|
||||
@@ -232,10 +232,10 @@
|
||||
<span class="desc">$T('explain-prowl_apikey')</span>
|
||||
</div>
|
||||
<!--#set $section_label = 'prowl'#-->
|
||||
<!--#for $type in $notify_types#-->
|
||||
<!--#for $type in $notify_keys#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${section_label}_prio_$type">
|
||||
$T($notify_types[$type]).replace('/', ' / ')
|
||||
$T($notify_texts[$type]).replace('/', ' / ') <!--#if $type == 'download'#--> / $T('link-pause') / $T('link-resume')<!--#end if#-->
|
||||
</label>
|
||||
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
|
||||
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
|
||||
@@ -298,10 +298,10 @@
|
||||
<span class="desc">$T('explain-pushover_emergency_expire')</span>
|
||||
</div>
|
||||
<!--#set $section_label = 'pushover'#-->
|
||||
<!--#for $type in $notify_types#-->
|
||||
<!--#for $type in $notify_keys#-->
|
||||
<div class="field-pair">
|
||||
<label class="config" for="${section_label}_prio_$type">
|
||||
$T($notify_types[$type]).replace('/', ' / ')
|
||||
$T($notify_texts[$type]).replace('/', ' / ') <!--#if $type == 'download'#--> / $T('link-pause') / $T('link-resume')<!--#end if#-->
|
||||
</label>
|
||||
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
|
||||
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="RSS"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/rss"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/rss"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
<div class="colmask">
|
||||
<!--#if not $active_feed#-->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Scheduling"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/scheduling"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/scheduling"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<%
|
||||
@@ -50,7 +50,7 @@ else:
|
||||
<select name="action" id="action">
|
||||
<optgroup label="$T('sch-action')">
|
||||
<!--#for $action in $actions#-->
|
||||
<option value="$action" data-action="" data-noarg="<!--#if $action == 'speedlimit' then 0 else 1#-->">$actions_lng[$action]</option>
|
||||
<option value="$action" data-action="" data-noarg="<!--#if $action is 'speedlimit' then 0 else 1#-->">$actions_lng[$action]</option>
|
||||
<!--#end for#-->
|
||||
</optgroup>
|
||||
<optgroup label="$T('cmenu-servers')">
|
||||
|
||||
@@ -1,9 +1,45 @@
|
||||
<!--#set global $pane="Servers"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/servers"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/servers"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<!--
|
||||
We need to find how many months we have recorded so far, so we
|
||||
loop over all the dates to find the lowest value and then use
|
||||
this to calculate the date-selector and maximum value per month.
|
||||
-->
|
||||
<!--#import json#-->
|
||||
<!--#import datetime#-->
|
||||
<!--#import sabnzbd.misc#-->
|
||||
|
||||
<!--#set month_names = [$T('January'), $T('February'), $T('March'), $T('April'), $T('May'), $T('June'), $T('July'), $T('August'), $T('September'), $T('October'), $T('November'), $T('December')] #-->
|
||||
<!--#set min_date = datetime.date.today()#-->
|
||||
<!--#set max_data_all = {}#-->
|
||||
|
||||
<!--#for $server in $servers #-->
|
||||
<!--#if 'amounts' in $server#-->
|
||||
<!--#set max_data_server = {}#-->
|
||||
<!--#for date in $server['amounts'][4]#-->
|
||||
<!--#set split_date = $date.split('-')#-->
|
||||
<!--#set min_date = min(min_date, datetime.date(int(split_date[0]), int(split_date[1]), 1))#-->
|
||||
|
||||
<!--#set month_date = $date[:7]#-->
|
||||
<!--#if $month_date not in $max_data_server#-->
|
||||
<!--#set max_data_server[$month_date] = 0#-->
|
||||
<!--#end if#-->
|
||||
<!--#set max_data_server[$month_date] = max(max_data_server[$month_date], $server['amounts'][4][$date])#-->
|
||||
<!--#end for#-->
|
||||
|
||||
<!--#for month_date in max_data_server#-->
|
||||
<!--#if $month_date not in $max_data_all#-->
|
||||
<!--#set max_data_all[$month_date] = 0#-->
|
||||
<!--#end if#-->
|
||||
<!--#set max_data_all[$month_date] = max(max_data_all[$month_date], max_data_server[$month_date])#-->
|
||||
<!--#end for#-->
|
||||
<!--#end if#-->
|
||||
<!--#end for#-->
|
||||
|
||||
<!--#set months_recorded = list(sabnzbd.misc.monthrange(min_date, datetime.date.today()))#-->
|
||||
<!--#$months_recorded.reverse()#-->
|
||||
|
||||
<script type="text/javascript">
|
||||
// Define variable needed for the server-plots
|
||||
@@ -17,13 +53,21 @@
|
||||
<input type="checkbox" id="advanced-settings-button" name="advanced-settings-button"> $T('button-advanced')
|
||||
</label>
|
||||
|
||||
<!--#if $months_recorded#-->
|
||||
<div class="advanced-buttonSeperator"></div>
|
||||
<div class="chart-selector-container" title="$T('srv-bandwidth')">
|
||||
<span class="glyphicon glyphicon-signal"></span>
|
||||
<!--#set today = datetime.date.today()#-->
|
||||
<input type="date" name="chart-start" id="chart-start" value="<!--#echo (today-datetime.timedelta(days=30)).strftime('%Y-%m-%d')#-->"> -
|
||||
<input type="date" name="chart-end" id="chart-end" value="<!--#echo today.strftime('%Y-%m-%d')#-->">
|
||||
<select name="chart-selector" id="chart-selector">
|
||||
<!--#for $cur_date in months_recorded#-->
|
||||
<!--#set month_date = '%d-%02d' % ($cur_date.year, $cur_date.month)#-->
|
||||
<!--#if $month_date not in $max_data_all#-->
|
||||
<!--#set max_data_all[$month_date] = 0#-->
|
||||
<!--#end if#-->
|
||||
<option value="$month_date" data-max="$max_data_all[$month_date]">$month_names[$cur_date.month-1] $cur_date.year</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
</div>
|
||||
<div class="section" id="addServerContent" style="display: none;">
|
||||
<div class="col2">
|
||||
@@ -243,7 +287,7 @@
|
||||
$T('today'): $(server['amounts'][3])B<br/>
|
||||
$T('thisWeek'): $(server['amounts'][2])B<br/>
|
||||
$T('thisMonth'): $(server['amounts'][1])B<br/>
|
||||
$T('custom'): <span id="server-data-value-${cur}"></span>
|
||||
<span id="server-data-label-${cur}"></span>: <span id="server-data-value-${cur}"></span>
|
||||
</div>
|
||||
<div class="server-chart" data-serverid="${cur}"s>
|
||||
<div id="server-chart-${cur}" class="ct-chart"></div>
|
||||
@@ -287,72 +331,58 @@
|
||||
}
|
||||
|
||||
function showCharts() {
|
||||
// Get the constants
|
||||
const startDate = new Date(\$('#chart-start').val())
|
||||
const endDate = new Date(\$('#chart-end').val())
|
||||
const oneDay = 24 * 60 * 60 * 1000
|
||||
const nrDays = Math.round((endDate-startDate)/oneDay)
|
||||
// This month
|
||||
var theMonth = \$('#chart-selector').val()
|
||||
var thisDay = new Date()
|
||||
|
||||
// Show only maximum 10 labels to avoid cluttering
|
||||
const labelStep = Math.round(nrDays/10)
|
||||
// What month are we doing?
|
||||
var inputDate = new Date(theMonth+'-01')
|
||||
var baseDate = new Date(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), 1)
|
||||
var maxDaysInMonth = new Date(baseDate.getFullYear(), baseDate.getMonth()+1, 0).getDate()
|
||||
|
||||
// Save largest value
|
||||
var maxVal = 0
|
||||
// Set the new maximum
|
||||
chartOptions.axisY.high = \$('#chart-selector :selected').data('max');
|
||||
chartOptions.axisY.low = 0
|
||||
|
||||
// For each chart
|
||||
\$('.server-chart').each(function(j, elemn) {
|
||||
const server_id = \$(elemn).data('serverid')
|
||||
var totalThisRange = 0
|
||||
\$('.server-chart').each(function(i, elemn) {
|
||||
var server_id = \$(elemn).data('serverid')
|
||||
|
||||
// Fill the data array
|
||||
var data = {
|
||||
labels: [],
|
||||
series: [[]]
|
||||
};
|
||||
|
||||
for(var i = 0; i < nrDays+1; i++) {
|
||||
// Update the date
|
||||
const checkDate = new Date(startDate)
|
||||
checkDate.setDate(checkDate.getDate() + i);
|
||||
|
||||
var totalThisMonth = 0
|
||||
for(var i = 1; i < maxDaysInMonth+1; i++) {
|
||||
// Add X-label
|
||||
if(i % labelStep === 0) {
|
||||
data['labels'].push(checkDate.getDate())
|
||||
if(i % 3 == 1) {
|
||||
data['labels'].push(i)
|
||||
} else {
|
||||
data['labels'].push(NaN)
|
||||
}
|
||||
|
||||
// Date we can check in the array
|
||||
const dateCheck = toFormattedDate(checkDate)
|
||||
// Get formatted date
|
||||
baseDate.setDate(i)
|
||||
var dateCheck = toFormattedDate(baseDate)
|
||||
|
||||
// Add data if we have it
|
||||
if(dateCheck in serverData[server_id]) {
|
||||
data['series'][0].push(serverData[server_id][dateCheck])
|
||||
totalThisRange += serverData[server_id][dateCheck]
|
||||
maxVal = Math.max(maxVal, serverData[server_id][dateCheck])
|
||||
} else {
|
||||
totalThisMonth += serverData[server_id][dateCheck]
|
||||
} else if(thisDay.getYear() == baseDate.getYear() && thisDay.getMonth() == baseDate.getMonth() && thisDay.getDate() < i) {
|
||||
data['series'][0].push(NaN)
|
||||
} else {
|
||||
data['series'][0].push(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the text value
|
||||
\$('#server-data-value-' + server_id).text(filesize(totalThisRange, {round: 1}))
|
||||
|
||||
// Save data in a very ugly way, but we need to do this
|
||||
// so we can calculate the maximum Y-axis for all graphs
|
||||
\$(elemn).data("chart-data", data)
|
||||
})
|
||||
|
||||
// Set the maximum
|
||||
chartOptions.axisY.high = maxVal;
|
||||
chartOptions.axisY.low = 0
|
||||
|
||||
// Update all the axis with the largest value and draw the graph
|
||||
\$('.server-chart').each(function(j, elemn) {
|
||||
const server_id = \$(elemn).data('serverid')
|
||||
\$('#server-data-label-' + server_id).text(\$('#chart-selector :selected').text())
|
||||
\$('#server-data-value-' + server_id).text(filesize(totalThisMonth, {round: 1}))
|
||||
|
||||
// Show the chart
|
||||
chart = new Chartist.Line('#server-chart-'+server_id, \$(elemn).data("chart-data"), chartOptions)
|
||||
chart = new Chartist.Line('#server-chart-'+server_id, data, chartOptions);
|
||||
chart.on('created', function(context) {
|
||||
// Make sure to add this as the first child so it's at the bottom
|
||||
context.svg.elem('rect', {
|
||||
@@ -361,7 +391,7 @@
|
||||
width: context.chartRect.width(),
|
||||
height: context.chartRect.height()+2,
|
||||
fill: 'none',
|
||||
stroke: '#b9b9b9',
|
||||
stroke: '#B9B9B9',
|
||||
'stroke-width': '1px'
|
||||
}, '', context.svg, true)
|
||||
\$('#server-chart-'+server_id+' .ct-label.ct-vertical').each(function(index, elmn) {
|
||||
@@ -369,10 +399,6 @@
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
// Limit input to sensible values
|
||||
\$('#chart-start').attr("max", \$('#chart-end').val())
|
||||
\$('#chart-end').attr("min", \$('#chart-start').val())
|
||||
}
|
||||
|
||||
// Need to mitigate timezone effects!
|
||||
@@ -399,7 +425,7 @@
|
||||
/**
|
||||
Update charts when changed
|
||||
**/
|
||||
\$('#chart-start, #chart-end').on('change', function(elemn) {
|
||||
\$('#chart-selector').on('change', function(elemn) {
|
||||
showCharts()
|
||||
|
||||
// Lets us leave (needs to be called after the change event)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Sorting"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/sorting"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/sorting"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Special"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/special"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/special"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--#set global $pane="Switches"#-->
|
||||
<!--#set global $help_uri="configuration/3.1/switches"#-->
|
||||
<!--#set global $help_uri="configuration/3.0/switches"#-->
|
||||
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
|
||||
|
||||
<div class="colmask">
|
||||
@@ -237,11 +237,6 @@
|
||||
<input type="checkbox" name="ignore_samples" id="ignore_samples" value="1" <!--#if int($ignore_samples) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-ignore_samples') $T('igsam-del').</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="deobfuscate_final_filenames">$T('opt-deobfuscate_final_filenames')</label>
|
||||
<input type="checkbox" name="deobfuscate_final_filenames" id="deobfuscate_final_filenames" value="1" <!--#if int($deobfuscate_final_filenames) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-deobfuscate_final_filenames')</span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="enable_meta">$T('opt-enable_meta')</label>
|
||||
<input type="checkbox" name="enable_meta" id="enable_meta" value="1" <!--#if int($enable_meta) > 0 then 'checked="checked"' else ""#--> />
|
||||
|
||||
@@ -795,7 +795,6 @@ input[type="submit"]:hover {
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="url"],
|
||||
input[type="date"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
textarea,
|
||||
|
||||
@@ -439,7 +439,7 @@
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">$T('Glitter-addNZB')</h4>
|
||||
</div>
|
||||
<div class="modal-body form-horizontal">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<form data-bind="submit: addNZBFromURL" class="col-sm-6">
|
||||
<fieldset>
|
||||
@@ -470,45 +470,29 @@
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('name')</label>
|
||||
<div class="row form-horizontal">
|
||||
<label class="col-sm-6 control-label">$T('Glitter-addnzbFilename')</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="nzbname" id="nzbname" placeholder="$T('Glitter-addnzbFilename')" class="form-control" />
|
||||
<input type="text" name="nzbname" id="nzbname" placeholder="$T('name')" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('srv-password')</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="password" id="password" placeholder="$T('srv-optional')" class="form-control" />
|
||||
</div>
|
||||
<hr />
|
||||
<div class="clearfix"></div>
|
||||
<div class="add-nzb-inputbox" title="$T('category')" data-tooltip="true" data-placement="left">
|
||||
<span class="glyphicon glyphicon-tag"></span>
|
||||
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText',"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('category')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText',"></select>
|
||||
<span class="glyphicon glyphicon-tag"></span>
|
||||
</div>
|
||||
<div class="add-nzb-inputbox" title="$T('priority')" data-tooltip="true" data-placement="left">
|
||||
<span class="glyphicon glyphicon-sort-by-attributes-alt"></span>
|
||||
<select name="Priority" class="form-control" data-bind="options: queue.priorityOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('priority')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Priority" class="form-control" data-bind="options: queue.priorityOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
|
||||
<span class="glyphicon glyphicon-sort-by-attributes-alt"></span>
|
||||
</div>
|
||||
<div class="add-nzb-inputbox" title="$T('swtag-pp')" data-tooltip="true" data-placement="left">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('swtag-pp')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">$T('eoq-scripts')</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '$T('default')', enable: (queue.scriptsList().length > 1)"></select>
|
||||
<span class="glyphicon glyphicon-flash"></span>
|
||||
</div>
|
||||
<div class="add-nzb-inputbox" title="$T('eoq-scripts')" data-tooltip="true" data-placement="left">
|
||||
<span class="glyphicon glyphicon-flash"></span>
|
||||
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '$T('default')'"></select>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -668,7 +668,6 @@ function ViewModel() {
|
||||
mode: "addurl",
|
||||
name: $(form.nzbURL).val(),
|
||||
nzbname: $('#nzbname').val(),
|
||||
password: $('#password').val(),
|
||||
script: $('#modal-add-nzb select[name="Post-processing"]').val(),
|
||||
priority: $('#modal-add-nzb select[name="Priority"]').val(),
|
||||
pp: $('#modal-add-nzb select[name="Processing"]').val()
|
||||
@@ -708,7 +707,6 @@ function ViewModel() {
|
||||
data.append("name", file);
|
||||
data.append("mode", "addfile");
|
||||
data.append("nzbname", $('#nzbname').val());
|
||||
data.append("password", $('#password').val());
|
||||
data.append("script", $('#modal-add-nzb select[name="Post-processing"]').val())
|
||||
data.append("priority", $('#modal-add-nzb select[name="Priority"]').val())
|
||||
data.append("apikey", apiKey);
|
||||
|
||||
@@ -228,11 +228,11 @@ function QueueListModel(parent) {
|
||||
switch($(event.currentTarget).data('action')) {
|
||||
case 'sortAgeAsc':
|
||||
sort = 'avg_age';
|
||||
dir = 'desc';
|
||||
dir = 'asc';
|
||||
break;
|
||||
case 'sortAgeDesc':
|
||||
sort = 'avg_age';
|
||||
dir = 'asc';
|
||||
dir = 'desc';
|
||||
break;
|
||||
case 'sortNameAsc':
|
||||
sort = 'name';
|
||||
@@ -751,4 +751,4 @@ function QueueModel(parent, data) {
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -852,24 +852,18 @@ tr.queue-item>td:first-child>a {
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox {
|
||||
width: 20%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox select {
|
||||
display: inline-block;
|
||||
width: calc(100% - 30px);
|
||||
margin: 5px 0px 5px 2px;
|
||||
}
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox-small {
|
||||
width: 80px;
|
||||
float: right;
|
||||
padding-left: 0;
|
||||
padding-top: 12px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox-small .label {
|
||||
margin-left: 5px;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox-small span {
|
||||
@@ -890,7 +884,7 @@ tr.queue-item>td:first-child>a {
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox-options {
|
||||
width: auto;
|
||||
padding-right: 7px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.multioperations-selector .add-nzb-inputbox-small label[for="multiedit-pause"],
|
||||
@@ -1282,8 +1276,7 @@ tr.queue-item>td:first-child>a {
|
||||
min-height: 270px;
|
||||
}
|
||||
|
||||
#modal-options .form-group,
|
||||
#modal-add-nzb .form-group {
|
||||
#modal-options .form-group {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -1395,8 +1388,7 @@ tr.queue-item>td:first-child>a {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#modal-options .col-sm-6,
|
||||
#modal-add-nzb .col-sm-6 {
|
||||
#modal-options .col-sm-6 {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
@@ -1528,14 +1520,25 @@ tr.queue-item>td:first-child>a {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#modal-add-nzb .col-sm-6:first-of-type label {
|
||||
text-align: right;
|
||||
#modal-add-nzb .add-nzb-inputbox:nth-child(even) select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#modal-add-nzb select {
|
||||
width: calc(100% - 35px);
|
||||
.add-nzb-inputbox {
|
||||
float: left;
|
||||
width: 50%;
|
||||
margin: 5px 0px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.add-nzb-inputbox select {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
|
||||
.add-nzb-inputbox span {
|
||||
display: inline-block;
|
||||
margin: 8px 2px 0px -20px;
|
||||
}
|
||||
|
||||
.btn-file {
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
<span id="warning_box"><b><a href="${path}status/#tabs-warnings" id="last_warning"><span id="have_warnings">$have_warnings</span> $T('warnings')</a></b></span>
|
||||
#if $pane=="Main"#
|
||||
#if $new_release#⋅ <a href="$new_rel_url" id="new_release" target="_blank">$T('Plush-updateAvailable').replace(' ',' ')</a>#end if#
|
||||
This skin is no longer actively maintained! <a href="${path}config/general/#web_dir"><strong>We recommend using the Glitter skin.</strong></a>
|
||||
#end if#
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -306,8 +306,8 @@ jQuery(function($){
|
||||
$('#queue_sort_list .queue_sort').click(function(event) {
|
||||
var sort, dir;
|
||||
switch ($(this).attr('id')) {
|
||||
case 'sortAgeAsc': sort='avg_age'; dir='desc'; break;
|
||||
case 'sortAgeDesc': sort='avg_age'; dir='asc'; break;
|
||||
case 'sortAgeAsc': sort='avg_age'; dir='asc'; break;
|
||||
case 'sortAgeDesc': sort='avg_age'; dir='desc'; break;
|
||||
case 'sortNameAsc': sort='name'; dir='asc'; break;
|
||||
case 'sortNameDesc': sort='name'; dir='desc'; break;
|
||||
case 'sortSizeAsc': sort='size'; dir='asc'; break;
|
||||
|
||||
BIN
osx/unrar/unrar
BIN
osx/unrar/unrar
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
|
||||
114
po/email/cs.po
114
po/email/cs.po
@@ -1,114 +0,0 @@
|
||||
# SABnzbd Translation Template file EMAIL
|
||||
# Copyright 2011-2020 The SABnzbd-Team
|
||||
# team@sabnzbd.org
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Language-Team: Czech (https://www.transifex.com/sabnzbd/teams/111101/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: cs\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
|
||||
|
||||
#: email/email.tmpl:1
|
||||
msgid ""
|
||||
"##\n"
|
||||
"## Default Email template for SABnzbd\n"
|
||||
"## This a Cheetah template\n"
|
||||
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Newlines and whitespace are significant!\n"
|
||||
"##\n"
|
||||
"## These are the email headers\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd has <!--#if $status then \"completed\" else \"failed\" #--> job $name\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"SABnzbd has downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
|
||||
"<!--#else#-->\n"
|
||||
"SABnzbd has failed to download \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
|
||||
"<!--#end if#-->\n"
|
||||
"Finished at $end_time\n"
|
||||
"Downloaded $size\n"
|
||||
"\n"
|
||||
"Results of the job:\n"
|
||||
"<!--#for $stage in $stages #-->\n"
|
||||
"Stage $stage <!--#slurp#-->\n"
|
||||
"<!--#for $result in $stages[$stage]#-->\n"
|
||||
" $result <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#if $script!=\"\" #-->\n"
|
||||
"Output from user script \"$script\" (Exit code = $script_ret):\n"
|
||||
"$script_output\n"
|
||||
"<!--#end if#-->\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"Enjoy!\n"
|
||||
"<!--#else#-->\n"
|
||||
"Sorry!\n"
|
||||
"<!--#end if#-->\n"
|
||||
msgstr ""
|
||||
|
||||
#: email/rss.tmpl:1
|
||||
msgid ""
|
||||
"##\n"
|
||||
"## RSS Email template for SABnzbd\n"
|
||||
"## This a Cheetah template\n"
|
||||
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Newlines and whitespace are significant!\n"
|
||||
"##\n"
|
||||
"## These are the email headers\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd has added $amount jobs to the queue\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"\n"
|
||||
"SABnzbd has added $amount job(s) to the queue.\n"
|
||||
"They are from RSS feed \"$feed\".\n"
|
||||
"<!--#for $job in $jobs#-->\n"
|
||||
" $job <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"\n"
|
||||
"Bye\n"
|
||||
msgstr ""
|
||||
|
||||
#: email/badfetch.tmpl:1
|
||||
msgid ""
|
||||
"##\n"
|
||||
"## Bad URL Fetch Email template for SABnzbd\n"
|
||||
"## This a Cheetah template\n"
|
||||
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Newlines and whitespace are significant!\n"
|
||||
"##\n"
|
||||
"## These are the email headers\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd failed to fetch an NZB\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"\n"
|
||||
"SABnzbd has failed to retrieve the NZB from $url.\n"
|
||||
"The error message was: $msg\n"
|
||||
"\n"
|
||||
"Bye\n"
|
||||
msgstr ""
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Danish (https://www.transifex.com/sabnzbd/teams/111101/da/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: German (https://www.transifex.com/sabnzbd/teams/111101/de/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Spanish (https://www.transifex.com/sabnzbd/teams/111101/es/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Finnish (https://www.transifex.com/sabnzbd/teams/111101/fi/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: French (https://www.transifex.com/sabnzbd/teams/111101/fr/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: ION, 2020\n"
|
||||
"Language-Team: Hebrew (https://www.transifex.com/sabnzbd/teams/111101/he/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Norwegian Bokmål (https://www.transifex.com/sabnzbd/teams/111101/nb/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Dutch (https://www.transifex.com/sabnzbd/teams/111101/nl/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Polish (https://www.transifex.com/sabnzbd/teams/111101/pl/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Romanian (https://www.transifex.com/sabnzbd/teams/111101/ro/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Russian (https://www.transifex.com/sabnzbd/teams/111101/ru/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Serbian (https://www.transifex.com/sabnzbd/teams/111101/sr/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Swedish (https://www.transifex.com/sabnzbd/teams/111101/sv/)\n"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Chinese (China) (https://www.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: team@sabnzbd.org\n"
|
||||
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
|
||||
@@ -13,16 +13,6 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -86,11 +76,6 @@ msgstr ""
|
||||
msgid "SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -110,6 +95,16 @@ msgstr ""
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr ""
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr ""
|
||||
@@ -153,11 +148,6 @@ msgstr ""
|
||||
msgid "Loading %s failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr ""
|
||||
@@ -211,12 +201,12 @@ msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords were tried)"
|
||||
msgid "WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords were tried)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -225,7 +215,7 @@ msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -238,12 +228,12 @@ msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -329,6 +319,10 @@ msgstr ""
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr ""
|
||||
@@ -595,10 +589,6 @@ msgstr ""
|
||||
msgid " <br />SABnzbd shutdown finished.<br />Wait for about 5 second and then click the button below.<br /><br /><strong><a href=\"..\">Refresh</a></strong><br />"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr ""
|
||||
@@ -680,11 +670,6 @@ msgstr ""
|
||||
msgid "m"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -695,23 +680,17 @@ msgstr ""
|
||||
msgid "Your password file contains more than 30 passwords, testing all these passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr ""
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
@@ -775,6 +754,11 @@ msgstr ""
|
||||
msgid "Unpacking"
|
||||
msgstr ""
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr ""
|
||||
@@ -947,6 +931,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr ""
|
||||
@@ -977,16 +971,6 @@ msgstr ""
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr ""
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr ""
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr ""
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1105,7 +1089,6 @@ msgstr ""
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr ""
|
||||
@@ -1238,10 +1221,20 @@ msgstr ""
|
||||
msgid "Limit Speed"
|
||||
msgstr ""
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr ""
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr ""
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1411,10 +1404,6 @@ msgstr ""
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr ""
|
||||
@@ -1458,10 +1447,6 @@ msgstr ""
|
||||
msgid "see logfile"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr ""
|
||||
@@ -1480,6 +1465,10 @@ msgstr ""
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr ""
|
||||
@@ -1512,11 +1501,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1537,11 +1521,6 @@ msgstr ""
|
||||
msgid "Error while shutting down system"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1564,6 +1543,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr ""
|
||||
@@ -1572,15 +1555,10 @@ msgstr ""
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr ""
|
||||
@@ -1669,16 +1647,6 @@ msgstr ""
|
||||
msgid "Join files"
|
||||
msgstr ""
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr ""
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr ""
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2011,10 +1979,6 @@ msgstr ""
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr ""
|
||||
@@ -3163,7 +3127,7 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Only unpack and run scripts on jobs that passed the verification stage. If turned off, all jobs will be marked as Completed even if they are incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
@@ -3383,14 +3347,6 @@ msgstr ""
|
||||
msgid "Delete after download"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "If filenames of (large) files in the final folder look obfuscated or meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -3866,7 +3822,7 @@ msgstr ""
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr ""
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr ""
|
||||
@@ -4497,6 +4453,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
5087
po/main/cs.po
5087
po/main/cs.po
File diff suppressed because it is too large
Load Diff
208
po/main/da.po
208
po/main/da.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Danish (https://www.transifex.com/sabnzbd/teams/111101/da/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: da\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Advarsel"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fejl"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -102,11 +92,6 @@ msgstr ""
|
||||
"SABnzbd blev startet med kodning %s, dette bør være UTF-8. Forvent problemer"
|
||||
" med Unicoded fil- og mappenavne i downloads."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -126,6 +111,16 @@ msgstr "Kunne ikke starte web-grænseflade: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s startet"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Advarsel"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fejl"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd lukning udført"
|
||||
@@ -169,11 +164,6 @@ msgstr "Fejl i tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Downloadning af %s mislykkedes"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "E-mail afsendelse mislykkedes"
|
||||
@@ -228,20 +218,20 @@ msgstr "Fatal fejl i Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"Pauset job \"%s\" på grund af krypterede RAR fil (hvis oplyst, alle "
|
||||
"adgangskoder blev forsøgt)"
|
||||
"Advarsel: Pauset job \"%s\" på grund af krypterede RAR fil (hvis oplyst, "
|
||||
"alle adgangskoder blev forsøgt)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"Afbrudt job \"%s\" på grund af krypterede RAR fil (hvis oplyst, alle "
|
||||
"adgangskoder blev forsøgt)"
|
||||
"Advarsel: Afbrudt job \"%s\" på grund af krypterede RAR fil (hvis oplyst, "
|
||||
"alle adgangskoder blev forsøgt)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, encryption detected"
|
||||
@@ -249,8 +239,8 @@ msgstr "Afbrudt, kryptering registreret"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "I \"%s\" uønsket extension i RAR fil. Uønsket fil er \"%s\" "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "Advarsel: I \"%s\" uønsket extension i RAR fil. Uønsket fil er \"%s\" "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -262,13 +252,13 @@ msgstr "Afbrudt, uønsket extension fundet"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "Pause job \"%s\" på grund af rating (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "ADVARSEL: Pause job \"%s\" på grund af rating (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "Afbrudt job \"%s\" på grund af rating (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "ADVARSEL: Afbrudt job \"%s\" på grund af rating (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -353,6 +343,10 @@ msgstr "%s er ikke et korrekt ciffer værdi"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC søgning \"%s\" er ikke tilladt her"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Fejl: Sti længde bør være under %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Fejl: Køen er ikke tom, kan ikke skifte mappe."
|
||||
@@ -653,12 +647,6 @@ msgstr ""
|
||||
"derefter på knappen nedenunder..<br /><br /><strong><a "
|
||||
"href=\"..\">Opdater</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Advarsel: Localhost er tvetydig, bruge numerisk IP-adresse."
|
||||
@@ -741,11 +729,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -758,25 +741,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Python script \"%s\" har ikke udfør (+x) tilladelsessæt"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Køre script"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Efterbehandling blev afbrudt (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -838,6 +815,11 @@ msgstr "Udpakning mislykkedes, arkivet kræver adgangskode"
|
||||
msgid "Unpacking"
|
||||
msgstr "Udpakker"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Udpak"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Udpakning mislykkedes, kunne ikke finde %s"
|
||||
@@ -1015,6 +997,16 @@ msgstr "Tjekker ekstra filer"
|
||||
msgid "Checking"
|
||||
msgstr "Kontrollerer"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Python script \"%s\" har ikke udfør (+x) tilladelsessæt"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Forsøger SFV verifikation"
|
||||
@@ -1047,19 +1039,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Start/lukning"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pause"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Genoptag"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1178,7 +1157,6 @@ msgstr "%s => mangler fra alle servere, afviser"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Ødelagt NZB fil %s, springer over (årsag=%s, linje=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tom NZB fil %s"
|
||||
@@ -1312,10 +1290,22 @@ msgstr "Tøm historik"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Hastighedsbegrænsning"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pause"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Genoptag"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1519,10 +1509,6 @@ msgstr "Overførslen kan mislykkes, kun %s af det krævede %s tilgængelig"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Download mislykkedes - ikke på din server (e)"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Efterbehandling"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Flytter"
|
||||
@@ -1566,10 +1552,6 @@ msgstr "Efterbehandling mislykkedes for %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "se logfil"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Download mislykkedes"
|
||||
@@ -1588,6 +1570,10 @@ msgstr "Overførsel fuldført"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Kan ikke oprette endelig mappe %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Efterbehandling"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Ingen par2 sæt"
|
||||
@@ -1620,11 +1606,6 @@ msgstr "RAR filer kontrolleres med succes"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "RAR filer kunne ikke bekræfte"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1645,11 +1626,6 @@ msgstr "Det lykkedes ikke systemet at gå i standby"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Fejl ved lukning af system"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1676,6 +1652,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Forkert RSS-feed beskrivelse \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Mislykkedes at hente RSS fra %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Har ikke gyldig godkendelse til feed %s"
|
||||
@@ -1684,15 +1664,10 @@ msgstr "Har ikke gyldig godkendelse til feed %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Server fejl (server kode %s); kunne ikke få %s på %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Mislykkedes at hente RSS fra %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Server %s bruger et upålideligt HTTPS-certifikat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS Feed %s er tom"
|
||||
@@ -1782,16 +1757,6 @@ msgstr "Downloader"
|
||||
msgid "Join files"
|
||||
msgstr "Sammenlægger filer"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Udpak"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2124,10 +2089,6 @@ msgstr "I dag"
|
||||
msgid "Total"
|
||||
msgstr "Totalt"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Tilpasse"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "på"
|
||||
@@ -3377,11 +3338,8 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Efterbehandling kun verificerede jobs"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgstr ""
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr "Kun udføre efterbehandling af jobs som har bestået PAR2 kontrollen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3631,16 +3589,6 @@ msgstr "Filtrerer prøve filer (f.eks. video eksempler)."
|
||||
msgid "Delete after download"
|
||||
msgstr "Fjern efter download"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "verifikation HTTPS certifikat"
|
||||
@@ -4141,7 +4089,7 @@ msgstr "Notifikation sendt!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Aktiver NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Notification Center"
|
||||
@@ -4796,6 +4744,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr "Glitter har nogle (nye) egenskaber, du kan lide!"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Tilpasse"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "Kompakt layout"
|
||||
|
||||
221
po/main/de.po
221
po/main/de.po
@@ -5,15 +5,12 @@
|
||||
# Translators:
|
||||
# N S <reloxx@interia.pl>, 2020
|
||||
# Safihre <safihre@sabnzbd.org>, 2020
|
||||
# C E <githubce@eiselt.ch>, 2020
|
||||
# Nikolai Bohl <n.kay01@gmail.com>, 2020
|
||||
# hotio, 2020
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: hotio, 2020\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\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"
|
||||
@@ -21,16 +18,6 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Achtung"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fehler"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -110,11 +97,6 @@ msgstr ""
|
||||
"sein. Es werden Probleme mit Unicode codierten Dateien und "
|
||||
"Ordnerbezeichnungen in Downloads erwartet."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr "Konnte weitere Zertifikate vom Paket certifi nicht laden."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -135,6 +117,16 @@ msgstr "Fehler beim Starten der Web-Oberfläche: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s gestartet"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Achtung"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fehler"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd wurde beendet"
|
||||
@@ -178,11 +170,6 @@ msgstr "Fehler in tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Fehler beim Laden von %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr "Zugriff auf PID Datei %s nicht möglich"
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "E-Mail erfolgreich versendet"
|
||||
@@ -238,20 +225,20 @@ msgstr "Schwerer Fehler im Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"\"%s\" wurde angehalten, da es ein verschlüsseltes RAR Archiv enthält (falls"
|
||||
" unterstützt, wurden alle Passwörter ausprobiert)"
|
||||
"ACHTUNG: \"%s\" wurde angehalten, da es ein verschlüsseltes RAR Archiv "
|
||||
"enthält (falls unterstützt, wurden alle Passwörter ausprobiert)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
" \"%s\" wurde abgebrochen, da es ein verschlüsseltes RAR Archiv enthält "
|
||||
"(falls unterstützt, wurden alle Passwörter ausprobiert)"
|
||||
"ACHTUNG: \"%s\" wurde abgebrochen, da es ein verschlüsseltes RAR Archiv "
|
||||
"enthält (falls unterstützt, wurden alle Passwörter ausprobiert)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, encryption detected"
|
||||
@@ -259,8 +246,9 @@ msgstr "Abgebrochen, Verschlüsselung vorhanden"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "Unerwünschter Typ \"%s\" in RAR Datei. Unerwünschte Datei ist %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"WARNUNG: Unerwünschter Typ \"%s\" in RAR Datei. Unerwünschte Datei ist %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -272,13 +260,13 @@ msgstr "Abgebrochen, unerwünschte Dateieindung gefunden"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "Aufgabe \"%s\" aufgrund der Bewertung (%s) pausiert."
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "WARNUNG: Aufgabe \"%s\" aufgrund der Bewertung (%s) pausiert."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "Aufgabe \"%s\" aufgrund der Bewertung (%s) abgebrochen"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "Warnung: Aufgabe \"%s\" aufgrund der Bewertung (%s) abgebrochen"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -366,6 +354,10 @@ msgstr "%s ist kein gültiger Oktal-Wert"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-Pfad \"%s\" ist hier nicht erlaubt"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Fehler: Dateipfadlänge sollte kürzer als %s sein."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr ""
|
||||
@@ -676,14 +668,6 @@ msgstr ""
|
||||
" danach auf folgenden Knopf.<br /><br /><strong><a "
|
||||
"href=\"..\">Aktualisieren</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
"Der \"Abgeschlossene Downloads\"-Ordner darf kein Unterordner des "
|
||||
"\"Temporäre Downloads\"-Ordners sein."
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr ""
|
||||
@@ -769,11 +753,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Hochladen der Datei %s fehlgeschlagen"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -789,25 +768,19 @@ msgstr ""
|
||||
"Passwörter dauert sehr lange. Versuchen Sie, nur nützliche Passwörter "
|
||||
"aufzulisten."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr "Konnte die Passwortdatei %s nicht lesen"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "[%s] Der Befehl in build_command ist nicht definiert."
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Dem Pythonskript \"%s\" fehlen die Ausführungsrechte (+x)"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Ausführen des Skripts"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Nachbearbeitung wurde abgebrochen (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skript"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -869,6 +842,11 @@ msgstr "Entpacken fehlgeschlagen. Archiv benötigt ein Passwort."
|
||||
msgid "Unpacking"
|
||||
msgstr "Entpacken"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Entpacken"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Entpacken fehlgeschlagen. Konnte %s nicht finden."
|
||||
@@ -1050,6 +1028,16 @@ msgstr "Überprüfe zusätzliche Dateien"
|
||||
msgid "Checking"
|
||||
msgstr "Wird überprüft"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "[%s] Der Befehl in build_command ist nicht definiert."
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Dem Pythonskript \"%s\" fehlen die Ausführungsrechte (+x)"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Versuche SFV-Überprüfung"
|
||||
@@ -1085,19 +1073,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Starten/Beenden"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Anhalten"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Fortsetzen"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1216,7 +1191,6 @@ msgstr "%s wurde auf keinem Server gefunden und daher übersprungen"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Ungültige NZB-Datei %s wird übersprungen: %s auf Zeile %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Leere NZB-Datei %s"
|
||||
@@ -1353,10 +1327,22 @@ msgstr "Verlauf leeren"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Geschwindigkeit begrenzen"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Anhalten"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "Min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Fortsetzen"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1572,10 +1558,6 @@ msgstr ""
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Download fehlgeschlagen - Nicht auf deinem/n Server/n vorhanden"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Nachbearbeitung"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Verschiebevorgang"
|
||||
@@ -1619,10 +1601,6 @@ msgstr "Nachbearbeitung von %s fehlgeschlagen (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "Beachten Sie die Protokolldatei"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr "Nachbearbeitung wurde abgebrochen"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Download Fehlgeschlagen"
|
||||
@@ -1641,6 +1619,10 @@ msgstr "Download fertig"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Konnte Download-Ordner %s nicht anlegen"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Nachbearbeitung"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Keine PAR2-Sätze"
|
||||
@@ -1673,11 +1655,6 @@ msgstr "RAR-Datei erfolgreich überprüft"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "RAR-Datei konnten nicht überprüft werden"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr "Keine zugehörige frühere RAR-Datei für %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1698,11 +1675,6 @@ msgstr "Fehler beim Wechsel in den Bereitschaftsmodus"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Fehler beim Herunterfahren des Systems"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr "DBus-Ausnahmefehler empfangen %s "
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1729,6 +1701,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Ungültige RSS-Feed-Beschreibung \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Abrufen des RSS-Feeds von %s fehlgeschlagen: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Keine gültige Berechtigung für Feed %s"
|
||||
@@ -1737,15 +1713,10 @@ msgstr "Keine gültige Berechtigung für Feed %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Server-Fehler (Code %s); konnte %s von %s nicht laden"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Abrufen des RSS-Feeds von %s fehlgeschlagen: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Der Server %s nutzt ein nicht vertrauenswürdiges HTTPS-Zertifikat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS-Feed %s war leer"
|
||||
@@ -1835,16 +1806,6 @@ msgstr "Herunterladen"
|
||||
msgid "Join files"
|
||||
msgstr "Dateien zusammenfügen"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Entpacken"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skript"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2177,10 +2138,6 @@ msgstr "Heute"
|
||||
msgid "Total"
|
||||
msgstr "Gesamt"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Benutzerdefiniert"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "An"
|
||||
@@ -3461,14 +3418,10 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Nur überprüfte Aufträge nachbearbeiten"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Entpacken und starten von Skripten nur bei verifizierten Jobs. Wenn "
|
||||
"ausgeschaltet werden alle Jobs als vollständig markiert, selbst wenn sie "
|
||||
"unvollständig sind."
|
||||
"Die Nachbearbeitung nur für Aufträge durchführen,<br />die alle "
|
||||
"PAR2-Überprüfungen bestanden haben."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3732,18 +3685,6 @@ msgstr "Beispieldateien herausfiltern (z.B. Videoausschnitte)"
|
||||
msgid "Delete after download"
|
||||
msgstr "Nach dem Download löschen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr "Entschleiere finale Dateinamen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
"Dateinamen von (großen) Dateien im Zielordner werden in den Auftragsnamen "
|
||||
"umbenannt, wenn sie verschleiert oder bedeutungslos aussehen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "HTTPS Zertifikat Überprüfung"
|
||||
@@ -4256,7 +4197,7 @@ msgstr "Benachrichtigung gesendet!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "NotifyOSD aktivieren"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Benachrichtigungscenter"
|
||||
@@ -4913,6 +4854,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr "Glitter hat ein paar (neue) Feature die du bestimmt magst!"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Benutzerdefiniert"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "Kompaktes Layout"
|
||||
|
||||
459
po/main/es.po
459
po/main/es.po
File diff suppressed because it is too large
Load Diff
200
po/main/fi.po
200
po/main/fi.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Finnish (https://www.transifex.com/sabnzbd/teams/111101/fi/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: fi\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Varoitus"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Virhe"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -102,11 +92,6 @@ msgstr ""
|
||||
"Unicode-merkkejä tiedosto- ja kansionimissä sisältävät lataukset voivat "
|
||||
"aiheuttaa ongelmia."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -127,6 +112,16 @@ msgstr "Web-käyttöliittymän käynnistys epäonnistui : "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s käynnistetty"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Varoitus"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Virhe"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd sammutus valmis"
|
||||
@@ -170,11 +165,6 @@ msgstr "Virhe tiedostossa tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Kohteen %s lataaminen epäonnistui"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "Sähköpostitus onnistui"
|
||||
@@ -229,15 +219,15 @@ msgstr "Vakava virhe kohteessa Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -246,8 +236,10 @@ msgstr "Peruutettu, salattu arkisto tunnistettu"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"Varoitus: Latauksessa \"%s\" ei toivottu tiedostopääte RAR arkistossa. Ei "
|
||||
"toivottu tiedosto on %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -259,13 +251,13 @@ msgstr "Peruutettu, ei toivottu tiedostopääte havaittu"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "VAROITUS : Keskeytetty lataus \"%s\", koska luokituksena (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "VAROITUS : Peruutettiin lataus \"%s\", koska luokituksena on (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -350,6 +342,10 @@ msgstr "%s ei ole oikea oktaalinen arvo"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "TUNT polku \"%s\" ei ole sallittu"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Virhe: Polun pituus täytyy olla alle %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Virhe: Jono ei ole tyhjä, kansiota ei voida vaihtaa."
|
||||
@@ -649,12 +645,6 @@ msgstr ""
|
||||
"sitten alapuolella olevaa nappia.<br /><br /><strong><a "
|
||||
"href=\"..\">Päivitä</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Varoitus: LOCALHOST on hämärä, käytä numeerista IP-osoitetta."
|
||||
@@ -737,11 +727,6 @@ msgstr "t"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -754,25 +739,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Ajetaan skripti"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Jälkikäsittely peruutettiin (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skripti"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -834,6 +813,11 @@ msgstr "Purkaminen epäonnistui, arkisto vaatii salasanan"
|
||||
msgid "Unpacking"
|
||||
msgstr "Puretaan"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Pura"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Purkaminen epäonnistui, %s ei löydy"
|
||||
@@ -1008,6 +992,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Tarkistetaan"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Yritetään SFV varmennusta"
|
||||
@@ -1040,19 +1034,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Käynnistys/Sammutus"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Keskeytä"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Jatka"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1171,7 +1152,6 @@ msgstr "%s => puuttuu kaikilta palvelimilta, hylätään"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Virheellinen NZB tiedosto %s, ohitetaan (syy=%s, rivi=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tyhjä NZB tiedosto %s"
|
||||
@@ -1305,10 +1285,22 @@ msgstr "Tyhjennä historia"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Nopeusrajoitus"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Keskeytä"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Jatka"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1512,10 +1504,6 @@ msgstr "Lataaminen saattaa epäonnistua, vain %s osaa %s osasta saatavilla"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Lataus epäonnistui - Ei ole palvelimilla"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Jälkikäsittely"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Siirretään"
|
||||
@@ -1559,10 +1547,6 @@ msgstr "Jälkikäsittely epäonnistui kohteelle %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "katso lokitiedosto"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Lataus epäonnistui"
|
||||
@@ -1581,6 +1565,10 @@ msgstr "Lataus valmistui"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Ei voitu luoda lopullista kansiota %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Jälkikäsittely"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Ei par2 arkistoja"
|
||||
@@ -1613,11 +1601,6 @@ msgstr "RAR arkistot varmennettiin onnistuneesti"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "RAR arkistoja ei voitu varmentaa"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1638,11 +1621,6 @@ msgstr "Järjestelmän valmiustilaan laittaminen epäonnistui"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Virhe sammutettaessa järjestelmää"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1667,6 +1645,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Virheellinen RSS syötteen kuvaus \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "RSS noutaminen epäonnistui kohteesta %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Ei ole käyttöoikeutta syötteeseen %s"
|
||||
@@ -1675,15 +1657,10 @@ msgstr "Ei ole käyttöoikeutta syötteeseen %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Palvelinpään virhe (virhekoodi %s); ei voitu noutaa %s kohteesta %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "RSS noutaminen epäonnistui kohteesta %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Palvelin %s käyttää epäluotettavaa HTTPS sertifikaattia"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS syöte %s oli tyhjä"
|
||||
@@ -1773,16 +1750,6 @@ msgstr "Lataa"
|
||||
msgid "Join files"
|
||||
msgstr "Yhdistä tiedostot"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Pura"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skripti"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2115,10 +2082,6 @@ msgstr "Tänään"
|
||||
msgid "Total"
|
||||
msgstr "Yhteensä"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Mukautettu"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "käytössä"
|
||||
@@ -3377,11 +3340,10 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Jälkikäsittele vain onnistuneet lataukset"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Suorittaa jälkikäsittelyn vain niille latauksille jotka läpäisevät kaikki "
|
||||
"PAR2 tarkistukset."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3632,16 +3594,6 @@ msgstr "Ohittaa näytetiedostot (esim. videonäytteet)."
|
||||
msgid "Delete after download"
|
||||
msgstr "Poista lataamisen jälkeen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "HTTPS sertfikaatin varmennus"
|
||||
@@ -4140,7 +4092,7 @@ msgstr "Ilmoitus lähetetty!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "NotifyOSD käytössä"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Ilmoituskeskus"
|
||||
@@ -4798,6 +4750,10 @@ msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
"Glitter-teemassa on muutamia (uusia) ominaisuuksia joista saatat pitää!"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Mukautettu"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "Tiivis käyttöliittymä"
|
||||
|
||||
223
po/main/fr.po
223
po/main/fr.po
@@ -8,7 +8,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Fred L <88com88@gmail.com>, 2020\n"
|
||||
"Language-Team: French (https://www.transifex.com/sabnzbd/teams/111101/fr/)\n"
|
||||
@@ -18,16 +18,6 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Avertissement"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -109,13 +99,6 @@ msgstr ""
|
||||
"Attendez-vous à des problèmes avec les noms de fichiers et de répertoires "
|
||||
"Unicode dans les téléchargements."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
"Impossible de charger les certificats supplémentaires à partir du package "
|
||||
"certifi"
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -135,6 +118,16 @@ msgstr "Impossible de démarrer l'interface web : "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s démarré"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Avertissement"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "Arrêt de SABnzbd terminé"
|
||||
@@ -178,11 +171,6 @@ msgstr "Échec dans tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Echec du chargement de %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr "Impossible d'accéder au fichier PID %s"
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "L'envoi de l'e-mail a réussi"
|
||||
@@ -237,20 +225,20 @@ msgstr "Erreur fatale dans l'assembleur"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"La tâche \"%s\" a été mise en pause à cause d'un fichier RAR chiffré (tous "
|
||||
"les mots de passe fournis ont été essayés)"
|
||||
"ATTENTION : la tâche \"%s\" a été mise en pause à cause d'un fichier RAR "
|
||||
"chiffré (tous les mots de passe fournis ont été essayés)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"La tâche \"%s\" a été abandonnée à cause d'un fichier RAR chiffré (tous les "
|
||||
"mots de passe fournis ont été essayés)"
|
||||
"ATTENTION : la tâche \"%s\" a été abandonnée à cause d'un fichier RAR "
|
||||
"chiffré (tous les mots de passe fournis ont été essayés)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, encryption detected"
|
||||
@@ -258,10 +246,10 @@ msgstr "Interrompu, cryptage détecté"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"Le fichier RAR \"%s\" contient une extension indésirable. Le fichier "
|
||||
"indésirable est %s "
|
||||
"AVERTISSEMENT : Le fichier RAR\"%s\" contient une extension indésirable. Le "
|
||||
"fichier indésirable est %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -273,13 +261,13 @@ msgstr "Interrompu, extension indésirable détectée"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "Tâche \"%s\" mise en pause à cause du classement (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "AVERTISSEMENT : Tâche \"%s\" mise en pause à cause du classement (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "Tâche \"%s\" annulée à cause du classement (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "AVERTISSEMENT : tâche \"%s\" annulée à cause du classement (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -368,6 +356,10 @@ msgstr "%s n'est pas une valeur octale correcte"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "Le chemin UNC \"%s\" n'est pas autorisé ici"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Erreur : la longueur du chemin doit être inférieure à %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr ""
|
||||
@@ -676,14 +668,6 @@ msgstr ""
|
||||
"secondes avant de cliquer sur le bouton ci-dessous.<br /><br /><strong><a "
|
||||
"href=\"..\">Rafraîchir</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
"Le dossier des téléchargements terminés ne peut pas être le même dossier que"
|
||||
" les téléchargements temporaires, ni être l'un de ses sous-dossiers"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr ""
|
||||
@@ -769,11 +753,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Échec de l'upload du fichier : %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -789,27 +768,19 @@ msgstr ""
|
||||
" ces mots de passe prend beaucoup de temps. Essayez de n'y lister que les "
|
||||
"mots de passe utiles."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr "Échec de lecture du fichier de mot de passe %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "[%s] La commande dans build_command n'est pas définie."
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
"Le script Python \"%s\" n'est pas configuré avec les permissions d’exécution"
|
||||
" (+x)"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Exécution du script"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Post-traitement interrompu (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -871,6 +842,11 @@ msgstr "Échec de l'extraction, l'archive nécessite un mot de passe"
|
||||
msgid "Unpacking"
|
||||
msgstr "Extraction"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Décompresser"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Échec de l'extraction, %s n'a pas été trouvé"
|
||||
@@ -1052,6 +1028,18 @@ msgstr "Vérification des fichiers supplémentaires"
|
||||
msgid "Checking"
|
||||
msgstr "Vérification"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "[%s] La commande dans build_command n'est pas définie."
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
"Le script Python \"%s\" n'est pas configuré avec les permissions d’exécution"
|
||||
" (+x)"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Essai vérification SFV"
|
||||
@@ -1087,19 +1075,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Démarrage/Arrêt"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Mettre en pause"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Reprendre"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1218,7 +1193,6 @@ msgstr "%s => absent de tous les serveurs, rejeté"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Fichier NZB invalide %s, ignoré (raison=%s, ligne=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fichier NZB %s vide"
|
||||
@@ -1352,10 +1326,22 @@ msgstr "Vider l'historique"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Limiter la vitesse"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Mettre en pause"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Reprendre"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1571,10 +1557,6 @@ msgstr ""
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Le téléchargement a échoué - absent de vos serveur(s)"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Post-traitement"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Déplacement"
|
||||
@@ -1618,10 +1600,6 @@ msgstr "Échec du post-traitement pour %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "voir le journal"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr "Le post-traitement a été interrompu"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Échec du téléchargement"
|
||||
@@ -1640,6 +1618,10 @@ msgstr "Téléchargement terminé"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Impossible de créer le dossier final %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Post-traitement"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Pas de fichiers par2"
|
||||
@@ -1672,11 +1654,6 @@ msgstr "Fichiers RAR vérifiés avec succès"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "Echec lors de la vérification des fichiers RAR"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr "Aucun fichier rar antérieur correspondant pour %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1697,11 +1674,6 @@ msgstr "Échec de la mise en veille"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Erreur lors de l'arrêt du système"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr "Exception DBus reçue %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1728,6 +1700,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Description du flux RSS incorrecte \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Échec de la récupération RSS de %s : %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Vous n'avez pas d'authentification valide pour ce flux %s"
|
||||
@@ -1737,15 +1713,10 @@ msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr ""
|
||||
"Erreur du côté serveur (code serveur %s) ; n'a pas pu obtenir %s sur %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Échec de la récupération RSS de %s : %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Le serveur %s utilise un certificat de sécurité HTTPS non authentifié"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "Le flux RSS %s était vide"
|
||||
@@ -1835,16 +1806,6 @@ msgstr "Télécharger"
|
||||
msgid "Join files"
|
||||
msgstr "Concaténer"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Décompresser"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2177,10 +2138,6 @@ msgstr "Aujourd'hui"
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Personnalisé"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "oui"
|
||||
@@ -3462,14 +3419,10 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Ne post-traiter que les tâches vérifiées"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Décompresser et lancer les scripts uniquement sur les tâches qui ont passé "
|
||||
"l'étape de vérification. Si désactivé, toutes les tâches seront marquées "
|
||||
"comme Terminées même si elles sont incomplètes."
|
||||
"Limite le post-traitement aux tâches qui ont passé avec succès toutes les "
|
||||
"vérifications PAR2."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3736,18 +3689,6 @@ msgstr "Exclure les fichiers échantillons (par ex. les samples vidéo)."
|
||||
msgid "Delete after download"
|
||||
msgstr "Supprimer après téléchargement"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr "Désobfusquer les noms de fichiers finaux"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
"Si les noms de fichiers (volumineux) dans le dossier final semblent obscurs "
|
||||
"ou dénués de sens, ils seront renommés avec le nom de la tâche."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "Vérification du certificat HTTPS"
|
||||
@@ -4261,7 +4202,7 @@ msgstr "Notification envoyée !"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Activer NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Centre de notification"
|
||||
@@ -4923,6 +4864,10 @@ msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
"Glitter a des (nouvelles) fonctionnalités que vous devriez apprécier !"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Personnalisé"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "Affichage compact"
|
||||
|
||||
213
po/main/he.po
213
po/main/he.po
@@ -8,7 +8,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: ION, 2020\n"
|
||||
"Language-Team: Hebrew (https://www.transifex.com/sabnzbd/teams/111101/he/)\n"
|
||||
@@ -18,16 +18,6 @@ msgstr ""
|
||||
"Language: he\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "אזהרה"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "שגיאה"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -98,11 +88,6 @@ msgstr ""
|
||||
".של קבצים וסיפריות בהורדות Unicode צפה לבעיות עם שמות .UTF-8 היא אמורה להיות"
|
||||
" ,%s הותחל עם קידוד SABnzbd"
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr "לא היה ניתן לטעון תעודות נוספות מחבילת תעודות"
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -122,6 +107,16 @@ msgstr "נכשל בהתחלת ממשק רשת: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "התחיל SABnzbd %s"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "אזהרה"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "שגיאה"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "הסתיים SABnzbd כיבוי"
|
||||
@@ -165,11 +160,6 @@ msgstr "tempfile.mkstemp-כישלון ב"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "נכשלה %s טעינת"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr "לא ניתן להשיג גישה אל קובץ PID בשם %s"
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "דוא״ל הצליח"
|
||||
@@ -224,17 +214,20 @@ msgstr "Assembler-שגיאה חמורה ב"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"העבודה \"%s\" הושהתה בגלל קובץ RAR מוצפן (במקרה שסיסמאות סופקו, כולן נוסו)"
|
||||
"(מוצפן (במקרה שסופקה, כל הסיסמאות נוסו RAR בגלל קובץ \"%s\" אזהרה: השהה את "
|
||||
"העבודה"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
msgstr "העבודה \"%s\" בוטלה בגלל קובץ RAR מוצפן (במקרה שסיסמאות סופקו, כולן נוסו)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"(מוצפן (במקרה שסופקה, כל הסיסמאות נוסו RAR בגלל קובץ \"%s\" אזהרה: ביטל את "
|
||||
"העבודה"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, encryption detected"
|
||||
@@ -242,8 +235,8 @@ msgstr "בוטל, הצפנה התגלתה"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "בעבודה \"%s\" יש סיומת בלתי רצויה בתוך קובץ RAR. הקובץ הבלתי רצוי הוא %s"
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "%s קובץ בלתי רצוי הוא .RAR סיומת בלתי רצויה בקובץ \"%s\"-אזהרה: ב "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -255,13 +248,13 @@ msgstr "בוטל, סיומת בלתי רצויה התגלתה"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "העבודה \"%s\" הושהתה בגלל דירוג (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "(%s) בגלל דירוג \"%s\" אזהרה: עבודה מושהת"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "העבודה \"%s\" בוטלה בגלל דירוג (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "(%s) בגלל דירוג \"%s\" אזהרה: עבודה בוטלה"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -346,6 +339,10 @@ msgstr "%s אינו ערך אוקטלי נכון"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "אינו מותר כאן \"%s\" UNC נתיב"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr ".שגיאה: אורך הנתיב צריך להיות מתחת אל %s"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr ".שגיאה: התור אינו ריק, לא יכול לשנות תיקייה"
|
||||
@@ -644,14 +641,6 @@ msgstr ""
|
||||
" <br />.הסתיים SABnzbd כיבוי<br />.המתן בערך 5 שניות ואז לחץ על הכפתור "
|
||||
"למטה<br /><br /><strong><a href=\"..\">רענן</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
"תיקיית ההורדות השלמות אינה יכולה להיות אותה תיקייה או תת־תיקייה של תיקיית "
|
||||
"ההורדות הזמניות"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr ".מספרית IP הוא דו־משמעי, השתמש בכתובת LOCALHOST :אזהרה"
|
||||
@@ -734,11 +723,6 @@ msgstr "ש"
|
||||
msgid "m"
|
||||
msgstr "ד"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "נכשל בהעלאת קובץ: %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -753,25 +737,19 @@ msgstr ""
|
||||
".קובץ הסיסמאות שלך מכיל יותר מ־30 סיסמאות, בחינת כל הסיסמאות האלו תיקח זמן "
|
||||
"רב. נסה לכתוב ברשימה רק סיסמאות שימושיות"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr "נכשל בקריאת קובץ הסיסמה %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "אינה מוגדרת build_command הפקודה ב [%s]"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "(+x) אין ערכת הרשאות ביצוע \"%s\" לתסריט פייתון"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "מריץ תסריט"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "(%s) בתר־עיבוד בוטל"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "תסריט"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -833,6 +811,11 @@ msgstr "פריקה נכשלה, ארכיון דורש סיסמה"
|
||||
msgid "Unpacking"
|
||||
msgstr "פורק"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "פרוק"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "%s פריקה נכשלה, לא היה ניתן למצוא את"
|
||||
@@ -1006,6 +989,16 @@ msgstr "בודק קבצי תוספת"
|
||||
msgid "Checking"
|
||||
msgstr "בודק"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "אינה מוגדרת build_command הפקודה ב [%s]"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "(+x) אין ערכת הרשאות ביצוע \"%s\" לתסריט פייתון"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "SFV מנסה וידוא"
|
||||
@@ -1040,19 +1033,6 @@ msgstr "וויקי"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "הזנק/כיבוי"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "השהה"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "המשך"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1171,7 +1151,6 @@ msgstr "חסר מכל השרתים, משליך <= %s"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "מדלג ,%s בלתי תקף NZB קובץ (סיבה=%s שורה=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "%s ריק NZB קובץ"
|
||||
@@ -1305,10 +1284,22 @@ msgstr "טהר היסטוריה"
|
||||
msgid "Limit Speed"
|
||||
msgstr "הגבל מהירות"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "השהה"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "דקות"
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "המשך"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1516,10 +1507,6 @@ msgstr "הורדה עשויה להיכשל, רק %s מתוך %s דרושים ז
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "הורדה נכשלה - לא בשרת(ים) שלך"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "בתר־עיבוד"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "מעביר"
|
||||
@@ -1563,10 +1550,6 @@ msgstr "%s (%s) בתר־עיבוד נכשל עבור"
|
||||
msgid "see logfile"
|
||||
msgstr "ראה קובץ יומן"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr "בתר־עיבוד בוטל"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "הורדה נכשלה"
|
||||
@@ -1585,6 +1568,10 @@ msgstr "הורדה הושלמה"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "%s לא יכול ליצור תיקייה סופית"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "בתר־עיבוד"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "par2 [%s] אין ערכות"
|
||||
@@ -1617,11 +1604,6 @@ msgstr "קבצי RAR וודאו בהצלחה"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "נכשלו בוידוא RAR קבצי"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr "אין קובץ rar קודם תואם עבור %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1642,11 +1624,6 @@ msgstr "נכשל בהיכוננות מערכת"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "שגיאה בזמן כיבוי מערכת"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr "חריגת DBus התקבלה %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1671,6 +1648,10 @@ msgstr ".מפתח זה מספק זהות למדדן. בדוק את המתאר ש
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "\"%s\" לא נכון RSS תיאור הזנת"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "%s: %s מן RSS נכשל באחזור"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "%s אין אימות תקף עבור ההזנה"
|
||||
@@ -1679,15 +1660,10 @@ msgstr "%s אין אימות תקף עבור ההזנה"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "%s על %s שגיאה צדדית של שרת (קוד שרת %s); לא היה ניתן להשיג את"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "%s: %s מן RSS נכשל באחזור"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "בלתי מהימן HTTPS משתמש באישור %s השרת"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "הייתה ריקה %s RSS הזנת"
|
||||
@@ -1777,16 +1753,6 @@ msgstr "הורדה"
|
||||
msgid "Join files"
|
||||
msgstr "אחד קבצים"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "פרוק"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "תסריט"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2119,10 +2085,6 @@ msgstr "היום"
|
||||
msgid "Total"
|
||||
msgstr "סה״כ"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "מותאם אישית"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "פועל"
|
||||
@@ -3366,13 +3328,8 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "בצע בתר־עיבוד רק על עבודות שוודאו"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgstr ""
|
||||
"פרוק והרץ רק על עבודות שעברו את שלב הוידוא. אם מכובה, כל העבודות יסומנו "
|
||||
"כשלמות אפילו אם הן בלתי שלמות."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ".PAR2-בצע בתר־עיבוד רק בעבודות שעברו את כל בדיקות ה"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3618,18 +3575,6 @@ msgstr "(סנן החוצה קבצי דוגמית (לדוגמה, דוגמיות
|
||||
msgid "Delete after download"
|
||||
msgstr "מחק לאחר הורדה"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr "בטל ערפול של שמות קובץ סופיים"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
"אם שמות קבצים של קבצים (גדולים) בתיקייה הסופית נראים מעורפלים או חסרי "
|
||||
"משמעות, שמותיהם ישונו אל שם העבודה."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "HTTPS וידוא אישור"
|
||||
@@ -4127,7 +4072,7 @@ msgstr "!התראה נשלחה"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "NotifyOSD אפשר"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "מרכז ההתראות"
|
||||
@@ -4777,6 +4722,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr "!יש מספר מאפיינים (חדשים) שאתה עשוי לאהוב Glitter אל"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "מותאם אישית"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "פריסה צפופה"
|
||||
|
||||
200
po/main/nb.po
200
po/main/nb.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Norwegian Bokmål (https://www.transifex.com/sabnzbd/teams/111101/nb/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: nb\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Advarsel"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Feil"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -100,11 +90,6 @@ msgstr ""
|
||||
"SABnzbd ble startet med koding %s, dette burde være UTF-8. Forvent problemer"
|
||||
" med Unicode filer- og katalognavn i nedlastinger."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -124,6 +109,16 @@ msgstr "Kunne ikke starte webgrensesnittet: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s startet"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Advarsel"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Feil"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd er nå avsluttet"
|
||||
@@ -167,11 +162,6 @@ msgstr "Feil i tempfil.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Lasting av %s mislyktes"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "E-post sendning lykkes"
|
||||
@@ -226,15 +216,15 @@ msgstr "Kritisk feil i Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -243,8 +233,8 @@ msgstr "Avbrutt, kryptering funnet"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "ADVARSEL: I \"%s\" uønsket filtype i RAR fil. Uønsket fil er %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -256,13 +246,13 @@ msgstr "Avbryt, uønsket forlenging oppdaget"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "ADVARSEL: Pauset jobb \"%s\" grunnet rangeringen (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "ADVARSEL: Avbrøt jobb \"%s\" grunnet rangering (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -347,6 +337,10 @@ msgstr "%s er ikke en korrekt oktal verdi"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-sti \"%s\" er ikke tillatt her"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Feil: Fillengde bør være kortere enn %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Feil: Køen er ikke tom, kan ikke bytte mappe."
|
||||
@@ -645,12 +639,6 @@ msgstr ""
|
||||
"deretter på knappen under.<br /><br /><strong><a href=\"..\">Last på "
|
||||
"nytt</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Advarsel: LOCALHOST er tvetydig, bruk numerisk IP-adresse."
|
||||
@@ -733,11 +721,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -750,25 +733,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Kjører skript"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Etterbehandling ble avbrutt (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skript"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -830,6 +807,11 @@ msgstr "Utpakking mislyktes, arkivet krever passord"
|
||||
msgid "Unpacking"
|
||||
msgstr "Utpakker"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Utpakking"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Utpakking mislyktes, kunne ikke finne %s"
|
||||
@@ -1005,6 +987,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Undersøker"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Prøver SFV-verifisering"
|
||||
@@ -1037,19 +1029,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Oppstart/avsluttning"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Stans midlertidig"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Gjenoppta"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1168,7 +1147,6 @@ msgstr "%s => mangler på alle servere, fjerner"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Feilaktig NZB fil %s, hopper over (årsak=%s, linje=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tom NZB-fil %s"
|
||||
@@ -1302,10 +1280,22 @@ msgstr "Slett historikk"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Hastighetsbegrensning"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Stans midlertidig"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Gjenoppta"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1509,10 +1499,6 @@ msgstr "Nedlasting kan feile, kun %s av kravet på %s tilgjengelig"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Nedlastning feilet - Finnes ikke på din(e) server(e)"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Etterbehandling"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Flytter"
|
||||
@@ -1556,10 +1542,6 @@ msgstr "Etterbehandling mislyktes for %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "se loggfil"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Nedlasting mislyktes"
|
||||
@@ -1578,6 +1560,10 @@ msgstr "Nedlasting ferdig"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Kan ikke opprette mappe %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Etterbehandling"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Ingen par2 deler"
|
||||
@@ -1610,11 +1596,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1635,11 +1616,6 @@ msgstr "Kunne ikke sette systemet i ventemodus"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Feil under avslutting av systemet"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1664,6 +1640,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Feilaktig RSS-kilde beskrivelse \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Kunne ikke hente RSS-kilde fra %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Ugyldig autentisering for nyhetsstrøm %s"
|
||||
@@ -1672,15 +1652,10 @@ msgstr "Ugyldig autentisering for nyhetsstrøm %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Serverside-feil (serverkode %s); kunne ikke hente %s på %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Kunne ikke hente RSS-kilde fra %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Server %s bruker et usikkert HTTP sertifikat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS-kilde %s var tom"
|
||||
@@ -1770,16 +1745,6 @@ msgstr "Nedlastning"
|
||||
msgid "Join files"
|
||||
msgstr "Slå sammen filer"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Utpakking"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skript"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2112,10 +2077,6 @@ msgstr "I dag"
|
||||
msgid "Total"
|
||||
msgstr "Totalt"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Tilpasse"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "på"
|
||||
@@ -3360,11 +3321,8 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Etterbehandle kun verifiserte nedlastinger"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgstr ""
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr "Etterbehandle kun nedlastinger som har passert PAR2 kontrollen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3609,16 +3567,6 @@ msgstr "Filtrere ut sample-filer (ex. video samplinger)."
|
||||
msgid "Delete after download"
|
||||
msgstr "Fjern etter nedlasting"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4116,7 +4064,7 @@ msgstr "Varsel sendt!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Aktiver NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Varselsenter"
|
||||
@@ -4766,6 +4714,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Tilpasse"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
222
po/main/nl.po
222
po/main/nl.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Dutch (https://www.transifex.com/sabnzbd/teams/111101/nl/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: nl\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Waarschuwing"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fout"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -104,11 +94,6 @@ msgstr ""
|
||||
"bij het downloaden, problemen krijgen met Unicode namen van bestanden en "
|
||||
"mappen."
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr "Extra certificaten uit het certifi pakket konden niet geladen worden"
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -128,6 +113,16 @@ msgstr "Webinterface kon niet gestart worden: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s is gestart"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Waarschuwing"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fout"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd is afgesloten"
|
||||
@@ -171,11 +166,6 @@ msgstr "Probleem met tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Inlezen van %s mislukt"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr "Kan het PID bestand %s niet benaderen"
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "E-mail verzonden"
|
||||
@@ -230,20 +220,20 @@ msgstr "Onherstelbare fout in de Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"Download \"%s\" is gepauzeerd vanwege een versleuteld RAR bestand (indien "
|
||||
"aanwezig, zijn alle wachtwoorden geprobeerd)"
|
||||
"WAARSCHUWING: Download \"%s\" is gepauzeerd vanwege een versleuteld RAR "
|
||||
"bestand (indien aanwezig, zijn alle wachtwoorden geprobeerd)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
"Download \"%s\" is afgebroken vanwege een versleuteld RAR bestand (indien "
|
||||
"aanwezig, zijn alle wachtwoorden geprobeerd)."
|
||||
"WAARSCHUWING: Download \"%s\" is afgebroken vanwege een versleuteld RAR "
|
||||
"bestand (indien aanwezig, zijn alle wachtwoorden"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, encryption detected"
|
||||
@@ -251,8 +241,10 @@ msgstr "Afgebroken, versleuteling ontdekt"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "Ongewenste extensie ontdekt in \"%s\". Het ongewenste bestand is \"%s\" "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"WAARSCHUWING: Ongewenste extensie ontdekt in \"%s\". Het ongewenste bestand "
|
||||
"is \"%s\" "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -264,13 +256,13 @@ msgstr "Afgebroken, ongewenste extensie ontdekt"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "Download '%s' gepauzeerd vanwege rating (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "WAARSCHUWING: Download '%s' gepauzeerd vanwege rating (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "Download '%s' afgebroken vanwege rating (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "WAARSCHUWING: Download '%s' afgebroken vanwege rating (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -359,6 +351,10 @@ msgstr "%s is geen correct octaal getal"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-pad '%s' hier niet toegestaan."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Fout: het opgegeven pad mag niet langer zijn dan %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Fout: Wachtrij is niet leeg, andere map kiezen niet mogelijk."
|
||||
@@ -665,14 +661,6 @@ msgstr ""
|
||||
"dan op onderstaande knop.<br /><br /><strong><a "
|
||||
"href=\"..\">Verversen</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
"De Map voor verwerkte downloads mag niet een map in de Tijdelijke download "
|
||||
"map zijn."
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Let op: LOCALHOST is niet eenduidig, gebruik een numeriek IP-adres."
|
||||
@@ -757,11 +745,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Kon het volgende bestand niet uploaden: %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -777,25 +760,19 @@ msgstr ""
|
||||
"deze wachtwoorden kost heel veel tijd. Zorg ervoor dat je alleen maar "
|
||||
"nuttige wachtwoorden in dit bestand zet."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr "Kan het wachtwoord bestand niet uitlezen %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "[%s] Het commando in build_command is ongedefinieerd"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Python-script '%s' heeft geen uitvoerpermissie (+x)"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Script uitvoeren"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Nabewerking is afgebroken (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -857,6 +834,11 @@ msgstr "Uitpakken mislukt, archief vereist wachtwoord"
|
||||
msgid "Unpacking"
|
||||
msgstr "Uitpakken"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Uitpakken"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Uitpakken mislukt, kan %s niet vinden"
|
||||
@@ -1034,6 +1016,16 @@ msgstr "Controleren van extra bestanden"
|
||||
msgid "Checking"
|
||||
msgstr "Controleren"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr "[%s] Het commando in build_command is ongedefinieerd"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Python-script '%s' heeft geen uitvoerpermissie (+x)"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Probeer SFV-verificatie"
|
||||
@@ -1069,19 +1061,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Opstarten/Afsluiten"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pauze"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Doorgaan"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1200,7 +1179,6 @@ msgstr "%s => ontbreekt op alle servers, overslaan"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Foutief NZB-bestand %s, overslaan (reden=%s, regel=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "NZB-bestand %s is leeg"
|
||||
@@ -1334,10 +1312,22 @@ msgstr "Wis de volledige geschiedenis"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Beperk snelheid"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pauze"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Doorgaan"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1546,10 +1536,6 @@ msgstr ""
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Download mislukt - Niet meer op je server(s)"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Nabewerking"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Verplaatsen"
|
||||
@@ -1593,10 +1579,6 @@ msgstr "Nabewerking van %s mislukt (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "zie logbestand"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr "Nabewerking is afgebroken"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Download mislukt"
|
||||
@@ -1615,6 +1597,10 @@ msgstr "Download voltooid"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Kan bestemmingsmap %s niet maken"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Nabewerking"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Geen par2 groepen"
|
||||
@@ -1647,11 +1633,6 @@ msgstr "RAR bestanden zijn succesvol geverifieerd"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "RAR bestanden zijn niet verifieerbaar"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr "Geen voorgaand rar-bestand gevonden bij %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1672,11 +1653,6 @@ msgstr "Kan het systeem niet in standby krijgen"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Fout bij het afsluiten van het systeem"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr "DBus foutmelding %s "
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1703,6 +1679,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Foutieve RSS-feed definitie \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Kan RSS-feed \"%s\" niet lezen vanwege: \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Geen geldige inlog gegevens beschikbaar voor RSS-feed %s"
|
||||
@@ -1711,15 +1691,10 @@ msgstr "Geen geldige inlog gegevens beschikbaar voor RSS-feed %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Server fout (code is %s); kon geen %s van %s krijgen"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Kan RSS-feed \"%s\" niet lezen vanwege: \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Server %s gebruikt een onbetrouwbaar HTTPS-certificaat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS-feed %s is leeg"
|
||||
@@ -1809,16 +1784,6 @@ msgstr "Download"
|
||||
msgid "Join files"
|
||||
msgstr "Samenvoegen"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Uitpakken"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2151,10 +2116,6 @@ msgstr "Vandaag"
|
||||
msgid "Total"
|
||||
msgstr "Totaal"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Aangepast"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "aan"
|
||||
@@ -3095,7 +3056,7 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "History Retention"
|
||||
msgstr "Geschiedenis bewaren"
|
||||
msgstr "Geschiedenis Bewaren"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
@@ -3421,14 +3382,10 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Verwerk alleen correct geverifieerde downloads"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Uitpakken en scripts worden alleen uitgevoerd op opdrachten die succesvol "
|
||||
"geverifieerd zijn. Als deze optie uitgeschakeld is zullen alle opdrachten "
|
||||
"gemarkeerd worden als succesvol, zelfs als dat niet zo is."
|
||||
"Voer de nabewerking alleen uit op downloads die de PAR2 controles hebben "
|
||||
"doorlopen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3538,7 +3495,7 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "On failure, try alternative NZB"
|
||||
msgstr "Bij mislukte download: probeer alternatieve NZB"
|
||||
msgstr "Bij mislukking: probeer alternatieve NZB"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Some servers provide an alternative NZB when a download fails."
|
||||
@@ -3688,19 +3645,6 @@ msgstr "Wat te doen met \"sample\"-bestanden?"
|
||||
msgid "Delete after download"
|
||||
msgstr "Verwijderen na download"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr "Verbeter bestandsnamen van voltooide downloads"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
"Als bestandsnamen van (grote) bestanden na een voltooide download onlogisch "
|
||||
"of verhaspelt lijken (obfuscated), worden ze vervangen door de naam van de "
|
||||
"download."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "HTTPS certificaatverificatie"
|
||||
@@ -4215,7 +4159,7 @@ msgstr "Melding verzonden"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "NotifyOSD activeren"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Berichtencentrum"
|
||||
@@ -4871,6 +4815,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr "Glitter heeft enkele (nieuwe) functies die je mogelijk aanspreken!"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Aangepast"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "Compacte weergave"
|
||||
|
||||
200
po/main/pl.po
200
po/main/pl.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Polish (https://www.transifex.com/sabnzbd/teams/111101/pl/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Ostrzeżenie"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Błąd"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -95,11 +85,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -119,6 +104,16 @@ msgstr "Nie udało się uruchomić interfejsu WWW: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "Uruchomiono SABnzbd %s"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Ostrzeżenie"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Błąd"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd został wyłączony"
|
||||
@@ -162,11 +157,6 @@ msgstr "Błąd w tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Nie udało się wczytać %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "Wiadomość wysłana"
|
||||
@@ -221,15 +211,15 @@ msgstr "Błąd krytyczny w module składającym"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -238,8 +228,10 @@ msgstr "Przerwano, wykryto szyfrowanie"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"UWAGA: Plik z niepożądanym rozszerzeniem wewnątrz pliku RAR \"%s\". "
|
||||
"Niepożądany plik to %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -251,13 +243,13 @@ msgstr "Przerwano, wykryto niepożądane rozszerzenie"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "UWAGA: Zadanie \"%s\" zostało wstrzymane z powodu oceny (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "UWAGA: Zadanie \"%s\" zostało przerwane z powodu oceny (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -342,6 +334,10 @@ msgstr "%s nie jest prawidłową wartością w systemie ósemkowym"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "Ścieżka UNC \"%s\" niedozwolona"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Błąd: Długość ścieżki powinna być mniejsza niż %s"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Błąd: Kolejka nie jest pusta, nie można zmienić katalogu."
|
||||
@@ -644,12 +640,6 @@ msgstr ""
|
||||
"następnie kliknij na przycisk poniżej.<br /><br /><strong><a "
|
||||
"href=\"..\">Odśwież</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Uwaga: LOCALHOST jest niejednoznaczne, użyj adresu IP."
|
||||
@@ -732,11 +722,6 @@ msgstr "g"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -749,25 +734,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Uruchamianie skryptu"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Przetwarzanie końcowe zostało przerwane (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skrypt"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -829,6 +808,11 @@ msgstr "Rozpakowywanie nie powiodło się, archiwum wymaga podania hasła"
|
||||
msgid "Unpacking"
|
||||
msgstr "Rozpakowywanie"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Rozpakuj"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Rozpakowywanie nie powiodło się, nie można znaleźć %s"
|
||||
@@ -1006,6 +990,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Sprawdzanie"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Próba weryfikacji SFV"
|
||||
@@ -1038,19 +1032,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Uruchomienie/Wyłączenie"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Wstrzymaj"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Wznów"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1169,7 +1150,6 @@ msgstr "%s => nie znaleziono na żadnym serwerze, porzucam"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Nieprawidłowy plik NZB %s, pomijam (powód=%s, linia=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Pusty plik NZB %s"
|
||||
@@ -1303,10 +1283,22 @@ msgstr "Wyczyść historię"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Ogranicz prędkość"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Wstrzymaj"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Wznów"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1514,10 +1506,6 @@ msgstr "Pobieranie może się nie udać, dostępne jedynie %s z wymaganych %s"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Pobieranie nieudane - Dane niedostępne na skonfigurowanych serwerach"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Przetwarzanie końcowe"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Przenoszenie"
|
||||
@@ -1561,10 +1549,6 @@ msgstr "Przetwarzanie końcowe nie powiodło się dla %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "sprawdź logi"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Pobieranie nie powiodło się"
|
||||
@@ -1583,6 +1567,10 @@ msgstr "Zakończono pobieranie"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Nie można utworzyć ostatecznego katalogu %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Przetwarzanie końcowe"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s} Brak zestawów par2"
|
||||
@@ -1615,11 +1603,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1640,11 +1623,6 @@ msgstr "Wstrzymanie systemu nie powiodło się"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Wyłączenie systemu nie powiodło się"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1669,6 +1647,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Nieprawidłowy opis kanału RSS \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Nie udało się pobrać RSS z %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Brak poprawnego uwierzytelnienia dla kanału %s"
|
||||
@@ -1677,15 +1659,10 @@ msgstr "Brak poprawnego uwierzytelnienia dla kanału %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Błąd po stronie serwera (kod: %s); nie udało się pobrać %s z %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Nie udało się pobrać RSS z %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Serwer %s używa niezaufanego certyfikatu HTTPS"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "Kanał RSS %s był pusty"
|
||||
@@ -1775,16 +1752,6 @@ msgstr "Pobierz"
|
||||
msgid "Join files"
|
||||
msgstr "Połącz pliki"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Rozpakuj"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skrypt"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2117,10 +2084,6 @@ msgstr "Dzisiaj"
|
||||
msgid "Total"
|
||||
msgstr "Razem"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Własny"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "włączone"
|
||||
@@ -3366,11 +3329,10 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Przetwarzanie końcowe tylko dla zweryfikowanych zadań"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Uruchom przetwarzanie końcowe tylko dla zadań, które zostały sprawdzone przy"
|
||||
" użyciu PAR2"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3618,16 +3580,6 @@ msgstr "Działania, które zostaną podjęte dla plików próbek (np. próbek wi
|
||||
msgid "Delete after download"
|
||||
msgstr "Usuń po pobraniu"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4126,7 +4078,7 @@ msgstr "Wysłano powiadomienie!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Włącz NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Centrum powiadomień"
|
||||
@@ -4776,6 +4728,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Własny"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
200
po/main/pt_BR.po
200
po/main/pt_BR.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: pt_BR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Alerta"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Erro"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -99,11 +89,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -123,6 +108,16 @@ msgstr "Falha ao iniciar a interface web "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s iniciado"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Alerta"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Erro"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "Encerramento do SABnzbd concluído"
|
||||
@@ -166,11 +161,6 @@ msgstr "Falha em tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Falha ao carregar %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "E-mail enviado com sucesso"
|
||||
@@ -225,15 +215,15 @@ msgstr "Erro fatal no Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -242,8 +232,10 @@ msgstr "Cancelado, criptografia detectada"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"ATENÇÃO: Extensão indesejada no arquivo RAR em \"%s\". O arquivo não "
|
||||
"desejado é %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -255,13 +247,13 @@ msgstr "Cancelado, extensão indesejada detectada"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "ATENÇÃO: Tarefa \"%s\" em pausa em razão de pontuação (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "ATENÇÃO: Tarefa \"%s\" interrompida em razão de pontuação (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -346,6 +338,10 @@ msgstr "%s não é um valor octal correto"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "O caminho UNC \"%s\" não é permitido aqui"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Erro: Tamanho do caminho deve ser menor que %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Erro: A fila não está vazia. Não será possível mudar de pasta."
|
||||
@@ -648,12 +644,6 @@ msgstr ""
|
||||
"segundos e, em seguida, clique no botão abaixo.<br /><br /><strong><a "
|
||||
"href=\"..\">Atualizar</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Atenção: LOCALHOST é ambíguo, use endereço IP numérico."
|
||||
@@ -736,11 +726,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -753,25 +738,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Executando script"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "O pós-processamento foi cancelado (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -833,6 +812,11 @@ msgstr "A descompactação falhou. O arquivo exige uma senha"
|
||||
msgid "Unpacking"
|
||||
msgstr "Descompactando"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Descompactar"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "A descompactação falhou. Não foi possível encontrar %s"
|
||||
@@ -1007,6 +991,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Verificando"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Tentando verificação SFV"
|
||||
@@ -1039,19 +1033,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Inicialização/Encerramento"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pausar"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Continuar"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1170,7 +1151,6 @@ msgstr "%s => faltando em todos os servidores. Descartando"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Arquivo NZB %s inválido. Pulando (razão=%s, linha=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Arquivo NZB %s vazio"
|
||||
@@ -1304,10 +1284,22 @@ msgstr "Limpar Histórico"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Limitar Velocidade"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pausar"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Continuar"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1516,10 +1508,6 @@ msgstr ""
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "O download falhou - Não está em seu(s) servidor(s)"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Pós-processamento"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Movendo"
|
||||
@@ -1563,10 +1551,6 @@ msgstr "O pós-processamento falhou para %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "veja o arquivo de log"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "O download falhou"
|
||||
@@ -1585,6 +1569,10 @@ msgstr "Download concluído"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Não é possível criar a pasta final %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Pós-processamento"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Nenhum conjunto par2"
|
||||
@@ -1617,11 +1605,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1642,11 +1625,6 @@ msgstr "Falha ao colocar o sistema em espera"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Erro ao desligar o sistema"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1671,6 +1649,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Descrição de feed RSS incorreta \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Falha ao obter RSS de %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Não há autenticação válida para o feed %s"
|
||||
@@ -1680,15 +1662,10 @@ msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr ""
|
||||
"Erro do servidor (código do servidor %s); não foi possível obter %s de %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Falha ao obter RSS de %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Servidor %s usa um certificado HTTPS não confiável"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "O feed RSS %s estava vazio"
|
||||
@@ -1778,16 +1755,6 @@ msgstr "Download"
|
||||
msgid "Join files"
|
||||
msgstr "Unir arquivos"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Descompactar"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2120,10 +2087,6 @@ msgstr "Hoje"
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Personalizado"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "ligado"
|
||||
@@ -3371,11 +3334,10 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Pós-processar apenas os trabalhos verificados"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Realizar pós-processamento apenas em trabalhos que passaram todas as "
|
||||
"verificações PAR2."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3622,16 +3584,6 @@ msgstr "Exclui arquivos de amostra. Exemplo: amostras de vídeo."
|
||||
msgid "Delete after download"
|
||||
msgstr "Excluir após download"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4129,7 +4081,7 @@ msgstr "Notificação Enviada!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Habilitar NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Centro de Notificações"
|
||||
@@ -4779,6 +4731,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Personalizado"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
199
po/main/ro.po
199
po/main/ro.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Romanian (https://www.transifex.com/sabnzbd/teams/111101/ro/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: ro\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Avertisment"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Eroare"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -99,11 +89,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -123,6 +108,16 @@ msgstr "Nu am putu porni interfața web: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s pornit"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Avertisment"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Eroare"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "Închidere SABnzbd terminată"
|
||||
@@ -166,11 +161,6 @@ msgstr "Eroare în tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Încărcarea %s nereuşită"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "Email reuşit"
|
||||
@@ -225,15 +215,15 @@ msgstr "Eroare fatală în Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -242,8 +232,10 @@ msgstr "Terminat, encriptare detectată"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"ATENȚIE: În fișierul RAR \"%s\" sunt extensii nedorite. Fișierul nedorit "
|
||||
"este %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -255,13 +247,13 @@ msgstr "Oprit, extensii nedorite detectate"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "ATENȚIE: Sarcina \"%s\" întrearuptă datorită ratingului (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "ATENȚIE: Sarcina \"%s\" anulată datorită ratingului (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -346,6 +338,10 @@ msgstr "%s nu este o valoare octală corectă"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "cale UNC \"%s\" nu este premisă aici"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Eroare: Lungimea cale ar trebuie să fie sub %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Eroare: Coada nu este goală, nu pot schimba dosar."
|
||||
@@ -647,12 +643,6 @@ msgstr ""
|
||||
" secunde şi apoi faceţi clic pe butonul de mai jos.<br /><br /><strong><a "
|
||||
"href=\"..\">Reîmprospătează</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Atenţie:LOCALHOST este ambiguu, folosiţi o adresă IP numerică"
|
||||
@@ -735,11 +725,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -752,25 +737,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Rulare script"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Post-Procesarea a fost abandonată (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -832,6 +811,11 @@ msgstr "Dezarhivare nereuşită, arhiva necesită o parolă"
|
||||
msgid "Unpacking"
|
||||
msgstr "Dezarhivare"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Dezarhivează"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Dezarhivare nereuşită, nu pot găsi %s"
|
||||
@@ -1007,6 +991,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Se verifică"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Încerc verificare SFV"
|
||||
@@ -1039,19 +1033,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Pornire/Închidere"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pauză"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Reia"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1170,7 +1151,6 @@ msgstr "%s => lipsă de pe toate serverele, ignorare"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Fişier NZB invalid %s, ignorăm (motiv=%s, line=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fişier NZB gol %s"
|
||||
@@ -1304,10 +1284,22 @@ msgstr "Şterge Istoricul"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Limitare de Viteză"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pauză"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Reia"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1515,10 +1507,6 @@ msgstr "Descărcarea ar putea eşua, doar %s din %s disponibil"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Descărcare euată, - Nu este pe serverul(ele) dumneavoastră"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Post-procesare"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Mutare"
|
||||
@@ -1562,10 +1550,6 @@ msgstr "Post Procesare Nereuşită pentru %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "vezi fişier jurnal"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Descărcarea a eșuat"
|
||||
@@ -1584,6 +1568,10 @@ msgstr "Descărcare terminată"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Nu pot crea dosar final %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Post-procesare"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Niciun set par2"
|
||||
@@ -1616,11 +1604,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1641,11 +1624,6 @@ msgstr "Punere sistem în aşteptare nereuşită"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Eroare la oprirea sistemului"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1670,6 +1648,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Descriere flux RSS incorectă \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Descărcare %s: %s din RSS nereuşită"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Autentificare invalida pentru flux %s"
|
||||
@@ -1678,15 +1660,10 @@ msgstr "Autentificare invalida pentru flux %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Eroare la server (codul server %s); nu am putu lua %s în data de %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Descărcare %s: %s din RSS nereuşită"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Serverul %s utilizează un certificat HTTPS nesigur"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "Fluxul RSS %s a fost gol"
|
||||
@@ -1776,16 +1753,6 @@ msgstr "Descarcă"
|
||||
msgid "Join files"
|
||||
msgstr "Uneşte fişierele"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Dezarhivează"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2118,10 +2085,6 @@ msgstr "Azi"
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Personalizat"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "activat"
|
||||
@@ -3363,11 +3326,9 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Post-Procesează Doar Sarcinile Verificate"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Execută post-procesarea doar dacă sarcina a trecut toate verificările PAR2."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3616,16 +3577,6 @@ msgstr "Ignoră fişiere monstră (de ex. monstre video)"
|
||||
msgid "Delete after download"
|
||||
msgstr "Şterge după descărcare"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4125,7 +4076,7 @@ msgstr "Notificare Trimisă!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Activează NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Centru Notificări"
|
||||
@@ -4775,6 +4726,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Personalizat"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "Aspect compact"
|
||||
|
||||
194
po/main/ru.po
194
po/main/ru.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Russian (https://www.transifex.com/sabnzbd/teams/111101/ru/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: ru\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Предупреждение"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -99,11 +89,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -123,6 +108,16 @@ msgstr ""
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr ""
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Предупреждение"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "Завершение работы SABnzbd закончено"
|
||||
@@ -166,11 +161,6 @@ msgstr "Ошибка в tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Ошибка загрузки %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "Электронное письмо успешно отправлено"
|
||||
@@ -225,15 +215,15 @@ msgstr ""
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -242,7 +232,7 @@ msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -255,12 +245,12 @@ msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -346,6 +336,10 @@ msgstr "%s не является правильным восьмеричным
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC-путь «%s» здесь не допускается"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Ошибка: очередь не пустая, папку нельзя изменить."
|
||||
@@ -644,12 +638,6 @@ msgstr ""
|
||||
"щёлкните ссылку ниже.<br /><br /><strong><a "
|
||||
"href=\"..\">Обновить</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr ""
|
||||
@@ -734,11 +722,6 @@ msgstr "ч"
|
||||
msgid "m"
|
||||
msgstr "м"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -751,25 +734,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Запуск сценария"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Пост-обработка была прервана (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Сценарий"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -831,6 +808,11 @@ msgstr "Ошибка распаковки: архив защищён парол
|
||||
msgid "Unpacking"
|
||||
msgstr "Распаковка"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Распаковать"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Ошибка распаковки: не удаётся найти %s"
|
||||
@@ -1007,6 +989,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Проверка"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Проверка SFV-суммы"
|
||||
@@ -1039,19 +1031,6 @@ msgstr "Вики-сайт"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Запуск/остановка"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Приостановить"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Возобновить"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1170,7 +1149,6 @@ msgstr "%s => отсутствует на всех серверах, отбро
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Недопустимый NZB-файл %s: пропущен (причина — %s, строка — %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Пустой NZB-файл %s"
|
||||
@@ -1304,10 +1282,22 @@ msgstr "Очистить историю"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Ограничение скорости"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Приостановить"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "мин."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Возобновить"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1513,10 +1503,6 @@ msgstr ""
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Пост-обработка"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Перемещение"
|
||||
@@ -1560,10 +1546,6 @@ msgstr "Ошибка пост-обработки для %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "см. журнал"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Не удалось загрузить"
|
||||
@@ -1582,6 +1564,10 @@ msgstr "Загрузка завершена"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Не удаётся создать конечную папку %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Пост-обработка"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Нет PAR2-файлов"
|
||||
@@ -1614,11 +1600,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1639,11 +1620,6 @@ msgstr "Не удалось перевести систему в состоян
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Не удалось завершить работу системы"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1668,6 +1644,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Неправильное описание RSS-ленты «%s»"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Не удалось получить RSS-ленту из %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Неправильные учётные данные для ленты %s"
|
||||
@@ -1676,15 +1656,10 @@ msgstr "Неправильные учётные данные для ленты %
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Не удалось получить RSS-ленту из %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS-лента %s была пустой"
|
||||
@@ -1774,16 +1749,6 @@ msgstr "Загрузить"
|
||||
msgid "Join files"
|
||||
msgstr "Объединить файлы"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Распаковать"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Сценарий"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2116,10 +2081,6 @@ msgstr "за сегодня"
|
||||
msgid "Total"
|
||||
msgstr "всего"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Другой"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "на"
|
||||
@@ -3363,11 +3324,8 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Обрабатывать только проверенные задания"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgstr ""
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr "Обрабатывать только задания, успешно прошедшие все проверки PAR2."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3608,16 +3566,6 @@ msgstr "Отфильтровывать файлы образцов (наприм
|
||||
msgid "Delete after download"
|
||||
msgstr "Удалить после загрузки"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4121,7 +4069,7 @@ msgstr "Уведомление отправлено"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Использовать NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr ""
|
||||
@@ -4769,6 +4717,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Другой"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
199
po/main/sr.po
199
po/main/sr.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Serbian (https://www.transifex.com/sabnzbd/teams/111101/sr/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: sr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Упозорење"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Грeшкa"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -97,11 +87,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -121,6 +106,16 @@ msgstr "Neuspešno pokretanje web interfejsa: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s покренут"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Упозорење"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Грeшкa"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "Гашење SABnzbd је завршено"
|
||||
@@ -164,11 +159,6 @@ msgstr "Грешка у tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Učitavanje %s neuspešno"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "Упешно слање е-поште"
|
||||
@@ -223,15 +213,15 @@ msgstr "Fatalna greška u Assembler-u"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -240,8 +230,10 @@ msgstr "Prekinuto, detektovana enkripcija"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"UPOZORENJE: U \"%s\" pronađena neželjena ekstenzija u RAR datoteci. "
|
||||
"Neželjena datoteka je %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -253,13 +245,13 @@ msgstr "Prekinuto, detektovana neželjena ekstenzija"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "UPOZORENJE: Posao \"%s\" pauziran zbog ocene (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "UPOZORENJE: Posao \"%s\" prekinut zbog ocene (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -344,6 +336,10 @@ msgstr "%s nije ispravna oktalna vrednost"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC путања \"%s\" није дозвољена"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Greška: Dužina putanje bi trebala biti ispod %s"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Грешка: ред није празан, фасцикла се не може променити."
|
||||
@@ -641,12 +637,6 @@ msgstr ""
|
||||
"кликните на линк испод.<br /><br /><strong><a "
|
||||
"href=\"..\">Освежи</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Пажња: LOCALHOST је двосмислен, користите ИП адресе."
|
||||
@@ -729,11 +719,6 @@ msgstr "с"
|
||||
msgid "m"
|
||||
msgstr "м"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -746,25 +731,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Покретање скрипта"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Пост-процесирање је заустављено (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Скрипт"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -826,6 +805,11 @@ msgstr "Neuspešno raspakivanje, arhiva zahteva lozinku"
|
||||
msgid "Unpacking"
|
||||
msgstr "Распакивање"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Распакуј"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Погрешно распакивање, не може да се нађе %s"
|
||||
@@ -1001,6 +985,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Провера"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Pokušaj SFV provere"
|
||||
@@ -1033,19 +1027,6 @@ msgstr "Вики"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Покретање/Гашење"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Пауза"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Настави"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1164,7 +1145,6 @@ msgstr "%s => фали на свим серверима, одбацивање"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Неважећи NZB %s, прескакање (разлог=%s, линија=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Празан NZB %s"
|
||||
@@ -1298,10 +1278,22 @@ msgstr "Очисти хронологију"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Ограничење брзине"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Пауза"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "мин."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Настави"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1505,10 +1497,6 @@ msgstr "Преузимање је можда погрешно. има %s од п
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Неуспешно преузимање - није на вашем серверу"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Пост-процесирање"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Премештање"
|
||||
@@ -1552,10 +1540,6 @@ msgstr "Грешка пост-процесирања за %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "видети извештај"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Неуспешно преузимање"
|
||||
@@ -1574,6 +1558,10 @@ msgstr "Преузимање завршено"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Немогуће креирање фасцикле %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Пост-процесирање"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Нема par2 датотеке"
|
||||
@@ -1606,11 +1594,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1631,11 +1614,6 @@ msgstr "Неуспено постављање система у стању пр
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Greška pri gašenju sistema"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1660,6 +1638,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Погрешан опис RSS фида \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Неуспешно преузимање RSS од %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Немам важећу аутентификацију за фид %s"
|
||||
@@ -1668,15 +1650,10 @@ msgstr "Немам важећу аутентификацију за фид %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Greška na strani servera (kod greške %s); nemoguće dobiti %s na %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Неуспешно преузимање RSS од %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Server %s koristi nepouzdan HTTPS sertifikat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS фид %s је празан"
|
||||
@@ -1766,16 +1743,6 @@ msgstr "Преузми"
|
||||
msgid "Join files"
|
||||
msgstr "Прилепити датотеке"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Распакуј"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Скрипт"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2108,10 +2075,6 @@ msgstr "Данас"
|
||||
msgid "Total"
|
||||
msgstr "Укупно"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Прилагођено"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "укљ."
|
||||
@@ -3351,11 +3314,9 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Пост-процесирај само проверени послови"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr ""
|
||||
"Огранићи пост-процесирање само за радове који су прешли све PAR2 провере."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3596,16 +3557,6 @@ msgstr "Филтрирај примерне датотеке (нпр. видео
|
||||
msgid "Delete after download"
|
||||
msgstr "Обриши после преузимања"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4102,7 +4053,7 @@ msgstr "Обавештење послато!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Упали „NotifyOSD“"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Центар за обавештења"
|
||||
@@ -4751,6 +4702,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Прилагођено"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
199
po/main/sv.po
199
po/main/sv.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Swedish (https://www.transifex.com/sabnzbd/teams/111101/sv/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: sv\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Varning"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fel"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -97,11 +87,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -121,6 +106,16 @@ msgstr "Misslyckades att starta webbgränsnitt: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s startad"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "Varning"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "Fel"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd nedstängning utförd."
|
||||
@@ -164,11 +159,6 @@ msgstr "Fel i tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Laddning av %s misslyckades"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "E-mail sändning lyckades"
|
||||
@@ -223,15 +213,15 @@ msgstr "Kritiskt fel i Assembler"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
@@ -240,8 +230,9 @@ msgstr "Avbruten, kryptering detekterad."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr ""
|
||||
"Varning: I \"%s\" otillåten filändelse i RAR-filen. Otillåtna filen är %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -253,13 +244,13 @@ msgstr "Avbruten, oönskad filändelse detekterad"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "VARNING: Pausat jobb \"%s\" pga betyg (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr ""
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "VARNING: Avbrutet jobb \"%s\" pga betyg (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -344,6 +335,10 @@ msgstr "%s är inte rätt siffervärde"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "UNC sökväg \"%s\" är inte tillåten här"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "Fel: Sökvägen skall vara under %s."
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "Fel: Kön är inte tom, kan inte byta mapp."
|
||||
@@ -643,12 +638,6 @@ msgstr ""
|
||||
"klicka sedan på knappen under..<br /><br /><strong><a href=\"..\">Ladda "
|
||||
"om</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "Varning: LOCALHOST är tvetydigt, använda numerisk IP-adress ."
|
||||
@@ -731,11 +720,6 @@ msgstr "h"
|
||||
msgid "m"
|
||||
msgstr "m"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -748,25 +732,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "Kör skript"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "Efterbehandling avbröts (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skript"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -828,6 +806,11 @@ msgstr "Uppackning misslyckades, arkivet kräver lösenord"
|
||||
msgid "Unpacking"
|
||||
msgstr "Packar upp"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Packa upp"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "Uppackning misslyckades, gick inte att hitta %s"
|
||||
@@ -1005,6 +988,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "Kontrollerar"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "Försöker verifiera SFV"
|
||||
@@ -1037,19 +1030,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "Starta/Stäng"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pausa"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Återuppta"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1168,7 +1148,6 @@ msgstr "%s => saknas från alla servrar, kastar"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "Felaktig NZB fil %s, hoppar över (orsak=%s, linje=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "NZB filen %s är tom"
|
||||
@@ -1302,10 +1281,22 @@ msgstr "Töm historik"
|
||||
msgid "Limit Speed"
|
||||
msgstr "Hastighetsbegränsning"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "Pausa"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "min."
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "Återuppta"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1511,10 +1502,6 @@ msgstr "Nerladdningen kan misslyckas, bara %s av krävda %s finns tillgängligt"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "Nerladdning misslyckades - Inte på din server eller servrar"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Efterbehandling"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "Flyttar"
|
||||
@@ -1558,10 +1545,6 @@ msgstr "Efterbehandling misslyckades för %s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "se loggfil"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "Hämtning misslyckades"
|
||||
@@ -1580,6 +1563,10 @@ msgstr "Hämtningen slutfördes"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "Kan inte skapa slutgiltig mapp %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "Efterbehandling"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] Ingen par2 sats"
|
||||
@@ -1612,11 +1599,6 @@ msgstr ""
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1637,11 +1619,6 @@ msgstr "Det gick inte att sätta systemet i viloläge"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "Fel uppstod då systemet skulle stängas"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1666,6 +1643,10 @@ msgstr ""
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "Felaktigt RSS-flödesbeskrivning \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Det gick inte att hämta RSS flödet från %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "Har inte giltig autentisering för flöde %s"
|
||||
@@ -1674,15 +1655,10 @@ msgstr "Har inte giltig autentisering för flöde %s"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "Server fel (serverkod %s); kunde inte få %s på %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "Det gick inte att hämta RSS flödet från %s: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "Server %s använder ett otillförlitlig HTTPS-certifikat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS-flödet %s var tomt"
|
||||
@@ -1772,16 +1748,6 @@ msgstr "Nedladdning"
|
||||
msgid "Join files"
|
||||
msgstr "Slår ihop filer"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "Packa upp"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "Skript"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2114,10 +2080,6 @@ msgstr "I dag"
|
||||
msgid "Total"
|
||||
msgstr "Totalt"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Anpassa"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "den"
|
||||
@@ -3360,11 +3322,8 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "Efterbehandla endast verifierade jobb"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgstr ""
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr "Efterbehandla enbart jobb som passerat PAR2 kontrollen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3608,16 +3567,6 @@ msgstr "Filtrera ut sample-filer (ex. video samplingar)."
|
||||
msgid "Delete after download"
|
||||
msgstr "Ta bort efter nedladdning"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr ""
|
||||
@@ -4115,7 +4064,7 @@ msgstr "Notis skickad!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "Aktivera NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "Meddelandecenter"
|
||||
@@ -4763,6 +4712,10 @@ msgstr ""
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "Anpassa"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr ""
|
||||
|
||||
204
po/main/zh_CN.po
204
po/main/zh_CN.po
@@ -7,7 +7,7 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.2.0-develop\n"
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Chinese (China) (https://www.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
|
||||
@@ -17,16 +17,6 @@ msgstr ""
|
||||
"Language: zh_CN\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "警告"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
#. Error message
|
||||
#: SABnzbd.py
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -95,11 +85,6 @@ msgid ""
|
||||
"with Unicoded file and directory names in downloads."
|
||||
msgstr "SABnzbd 以 %s 编码启动了,正常应该是 UTF-8。会导致下载文件夹中统一标准编码的文件和文件夹名称出现问题。"
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Could not load additional certificates from certifi package"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: SABnzbd.py
|
||||
msgid "Disabled HTTPS because of missing CERT and KEY files"
|
||||
@@ -119,6 +104,16 @@ msgstr "无法启动 web 界面: "
|
||||
msgid "SABnzbd %s started"
|
||||
msgstr "SABnzbd %s 已启动"
|
||||
|
||||
#. Notification - Status page, table column header, actual message
|
||||
#: SABnzbd.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Warning"
|
||||
msgstr "警告"
|
||||
|
||||
#. Notification
|
||||
#: SABnzbd.py, sabnzbd/notifier.py
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
#: SABnzbd.py, sabnzbd/interface.py
|
||||
msgid "SABnzbd shutdown finished"
|
||||
msgstr "SABnzbd 关闭完成"
|
||||
@@ -162,11 +157,6 @@ msgstr "tempfile.mkstemp 出错"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "加载 %s 失败"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/api.py, sabnzbd/emailer.py
|
||||
msgid "Email succeeded"
|
||||
msgstr "成功发送电子邮件"
|
||||
@@ -221,16 +211,16 @@ msgstr "Assembler 出现致命错误"
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
|
||||
"were tried)"
|
||||
msgstr "\"%s\" 任务已暂停,因其包含加密 RAR 文件 (已尝试所有的密码,如果提供了的话)"
|
||||
"WARNING: Paused job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr "警告:\"%s\" 任务已暂停,因其包含加密 RAR 文件 (已尝试所有的密码,如果提供了的话)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid ""
|
||||
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
|
||||
" were tried)"
|
||||
msgstr "\"%s\" 任务已终止,因其包含加密 RAR 文件 (已尝试所有的密码,如果提供了的话)"
|
||||
"WARNING: Aborted job \"%s\" because of encrypted RAR file (if supplied, all "
|
||||
"passwords were tried)"
|
||||
msgstr "警告:\"%s\" 任务已终止,因其包含加密 RAR 文件 (已尝试所有的密码,如果提供了的话)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, encryption detected"
|
||||
@@ -238,8 +228,8 @@ msgstr "已中止,发现加密文件"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "RAR 文件“%s”中出现不需要的扩展名。不需要的文件名为 %s "
|
||||
msgid "WARNING: In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
|
||||
msgstr "*警告*: RAR 文件“%s”中出现不需要的扩展名。不需要的文件名为 %s "
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
@@ -251,13 +241,13 @@ msgstr "已中止,侦测到不需要的扩展名"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "任务“%s”已暂停,由于评分过低 (%s)"
|
||||
msgid "WARNING: Paused job \"%s\" because of rating (%s)"
|
||||
msgstr "*警告*: 任务“%s”已暂停,由于评分过低 (%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "任务“%s”已中止,由于评分过低 (%s)"
|
||||
msgid "WARNING: Aborted job \"%s\" because of rating (%s)"
|
||||
msgstr "*警告*: 任务“%s”已中止,由于评分过低 (%s)"
|
||||
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, rating filter matched (%s)"
|
||||
@@ -342,6 +332,10 @@ msgstr "%s 不是有效的八进制值"
|
||||
msgid "UNC path \"%s\" not allowed here"
|
||||
msgstr "此处不允许使用 UNC 路径 \"%s\""
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Path length should be below %s."
|
||||
msgstr "错误: 路径长度应不超过 %s。"
|
||||
|
||||
#: sabnzbd/config.py
|
||||
msgid "Error: Queue not empty, cannot change folder."
|
||||
msgstr "错误: 队列非空,无法变更文件夹。"
|
||||
@@ -633,12 +627,6 @@ msgstr ""
|
||||
" <br />SABnzbd 关闭完成。<br />请等待约 5 秒后点击下面的按钮。<br /><br /><strong><a "
|
||||
"href=\"..\">刷新</a></strong><br />"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"The Completed Download Folder cannot be the same or a subfolder of the "
|
||||
"Temporary Download Folder"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Warning: LOCALHOST is ambiguous, use numerical IP-address."
|
||||
msgstr "警告: LOCALHOST 太含糊,请使用数字 IP 地址。"
|
||||
@@ -721,11 +709,6 @@ msgstr "小时"
|
||||
msgid "m"
|
||||
msgstr "分钟"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Error creating SSL key and certificate"
|
||||
@@ -738,25 +721,19 @@ msgid ""
|
||||
"passwords takes a lot of time. Try to only list useful passwords."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Failed to read the password file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Python 脚本 \"%s\" 不具有执行 (+x) 权限"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "Running script"
|
||||
msgstr "正在执行脚本"
|
||||
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/postproc.py
|
||||
msgid "PostProcessing was aborted (%s)"
|
||||
msgstr "后期处理已中止 (%s)"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "脚本"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpack nesting too deep [%s]"
|
||||
@@ -818,6 +795,11 @@ msgstr "解压失败,压缩文件需要密码"
|
||||
msgid "Unpacking"
|
||||
msgstr "正在解压"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/newsunpack.py, sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "解压"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Unpacking failed, unable to find %s"
|
||||
msgstr "解压失败,找不到 %s"
|
||||
@@ -991,6 +973,16 @@ msgstr ""
|
||||
msgid "Checking"
|
||||
msgstr "正在检查"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "[%s] The command in build_command is undefined."
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Python script \"%s\" does not have execute (+x) permission set"
|
||||
msgstr "Python 脚本 \"%s\" 不具有执行 (+x) 权限"
|
||||
|
||||
#: sabnzbd/newsunpack.py
|
||||
msgid "Trying SFV verification"
|
||||
msgstr "正在尝试 SFV 验证"
|
||||
@@ -1023,19 +1015,6 @@ msgstr "Wiki"
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "启动/关闭"
|
||||
|
||||
#. Notification - Pause downloading - Four way switch for duplicates -
|
||||
#. Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "暂停"
|
||||
|
||||
#. Notification - Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/notifier.py, sabnzbd/osxmenu.py, sabnzbd/sabtray.py,
|
||||
#: sabnzbd/sabtraylinux.py, sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "续传"
|
||||
|
||||
#. Notification - Config->RSS after adding to queue
|
||||
#: sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Added NZB"
|
||||
@@ -1154,7 +1133,6 @@ msgstr "%s => 所有服务器均缺失,正在舍弃"
|
||||
msgid "Invalid NZB file %s, skipping (reason=%s, line=%s)"
|
||||
msgstr "无效 NZB 文件 %s,正在跳过 (原因=%s, 行=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "空 NZB 文件 %s"
|
||||
@@ -1288,10 +1266,22 @@ msgstr "清空历史"
|
||||
msgid "Limit Speed"
|
||||
msgstr "限速"
|
||||
|
||||
#. Pause downloading - Four way switch for duplicates - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Pause"
|
||||
msgstr "暂停"
|
||||
|
||||
#: sabnzbd/osxmenu.py
|
||||
msgid "min."
|
||||
msgstr "分钟"
|
||||
|
||||
#. Resume downloading - Config->Scheduling
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Resume"
|
||||
msgstr "续传"
|
||||
|
||||
#. #: Config->Scheduler
|
||||
#: sabnzbd/osxmenu.py, sabnzbd/skintext.py
|
||||
msgid "Scan watched folder"
|
||||
@@ -1495,10 +1485,6 @@ msgstr "下载可能会失败,只有 %s 块 (需要 %s) 可用"
|
||||
msgid "Download failed - Not on your server(s)"
|
||||
msgstr "下载失败 - 不在该服务器上"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "后期处理"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Moving"
|
||||
msgstr "正在移动"
|
||||
@@ -1542,10 +1528,6 @@ msgstr "后期处理失败:%s (%s)"
|
||||
msgid "see logfile"
|
||||
msgstr "查看日志文件"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing was aborted"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Download Failed"
|
||||
msgstr "下载失败"
|
||||
@@ -1564,6 +1546,10 @@ msgstr "下载完成"
|
||||
msgid "Cannot create final folder %s"
|
||||
msgstr "无法创建最终文件夹 %s"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Post-processing"
|
||||
msgstr "后期处理"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "[%s] No par2 sets"
|
||||
msgstr "[%s] 无 par2 集合"
|
||||
@@ -1596,11 +1582,6 @@ msgstr "RAR 文件验证成功"
|
||||
msgid "RAR files failed to verify"
|
||||
msgstr "RAR 文件验证失败"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "No matching earlier rar file for %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Removing %s failed"
|
||||
@@ -1621,11 +1602,6 @@ msgstr "系统待机失败"
|
||||
msgid "Error while shutting down system"
|
||||
msgstr "关闭系统时出错"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/powersup.py
|
||||
msgid "Received a DBus exception %s"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rating.py
|
||||
msgid "Indexer id (%s) not found for ratings file"
|
||||
@@ -1650,6 +1626,10 @@ msgstr "这个密钥用来向服务器表明身份。查看您在索引网站上
|
||||
msgid "Incorrect RSS feed description \"%s\""
|
||||
msgstr "RSS feed 描述不正确 \"%s\""
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "无法检索 %s 的 RSS: %s"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Do not have valid authentication for feed %s"
|
||||
msgstr "feed %s 无有效的身份认证凭据"
|
||||
@@ -1658,15 +1638,10 @@ msgstr "feed %s 无有效的身份认证凭据"
|
||||
msgid "Server side error (server code %s); could not get %s on %s"
|
||||
msgstr "服务器端错误 (服务器代码 %s);无法获取 %s (服务器 %s)"
|
||||
|
||||
#: sabnzbd/rss.py
|
||||
msgid "Failed to retrieve RSS from %s: %s"
|
||||
msgstr "无法检索 %s 的 RSS: %s"
|
||||
|
||||
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
|
||||
msgid "Server %s uses an untrusted HTTPS certificate"
|
||||
msgstr "服务器 %s 使用的 HTTPS 证书不受信任"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/rss.py
|
||||
msgid "RSS Feed %s was empty"
|
||||
msgstr "RSS Feed %s 为空"
|
||||
@@ -1756,16 +1731,6 @@ msgstr "下载"
|
||||
msgid "Join files"
|
||||
msgstr "合并文件"
|
||||
|
||||
#. PP phase "unpack"
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Unpack"
|
||||
msgstr "解压"
|
||||
|
||||
#. PP phase "script" - Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Script"
|
||||
msgstr "脚本"
|
||||
|
||||
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Source"
|
||||
@@ -2098,10 +2063,6 @@ msgstr "今天"
|
||||
msgid "Total"
|
||||
msgstr "总计"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "自定义"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "on"
|
||||
msgstr "开"
|
||||
@@ -3307,11 +3268,8 @@ msgid "Post-Process Only Verified Jobs"
|
||||
msgstr "仅对经验证的任务进行后期处理"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Only unpack and run scripts on jobs that passed the verification stage. If "
|
||||
"turned off, all jobs will be marked as Completed even if they are "
|
||||
"incomplete."
|
||||
msgstr ""
|
||||
msgid "Only perform post-processing on jobs that passed all PAR2 checks."
|
||||
msgstr "仅对通过全部 PAR2 检查的任务执行后期处理。"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Action when encrypted RAR is downloaded"
|
||||
@@ -3547,16 +3505,6 @@ msgstr "过滤样本文件 (如视频样本)。"
|
||||
msgid "Delete after download"
|
||||
msgstr "下载后删除"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Deobfuscate final filenames"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"If filenames of (large) files in the final folder look obfuscated or "
|
||||
"meaningless they will be renamed to the job name."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
msgstr "HTTPS 证书验证"
|
||||
@@ -4047,7 +3995,7 @@ msgstr "通知已发送!"
|
||||
msgid "Enable NotifyOSD"
|
||||
msgstr "启用NotifyOSD"
|
||||
|
||||
#. Header for macOS Notfication Center section
|
||||
#. Header for OSX Notfication Center section
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notification Center"
|
||||
msgstr "通知中心"
|
||||
@@ -4694,6 +4642,10 @@ msgstr "您的浏览器已禁用 LocalStorage (cookies)。界面设置将在您
|
||||
msgid "Glitter has some (new) features you might like!"
|
||||
msgstr "你可能会喜欢一些 Glitter 的(新)功能!"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Custom"
|
||||
msgstr "自定义"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Compact layout"
|
||||
msgstr "精简外观"
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# SABnzbd Translation Template file NSIS
|
||||
# Copyright 2011-2020 The SABnzbd-Team
|
||||
# team@sabnzbd.org
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Language-Team: Czech (https://www.transifex.com/sabnzbd/teams/111101/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: cs\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Show Release Notes"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid ""
|
||||
"The SABnzbd Windows Service changed in SABnzbd 3.0.0. \\nYou will need to "
|
||||
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
|
||||
"services or `Cancel` to cancel this upgrade."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid ""
|
||||
"The installer only supports 64-bit Windows, use the standalone version to "
|
||||
"run on 32-bit Windows."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Run at startup"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Desktop Icon"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "NZB File association"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Delete Program"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Delete Settings"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr ""
|
||||
@@ -4,13 +4,12 @@
|
||||
#
|
||||
# Translators:
|
||||
# Safihre <safihre@sabnzbd.org>, 2020
|
||||
# Ester Molla Aragones <moarages@gmail.com>, 2020
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-3.0.0-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
|
||||
"Last-Translator: Ester Molla Aragones <moarages@gmail.com>, 2020\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
|
||||
"Language-Team: Spanish (https://www.transifex.com/sabnzbd/teams/111101/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -36,18 +35,12 @@ msgid ""
|
||||
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
|
||||
"services or `Cancel` to cancel this upgrade."
|
||||
msgstr ""
|
||||
"El servicio de Windows para SABnzbd ha cambiado en la versión SABnzbd "
|
||||
"3.0.0.\\nNecesitará volver a instalar el servicio SABnzbd. \\n\\nHaga clic "
|
||||
"en \"OK\" para eliminar los servicios existentes o \"Cancelar\" para "
|
||||
"cancelar la actualización."
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid ""
|
||||
"The installer only supports 64-bit Windows, use the standalone version to "
|
||||
"run on 32-bit Windows."
|
||||
msgstr ""
|
||||
"El instalador solo admite Windows 64-bit, utilice la versión independiente "
|
||||
"para ejecutar Windows 32-bit."
|
||||
|
||||
#: NSIS_Installer.nsi
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
sabyenc3>=4.0.0
|
||||
cheetah3>=3.0.0
|
||||
cryptography
|
||||
feedparser>=6.0.0
|
||||
feedparser
|
||||
configobj
|
||||
cheroot<8.4.3
|
||||
cherrypy
|
||||
|
||||
@@ -15,19 +15,23 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Imported to be referenced from other files directly
|
||||
from sabnzbd.version import __version__, __baseline__
|
||||
|
||||
import os
|
||||
import logging
|
||||
import datetime
|
||||
import tempfile
|
||||
import pickle
|
||||
import gzip
|
||||
import subprocess
|
||||
import time
|
||||
import socket
|
||||
import cherrypy
|
||||
import sys
|
||||
import re
|
||||
import ssl
|
||||
from threading import Lock, Thread
|
||||
from typing import Any, AnyStr
|
||||
|
||||
##############################################################################
|
||||
# Determine platform flags
|
||||
@@ -70,40 +74,35 @@ elif os.name == "posix":
|
||||
except:
|
||||
pass
|
||||
|
||||
# Imported to be referenced from other files directly
|
||||
from sabnzbd.version import __version__, __baseline__
|
||||
|
||||
# Now we can import safely
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
from sabnzbd.postproc import PostProcessor
|
||||
from sabnzbd.downloader import Downloader
|
||||
from sabnzbd.decoder import Decoder
|
||||
from sabnzbd.assembler import Assembler
|
||||
from sabnzbd.rating import Rating
|
||||
import sabnzbd.misc as misc
|
||||
import sabnzbd.filesystem as filesystem
|
||||
import sabnzbd.powersup as powersup
|
||||
from sabnzbd.dirscanner import DirScanner
|
||||
from sabnzbd.urlgrabber import URLGrabber
|
||||
import sabnzbd.scheduler as scheduler
|
||||
import sabnzbd.rss as rss
|
||||
import sabnzbd.emailer as emailer
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
import sabnzbd.newsunpack
|
||||
import sabnzbd.encoding as encoding
|
||||
import sabnzbd.config as config
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
import sabnzbd.cfg as cfg
|
||||
import sabnzbd.database
|
||||
import sabnzbd.lang as lang
|
||||
import sabnzbd.par2file as par2file
|
||||
import sabnzbd.nzbparser as nzbparser
|
||||
import sabnzbd.nzbstuff
|
||||
import sabnzbd.getipaddress
|
||||
import sabnzbd.newsunpack
|
||||
import sabnzbd.par2file
|
||||
import sabnzbd.api
|
||||
import sabnzbd.interface
|
||||
import sabnzbd.zconfig
|
||||
import sabnzbd.nzbstuff as nzbstuff
|
||||
import sabnzbd.directunpacker as directunpacker
|
||||
import sabnzbd.dirscanner
|
||||
import sabnzbd.urlgrabber
|
||||
import sabnzbd.nzbqueue
|
||||
import sabnzbd.postproc
|
||||
import sabnzbd.downloader
|
||||
import sabnzbd.decoder
|
||||
import sabnzbd.assembler
|
||||
import sabnzbd.rating
|
||||
import sabnzbd.articlecache
|
||||
import sabnzbd.bpsmeter
|
||||
import sabnzbd.scheduler as scheduler
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.constants import (
|
||||
DEFAULT_PRIORITY,
|
||||
@@ -113,29 +112,18 @@ from sabnzbd.constants import (
|
||||
QUEUE_VERSION,
|
||||
QUEUE_FILE_TMPL,
|
||||
)
|
||||
import sabnzbd.utils.ssdp
|
||||
import sabnzbd.getipaddress as getipaddress
|
||||
|
||||
# Storage for the threads, variables are filled during initialization
|
||||
ArticleCache: sabnzbd.articlecache.ArticleCache
|
||||
Rating: sabnzbd.rating.Rating
|
||||
Assembler: sabnzbd.assembler.Assembler
|
||||
Decoder: sabnzbd.decoder.Decoder
|
||||
Downloader: sabnzbd.downloader.Downloader
|
||||
PostProcessor: sabnzbd.postproc.PostProcessor
|
||||
NzbQueue: sabnzbd.nzbqueue.NzbQueue
|
||||
URLGrabber: sabnzbd.urlgrabber.URLGrabber
|
||||
DirScanner: sabnzbd.dirscanner.DirScanner
|
||||
BPSMeter: sabnzbd.bpsmeter.BPSMeter
|
||||
RSSReader: sabnzbd.rss.RSSReader
|
||||
Scheduler: sabnzbd.scheduler.Scheduler
|
||||
LINUX_POWER = powersup.HAVE_DBUS
|
||||
|
||||
# Regular constants
|
||||
START = datetime.datetime.now()
|
||||
|
||||
MY_NAME = None
|
||||
MY_FULLNAME = None
|
||||
RESTART_ARGS = []
|
||||
NEW_VERSION = (None, None)
|
||||
DIR_HOME = None
|
||||
DIR_APPDATA = None
|
||||
DIR_LCLDATA = None
|
||||
DIR_PROG = None
|
||||
DIR_INTERFACES = None
|
||||
@@ -147,7 +135,6 @@ QUEUECOMPLETEACTION = None # stores the name of the function to be called
|
||||
QUEUECOMPLETEARG = None # stores an extra arguments that need to be passed
|
||||
|
||||
DAEMON = None
|
||||
LINUX_POWER = powersup.HAVE_DBUS
|
||||
|
||||
LOGFILE = None
|
||||
WEBLOGFILE = None
|
||||
@@ -171,6 +158,8 @@ PAUSED_ALL = False
|
||||
TRIGGER_RESTART = False # To trigger restart for Scheduler, WinService and Mac
|
||||
WINTRAY = None # Thread for the Windows SysTray icon
|
||||
WEBUI_READY = False
|
||||
LAST_WARNING = None
|
||||
LAST_ERROR = None
|
||||
EXTERNAL_IPV6 = False
|
||||
LAST_HISTORY_UPDATE = 1
|
||||
|
||||
@@ -180,7 +169,6 @@ DOWNLOAD_DIR_SPEED = 0
|
||||
COMPLETE_DIR_SPEED = 0
|
||||
INTERNET_BANDWIDTH = 0
|
||||
|
||||
|
||||
# Rendering of original command line arguments in Config
|
||||
CMDLINE = " ".join(['"%s"' % arg for arg in sys.argv])
|
||||
|
||||
@@ -192,6 +180,7 @@ __SHUTTING_DOWN__ = False
|
||||
# Signal Handler
|
||||
##############################################################################
|
||||
def sig_handler(signum=None, frame=None):
|
||||
global SABSTOP, WINTRAY
|
||||
if sabnzbd.WIN32 and signum is not None and DAEMON and signum == 5:
|
||||
# Ignore the "logoff" event when running as a Win32 daemon
|
||||
return True
|
||||
@@ -208,7 +197,7 @@ def sig_handler(signum=None, frame=None):
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
pid_file()
|
||||
sabnzbd.SABSTOP = True
|
||||
SABSTOP = True
|
||||
os._exit(0)
|
||||
|
||||
|
||||
@@ -226,11 +215,13 @@ def get_db_connection(thread_index=0):
|
||||
|
||||
|
||||
@synchronized(INIT_LOCK)
|
||||
def initialize(pause_downloader=False, clean_up=False, repair=0):
|
||||
if sabnzbd.__INITIALIZED__:
|
||||
def initialize(pause_downloader=False, clean_up=False, evalSched=False, repair=0):
|
||||
global __INITIALIZED__, __SHUTTING_DOWN__, LOGFILE, WEBLOGFILE, LOGHANDLER, GUIHANDLER, AMBI_LOCALHOST, WAITEXIT, DAEMON, MY_NAME, MY_FULLNAME, NEW_VERSION, DIR_HOME, DIR_APPDATA, DIR_LCLDATA, DIR_PROG, DIR_INTERFACES, DARWIN, RESTART_REQ
|
||||
|
||||
if __INITIALIZED__:
|
||||
return False
|
||||
|
||||
sabnzbd.__SHUTTING_DOWN__ = False
|
||||
__SHUTTING_DOWN__ = False
|
||||
|
||||
# Set global database connection for Web-UI threads
|
||||
cherrypy.engine.subscribe("start_thread", get_db_connection)
|
||||
@@ -281,6 +272,11 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
|
||||
cfg.enable_https_verification.callback(guard_https_ver)
|
||||
guard_https_ver()
|
||||
|
||||
# Set cache limit
|
||||
if not cfg.cache_limit() or (cfg.cache_limit() in ("200M", "450M") and (sabnzbd.WIN32 or sabnzbd.DARWIN)):
|
||||
cfg.cache_limit.set(misc.get_cache_limit())
|
||||
ArticleCache.do.new_limit(cfg.cache_limit.get_int())
|
||||
|
||||
check_incomplete_vs_complete()
|
||||
|
||||
# Set language files
|
||||
@@ -288,9 +284,26 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
|
||||
lang.set_language(cfg.language())
|
||||
sabnzbd.api.clear_trans_cache()
|
||||
|
||||
# Set end-of-queue action
|
||||
sabnzbd.change_queue_complete_action(cfg.queue_complete(), new=False)
|
||||
|
||||
# One time conversion "speedlimit" in schedules.
|
||||
if not cfg.sched_converted():
|
||||
schedules = cfg.schedules()
|
||||
newsched = []
|
||||
for sched in schedules:
|
||||
if "speedlimit" in sched:
|
||||
newsched.append(re.sub(r"(speedlimit \d+)$", r"\1K", sched))
|
||||
else:
|
||||
newsched.append(sched)
|
||||
cfg.schedules.set(newsched)
|
||||
cfg.sched_converted.set(1)
|
||||
|
||||
# Second time schedule conversion
|
||||
if cfg.sched_converted() != 2:
|
||||
cfg.schedules.set(["%s %s" % (1, schedule) for schedule in cfg.schedules()])
|
||||
cfg.sched_converted.set(2)
|
||||
config.save_config()
|
||||
|
||||
# Convert auto-sort
|
||||
if cfg.auto_sort() == "0":
|
||||
cfg.auto_sort.set("")
|
||||
@@ -307,97 +320,103 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
|
||||
pause_downloader = True
|
||||
|
||||
# Initialize threads
|
||||
sabnzbd.ArticleCache = sabnzbd.articlecache.ArticleCache()
|
||||
sabnzbd.BPSMeter = sabnzbd.bpsmeter.BPSMeter()
|
||||
sabnzbd.NzbQueue = sabnzbd.nzbqueue.NzbQueue()
|
||||
sabnzbd.Downloader = sabnzbd.downloader.Downloader(pause_downloader or sabnzbd.BPSMeter.read())
|
||||
sabnzbd.Decoder = sabnzbd.decoder.Decoder()
|
||||
sabnzbd.Assembler = sabnzbd.assembler.Assembler()
|
||||
sabnzbd.PostProcessor = sabnzbd.postproc.PostProcessor()
|
||||
sabnzbd.DirScanner = sabnzbd.dirscanner.DirScanner()
|
||||
sabnzbd.Rating = sabnzbd.rating.Rating()
|
||||
sabnzbd.URLGrabber = sabnzbd.urlgrabber.URLGrabber()
|
||||
sabnzbd.RSSReader = sabnzbd.rss.RSSReader()
|
||||
sabnzbd.Scheduler = sabnzbd.scheduler.Scheduler()
|
||||
rss.init()
|
||||
|
||||
# Run startup tasks
|
||||
sabnzbd.NzbQueue.read_queue(repair)
|
||||
sabnzbd.Scheduler.analyse(pause_downloader)
|
||||
paused = BPSMeter.do.read()
|
||||
|
||||
# Set cache limit for new users
|
||||
if not cfg.cache_limit():
|
||||
cfg.cache_limit.set(misc.get_cache_limit())
|
||||
sabnzbd.ArticleCache.new_limit(cfg.cache_limit.get_int())
|
||||
NzbQueue()
|
||||
|
||||
Downloader(pause_downloader or paused)
|
||||
|
||||
Decoder()
|
||||
|
||||
Assembler()
|
||||
|
||||
PostProcessor()
|
||||
|
||||
NzbQueue.do.read_queue(repair)
|
||||
|
||||
DirScanner()
|
||||
|
||||
Rating()
|
||||
|
||||
URLGrabber()
|
||||
|
||||
scheduler.init()
|
||||
|
||||
if evalSched:
|
||||
scheduler.analyse(pause_downloader)
|
||||
|
||||
logging.info("All processes started")
|
||||
sabnzbd.RESTART_REQ = False
|
||||
sabnzbd.__INITIALIZED__ = True
|
||||
RESTART_REQ = False
|
||||
__INITIALIZED__ = True
|
||||
return True
|
||||
|
||||
|
||||
@synchronized(INIT_LOCK)
|
||||
def start():
|
||||
if sabnzbd.__INITIALIZED__:
|
||||
global __INITIALIZED__
|
||||
|
||||
if __INITIALIZED__:
|
||||
logging.debug("Starting postprocessor")
|
||||
sabnzbd.PostProcessor.start()
|
||||
PostProcessor.do.start()
|
||||
|
||||
logging.debug("Starting assembler")
|
||||
sabnzbd.Assembler.start()
|
||||
Assembler.do.start()
|
||||
|
||||
logging.debug("Starting downloader")
|
||||
sabnzbd.Downloader.start()
|
||||
Downloader.do.start()
|
||||
|
||||
logging.debug("Starting decoders")
|
||||
sabnzbd.Decoder.start()
|
||||
Decoder.do.start()
|
||||
|
||||
logging.debug("Starting scheduler")
|
||||
sabnzbd.Scheduler.start()
|
||||
scheduler.start()
|
||||
|
||||
logging.debug("Starting dirscanner")
|
||||
sabnzbd.DirScanner.start()
|
||||
DirScanner.do.start()
|
||||
|
||||
logging.debug("Starting rating")
|
||||
sabnzbd.Rating.start()
|
||||
Rating.do.start()
|
||||
|
||||
logging.debug("Starting urlgrabber")
|
||||
sabnzbd.URLGrabber.start()
|
||||
URLGrabber.do.start()
|
||||
|
||||
|
||||
@synchronized(INIT_LOCK)
|
||||
def halt():
|
||||
if sabnzbd.__INITIALIZED__:
|
||||
global __INITIALIZED__, __SHUTTING_DOWN__
|
||||
|
||||
if __INITIALIZED__:
|
||||
logging.info("SABnzbd shutting down...")
|
||||
sabnzbd.__SHUTTING_DOWN__ = True
|
||||
__SHUTTING_DOWN__ = True
|
||||
|
||||
# Stop the windows tray icon
|
||||
if sabnzbd.WINTRAY:
|
||||
sabnzbd.WINTRAY.terminate = True
|
||||
|
||||
sabnzbd.zconfig.remove_server()
|
||||
sabnzbd.utils.ssdp.stop_ssdp()
|
||||
|
||||
sabnzbd.directunpacker.abort_all()
|
||||
|
||||
logging.debug("Stopping RSSReader")
|
||||
sabnzbd.RSSReader.stop()
|
||||
rss.stop()
|
||||
|
||||
logging.debug("Stopping URLGrabber")
|
||||
sabnzbd.URLGrabber.stop()
|
||||
URLGrabber.do.stop()
|
||||
try:
|
||||
sabnzbd.URLGrabber.join()
|
||||
URLGrabber.do.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
logging.debug("Stopping rating")
|
||||
sabnzbd.Rating.stop()
|
||||
Rating.do.stop()
|
||||
try:
|
||||
sabnzbd.Rating.join()
|
||||
Rating.do.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
logging.debug("Stopping dirscanner")
|
||||
sabnzbd.DirScanner.stop()
|
||||
DirScanner.do.stop()
|
||||
try:
|
||||
sabnzbd.DirScanner.join()
|
||||
DirScanner.do.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -407,20 +426,20 @@ def halt():
|
||||
|
||||
# Decoder handles join gracefully
|
||||
logging.debug("Stopping decoders")
|
||||
sabnzbd.Decoder.stop()
|
||||
sabnzbd.Decoder.join()
|
||||
Decoder.do.stop()
|
||||
Decoder.do.join()
|
||||
|
||||
logging.debug("Stopping assembler")
|
||||
sabnzbd.Assembler.stop()
|
||||
Assembler.do.stop()
|
||||
try:
|
||||
sabnzbd.Assembler.join()
|
||||
Assembler.do.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
logging.debug("Stopping postprocessor")
|
||||
sabnzbd.PostProcessor.stop()
|
||||
PostProcessor.do.stop()
|
||||
try:
|
||||
sabnzbd.PostProcessor.join()
|
||||
PostProcessor.do.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -434,12 +453,11 @@ def halt():
|
||||
# Since all warm-restarts have been removed, it's not longer
|
||||
# needed to stop the scheduler.
|
||||
# We must tell the scheduler to deactivate.
|
||||
logging.debug("Terminating scheduler")
|
||||
sabnzbd.Scheduler.abort()
|
||||
scheduler.abort()
|
||||
|
||||
logging.info("All processes stopped")
|
||||
|
||||
sabnzbd.__INITIALIZED__ = False
|
||||
__INITIALIZED__ = False
|
||||
|
||||
|
||||
def trigger_restart(timeout=None):
|
||||
@@ -448,6 +466,15 @@ def trigger_restart(timeout=None):
|
||||
if timeout:
|
||||
time.sleep(timeout)
|
||||
|
||||
# Add extra arguments
|
||||
if sabnzbd.downloader.Downloader.do.paused:
|
||||
sabnzbd.RESTART_ARGS.append("-p")
|
||||
sys.argv = sabnzbd.RESTART_ARGS
|
||||
|
||||
# Stop all services
|
||||
sabnzbd.halt()
|
||||
cherrypy.engine.exit()
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
# Remove connection info for faster restart
|
||||
del_connection_info()
|
||||
@@ -456,15 +483,6 @@ def trigger_restart(timeout=None):
|
||||
if hasattr(sys, "frozen"):
|
||||
sabnzbd.TRIGGER_RESTART = True
|
||||
else:
|
||||
# Add extra arguments
|
||||
if sabnzbd.Downloader.paused:
|
||||
sabnzbd.RESTART_ARGS.append("-p")
|
||||
sys.argv = sabnzbd.RESTART_ARGS
|
||||
|
||||
# Stop all services
|
||||
sabnzbd.halt()
|
||||
cherrypy.engine.exit()
|
||||
|
||||
# Do the restart right now
|
||||
cherrypy.engine._do_execv()
|
||||
|
||||
@@ -474,17 +492,18 @@ def trigger_restart(timeout=None):
|
||||
##############################################################################
|
||||
def new_limit():
|
||||
""" Callback for article cache changes """
|
||||
sabnzbd.ArticleCache.new_limit(cfg.cache_limit.get_int())
|
||||
ArticleCache.do.new_limit(cfg.cache_limit.get_int())
|
||||
|
||||
|
||||
def guard_restart():
|
||||
""" Callback for config options requiring a restart """
|
||||
global RESTART_REQ
|
||||
sabnzbd.RESTART_REQ = True
|
||||
|
||||
|
||||
def guard_top_only():
|
||||
""" Callback for change of top_only option """
|
||||
sabnzbd.NzbQueue.set_top_only(cfg.top_only())
|
||||
NzbQueue.do.set_top_only(cfg.top_only())
|
||||
|
||||
|
||||
def guard_pause_on_pp():
|
||||
@@ -493,17 +512,17 @@ def guard_pause_on_pp():
|
||||
pass # Not safe to idle downloader, because we don't know
|
||||
# if post-processing is active now
|
||||
else:
|
||||
sabnzbd.Downloader.resume_from_postproc()
|
||||
Downloader.do.resume_from_postproc()
|
||||
|
||||
|
||||
def guard_quota_size():
|
||||
""" Callback for change of quota_size """
|
||||
sabnzbd.BPSMeter.change_quota()
|
||||
BPSMeter.do.change_quota()
|
||||
|
||||
|
||||
def guard_quota_dp():
|
||||
""" Callback for change of quota_day or quota_period """
|
||||
sabnzbd.Scheduler.restart()
|
||||
scheduler.restart(force=True)
|
||||
|
||||
|
||||
def guard_language():
|
||||
@@ -513,8 +532,8 @@ def guard_language():
|
||||
|
||||
|
||||
def set_https_verification(value):
|
||||
"""Set HTTPS-verification state while returning current setting
|
||||
False = disable verification
|
||||
""" Set HTTPS-verification state while returning current setting
|
||||
False = disable verification
|
||||
"""
|
||||
prev = ssl._create_default_https_context == ssl.create_default_context
|
||||
if value:
|
||||
@@ -529,7 +548,7 @@ def guard_https_ver():
|
||||
set_https_verification(cfg.enable_https_verification())
|
||||
|
||||
|
||||
def add_url(url, pp=None, script=None, cat=None, priority=None, nzbname=None, password=None):
|
||||
def add_url(url, pp=None, script=None, cat=None, priority=None, nzbname=None):
|
||||
""" Add NZB based on a URL, attributes optional """
|
||||
if "http" not in url:
|
||||
return
|
||||
@@ -547,39 +566,35 @@ def add_url(url, pp=None, script=None, cat=None, priority=None, nzbname=None, pa
|
||||
msg = "%s - %s" % (nzbname, msg)
|
||||
|
||||
# Generate the placeholder
|
||||
future_nzo = sabnzbd.NzbQueue.generate_future(msg, pp, script, cat, url=url, priority=priority, nzbname=nzbname)
|
||||
|
||||
# Set password
|
||||
if not future_nzo.password:
|
||||
future_nzo.password = password
|
||||
|
||||
# Get it!
|
||||
sabnzbd.URLGrabber.add(url, future_nzo)
|
||||
future_nzo = NzbQueue.do.generate_future(msg, pp, script, cat, url=url, priority=priority, nzbname=nzbname)
|
||||
URLGrabber.do.add(url, future_nzo)
|
||||
return future_nzo.nzo_id
|
||||
|
||||
|
||||
def save_state():
|
||||
""" Save all internal bookkeeping to disk """
|
||||
sabnzbd.ArticleCache.flush_articles()
|
||||
sabnzbd.NzbQueue.save()
|
||||
sabnzbd.BPSMeter.save()
|
||||
sabnzbd.Rating.save()
|
||||
sabnzbd.DirScanner.save()
|
||||
sabnzbd.PostProcessor.save()
|
||||
sabnzbd.RSSReader.save()
|
||||
ArticleCache.do.flush_articles()
|
||||
NzbQueue.do.save()
|
||||
BPSMeter.do.save()
|
||||
rss.save()
|
||||
Rating.do.save()
|
||||
DirScanner.do.save()
|
||||
PostProcessor.do.save()
|
||||
|
||||
|
||||
def pause_all():
|
||||
""" Pause all activities than cause disk access """
|
||||
sabnzbd.PAUSED_ALL = True
|
||||
sabnzbd.Downloader.pause()
|
||||
global PAUSED_ALL
|
||||
PAUSED_ALL = True
|
||||
Downloader.do.pause()
|
||||
logging.debug("PAUSED_ALL active")
|
||||
|
||||
|
||||
def unpause_all():
|
||||
""" Resume all activities """
|
||||
sabnzbd.PAUSED_ALL = False
|
||||
sabnzbd.Downloader.resume()
|
||||
global PAUSED_ALL
|
||||
PAUSED_ALL = False
|
||||
Downloader.do.resume()
|
||||
logging.debug("PAUSED_ALL inactive")
|
||||
|
||||
|
||||
@@ -588,20 +603,20 @@ def unpause_all():
|
||||
##############################################################################
|
||||
|
||||
|
||||
def backup_exists(filename: str) -> bool:
|
||||
def backup_exists(filename):
|
||||
""" Return True if backup exists and no_dupes is set """
|
||||
path = cfg.nzb_backup_dir.get_path()
|
||||
return path and os.path.exists(os.path.join(path, filename + ".gz"))
|
||||
|
||||
|
||||
def backup_nzb(filename: str, data: AnyStr):
|
||||
def backup_nzb(filename, data):
|
||||
""" Backup NZB file """
|
||||
path = cfg.nzb_backup_dir.get_path()
|
||||
if path:
|
||||
save_compressed(path, filename, data)
|
||||
|
||||
|
||||
def save_compressed(folder: str, filename: str, data: AnyStr):
|
||||
def save_compressed(folder, filename, data):
|
||||
""" Save compressed NZB file in folder """
|
||||
if filename.endswith(".nzb"):
|
||||
filename += ".gz"
|
||||
@@ -611,7 +626,7 @@ def save_compressed(folder: str, filename: str, data: AnyStr):
|
||||
try:
|
||||
# Have to get around the path being put inside the tgz
|
||||
with open(os.path.join(folder, filename), "wb") as tgz_file:
|
||||
f = gzip.GzipFile(filename, fileobj=tgz_file, mode="wb")
|
||||
f = gzip.GzipFile(filename, fileobj=tgz_file)
|
||||
f.write(encoding.utob(data))
|
||||
f.flush()
|
||||
f.close()
|
||||
@@ -640,8 +655,8 @@ def add_nzbfile(
|
||||
password=None,
|
||||
nzo_id=None,
|
||||
):
|
||||
"""Add file, either a single NZB-file or an archive.
|
||||
All other parameters are passed to the NZO-creation.
|
||||
""" Add file, either a single NZB-file or an archive.
|
||||
All other parameters are passed to the NZO-creation.
|
||||
"""
|
||||
if pp == "-1":
|
||||
pp = None
|
||||
@@ -725,7 +740,7 @@ def enable_server(server):
|
||||
logging.warning(T("Trying to set status of non-existing server %s"), server)
|
||||
return
|
||||
config.save_config()
|
||||
sabnzbd.Downloader.update_server(server, server)
|
||||
Downloader.do.update_server(server, server)
|
||||
|
||||
|
||||
def disable_server(server):
|
||||
@@ -736,7 +751,7 @@ def disable_server(server):
|
||||
logging.warning(T("Trying to set status of non-existing server %s"), server)
|
||||
return
|
||||
config.save_config()
|
||||
sabnzbd.Downloader.update_server(server, server)
|
||||
Downloader.do.update_server(server, server)
|
||||
|
||||
|
||||
def system_shutdown():
|
||||
@@ -795,17 +810,19 @@ def restart_program():
|
||||
|
||||
|
||||
def change_queue_complete_action(action, new=True):
|
||||
"""Action or script to be performed once the queue has been completed
|
||||
Scripts are prefixed with 'script_'
|
||||
When "new" is False, check whether non-script actions are acceptable
|
||||
""" Action or script to be performed once the queue has been completed
|
||||
Scripts are prefixed with 'script_'
|
||||
When "new" is False, check whether non-script actions are acceptable
|
||||
"""
|
||||
global QUEUECOMPLETE, QUEUECOMPLETEACTION, QUEUECOMPLETEARG
|
||||
|
||||
_action = None
|
||||
_argument = None
|
||||
if action.startswith("script_"):
|
||||
if "script_" in action:
|
||||
# all scripts are labeled script_xxx
|
||||
_action = run_script
|
||||
_argument = action.replace("script_", "", 1)
|
||||
elif new or cfg.queue_complete_pers():
|
||||
_argument = action.replace("script_", "")
|
||||
elif new or cfg.queue_complete_pers.get():
|
||||
if action == "shutdown_pc":
|
||||
_action = system_shutdown
|
||||
elif action == "hibernate_pc":
|
||||
@@ -824,9 +841,9 @@ def change_queue_complete_action(action, new=True):
|
||||
config.save_config()
|
||||
|
||||
# keep the name of the action for matching the current select in queue.tmpl
|
||||
sabnzbd.QUEUECOMPLETE = action
|
||||
sabnzbd.QUEUECOMPLETEACTION = _action
|
||||
sabnzbd.QUEUECOMPLETEARG = _argument
|
||||
QUEUECOMPLETE = action
|
||||
QUEUECOMPLETEACTION = _action
|
||||
QUEUECOMPLETEARG = _argument
|
||||
|
||||
|
||||
def run_script(script):
|
||||
@@ -834,20 +851,35 @@ def run_script(script):
|
||||
script_path = filesystem.make_script_path(script)
|
||||
if script_path:
|
||||
try:
|
||||
script_output = misc.run_command([script_path])
|
||||
logging.info("Output of queue-complete script %s: \n%s", script, script_output)
|
||||
stup, need_shell, command, creationflags = sabnzbd.newsunpack.build_command([script_path])
|
||||
logging.info("Spawning external command %s", command)
|
||||
subprocess.Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
except:
|
||||
logging.info("Failed queue-complete script %s, Traceback: ", script, exc_info=True)
|
||||
logging.debug("Failed script %s, Traceback: ", script, exc_info=True)
|
||||
|
||||
|
||||
def empty_queues():
|
||||
""" Return True if queues empty or non-existent """
|
||||
global __INITIALIZED__
|
||||
return (not __INITIALIZED__) or (PostProcessor.do.empty() and NzbQueue.do.is_empty())
|
||||
|
||||
|
||||
def keep_awake():
|
||||
""" If we still have work to do, keep Windows/macOS system awake """
|
||||
""" If we still have work to do, keep Windows/OSX system awake """
|
||||
if KERNEL32 or FOUNDATION:
|
||||
if sabnzbd.cfg.keep_awake():
|
||||
ES_CONTINUOUS = 0x80000000
|
||||
ES_SYSTEM_REQUIRED = 0x00000001
|
||||
if (not sabnzbd.Downloader.is_paused() and not sabnzbd.NzbQueue.is_empty()) or (
|
||||
not sabnzbd.PostProcessor.paused and not sabnzbd.PostProcessor.empty()
|
||||
if (not Downloader.do.is_paused() and not NzbQueue.do.is_empty()) or (
|
||||
not PostProcessor.do.paused and not PostProcessor.do.empty()
|
||||
):
|
||||
if KERNEL32:
|
||||
# Set ES_SYSTEM_REQUIRED until the next call
|
||||
@@ -868,8 +900,8 @@ def keep_awake():
|
||||
|
||||
|
||||
def get_new_id(prefix, folder, check_list=None):
|
||||
"""Return unique prefixed admin identifier within folder
|
||||
optionally making sure that id is not in the check_list.
|
||||
""" Return unique prefixed admin identifier within folder
|
||||
optionally making sure that id is not in the check_list.
|
||||
"""
|
||||
for n in range(100):
|
||||
try:
|
||||
@@ -947,7 +979,7 @@ def load_data(data_id, path, remove=True, do_pickle=True, silent=False):
|
||||
return data
|
||||
|
||||
|
||||
def remove_data(_id: str, path: str):
|
||||
def remove_data(_id, path):
|
||||
""" Remove admin file """
|
||||
path = os.path.join(path, _id)
|
||||
try:
|
||||
@@ -957,13 +989,13 @@ def remove_data(_id: str, path: str):
|
||||
logging.debug("Failed to remove %s", path)
|
||||
|
||||
|
||||
def save_admin(data: Any, data_id: str):
|
||||
def save_admin(data, data_id):
|
||||
""" Save data in admin folder in specified format """
|
||||
logging.debug("[%s] Saving data for %s", misc.caller_name(), data_id)
|
||||
save_data(data, data_id, cfg.admin_dir.get_path())
|
||||
|
||||
|
||||
def load_admin(data_id: str, remove=False, silent=False) -> Any:
|
||||
def load_admin(data_id, remove=False, silent=False):
|
||||
""" Read data in admin folder in specified format """
|
||||
logging.debug("[%s] Loading data for %s", misc.caller_name(), data_id)
|
||||
return load_data(data_id, cfg.admin_dir.get_path(), remove=remove, silent=silent)
|
||||
@@ -992,87 +1024,85 @@ def check_repair_request():
|
||||
|
||||
|
||||
def check_all_tasks():
|
||||
"""Check every task and restart safe ones, else restart program
|
||||
Return True when everything is under control
|
||||
""" Check every task and restart safe ones, else restart program
|
||||
Return True when everything is under control
|
||||
"""
|
||||
if __SHUTTING_DOWN__ or not __INITIALIZED__:
|
||||
return True
|
||||
|
||||
# Non-restartable threads, require program restart
|
||||
if not sabnzbd.PostProcessor.is_alive():
|
||||
if not sabnzbd.PostProcessor.do.is_alive():
|
||||
logging.info("Restarting because of crashed postprocessor")
|
||||
return False
|
||||
if not sabnzbd.Downloader.is_alive():
|
||||
if not Downloader.do.is_alive():
|
||||
logging.info("Restarting because of crashed downloader")
|
||||
return False
|
||||
if not sabnzbd.Decoder.is_alive():
|
||||
if not Decoder.do.is_alive():
|
||||
logging.info("Restarting because of crashed decoder")
|
||||
return False
|
||||
if not sabnzbd.Assembler.is_alive():
|
||||
if not Assembler.do.is_alive():
|
||||
logging.info("Restarting because of crashed assembler")
|
||||
return False
|
||||
|
||||
# Kick the downloader, in case it missed the semaphore
|
||||
sabnzbd.Downloader.wakeup()
|
||||
Downloader.do.wakeup()
|
||||
|
||||
# Make sure the right servers are active
|
||||
sabnzbd.Downloader.check_timers()
|
||||
Downloader.do.check_timers()
|
||||
|
||||
# Restartable threads
|
||||
if not sabnzbd.DirScanner.is_alive():
|
||||
if not DirScanner.do.is_alive():
|
||||
logging.info("Restarting crashed dirscanner")
|
||||
sabnzbd.DirScanner.__init__()
|
||||
if not sabnzbd.URLGrabber.is_alive():
|
||||
DirScanner.do.__init__()
|
||||
if not URLGrabber.do.is_alive():
|
||||
logging.info("Restarting crashed urlgrabber")
|
||||
sabnzbd.URLGrabber.__init__()
|
||||
if not sabnzbd.Rating.is_alive():
|
||||
URLGrabber.do.__init__()
|
||||
if not Rating.do.is_alive():
|
||||
logging.info("Restarting crashed rating")
|
||||
sabnzbd.Rating.__init__()
|
||||
if not sabnzbd.Scheduler.is_alive():
|
||||
Rating.do.__init__()
|
||||
if not sabnzbd.scheduler.sched_check():
|
||||
logging.info("Restarting crashed scheduler")
|
||||
sabnzbd.Scheduler.restart()
|
||||
sabnzbd.Downloader.unblock_all()
|
||||
sabnzbd.scheduler.init()
|
||||
sabnzbd.downloader.Downloader.do.unblock_all()
|
||||
|
||||
# Check one-shot pause
|
||||
sabnzbd.Scheduler.pause_check()
|
||||
sabnzbd.scheduler.pause_check()
|
||||
|
||||
# Check (and terminate) idle jobs
|
||||
sabnzbd.NzbQueue.stop_idle_jobs()
|
||||
sabnzbd.nzbqueue.NzbQueue.do.stop_idle_jobs()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def pid_file(pid_path=None, pid_file=None, port=0):
|
||||
""" Create or remove pid file """
|
||||
global DIR_PID
|
||||
if not sabnzbd.WIN32:
|
||||
if pid_path and pid_path.startswith("/"):
|
||||
sabnzbd.DIR_PID = os.path.join(pid_path, "sabnzbd-%d.pid" % port)
|
||||
DIR_PID = os.path.join(pid_path, "sabnzbd-%d.pid" % port)
|
||||
elif pid_file and pid_file.startswith("/"):
|
||||
sabnzbd.DIR_PID = pid_file
|
||||
DIR_PID = pid_file
|
||||
|
||||
if sabnzbd.DIR_PID:
|
||||
if DIR_PID:
|
||||
try:
|
||||
if port:
|
||||
with open(sabnzbd.DIR_PID, "w") as f:
|
||||
with open(DIR_PID, "w") as f:
|
||||
f.write("%d\n" % os.getpid())
|
||||
else:
|
||||
filesystem.remove_file(sabnzbd.DIR_PID)
|
||||
filesystem.remove_file(DIR_PID)
|
||||
except:
|
||||
logging.warning(T("Cannot access PID file %s"), sabnzbd.DIR_PID)
|
||||
logging.warning("Cannot access PID file %s", DIR_PID)
|
||||
|
||||
|
||||
def check_incomplete_vs_complete():
|
||||
"""Make sure download_dir and complete_dir are not identical
|
||||
or that download_dir is not a subfolder of complete_dir"""
|
||||
""" Make sure "incomplete" and "complete" are not identical """
|
||||
complete = cfg.complete_dir.get_path()
|
||||
if filesystem.same_file(cfg.download_dir.get_path(), complete):
|
||||
if filesystem.real_path("X", cfg.download_dir()) == filesystem.long_path(cfg.download_dir()):
|
||||
# Abs path, so set download_dir as an abs path inside the complete_dir
|
||||
if filesystem.real_path("X", cfg.download_dir()) == cfg.download_dir():
|
||||
# Abs path, so set an abs path too
|
||||
cfg.download_dir.set(os.path.join(complete, "incomplete"))
|
||||
else:
|
||||
cfg.download_dir.set("incomplete")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def wait_for_download_folder():
|
||||
@@ -1082,13 +1112,18 @@ def wait_for_download_folder():
|
||||
time.sleep(2.0)
|
||||
|
||||
|
||||
# Required wrapper because nzbstuff.py cannot import downloader.py
|
||||
def highest_server(me):
|
||||
return sabnzbd.downloader.Downloader.do.highest_server(me)
|
||||
|
||||
|
||||
def test_ipv6():
|
||||
""" Check if external IPv6 addresses are reachable """
|
||||
if not cfg.selftest_host():
|
||||
# User disabled the test, assume active IPv6
|
||||
return True
|
||||
try:
|
||||
info = sabnzbd.getipaddress.addresslookup6(cfg.selftest_host())
|
||||
info = getipaddress.addresslookup6(cfg.selftest_host())
|
||||
except:
|
||||
logging.debug(
|
||||
"Test IPv6: Disabling IPv6, because it looks like it's not available. Reason: %s", sys.exc_info()[0]
|
||||
|
||||
371
sabnzbd/api.py
371
sabnzbd/api.py
@@ -22,14 +22,13 @@ sabnzbd.api - api
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import gc
|
||||
import datetime
|
||||
import time
|
||||
import json
|
||||
import cherrypy
|
||||
import locale
|
||||
|
||||
from threading import Thread
|
||||
from typing import List
|
||||
|
||||
try:
|
||||
import win32api
|
||||
@@ -42,15 +41,20 @@ from sabnzbd.constants import (
|
||||
VALID_ARCHIVES,
|
||||
VALID_NZB_FILES,
|
||||
Status,
|
||||
FORCE_PRIORITY,
|
||||
TOP_PRIORITY,
|
||||
REPAIR_PRIORITY,
|
||||
HIGH_PRIORITY,
|
||||
NORMAL_PRIORITY,
|
||||
INTERFACE_PRIORITIES,
|
||||
LOW_PRIORITY,
|
||||
KIBI,
|
||||
MEBI,
|
||||
GIGI,
|
||||
)
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.downloader import Downloader
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
import sabnzbd.scheduler as scheduler
|
||||
from sabnzbd.skintext import SKIN_TEXT
|
||||
from sabnzbd.utils.pathbrowser import folders_at_path
|
||||
from sabnzbd.utils.getperformance import getcpu
|
||||
@@ -64,15 +68,19 @@ from sabnzbd.misc import (
|
||||
calc_age,
|
||||
opts_to_pp,
|
||||
)
|
||||
from sabnzbd.filesystem import diskspace, get_ext, globber_full, clip_path, remove_all, userxbit
|
||||
from sabnzbd.filesystem import diskspace, get_ext, globber_full, clip_path, remove_all
|
||||
from sabnzbd.encoding import xml_name
|
||||
from sabnzbd.postproc import PostProcessor
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
from sabnzbd.utils.servertests import test_nntp_server_dict
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
from sabnzbd.rating import Rating
|
||||
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6, addresslookup
|
||||
from sabnzbd.newsunpack import userxbit
|
||||
from sabnzbd.database import build_history_info, unpack_history_info, HistoryDB
|
||||
import sabnzbd.notifier
|
||||
import sabnzbd.rss
|
||||
import sabnzbd.emailer
|
||||
import sabnzbd.sorting
|
||||
|
||||
##############################################################################
|
||||
# API error messages
|
||||
@@ -194,12 +202,12 @@ def _api_queue(name, output, kwargs):
|
||||
def _api_queue_delete(output, value, kwargs):
|
||||
""" API: accepts output, value """
|
||||
if value.lower() == "all":
|
||||
removed = sabnzbd.NzbQueue.remove_all(kwargs.get("search"))
|
||||
removed = NzbQueue.do.remove_all(kwargs.get("search"))
|
||||
return report(output, keyword="", data={"status": bool(removed), "nzo_ids": removed})
|
||||
elif value:
|
||||
items = value.split(",")
|
||||
delete_all_data = int_conv(kwargs.get("del_files"))
|
||||
removed = sabnzbd.NzbQueue.remove_multiple(items, delete_all_data=delete_all_data)
|
||||
removed = NzbQueue.do.remove_multiple(items, delete_all_data=delete_all_data)
|
||||
return report(output, keyword="", data={"status": bool(removed), "nzo_ids": removed})
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE)
|
||||
@@ -209,7 +217,7 @@ def _api_queue_delete_nzf(output, value, kwargs):
|
||||
""" API: accepts value(=nzo_id), value2(=nzf_id) """
|
||||
value2 = kwargs.get("value2")
|
||||
if value and value2:
|
||||
removed = sabnzbd.NzbQueue.remove_nzf(value, value2, force_delete=True)
|
||||
removed = NzbQueue.do.remove_nzf(value, value2, force_delete=True)
|
||||
return report(output, keyword="", data={"status": bool(removed), "nzf_ids": removed})
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE2)
|
||||
@@ -220,7 +228,7 @@ def _api_queue_rename(output, value, kwargs):
|
||||
value2 = kwargs.get("value2")
|
||||
value3 = kwargs.get("value3")
|
||||
if value and value2:
|
||||
ret = sabnzbd.NzbQueue.change_name(value, value2, value3)
|
||||
ret = NzbQueue.do.change_name(value, value2, value3)
|
||||
return report(output, keyword="", data={"status": ret})
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE2)
|
||||
@@ -234,7 +242,7 @@ def _api_queue_change_complete_action(output, value, kwargs):
|
||||
|
||||
def _api_queue_purge(output, value, kwargs):
|
||||
""" API: accepts output """
|
||||
removed = sabnzbd.NzbQueue.remove_all(kwargs.get("search"))
|
||||
removed = NzbQueue.do.remove_all(kwargs.get("search"))
|
||||
return report(output, keyword="", data={"status": bool(removed), "nzo_ids": removed})
|
||||
|
||||
|
||||
@@ -242,7 +250,7 @@ def _api_queue_pause(output, value, kwargs):
|
||||
""" API: accepts output, value(=list of nzo_id) """
|
||||
if value:
|
||||
items = value.split(",")
|
||||
handled = sabnzbd.NzbQueue.pause_multiple_nzo(items)
|
||||
handled = NzbQueue.do.pause_multiple_nzo(items)
|
||||
else:
|
||||
handled = False
|
||||
return report(output, keyword="", data={"status": bool(handled), "nzo_ids": handled})
|
||||
@@ -252,7 +260,7 @@ def _api_queue_resume(output, value, kwargs):
|
||||
""" API: accepts output, value(=list of nzo_id) """
|
||||
if value:
|
||||
items = value.split(",")
|
||||
handled = sabnzbd.NzbQueue.resume_multiple_nzo(items)
|
||||
handled = NzbQueue.do.resume_multiple_nzo(items)
|
||||
else:
|
||||
handled = False
|
||||
return report(output, keyword="", data={"status": bool(handled), "nzo_ids": handled})
|
||||
@@ -267,7 +275,7 @@ def _api_queue_priority(output, value, kwargs):
|
||||
priority = int(value2)
|
||||
except:
|
||||
return report(output, _MSG_INT_VALUE)
|
||||
pos = sabnzbd.NzbQueue.set_priority(value, priority)
|
||||
pos = NzbQueue.do.set_priority(value, priority)
|
||||
# Returns the position in the queue, -1 is incorrect job-id
|
||||
return report(output, keyword="position", data=pos)
|
||||
except:
|
||||
@@ -281,7 +289,7 @@ def _api_queue_sort(output, value, kwargs):
|
||||
sort = kwargs.get("sort")
|
||||
direction = kwargs.get("dir", "")
|
||||
if sort:
|
||||
sabnzbd.NzbQueue.sort_queue(sort, direction)
|
||||
NzbQueue.do.sort_queue(sort, direction)
|
||||
return report(output)
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE2)
|
||||
@@ -299,13 +307,13 @@ def _api_queue_default(output, value, kwargs):
|
||||
|
||||
def _api_queue_rating(output, value, kwargs):
|
||||
""" API: accepts output, value(=nzo_id), type, setting, detail """
|
||||
vote_map = {"up": sabnzbd.Rating.VOTE_UP, "down": sabnzbd.Rating.VOTE_DOWN}
|
||||
vote_map = {"up": Rating.VOTE_UP, "down": Rating.VOTE_DOWN}
|
||||
flag_map = {
|
||||
"spam": sabnzbd.Rating.FLAG_SPAM,
|
||||
"encrypted": sabnzbd.Rating.FLAG_ENCRYPTED,
|
||||
"expired": sabnzbd.Rating.FLAG_EXPIRED,
|
||||
"other": sabnzbd.Rating.FLAG_OTHER,
|
||||
"comment": sabnzbd.Rating.FLAG_COMMENT,
|
||||
"spam": Rating.FLAG_SPAM,
|
||||
"encrypted": Rating.FLAG_ENCRYPTED,
|
||||
"expired": Rating.FLAG_EXPIRED,
|
||||
"other": Rating.FLAG_OTHER,
|
||||
"comment": Rating.FLAG_COMMENT,
|
||||
}
|
||||
content_type = kwargs.get("type")
|
||||
setting = kwargs.get("setting")
|
||||
@@ -321,7 +329,7 @@ def _api_queue_rating(output, value, kwargs):
|
||||
if content_type == "flag":
|
||||
flag = flag_map[setting]
|
||||
if cfg.rating_enable():
|
||||
sabnzbd.Rating.update_user_rating(value, video, audio, vote, flag, kwargs.get("detail"))
|
||||
Rating.do.update_user_rating(value, video, audio, vote, flag, kwargs.get("detail"))
|
||||
return report(output)
|
||||
except:
|
||||
return report(output, _MSG_BAD_SERVER_PARMS)
|
||||
@@ -358,7 +366,6 @@ def _api_addfile(name, output, kwargs):
|
||||
cat=cat,
|
||||
priority=kwargs.get("priority"),
|
||||
nzbname=kwargs.get("nzbname"),
|
||||
password=kwargs.get("password"),
|
||||
)
|
||||
return report(output, keyword="", data={"status": res == 0, "nzo_ids": nzo_ids})
|
||||
else:
|
||||
@@ -384,7 +391,7 @@ def _api_retry(name, output, kwargs):
|
||||
def _api_cancel_pp(name, output, kwargs):
|
||||
""" API: accepts name, output, value(=nzo_id) """
|
||||
nzo_id = kwargs.get("value")
|
||||
if sabnzbd.PostProcessor.cancel_pp(nzo_id):
|
||||
if PostProcessor.do.cancel_pp(nzo_id):
|
||||
return report(output, keyword="", data={"status": True, "nzo_id": nzo_id})
|
||||
else:
|
||||
return report(output, _MSG_NO_ITEM)
|
||||
@@ -403,18 +410,10 @@ def _api_addlocalfile(name, output, kwargs):
|
||||
cat = cat_convert(xcat)
|
||||
priority = kwargs.get("priority")
|
||||
nzbname = kwargs.get("nzbname")
|
||||
password = kwargs.get("password")
|
||||
|
||||
if get_ext(name) in VALID_ARCHIVES + VALID_NZB_FILES:
|
||||
res, nzo_ids = sabnzbd.add_nzbfile(
|
||||
name,
|
||||
pp=pp,
|
||||
script=script,
|
||||
cat=cat,
|
||||
priority=priority,
|
||||
keep=True,
|
||||
nzbname=nzbname,
|
||||
password=password,
|
||||
name, pp=pp, script=script, cat=cat, priority=priority, keep=True, nzbname=nzbname
|
||||
)
|
||||
return report(output, keyword="", data={"status": res == 0, "nzo_ids": nzo_ids})
|
||||
else:
|
||||
@@ -433,7 +432,7 @@ def _api_switch(name, output, kwargs):
|
||||
value = kwargs.get("value")
|
||||
value2 = kwargs.get("value2")
|
||||
if value and value2:
|
||||
pos, prio = sabnzbd.NzbQueue.switch(value, value2)
|
||||
pos, prio = NzbQueue.do.switch(value, value2)
|
||||
# Returns the new position and new priority (if different)
|
||||
return report(output, keyword="result", data={"position": pos, "priority": prio})
|
||||
else:
|
||||
@@ -449,7 +448,7 @@ def _api_change_cat(name, output, kwargs):
|
||||
cat = value2
|
||||
if cat == "None":
|
||||
cat = None
|
||||
result = sabnzbd.NzbQueue.change_cat(nzo_id, cat)
|
||||
result = NzbQueue.do.change_cat(nzo_id, cat)
|
||||
return report(output, keyword="status", data=bool(result > 0))
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE)
|
||||
@@ -464,7 +463,7 @@ def _api_change_script(name, output, kwargs):
|
||||
script = value2
|
||||
if script.lower() == "none":
|
||||
script = None
|
||||
result = sabnzbd.NzbQueue.change_script(nzo_id, script)
|
||||
result = NzbQueue.do.change_script(nzo_id, script)
|
||||
return report(output, keyword="status", data=bool(result > 0))
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE)
|
||||
@@ -476,7 +475,7 @@ def _api_change_opts(name, output, kwargs):
|
||||
value2 = kwargs.get("value2")
|
||||
result = 0
|
||||
if value and value2 and value2.isdigit():
|
||||
result = sabnzbd.NzbQueue.change_opts(value, int(value2))
|
||||
result = NzbQueue.do.change_opts(value, int(value2))
|
||||
return report(output, keyword="status", data=bool(result > 0))
|
||||
|
||||
|
||||
@@ -493,7 +492,7 @@ def _api_history(name, output, kwargs):
|
||||
limit = int_conv(kwargs.get("limit"))
|
||||
last_history_update = int_conv(kwargs.get("last_history_update", 0))
|
||||
search = kwargs.get("search")
|
||||
failed_only = int_conv(kwargs.get("failed_only"))
|
||||
failed_only = kwargs.get("failed_only")
|
||||
categories = kwargs.get("category")
|
||||
|
||||
# Do we need to send anything?
|
||||
@@ -529,7 +528,7 @@ def _api_history(name, output, kwargs):
|
||||
return report(output, _MSG_NO_VALUE)
|
||||
elif not name:
|
||||
history = {}
|
||||
grand, month, week, day = sabnzbd.BPSMeter.get_sums()
|
||||
grand, month, week, day = BPSMeter.do.get_sums()
|
||||
history["total_size"], history["month_size"], history["week_size"], history["day_size"] = (
|
||||
to_units(grand),
|
||||
to_units(month),
|
||||
@@ -537,7 +536,7 @@ def _api_history(name, output, kwargs):
|
||||
to_units(day),
|
||||
)
|
||||
history["slots"], fetched_items, history["noofslots"] = build_history(
|
||||
start=start, limit=limit, search=search, failed_only=failed_only, categories=categories
|
||||
start=start, limit=limit, search=search, failed_only=failed_only, categories=categories, output=output
|
||||
)
|
||||
history["last_history_update"] = sabnzbd.LAST_HISTORY_UPDATE
|
||||
history["version"] = sabnzbd.__version__
|
||||
@@ -562,10 +561,9 @@ def _api_addurl(name, output, kwargs):
|
||||
cat = kwargs.get("cat")
|
||||
priority = kwargs.get("priority")
|
||||
nzbname = kwargs.get("nzbname", "")
|
||||
password = kwargs.get("password", "")
|
||||
|
||||
if name:
|
||||
nzo_id = sabnzbd.add_url(name, pp, script, cat, priority, nzbname, password)
|
||||
nzo_id = sabnzbd.add_url(name, pp, script, cat, priority, nzbname)
|
||||
# Reporting a list of NZO's, for compatibility with other add-methods
|
||||
return report(output, keyword="", data={"status": True, "nzo_ids": [nzo_id]})
|
||||
else:
|
||||
@@ -575,14 +573,14 @@ def _api_addurl(name, output, kwargs):
|
||||
|
||||
def _api_pause(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
sabnzbd.Downloader.pause()
|
||||
scheduler.plan_resume(0)
|
||||
Downloader.do.pause()
|
||||
return report(output)
|
||||
|
||||
|
||||
def _api_resume(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
scheduler.plan_resume(0)
|
||||
sabnzbd.unpause_all()
|
||||
return report(output)
|
||||
|
||||
@@ -649,14 +647,13 @@ def _api_restart_repair(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
logging.info("Queue repair requested by API")
|
||||
sabnzbd.request_repair()
|
||||
# Do the shutdown async to still send goodbye to browser
|
||||
Thread(target=sabnzbd.trigger_restart, kwargs={"timeout": 1}).start()
|
||||
sabnzbd.trigger_restart()
|
||||
return report(output)
|
||||
|
||||
|
||||
def _api_disconnect(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.Downloader.disconnect()
|
||||
Downloader.do.disconnect()
|
||||
return report(output)
|
||||
|
||||
|
||||
@@ -669,7 +666,7 @@ def _api_osx_icon(name, output, kwargs):
|
||||
|
||||
def _api_rescan(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.NzbQueue.scan_jobs(all_jobs=False, action=True)
|
||||
NzbQueue.do.scan_jobs(all_jobs=False, action=True)
|
||||
return report(output)
|
||||
|
||||
|
||||
@@ -688,26 +685,26 @@ def _api_eval_sort(name, output, kwargs):
|
||||
|
||||
def _api_watched_now(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.DirScanner.scan()
|
||||
sabnzbd.dirscanner.dirscan()
|
||||
return report(output)
|
||||
|
||||
|
||||
def _api_resume_pp(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.PostProcessor.paused = False
|
||||
PostProcessor.do.paused = False
|
||||
return report(output)
|
||||
|
||||
|
||||
def _api_pause_pp(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sabnzbd.PostProcessor.paused = True
|
||||
PostProcessor.do.paused = True
|
||||
return report(output)
|
||||
|
||||
|
||||
def _api_rss_now(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
# Run RSS scan async, because it can take a long time
|
||||
sabnzbd.Scheduler.force_rss()
|
||||
scheduler.force_rss()
|
||||
return report(output)
|
||||
|
||||
|
||||
@@ -718,8 +715,7 @@ def _api_retry_all(name, output, kwargs):
|
||||
|
||||
def _api_reset_quota(name, output, kwargs):
|
||||
""" Reset quota left """
|
||||
sabnzbd.BPSMeter.reset_quota(force=True)
|
||||
return report(output)
|
||||
BPSMeter.do.reset_quota(force=True)
|
||||
|
||||
|
||||
def _api_test_email(name, output, kwargs):
|
||||
@@ -824,13 +820,13 @@ def _api_config_speedlimit(output, kwargs):
|
||||
value = kwargs.get("value")
|
||||
if not value:
|
||||
value = "0"
|
||||
sabnzbd.Downloader.limit_speed(value)
|
||||
Downloader.do.limit_speed(value)
|
||||
return report(output)
|
||||
|
||||
|
||||
def _api_config_get_speedlimit(output, kwargs):
|
||||
""" API: accepts output """
|
||||
return report(output, keyword="speedlimit", data=sabnzbd.Downloader.get_limit())
|
||||
return report(output, keyword="speedlimit", data=Downloader.do.get_limit())
|
||||
|
||||
|
||||
def _api_config_set_colorscheme(output, kwargs):
|
||||
@@ -846,7 +842,7 @@ def _api_config_set_colorscheme(output, kwargs):
|
||||
def _api_config_set_pause(output, kwargs):
|
||||
""" API: accepts output, value(=pause interval) """
|
||||
value = kwargs.get("value")
|
||||
sabnzbd.Scheduler.plan_resume(int_conv(value))
|
||||
scheduler.plan_resume(int_conv(value))
|
||||
return report(output)
|
||||
|
||||
|
||||
@@ -895,24 +891,16 @@ def _api_config_undefined(output, kwargs):
|
||||
|
||||
def _api_server_stats(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sum_t, sum_m, sum_w, sum_d = sabnzbd.BPSMeter.get_sums()
|
||||
sum_t, sum_m, sum_w, sum_d = BPSMeter.do.get_sums()
|
||||
stats = {"total": sum_t, "month": sum_m, "week": sum_w, "day": sum_d, "servers": {}}
|
||||
|
||||
for svr in config.get_servers():
|
||||
t, m, w, d, daily = sabnzbd.BPSMeter.amounts(svr)
|
||||
t, m, w, d, daily = BPSMeter.do.amounts(svr)
|
||||
stats["servers"][svr] = {"total": t or 0, "month": m or 0, "week": w or 0, "day": d or 0, "daily": daily or {}}
|
||||
|
||||
return report(output, keyword="", data=stats)
|
||||
|
||||
|
||||
def _api_gc_stats(name, output, kwargs):
|
||||
"""Function only intended for internal testing of the memory handling"""
|
||||
# Collect before we check
|
||||
gc.collect()
|
||||
# We cannot create any lists/dicts, as they would create a reference
|
||||
return report(output, data=[str(obj) for obj in gc.get_objects() if isinstance(obj, sabnzbd.nzbstuff.TryList)])
|
||||
|
||||
|
||||
##############################################################################
|
||||
_api_table = {
|
||||
"server_stats": (_api_server_stats, 2),
|
||||
@@ -949,7 +937,6 @@ _api_table = {
|
||||
"restart_repair": (_api_restart_repair, 2),
|
||||
"disconnect": (_api_disconnect, 2),
|
||||
"osx_icon": (_api_osx_icon, 3),
|
||||
"gc_stats": (_api_gc_stats, 3),
|
||||
"rescan": (_api_rescan, 2),
|
||||
"eval_sort": (_api_eval_sort, 2),
|
||||
"watched_now": (_api_watched_now, 2),
|
||||
@@ -1007,10 +994,10 @@ def api_level(cmd, name):
|
||||
|
||||
|
||||
def report(output, error=None, keyword="value", data=None):
|
||||
"""Report message in json, xml or plain text
|
||||
If error is set, only an status/error report is made.
|
||||
If no error and no data, only a status report is made.
|
||||
Else, a data report is made (optional 'keyword' for outer XML section).
|
||||
""" Report message in json, xml or plain text
|
||||
If error is set, only an status/error report is made.
|
||||
If no error and no data, only a status report is made.
|
||||
Else, a data report is made (optional 'keyword' for outer XML section).
|
||||
"""
|
||||
if output == "json":
|
||||
content = "application/json;charset=UTF-8"
|
||||
@@ -1054,10 +1041,10 @@ def report(output, error=None, keyword="value", data=None):
|
||||
|
||||
|
||||
class xml_factory:
|
||||
"""Recursive xml string maker. Feed it a mixed tuple/dict/item object and will output into an xml string
|
||||
Current limitations:
|
||||
In Two tiered lists hard-coded name of "item": <cat_list><item> </item></cat_list>
|
||||
In Three tiered lists hard-coded name of "slot": <tier1><slot><tier2> </tier2></slot></tier1>
|
||||
""" Recursive xml string maker. Feed it a mixed tuple/dict/item object and will output into an xml string
|
||||
Current limitations:
|
||||
In Two tiered lists hard-coded name of "item": <cat_list><item> </item></cat_list>
|
||||
In Three tiered lists hard-coded name of "slot": <tier1><slot><tier2> </tier2></slot></tier1>
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -1125,7 +1112,7 @@ def handle_server_api(output, kwargs):
|
||||
else:
|
||||
config.ConfigServer(name, kwargs)
|
||||
old_name = None
|
||||
sabnzbd.Downloader.update_server(old_name, name)
|
||||
Downloader.do.update_server(old_name, name)
|
||||
return name
|
||||
|
||||
|
||||
@@ -1186,7 +1173,7 @@ def build_status(skip_dashboard=False, output=None):
|
||||
info["logfile"] = sabnzbd.LOGFILE
|
||||
info["weblogfile"] = sabnzbd.WEBLOGFILE
|
||||
info["loglevel"] = str(cfg.log_level())
|
||||
info["folders"] = sabnzbd.NzbQueue.scan_jobs(all_jobs=False, action=False)
|
||||
info["folders"] = NzbQueue.do.scan_jobs(all_jobs=False, action=False)
|
||||
info["configfn"] = config.get_filename()
|
||||
|
||||
# Dashboard: Speed of System
|
||||
@@ -1217,7 +1204,7 @@ def build_status(skip_dashboard=False, output=None):
|
||||
info["dnslookup"] = None
|
||||
|
||||
info["servers"] = []
|
||||
servers = sorted(sabnzbd.Downloader.servers[:], key=lambda svr: "%02d%s" % (svr.priority, svr.displayname.lower()))
|
||||
servers = sorted(Downloader.do.servers[:], key=lambda svr: "%02d%s" % (svr.priority, svr.displayname.lower()))
|
||||
for server in servers:
|
||||
serverconnections = []
|
||||
connected = 0
|
||||
@@ -1302,6 +1289,13 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
)
|
||||
|
||||
datestart = datetime.datetime.now()
|
||||
priorities = {
|
||||
TOP_PRIORITY: "Force",
|
||||
REPAIR_PRIORITY: "Repair",
|
||||
HIGH_PRIORITY: "High",
|
||||
NORMAL_PRIORITY: "Normal",
|
||||
LOW_PRIORITY: "Low",
|
||||
}
|
||||
limit = int_conv(limit)
|
||||
start = int_conv(start)
|
||||
|
||||
@@ -1332,7 +1326,7 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
slot["index"] = n
|
||||
slot["nzo_id"] = str(nzo_id)
|
||||
slot["unpackopts"] = str(opts_to_pp(pnfo.repair, pnfo.unpack, pnfo.delete))
|
||||
slot["priority"] = INTERFACE_PRIORITIES.get(priority, NORMAL_PRIORITY)
|
||||
slot["priority"] = priorities[priority] if priority >= LOW_PRIORITY else priorities[NORMAL_PRIORITY]
|
||||
slot["script"] = pnfo.script if pnfo.script else "None"
|
||||
slot["filename"] = pnfo.filename
|
||||
slot["labels"] = pnfo.labels
|
||||
@@ -1340,8 +1334,8 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
slot["cat"] = pnfo.category if pnfo.category else "None"
|
||||
slot["mbleft"] = "%.2f" % mbleft
|
||||
slot["mb"] = "%.2f" % mb
|
||||
slot["size"] = to_units(bytes_total, "B")
|
||||
slot["sizeleft"] = to_units(bytesleft, "B")
|
||||
slot["size"] = format_bytes(bytes_total)
|
||||
slot["sizeleft"] = format_bytes(bytesleft)
|
||||
slot["percentage"] = "%s" % (int(((mb - mbleft) / mb) * 100)) if mb != mbleft else "0"
|
||||
slot["mbmissing"] = "%.2f" % (pnfo.bytes_missing / MEBI)
|
||||
slot["direct_unpack"] = pnfo.direct_unpack
|
||||
@@ -1349,7 +1343,7 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
slot["mb_fmt"] = locale.format_string("%d", int(mb), True)
|
||||
slot["mbdone_fmt"] = locale.format_string("%d", int(mb - mbleft), True)
|
||||
|
||||
if not sabnzbd.Downloader.paused and status not in (Status.PAUSED, Status.FETCHING, Status.GRABBING):
|
||||
if not Downloader.do.paused and status not in (Status.PAUSED, Status.FETCHING, Status.GRABBING):
|
||||
if is_propagating:
|
||||
slot["status"] = Status.PROP
|
||||
elif status == Status.CHECKING:
|
||||
@@ -1358,16 +1352,16 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
slot["status"] = Status.DOWNLOADING
|
||||
else:
|
||||
# Ensure compatibility of API status
|
||||
if status == Status.DELETED or priority == FORCE_PRIORITY:
|
||||
if status == Status.DELETED or priority == TOP_PRIORITY:
|
||||
status = Status.DOWNLOADING
|
||||
slot["status"] = "%s" % status
|
||||
|
||||
if (
|
||||
sabnzbd.Downloader.paused
|
||||
or sabnzbd.Downloader.postproc
|
||||
Downloader.do.paused
|
||||
or Downloader.do.postproc
|
||||
or is_propagating
|
||||
or status not in (Status.DOWNLOADING, Status.FETCHING, Status.QUEUED)
|
||||
) and priority != FORCE_PRIORITY:
|
||||
) and priority != TOP_PRIORITY:
|
||||
slot["timeleft"] = "0:00:00"
|
||||
slot["eta"] = "unknown"
|
||||
else:
|
||||
@@ -1387,7 +1381,7 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
else:
|
||||
slot["avg_age"] = calc_age(average_date, bool(trans))
|
||||
|
||||
rating = sabnzbd.Rating.get_rating_by_nzo(nzo_id)
|
||||
rating = Rating.do.get_rating_by_nzo(nzo_id)
|
||||
slot["has_rating"] = rating is not None
|
||||
if rating:
|
||||
slot["rating_avg_video"] = rating.avg_video
|
||||
@@ -1406,17 +1400,18 @@ def build_queue(start=0, limit=0, trans=False, output=None, search=None):
|
||||
|
||||
def fast_queue():
|
||||
""" Return paused, bytes_left, bpsnow, time_left """
|
||||
bytes_left = sabnzbd.sabnzbd.NzbQueue.remaining()
|
||||
paused = sabnzbd.Downloader.paused
|
||||
bpsnow = sabnzbd.BPSMeter.bps
|
||||
bytes_left = NzbQueue.do.remaining()
|
||||
paused = Downloader.do.paused
|
||||
bpsnow = BPSMeter.do.bps
|
||||
time_left = calc_timeleft(bytes_left, bpsnow)
|
||||
return paused, bytes_left, bpsnow, time_left
|
||||
|
||||
|
||||
def build_file_list(nzo_id: str):
|
||||
"""Build file lists for specified job"""
|
||||
def build_file_list(nzo_id):
|
||||
""" Build file lists for specified job
|
||||
"""
|
||||
jobs = []
|
||||
nzo = sabnzbd.sabnzbd.NzbQueue.get_nzo(nzo_id)
|
||||
nzo = NzbQueue.do.get_nzo(nzo_id)
|
||||
if nzo:
|
||||
pnfo = nzo.gather_info(full=True)
|
||||
|
||||
@@ -1493,7 +1488,7 @@ def retry_job(job, new_nzb=None, password=None):
|
||||
nzo_id = sabnzbd.add_url(url, pp, script, cat)
|
||||
else:
|
||||
path = history_db.get_path(job)
|
||||
nzo_id = sabnzbd.NzbQueue.repair_job(path, new_nzb, password)
|
||||
nzo_id = NzbQueue.do.repair_job(path, new_nzb, password)
|
||||
if nzo_id:
|
||||
# Only remove from history if we repaired something
|
||||
history_db.remove_history(job)
|
||||
@@ -1522,9 +1517,9 @@ def del_job_files(job_paths):
|
||||
def del_hist_job(job, del_files):
|
||||
""" Remove history element """
|
||||
if job:
|
||||
path = sabnzbd.PostProcessor.get_path(job)
|
||||
path = PostProcessor.do.get_path(job)
|
||||
if path:
|
||||
sabnzbd.PostProcessor.delete(job, del_files=del_files)
|
||||
PostProcessor.do.delete(job, del_files=del_files)
|
||||
else:
|
||||
history_db = sabnzbd.get_db_connection()
|
||||
remove_all(history_db.get_path(job), recursive=True)
|
||||
@@ -1545,9 +1540,9 @@ _SKIN_CACHE = {} # Stores pre-translated acronyms
|
||||
|
||||
|
||||
def Ttemplate(txt):
|
||||
"""Translation function for Skin texts
|
||||
This special is to be used in interface.py for template processing
|
||||
to be passed for the $T function: so { ..., 'T' : Ttemplate, ...}
|
||||
""" Translation function for Skin texts
|
||||
This special is to be used in interface.py for template processing
|
||||
to be passed for the $T function: so { ..., 'T' : Ttemplate, ...}
|
||||
"""
|
||||
global _SKIN_CACHE
|
||||
if txt in _SKIN_CACHE:
|
||||
@@ -1574,10 +1569,10 @@ def build_header(webdir="", output=None, trans_functions=True):
|
||||
except:
|
||||
uptime = "-"
|
||||
|
||||
speed_limit = sabnzbd.Downloader.get_limit()
|
||||
speed_limit = Downloader.do.get_limit()
|
||||
if speed_limit <= 0:
|
||||
speed_limit = 100
|
||||
speed_limit_abs = sabnzbd.Downloader.get_limit_abs()
|
||||
speed_limit_abs = Downloader.do.get_limit_abs()
|
||||
if speed_limit_abs <= 0:
|
||||
speed_limit_abs = ""
|
||||
|
||||
@@ -1609,14 +1604,14 @@ def build_header(webdir="", output=None, trans_functions=True):
|
||||
header["darwin"] = sabnzbd.DARWIN
|
||||
|
||||
header["power_options"] = sabnzbd.WIN32 or sabnzbd.DARWIN or sabnzbd.LINUX_POWER
|
||||
header["pp_pause_event"] = sabnzbd.Scheduler.pp_pause_event
|
||||
header["pp_pause_event"] = sabnzbd.scheduler.pp_pause_event()
|
||||
|
||||
header["apikey"] = cfg.api_key()
|
||||
header["new_release"], header["new_rel_url"] = sabnzbd.NEW_VERSION
|
||||
|
||||
header["version"] = sabnzbd.__version__
|
||||
header["paused"] = bool(sabnzbd.Downloader.paused or sabnzbd.Downloader.postproc)
|
||||
header["pause_int"] = sabnzbd.Scheduler.pause_int()
|
||||
header["paused"] = bool(Downloader.do.paused or Downloader.do.postproc)
|
||||
header["pause_int"] = scheduler.pause_int()
|
||||
header["paused_all"] = sabnzbd.PAUSED_ALL
|
||||
|
||||
header["diskspace1"] = "%.2f" % diskspace_info["download_dir"][1]
|
||||
@@ -1632,13 +1627,13 @@ def build_header(webdir="", output=None, trans_functions=True):
|
||||
header["have_warnings"] = str(sabnzbd.GUIHANDLER.count())
|
||||
header["finishaction"] = sabnzbd.QUEUECOMPLETE
|
||||
|
||||
header["quota"] = to_units(sabnzbd.BPSMeter.quota)
|
||||
header["have_quota"] = bool(sabnzbd.BPSMeter.quota > 0.0)
|
||||
header["left_quota"] = to_units(sabnzbd.BPSMeter.left)
|
||||
header["quota"] = to_units(BPSMeter.do.quota)
|
||||
header["have_quota"] = bool(BPSMeter.do.quota > 0.0)
|
||||
header["left_quota"] = to_units(BPSMeter.do.left)
|
||||
|
||||
anfo = sabnzbd.ArticleCache.cache_info()
|
||||
anfo = ArticleCache.do.cache_info()
|
||||
header["cache_art"] = str(anfo.article_sum)
|
||||
header["cache_size"] = to_units(anfo.cache_size, "B")
|
||||
header["cache_size"] = format_bytes(anfo.cache_size)
|
||||
header["cache_max"] = str(anfo.cache_limit)
|
||||
|
||||
return header
|
||||
@@ -1649,8 +1644,8 @@ def build_queue_header(search=None, start=0, limit=0, output=None):
|
||||
|
||||
header = build_header(output=output)
|
||||
|
||||
bytespersec = sabnzbd.BPSMeter.bps
|
||||
qnfo = sabnzbd.NzbQueue.queue_info(search=search, start=start, limit=limit)
|
||||
bytespersec = BPSMeter.do.bps
|
||||
qnfo = NzbQueue.do.queue_info(search=search, start=start, limit=limit)
|
||||
|
||||
bytesleft = qnfo.bytes_left
|
||||
bytes_total = qnfo.bytes
|
||||
@@ -1659,11 +1654,11 @@ def build_queue_header(search=None, start=0, limit=0, output=None):
|
||||
header["speed"] = to_units(bytespersec)
|
||||
header["mbleft"] = "%.2f" % (bytesleft / MEBI)
|
||||
header["mb"] = "%.2f" % (bytes_total / MEBI)
|
||||
header["sizeleft"] = to_units(bytesleft, "B")
|
||||
header["size"] = to_units(bytes_total, "B")
|
||||
header["sizeleft"] = format_bytes(bytesleft)
|
||||
header["size"] = format_bytes(bytes_total)
|
||||
header["noofslots_total"] = qnfo.q_fullsize
|
||||
|
||||
if sabnzbd.Downloader.paused or sabnzbd.Downloader.postproc:
|
||||
if Downloader.do.paused or Downloader.do.postproc:
|
||||
status = Status.PAUSED
|
||||
elif bytespersec > 0:
|
||||
status = Status.DOWNLOADING
|
||||
@@ -1682,48 +1677,50 @@ def build_queue_header(search=None, start=0, limit=0, output=None):
|
||||
return header, qnfo.list, bytespersec, qnfo.q_fullsize, qnfo.bytes_left_previous_page
|
||||
|
||||
|
||||
def build_history(start=0, limit=0, search=None, failed_only=0, categories=None):
|
||||
"""Combine the jobs still in post-processing and the database history"""
|
||||
def build_history(start=None, limit=None, search=None, failed_only=0, categories=None, output=None):
|
||||
limit = int_conv(limit)
|
||||
if not limit:
|
||||
limit = 1000000
|
||||
start = int_conv(start)
|
||||
failed_only = int_conv(failed_only)
|
||||
|
||||
def matches_search(text, search_text):
|
||||
# Replace * with .* and ' ' with .
|
||||
search_text = search_text.strip().replace("*", ".*").replace(" ", ".*") + ".*?"
|
||||
try:
|
||||
re_search = re.compile(search_text, re.I)
|
||||
except:
|
||||
logging.error(T("Failed to compile regex for search term: %s"), search_text)
|
||||
return False
|
||||
return re_search.search(text)
|
||||
|
||||
# Grab any items that are active or queued in postproc
|
||||
postproc_queue = sabnzbd.PostProcessor.get_queue()
|
||||
queue = PostProcessor.do.get_queue()
|
||||
|
||||
# Filter out any items that don't match the search term or category
|
||||
if postproc_queue:
|
||||
# It would be more efficient to iterate only once, but we accept the penalty for code clarity
|
||||
if isinstance(search, list):
|
||||
postproc_queue = [nzo for nzo in postproc_queue if nzo.cat in categories]
|
||||
|
||||
if isinstance(search, str):
|
||||
# Replace * with .* and ' ' with .
|
||||
search_text = search.strip().replace("*", ".*").replace(" ", ".*") + ".*?"
|
||||
try:
|
||||
re_search = re.compile(search_text, re.I)
|
||||
postproc_queue = [nzo for nzo in postproc_queue if re_search.search(nzo.final_name)]
|
||||
except:
|
||||
logging.error(T("Failed to compile regex for search term: %s"), search_text)
|
||||
# Filter out any items that don't match the search
|
||||
if search:
|
||||
queue = [nzo for nzo in queue if matches_search(nzo.final_name, search)]
|
||||
|
||||
# Multi-page support for postproc items
|
||||
postproc_queue_size = len(postproc_queue)
|
||||
if start > postproc_queue_size:
|
||||
full_queue_size = len(queue)
|
||||
if start > full_queue_size:
|
||||
# On a page where we shouldn't show postproc items
|
||||
postproc_queue = []
|
||||
database_history_limit = limit
|
||||
queue = []
|
||||
h_limit = limit
|
||||
else:
|
||||
try:
|
||||
if limit:
|
||||
postproc_queue = postproc_queue[start : start + limit]
|
||||
queue = queue[start : start + limit]
|
||||
else:
|
||||
postproc_queue = postproc_queue[start:]
|
||||
queue = queue[start:]
|
||||
except:
|
||||
pass
|
||||
# Remove the amount of postproc items from the db request for history items
|
||||
database_history_limit = max(limit - len(postproc_queue), 0)
|
||||
database_history_start = max(start - postproc_queue_size, 0)
|
||||
h_limit = max(limit - len(queue), 0)
|
||||
|
||||
# Acquire the db instance
|
||||
h_start = max(start - full_queue_size, 0)
|
||||
|
||||
# Aquire the db instance
|
||||
try:
|
||||
history_db = sabnzbd.get_db_connection()
|
||||
close_db = False
|
||||
@@ -1733,53 +1730,50 @@ def build_history(start=0, limit=0, search=None, failed_only=0, categories=None)
|
||||
close_db = True
|
||||
|
||||
# Fetch history items
|
||||
if not database_history_limit:
|
||||
items, fetched_items, total_items = history_db.fetch_history(
|
||||
database_history_start, 1, search, failed_only, categories
|
||||
)
|
||||
if not h_limit:
|
||||
items, fetched_items, total_items = history_db.fetch_history(h_start, 1, search, failed_only, categories)
|
||||
items = []
|
||||
else:
|
||||
items, fetched_items, total_items = history_db.fetch_history(
|
||||
database_history_start, database_history_limit, search, failed_only, categories
|
||||
)
|
||||
items, fetched_items, total_items = history_db.fetch_history(h_start, h_limit, search, failed_only, categories)
|
||||
|
||||
# Reverse the queue to add items to the top (faster than insert)
|
||||
items.reverse()
|
||||
|
||||
# Add the postproc items to the top of the history
|
||||
items = get_active_history(postproc_queue, items)
|
||||
items = get_active_history(queue, items)
|
||||
|
||||
# Un-reverse the queue
|
||||
# Unreverse the queue
|
||||
items.reverse()
|
||||
|
||||
# Global check if rating is enabled
|
||||
rating_enabled = cfg.rating_enable()
|
||||
|
||||
for item in items:
|
||||
item["size"] = to_units(item["bytes"], "B")
|
||||
item["size"] = format_bytes(item["bytes"])
|
||||
|
||||
if "loaded" not in item:
|
||||
item["loaded"] = False
|
||||
|
||||
path = item.get("path", "")
|
||||
|
||||
item["retry"] = int_conv(item.get("status") == Status.FAILED and path and os.path.exists(path))
|
||||
# Retry of failed URL-fetch
|
||||
if item["report"] == "future":
|
||||
item["retry"] = True
|
||||
|
||||
if rating_enabled:
|
||||
rating = sabnzbd.Rating.get_rating_by_nzo(item["nzo_id"])
|
||||
item["has_rating"] = rating is not None
|
||||
if rating:
|
||||
item["rating_avg_video"] = rating.avg_video
|
||||
item["rating_avg_audio"] = rating.avg_audio
|
||||
item["rating_avg_vote_up"] = rating.avg_vote_up
|
||||
item["rating_avg_vote_down"] = rating.avg_vote_down
|
||||
item["rating_user_video"] = rating.user_video
|
||||
item["rating_user_audio"] = rating.user_audio
|
||||
item["rating_user_vote"] = rating.user_vote
|
||||
if Rating.do:
|
||||
rating = Rating.do.get_rating_by_nzo(item["nzo_id"])
|
||||
else:
|
||||
rating = None
|
||||
|
||||
total_items += postproc_queue_size
|
||||
item["has_rating"] = rating is not None
|
||||
if rating:
|
||||
item["rating_avg_video"] = rating.avg_video
|
||||
item["rating_avg_audio"] = rating.avg_audio
|
||||
item["rating_avg_vote_up"] = rating.avg_vote_up
|
||||
item["rating_avg_vote_down"] = rating.avg_vote_down
|
||||
item["rating_user_video"] = rating.user_video
|
||||
item["rating_user_audio"] = rating.user_audio
|
||||
item["rating_user_vote"] = rating.user_vote
|
||||
|
||||
total_items += full_queue_size
|
||||
fetched_items = len(items)
|
||||
|
||||
if close_db:
|
||||
@@ -1788,9 +1782,15 @@ def build_history(start=0, limit=0, search=None, failed_only=0, categories=None)
|
||||
return items, fetched_items, total_items
|
||||
|
||||
|
||||
def get_active_history(queue, items):
|
||||
def get_active_history(queue=None, items=None):
|
||||
""" Get the currently in progress and active history queue. """
|
||||
if items is None:
|
||||
items = []
|
||||
if queue is None:
|
||||
queue = PostProcessor.do.get_queue()
|
||||
|
||||
for nzo in queue:
|
||||
history = build_history_info(nzo)
|
||||
item = {}
|
||||
(
|
||||
item["completed"],
|
||||
@@ -1811,19 +1811,20 @@ def get_active_history(queue, items):
|
||||
item["postproc_time"],
|
||||
item["stage_log"],
|
||||
item["downloaded"],
|
||||
item["completeness"],
|
||||
item["fail_message"],
|
||||
item["url_info"],
|
||||
item["bytes"],
|
||||
_,
|
||||
_,
|
||||
item["password"],
|
||||
) = build_history_info(nzo)
|
||||
) = history
|
||||
item["action_line"] = nzo.action_line
|
||||
item = unpack_history_info(item)
|
||||
|
||||
item["loaded"] = nzo.pp_active
|
||||
if item["bytes"]:
|
||||
item["size"] = to_units(item["bytes"], "B")
|
||||
item["size"] = format_bytes(item["bytes"])
|
||||
else:
|
||||
item["size"] = ""
|
||||
items.append(item)
|
||||
@@ -1831,6 +1832,14 @@ def get_active_history(queue, items):
|
||||
return items
|
||||
|
||||
|
||||
def format_bytes(bytes_string):
|
||||
b = to_units(bytes_string)
|
||||
if b == "":
|
||||
return b
|
||||
else:
|
||||
return b + "B"
|
||||
|
||||
|
||||
def calc_timeleft(bytesleft, bps):
|
||||
""" Calculate the time left in the format HH:MM:SS """
|
||||
try:
|
||||
@@ -1854,7 +1863,7 @@ def calc_timeleft(bytesleft, bps):
|
||||
return "0:00:00"
|
||||
|
||||
|
||||
def list_scripts(default: bool = False, none: bool = True) -> List[str]:
|
||||
def list_scripts(default=False, none=True):
|
||||
""" Return a list of script names, optionally with 'Default' added """
|
||||
lst = []
|
||||
path = cfg.script_dir.get_path()
|
||||
@@ -1871,8 +1880,6 @@ def list_scripts(default: bool = False, none: bool = True) -> List[str]:
|
||||
or (not sabnzbd.WIN32 and userxbit(script) and not os.path.basename(script).startswith("."))
|
||||
):
|
||||
lst.append(os.path.basename(script))
|
||||
# Make sure capitalization is ignored to avoid strange results
|
||||
lst = sorted(lst, key=str.casefold)
|
||||
if none:
|
||||
lst.insert(0, "None")
|
||||
if default:
|
||||
@@ -1881,8 +1888,8 @@ def list_scripts(default: bool = False, none: bool = True) -> List[str]:
|
||||
|
||||
|
||||
def list_cats(default=True):
|
||||
"""Return list of (ordered) categories,
|
||||
when default==False use '*' for Default category
|
||||
""" Return list of (ordered) categories,
|
||||
when default==False use '*' for Default category
|
||||
"""
|
||||
lst = [cat["name"] for cat in config.get_ordered_categories()]
|
||||
if default:
|
||||
@@ -1921,7 +1928,7 @@ def del_from_section(kwargs):
|
||||
del item
|
||||
config.save_config()
|
||||
if section == "servers":
|
||||
sabnzbd.Downloader.update_server(keyword, None)
|
||||
Downloader.do.update_server(keyword, None)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -1930,13 +1937,15 @@ def del_from_section(kwargs):
|
||||
def history_remove_failed():
|
||||
""" Remove all failed jobs from history, including files """
|
||||
logging.info("Scheduled removal of all failed jobs")
|
||||
with HistoryDB() as history_db:
|
||||
del_job_files(history_db.get_failed_paths())
|
||||
history_db.remove_failed()
|
||||
history_db = HistoryDB()
|
||||
del_job_files(history_db.get_failed_paths())
|
||||
history_db.remove_failed()
|
||||
history_db.close()
|
||||
|
||||
|
||||
def history_remove_completed():
|
||||
""" Remove all completed jobs from history """
|
||||
logging.info("Scheduled removal of all completed jobs")
|
||||
with HistoryDB() as history_db:
|
||||
history_db.remove_completed()
|
||||
history_db = HistoryDB()
|
||||
history_db.remove_completed()
|
||||
history_db.close()
|
||||
|
||||
@@ -22,24 +22,25 @@ sabnzbd.articlecache - Article cache handling
|
||||
import logging
|
||||
import threading
|
||||
import struct
|
||||
from typing import Dict, List
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.constants import GIGI, ANFO, MEBI, LIMIT_DECODE_QUEUE, MIN_DECODE_QUEUE
|
||||
from sabnzbd.nzbstuff import Article
|
||||
|
||||
# Operations on the article table are handled via try/except.
|
||||
# The counters need to be made atomic to ensure consistency.
|
||||
ARTICLE_COUNTER_LOCK = threading.RLock()
|
||||
# Operations on lists and dicts are atomic, but for
|
||||
# the bytes counter we do need a lock
|
||||
ARTICLE_LOCK = threading.Lock()
|
||||
|
||||
|
||||
class ArticleCache:
|
||||
do = None
|
||||
|
||||
def __init__(self):
|
||||
self.__cache_limit_org = 0
|
||||
self.__cache_limit = 0
|
||||
self.__cache_size = 0
|
||||
self.__article_table: Dict[Article, bytes] = {} # Dict of buffered articles
|
||||
self.__article_list = [] # List of buffered articles
|
||||
self.__article_table = {} # Dict of buffered articles
|
||||
|
||||
# Limit for the decoder is based on the total available cache
|
||||
# so it can be larger on memory-rich systems
|
||||
@@ -51,10 +52,12 @@ class ArticleCache:
|
||||
if sabnzbd.DARWIN or sabnzbd.WIN64 or (struct.calcsize("P") * 8) == 64:
|
||||
self.__cache_upper_limit = 4 * GIGI
|
||||
|
||||
def cache_info(self):
|
||||
return ANFO(len(self.__article_table), abs(self.__cache_size), self.__cache_limit_org)
|
||||
ArticleCache.do = self
|
||||
|
||||
def new_limit(self, limit: int):
|
||||
def cache_info(self):
|
||||
return ANFO(len(self.__article_list), abs(self.__cache_size), self.__cache_limit_org)
|
||||
|
||||
def new_limit(self, limit):
|
||||
""" Called when cache limit changes """
|
||||
self.__cache_limit_org = limit
|
||||
if limit < 0:
|
||||
@@ -68,21 +71,21 @@ class ArticleCache:
|
||||
# The cache should also not be too small
|
||||
self.decoder_cache_article_limit = max(decoder_cache_limit, MIN_DECODE_QUEUE)
|
||||
|
||||
@synchronized(ARTICLE_COUNTER_LOCK)
|
||||
def reserve_space(self, data_size: int):
|
||||
@synchronized(ARTICLE_LOCK)
|
||||
def reserve_space(self, data_size):
|
||||
""" Reserve space in the cache """
|
||||
self.__cache_size += data_size
|
||||
|
||||
@synchronized(ARTICLE_COUNTER_LOCK)
|
||||
def free_reserved_space(self, data_size: int):
|
||||
@synchronized(ARTICLE_LOCK)
|
||||
def free_reserved_space(self, data_size):
|
||||
""" Remove previously reserved space """
|
||||
self.__cache_size -= data_size
|
||||
|
||||
def space_left(self) -> bool:
|
||||
def space_left(self):
|
||||
""" Is there space left in the set limit? """
|
||||
return self.__cache_size < self.__cache_limit
|
||||
|
||||
def save_article(self, article: Article, data: bytes):
|
||||
def save_article(self, article, data):
|
||||
""" Save article in cache, either memory or disk """
|
||||
nzo = article.nzf.nzo
|
||||
if nzo.is_gone():
|
||||
@@ -105,6 +108,7 @@ class ArticleCache:
|
||||
self.reserve_space(data_size)
|
||||
if self.space_left():
|
||||
# Add new article to the cache
|
||||
self.__article_list.append(article)
|
||||
self.__article_table[article] = data
|
||||
else:
|
||||
# Return the space and save to disk
|
||||
@@ -114,52 +118,40 @@ class ArticleCache:
|
||||
# No data saved in memory, direct to disk
|
||||
self.__flush_article_to_disk(article, data)
|
||||
|
||||
def load_article(self, article: Article):
|
||||
def load_article(self, article):
|
||||
""" Load the data of the article """
|
||||
data = None
|
||||
nzo = article.nzf.nzo
|
||||
|
||||
if article in self.__article_table:
|
||||
try:
|
||||
data = self.__article_table.pop(article)
|
||||
self.free_reserved_space(len(data))
|
||||
except KeyError:
|
||||
# Could fail due the article already being deleted by purge_articles, for example
|
||||
# when post-processing deletes the job while delayed articles still come in
|
||||
logging.debug("Failed to load %s from cache, probably already deleted", article)
|
||||
return data
|
||||
if article in self.__article_list:
|
||||
data = self.__article_table.pop(article)
|
||||
self.__article_list.remove(article)
|
||||
self.free_reserved_space(len(data))
|
||||
elif article.art_id:
|
||||
data = sabnzbd.load_data(article.art_id, nzo.admin_path, remove=True, do_pickle=False, silent=True)
|
||||
data = sabnzbd.load_data(article.art_id, nzo.workpath, remove=True, do_pickle=False, silent=True)
|
||||
nzo.remove_saved_article(article)
|
||||
|
||||
return data
|
||||
|
||||
@synchronized(ARTICLE_LOCK)
|
||||
def flush_articles(self):
|
||||
logging.debug("Saving %s cached articles to disk", len(self.__article_table))
|
||||
self.__cache_size = 0
|
||||
while self.__article_table:
|
||||
try:
|
||||
article, data = self.__article_table.popitem()
|
||||
self.__flush_article_to_disk(article, data)
|
||||
except KeyError:
|
||||
# Could fail if already deleted by purge_articles or load_data
|
||||
logging.debug("Failed to flush item from cache, probably already deleted or written to disk")
|
||||
while self.__article_list:
|
||||
article = self.__article_list.pop(0)
|
||||
data = self.__article_table.pop(article)
|
||||
self.__flush_article_to_disk(article, data)
|
||||
|
||||
def purge_articles(self, articles: List[Article]):
|
||||
def purge_articles(self, articles):
|
||||
""" Remove all saved articles, from memory and disk """
|
||||
logging.debug("Purging %s articles from the cache/disk", len(articles))
|
||||
for article in articles:
|
||||
if article in self.__article_table:
|
||||
try:
|
||||
data = self.__article_table.pop(article)
|
||||
self.free_reserved_space(len(data))
|
||||
except KeyError:
|
||||
# Could fail if already deleted by flush_articles or load_data
|
||||
logging.debug("Failed to flush %s from cache, probably already deleted or written to disk", article)
|
||||
elif article.art_id:
|
||||
sabnzbd.remove_data(article.art_id, article.nzf.nzo.admin_path)
|
||||
if article in self.__article_list:
|
||||
self.__article_list.remove(article)
|
||||
data = self.__article_table.pop(article)
|
||||
self.free_reserved_space(len(data))
|
||||
if article.art_id:
|
||||
sabnzbd.remove_data(article.art_id, article.nzf.nzo.workpath)
|
||||
|
||||
@staticmethod
|
||||
def __flush_article_to_disk(article: Article, data):
|
||||
def __flush_article_to_disk(self, article, data):
|
||||
nzo = article.nzf.nzo
|
||||
if nzo.is_gone():
|
||||
# Don't store deleted jobs
|
||||
@@ -167,4 +159,8 @@ class ArticleCache:
|
||||
|
||||
# Save data, but don't complain when destination folder is missing
|
||||
# because this flush may come after completion of the NZO.
|
||||
sabnzbd.save_data(data, article.get_art_id(), nzo.admin_path, do_pickle=False, silent=True)
|
||||
sabnzbd.save_data(data, article.get_art_id(), nzo.workpath, do_pickle=False, silent=True)
|
||||
|
||||
|
||||
# Create the instance
|
||||
ArticleCache()
|
||||
|
||||
@@ -26,43 +26,46 @@ import re
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
import hashlib
|
||||
from typing import Tuple, Optional, List
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.misc import get_all_passwords
|
||||
from sabnzbd.filesystem import set_permissions, clip_path, has_win_device, diskspace, get_filename, get_ext
|
||||
from sabnzbd.constants import Status, GIGI, MAX_ASSEMBLER_QUEUE
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.nzbstuff import NzbObject, NzbFile
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
from sabnzbd.postproc import PostProcessor
|
||||
import sabnzbd.downloader
|
||||
import sabnzbd.par2file as par2file
|
||||
import sabnzbd.utils.rarfile as rarfile
|
||||
from sabnzbd.rating import Rating
|
||||
|
||||
|
||||
class Assembler(Thread):
|
||||
do = None # Link to the instance of this method
|
||||
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.queue: queue.Queue[Tuple[Optional[NzbObject], Optional[NzbFile], Optional[bool]]] = queue.Queue()
|
||||
self.queue = queue.Queue()
|
||||
Assembler.do = self
|
||||
|
||||
def stop(self):
|
||||
self.queue.put((None, None, None))
|
||||
self.process(None)
|
||||
|
||||
def process(self, nzo: NzbObject, nzf: Optional[NzbFile] = None, file_done: Optional[bool] = None):
|
||||
self.queue.put((nzo, nzf, file_done))
|
||||
def process(self, job):
|
||||
self.queue.put(job)
|
||||
|
||||
def queue_full(self):
|
||||
return self.queue.qsize() >= MAX_ASSEMBLER_QUEUE
|
||||
|
||||
def run(self):
|
||||
while 1:
|
||||
# Set NzbObject and NzbFile objects to None so references
|
||||
# from this thread do not keep the objects alive (see #1628)
|
||||
nzo = nzf = None
|
||||
nzo, nzf, file_done = self.queue.get()
|
||||
if not nzo:
|
||||
job = self.queue.get()
|
||||
if not job:
|
||||
logging.info("Shutting down")
|
||||
break
|
||||
|
||||
nzo, nzf, file_done = job
|
||||
|
||||
if nzf:
|
||||
# Check if enough disk space is free after each file is done
|
||||
# If not enough space left, pause downloader and send email
|
||||
@@ -71,10 +74,10 @@ class Assembler(Thread):
|
||||
and diskspace(force=True)["download_dir"][1] < (cfg.download_free.get_float() + nzf.bytes) / GIGI
|
||||
):
|
||||
# Only warn and email once
|
||||
if not sabnzbd.Downloader.paused:
|
||||
if not sabnzbd.downloader.Downloader.do.paused:
|
||||
logging.warning(T("Too little diskspace forcing PAUSE"))
|
||||
# Pause downloader, but don't save, since the disk is almost full!
|
||||
sabnzbd.Downloader.pause()
|
||||
sabnzbd.downloader.Downloader.do.pause()
|
||||
sabnzbd.emailer.diskfull_mail()
|
||||
# Abort all direct unpackers, just to be sure
|
||||
sabnzbd.directunpacker.abort_all()
|
||||
@@ -97,7 +100,7 @@ class Assembler(Thread):
|
||||
# Log traceback
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
# Pause without saving
|
||||
sabnzbd.Downloader.pause()
|
||||
sabnzbd.downloader.Downloader.do.pause()
|
||||
continue
|
||||
except:
|
||||
logging.error(T("Fatal error in Assembler"), exc_info=True)
|
||||
@@ -118,30 +121,34 @@ class Assembler(Thread):
|
||||
if rar_encrypted:
|
||||
if cfg.pause_on_pwrar() == 1:
|
||||
logging.warning(
|
||||
T(
|
||||
'Paused job "%s" because of encrypted RAR file (if supplied, all passwords were tried)'
|
||||
remove_warning_label(
|
||||
T(
|
||||
'WARNING: Paused job "%s" because of encrypted RAR file (if supplied, all passwords were tried)'
|
||||
)
|
||||
),
|
||||
nzo.final_name,
|
||||
)
|
||||
nzo.pause()
|
||||
else:
|
||||
logging.warning(
|
||||
T(
|
||||
'Aborted job "%s" because of encrypted RAR file (if supplied, all passwords were tried)'
|
||||
remove_warning_label(
|
||||
T(
|
||||
'WARNING: Aborted job "%s" because of encrypted RAR file (if supplied, all passwords were tried)'
|
||||
)
|
||||
),
|
||||
nzo.final_name,
|
||||
)
|
||||
nzo.fail_msg = T("Aborted, encryption detected")
|
||||
sabnzbd.NzbQueue.end_job(nzo)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
|
||||
|
||||
if unwanted_file:
|
||||
# Don't repeat the warning after a user override of an unwanted extension pause
|
||||
if nzo.unwanted_ext == 0:
|
||||
logging.warning(
|
||||
T('In "%s" unwanted extension in RAR file. Unwanted file is %s '),
|
||||
nzo.final_name,
|
||||
unwanted_file,
|
||||
)
|
||||
logging.warning(
|
||||
remove_warning_label(
|
||||
T('WARNING: In "%s" unwanted extension in RAR file. Unwanted file is %s ')
|
||||
),
|
||||
nzo.final_name,
|
||||
unwanted_file,
|
||||
)
|
||||
logging.debug(T("Unwanted extension is in rar file %s"), filepath)
|
||||
if cfg.action_on_unwanted_extensions() == 1 and nzo.unwanted_ext == 0:
|
||||
logging.debug("Unwanted extension ... pausing")
|
||||
@@ -150,7 +157,7 @@ class Assembler(Thread):
|
||||
if cfg.action_on_unwanted_extensions() == 2:
|
||||
logging.debug("Unwanted extension ... aborting")
|
||||
nzo.fail_msg = T("Aborted, unwanted extension detected")
|
||||
sabnzbd.NzbQueue.end_job(nzo)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
|
||||
|
||||
# Add to direct unpack
|
||||
nzo.add_to_direct_unpacker(nzf)
|
||||
@@ -162,29 +169,28 @@ class Assembler(Thread):
|
||||
filter_output, reason = nzo_filtered_by_rating(nzo)
|
||||
if filter_output == 1:
|
||||
logging.warning(
|
||||
T('Paused job "%s" because of rating (%s)'),
|
||||
remove_warning_label(T('WARNING: Paused job "%s" because of rating (%s)')),
|
||||
nzo.final_name,
|
||||
reason,
|
||||
)
|
||||
nzo.pause()
|
||||
elif filter_output == 2:
|
||||
logging.warning(
|
||||
T('Aborted job "%s" because of rating (%s)'),
|
||||
remove_warning_label(T('WARNING: Aborted job "%s" because of rating (%s)')),
|
||||
nzo.final_name,
|
||||
reason,
|
||||
)
|
||||
nzo.fail_msg = T("Aborted, rating filter matched (%s)") % reason
|
||||
sabnzbd.NzbQueue.end_job(nzo)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
|
||||
|
||||
else:
|
||||
sabnzbd.NzbQueue.remove(nzo.nzo_id, cleanup=False)
|
||||
sabnzbd.PostProcessor.process(nzo)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo.nzo_id, add_to_history=False, cleanup=False)
|
||||
PostProcessor.do.process(nzo)
|
||||
|
||||
@staticmethod
|
||||
def assemble(nzf: NzbFile, file_done: bool):
|
||||
"""Assemble a NZF from its table of articles
|
||||
1) Partial write: write what we have
|
||||
2) Nothing written before: write all
|
||||
def assemble(self, nzf, file_done):
|
||||
""" Assemble a NZF from its table of articles
|
||||
1) Partial write: write what we have
|
||||
2) Nothing written before: write all
|
||||
"""
|
||||
# New hash-object needed?
|
||||
if not nzf.md5:
|
||||
@@ -202,7 +208,7 @@ class Assembler(Thread):
|
||||
|
||||
# Write all decoded articles
|
||||
if article.decoded:
|
||||
data = sabnzbd.ArticleCache.load_article(article)
|
||||
data = ArticleCache.do.load_article(article)
|
||||
# Could be empty in case nzo was deleted
|
||||
if data:
|
||||
fout.write(data)
|
||||
@@ -225,14 +231,14 @@ class Assembler(Thread):
|
||||
nzf.md5sum = nzf.md5.digest()
|
||||
|
||||
|
||||
def file_has_articles(nzf: NzbFile):
|
||||
"""Do a quick check to see if any articles are present for this file.
|
||||
Destructive: only to be used to differentiate between unknown encoding and no articles.
|
||||
def file_has_articles(nzf):
|
||||
""" Do a quick check to see if any articles are present for this file.
|
||||
Destructive: only to be used to differentiate between unknown encoding and no articles.
|
||||
"""
|
||||
has = False
|
||||
for article in nzf.decodetable:
|
||||
sleep(0.01)
|
||||
data = sabnzbd.ArticleCache.load_article(article)
|
||||
data = ArticleCache.do.load_article(article)
|
||||
if data:
|
||||
has = True
|
||||
return has
|
||||
@@ -242,7 +248,7 @@ RE_SUBS = re.compile(r"\W+sub|subs|subpack|subtitle|subtitles(?![a-z])", re.I)
|
||||
SAFE_EXTS = (".mkv", ".mp4", ".avi", ".wmv", ".mpg", ".webm")
|
||||
|
||||
|
||||
def is_cloaked(nzo: NzbObject, path: str, names: List[str]) -> bool:
|
||||
def is_cloaked(nzo, path, names):
|
||||
""" Return True if this is likely to be a cloaked encrypted post """
|
||||
fname = os.path.splitext(get_filename(path.lower()))[0]
|
||||
for name in names:
|
||||
@@ -271,7 +277,7 @@ def is_cloaked(nzo: NzbObject, path: str, names: List[str]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> Tuple[bool, Optional[str]]:
|
||||
def check_encrypted_and_unwanted_files(nzo, filepath):
|
||||
""" Combines check for unwanted and encrypted files to save on CPU and IO """
|
||||
encrypted = False
|
||||
unwanted = None
|
||||
@@ -365,9 +371,9 @@ def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> Tuple[b
|
||||
return encrypted, unwanted
|
||||
|
||||
|
||||
def nzo_filtered_by_rating(nzo: NzbObject) -> Tuple[int, str]:
|
||||
if cfg.rating_enable() and cfg.rating_filter_enable() and (nzo.rating_filtered < 2):
|
||||
rating = sabnzbd.Rating.get_rating_by_nzo(nzo.nzo_id)
|
||||
def nzo_filtered_by_rating(nzo):
|
||||
if Rating.do and cfg.rating_enable() and cfg.rating_filter_enable() and (nzo.rating_filtered < 2):
|
||||
rating = Rating.do.get_rating_by_nzo(nzo.nzo_id)
|
||||
if rating is not None:
|
||||
nzo.rating_filtered = 1
|
||||
reason = rating_filtered(rating, nzo.filename.lower(), True)
|
||||
@@ -411,3 +417,11 @@ def rating_filtered(rating, filename, abort):
|
||||
if any(check_keyword(k) for k in keywords.split(",")):
|
||||
return T("keywords")
|
||||
return None
|
||||
|
||||
|
||||
def remove_warning_label(msg):
|
||||
""" Standardize errors by removing obsolete
|
||||
"WARNING:" part in all languages """
|
||||
if ":" in msg:
|
||||
return msg.split(":")[1].strip()
|
||||
return msg
|
||||
|
||||
@@ -22,7 +22,6 @@ sabnzbd.bpsmeter - bpsmeter
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
from typing import List, Dict
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import BYTES_FILE_NAME, KIBI
|
||||
@@ -88,6 +87,8 @@ def next_month(t):
|
||||
|
||||
|
||||
class BPSMeter:
|
||||
do = None
|
||||
|
||||
def __init__(self):
|
||||
t = time.time()
|
||||
self.start_time = t
|
||||
@@ -95,20 +96,20 @@ class BPSMeter:
|
||||
self.speed_log_time = t
|
||||
self.last_update = t
|
||||
self.bps = 0.0
|
||||
self.bps_list: List[int] = []
|
||||
self.bps_list = []
|
||||
self.bps_list_max = 275
|
||||
|
||||
self.day_total: Dict[str, int] = {}
|
||||
self.week_total: Dict[str, int] = {}
|
||||
self.month_total: Dict[str, int] = {}
|
||||
self.grand_total: Dict[str, int] = {}
|
||||
self.day_total = {}
|
||||
self.week_total = {}
|
||||
self.month_total = {}
|
||||
self.grand_total = {}
|
||||
|
||||
self.timeline_total: Dict[str, Dict[str, int]] = {}
|
||||
self.timeline_total = {}
|
||||
|
||||
self.day_label: str = time.strftime("%Y-%m-%d")
|
||||
self.end_of_day: float = tomorrow(t) # Time that current day will end
|
||||
self.end_of_week: float = next_week(t) # Time that current day will end
|
||||
self.end_of_month: float = next_month(t) # Time that current month will end
|
||||
self.day_label = time.strftime("%Y-%m-%d")
|
||||
self.end_of_day = tomorrow(t) # Time that current day will end
|
||||
self.end_of_week = next_week(t) # Time that current day will end
|
||||
self.end_of_month = next_month(t) # Time that current month will end
|
||||
self.q_day = 1 # Day of quota reset
|
||||
self.q_period = "m" # Daily/Weekly/Monthly quota = d/w/m
|
||||
self.quota = self.left = 0.0 # Quota and remaining quota
|
||||
@@ -117,32 +118,32 @@ class BPSMeter:
|
||||
self.q_hour = 0 # Quota reset hour
|
||||
self.q_minute = 0 # Quota reset minute
|
||||
self.quota_enabled = True # Scheduled quota enable/disable
|
||||
BPSMeter.do = self
|
||||
|
||||
def save(self):
|
||||
""" Save admin to disk """
|
||||
sabnzbd.save_admin(
|
||||
(
|
||||
self.last_update,
|
||||
self.grand_total,
|
||||
self.day_total,
|
||||
self.week_total,
|
||||
self.month_total,
|
||||
self.end_of_day,
|
||||
self.end_of_week,
|
||||
self.end_of_month,
|
||||
self.quota,
|
||||
self.left,
|
||||
self.q_time,
|
||||
self.timeline_total,
|
||||
),
|
||||
BYTES_FILE_NAME,
|
||||
data = (
|
||||
self.last_update,
|
||||
self.grand_total,
|
||||
self.day_total,
|
||||
self.week_total,
|
||||
self.month_total,
|
||||
self.end_of_day,
|
||||
self.end_of_week,
|
||||
self.end_of_month,
|
||||
self.quota,
|
||||
self.left,
|
||||
self.q_time,
|
||||
self.timeline_total,
|
||||
)
|
||||
sabnzbd.save_admin(data, BYTES_FILE_NAME)
|
||||
|
||||
def defaults(self):
|
||||
""" Get the latest data from the database and assign to a fake server """
|
||||
logging.debug("Setting default BPS meter values")
|
||||
with sabnzbd.database.HistoryDB() as history_db:
|
||||
grand, month, week = history_db.get_history_size()
|
||||
history_db = sabnzbd.database.HistoryDB()
|
||||
grand, month, week = history_db.get_history_size()
|
||||
history_db.close()
|
||||
self.grand_total = {}
|
||||
self.month_total = {}
|
||||
self.week_total = {}
|
||||
@@ -190,9 +191,12 @@ class BPSMeter:
|
||||
self.update()
|
||||
return res
|
||||
|
||||
def update(self, server=None, amount=0):
|
||||
def update(self, server=None, amount=0, testtime=None):
|
||||
""" Update counters for "server" with "amount" bytes """
|
||||
t = time.time()
|
||||
if testtime:
|
||||
t = testtime
|
||||
else:
|
||||
t = time.time()
|
||||
if t > self.end_of_day:
|
||||
# current day passed. get new end of day
|
||||
self.day_label = time.strftime("%Y-%m-%d")
|
||||
@@ -234,8 +238,8 @@ class BPSMeter:
|
||||
if self.have_quota and self.quota_enabled:
|
||||
self.left -= amount
|
||||
if self.left <= 0.0:
|
||||
if not sabnzbd.Downloader.paused:
|
||||
sabnzbd.Downloader.pause()
|
||||
if sabnzbd.downloader.Downloader.do and not sabnzbd.downloader.Downloader.do.paused:
|
||||
sabnzbd.downloader.Downloader.do.pause()
|
||||
logging.warning(T("Quota spent, pausing downloading"))
|
||||
|
||||
# Speedometer
|
||||
@@ -320,9 +324,9 @@ class BPSMeter:
|
||||
return self.bps_list[::refresh_rate]
|
||||
|
||||
def get_stable_speed(self, timespan=10):
|
||||
"""See if there is a stable speed the last <timespan> seconds
|
||||
None: indicates it can't determine yet
|
||||
False: the speed was not stable during <timespan>
|
||||
""" See if there is a stable speed the last <timespan> seconds
|
||||
None: indicates it can't determine yet
|
||||
False: the speed was not stable during <timespan>
|
||||
"""
|
||||
if len(self.bps_list) < timespan:
|
||||
return None
|
||||
@@ -346,15 +350,16 @@ class BPSMeter:
|
||||
return None
|
||||
|
||||
def reset_quota(self, force=False):
|
||||
"""Check if it's time to reset the quota, optionally resuming
|
||||
Return True, when still paused or should be paused
|
||||
""" Check if it's time to reset the quota, optionally resuming
|
||||
Return True, when still paused
|
||||
"""
|
||||
if force or (self.have_quota and time.time() > (self.q_time - 50)):
|
||||
self.quota = self.left = cfg.quota_size.get_float()
|
||||
logging.info("Quota was reset to %s", self.quota)
|
||||
if cfg.quota_resume():
|
||||
logging.info("Auto-resume due to quota reset")
|
||||
sabnzbd.Downloader.resume()
|
||||
if sabnzbd.downloader.Downloader.do:
|
||||
sabnzbd.downloader.Downloader.do.resume()
|
||||
self.next_reset()
|
||||
return False
|
||||
else:
|
||||
@@ -459,11 +464,10 @@ class BPSMeter:
|
||||
if action and not status:
|
||||
self.resume()
|
||||
|
||||
@staticmethod
|
||||
def resume():
|
||||
def resume(self):
|
||||
""" Resume downloading """
|
||||
if cfg.quota_resume() and sabnzbd.Downloader.paused:
|
||||
sabnzbd.Downloader.resume()
|
||||
if cfg.quota_resume() and sabnzbd.downloader.Downloader.do and sabnzbd.downloader.Downloader.do.paused:
|
||||
sabnzbd.downloader.Downloader.do.resume()
|
||||
|
||||
def midnight(self):
|
||||
""" Midnight action: dummy update for all servers """
|
||||
@@ -474,4 +478,12 @@ class BPSMeter:
|
||||
def quota_handler():
|
||||
""" To be called from scheduler """
|
||||
logging.debug("Checking quota")
|
||||
sabnzbd.BPSMeter.reset_quota()
|
||||
BPSMeter.do.reset_quota()
|
||||
|
||||
|
||||
def midnight_action():
|
||||
if BPSMeter.do:
|
||||
BPSMeter.do.midnight()
|
||||
|
||||
|
||||
BPSMeter()
|
||||
|
||||
@@ -34,7 +34,6 @@ from sabnzbd.config import (
|
||||
create_api_key,
|
||||
validate_notempty,
|
||||
clean_nice_ionice_parameters,
|
||||
validate_strip_right_slash,
|
||||
)
|
||||
from sabnzbd.constants import (
|
||||
DEF_HOST,
|
||||
@@ -45,7 +44,6 @@ from sabnzbd.constants import (
|
||||
DEF_NZBBACK_DIR,
|
||||
DEF_SCANRATE,
|
||||
DEF_COMPLETE_DIR,
|
||||
DEF_FOLDER_MAX,
|
||||
)
|
||||
|
||||
##############################################################################
|
||||
@@ -85,11 +83,11 @@ queue_complete_pers = OptionBool("misc", "queue_complete_pers", False)
|
||||
bandwidth_perc = OptionNumber("misc", "bandwidth_perc", 0, 0, 100)
|
||||
refresh_rate = OptionNumber("misc", "refresh_rate", 0)
|
||||
log_level = OptionNumber("logging", "log_level", 1, -1, 2)
|
||||
log_size = OptionNumber("logging", "max_log_size", 5242880)
|
||||
log_size = OptionStr("logging", "max_log_size", "5242880")
|
||||
log_backups = OptionNumber("logging", "log_backups", 5, 1, 1024)
|
||||
queue_limit = OptionNumber("misc", "queue_limit", 20, 0)
|
||||
|
||||
configlock = OptionBool("misc", "config_lock", False)
|
||||
configlock = OptionBool("misc", "config_lock", 0)
|
||||
|
||||
|
||||
##############################################################################
|
||||
@@ -115,7 +113,7 @@ password = OptionPassword("misc", "password")
|
||||
bandwidth_max = OptionStr("misc", "bandwidth_max")
|
||||
cache_limit = OptionStr("misc", "cache_limit")
|
||||
web_dir = OptionStr("misc", "web_dir", DEF_STDINTF)
|
||||
web_color = OptionStr("misc", "web_color")
|
||||
web_color = OptionStr("misc", "web_color", "")
|
||||
https_cert = OptionDir("misc", "https_cert", "server.cert", create=False)
|
||||
https_key = OptionDir("misc", "https_key", "server.key", create=False)
|
||||
https_chain = OptionDir("misc", "https_chain", create=False)
|
||||
@@ -130,7 +128,7 @@ nzb_key = OptionStr("misc", "nzb_key", create_api_key())
|
||||
##############################################################################
|
||||
# Config - Folders
|
||||
##############################################################################
|
||||
umask = OptionStr("misc", "permissions", validation=validate_octal)
|
||||
umask = OptionStr("misc", "permissions", "", validation=validate_octal)
|
||||
download_dir = OptionDir("misc", "download_dir", DEF_DOWNLOAD_DIR, create=False, validation=validate_safedir)
|
||||
download_free = OptionStr("misc", "download_free")
|
||||
complete_dir = OptionDir(
|
||||
@@ -154,13 +152,14 @@ top_only = OptionBool("misc", "top_only", False)
|
||||
sfv_check = OptionBool("misc", "sfv_check", True)
|
||||
quick_check_ext_ignore = OptionList("misc", "quick_check_ext_ignore", ["nfo", "sfv", "srr"])
|
||||
script_can_fail = OptionBool("misc", "script_can_fail", False)
|
||||
ssl_ciphers = OptionStr("misc", "ssl_ciphers", "") # Now per-server setting
|
||||
enable_recursive = OptionBool("misc", "enable_recursive", True)
|
||||
flat_unpack = OptionBool("misc", "flat_unpack", False)
|
||||
par_option = OptionStr("misc", "par_option")
|
||||
par_option = OptionStr("misc", "par_option", "")
|
||||
pre_check = OptionBool("misc", "pre_check", False)
|
||||
nice = OptionStr("misc", "nice", validation=clean_nice_ionice_parameters)
|
||||
nice = OptionStr("misc", "nice", "", validation=clean_nice_ionice_parameters)
|
||||
win_process_prio = OptionNumber("misc", "win_process_prio", 3)
|
||||
ionice = OptionStr("misc", "ionice", validation=clean_nice_ionice_parameters)
|
||||
ionice = OptionStr("misc", "ionice", "", validation=clean_nice_ionice_parameters)
|
||||
fail_hopeless_jobs = OptionBool("misc", "fail_hopeless_jobs", True)
|
||||
fast_fail = OptionBool("misc", "fast_fail", True)
|
||||
autodisconnect = OptionBool("misc", "auto_disconnect", True)
|
||||
@@ -169,7 +168,6 @@ no_series_dupes = OptionNumber("misc", "no_series_dupes", 0)
|
||||
series_propercheck = OptionBool("misc", "series_propercheck", True)
|
||||
pause_on_pwrar = OptionNumber("misc", "pause_on_pwrar", 1)
|
||||
ignore_samples = OptionBool("misc", "ignore_samples", False)
|
||||
deobfuscate_final_filenames = OptionBool("misc", "deobfuscate_final_filenames", False)
|
||||
auto_sort = OptionStr("misc", "auto_sort")
|
||||
direct_unpack = OptionBool("misc", "direct_unpack", False)
|
||||
direct_unpack_threads = OptionNumber("misc", "direct_unpack_threads", 3, 1)
|
||||
@@ -256,16 +254,17 @@ enable_filejoin = OptionBool("misc", "enable_filejoin", True)
|
||||
enable_tsjoin = OptionBool("misc", "enable_tsjoin", True)
|
||||
overwrite_files = OptionBool("misc", "overwrite_files", False)
|
||||
ignore_unrar_dates = OptionBool("misc", "ignore_unrar_dates", False)
|
||||
ignore_wrong_unrar = OptionBool("misc", "ignore_wrong_unrar", False)
|
||||
backup_for_duplicates = OptionBool("misc", "backup_for_duplicates", True)
|
||||
empty_postproc = OptionBool("misc", "empty_postproc", False)
|
||||
wait_for_dfolder = OptionBool("misc", "wait_for_dfolder", False)
|
||||
warn_empty_nzb = OptionBool("misc", "warn_empty_nzb", True)
|
||||
rss_filenames = OptionBool("misc", "rss_filenames", False)
|
||||
api_logging = OptionBool("misc", "api_logging", True)
|
||||
html_login = OptionBool("misc", "html_login", True)
|
||||
osx_menu = OptionBool("misc", "osx_menu", True)
|
||||
osx_speed = OptionBool("misc", "osx_speed", True)
|
||||
warn_dupl_jobs = OptionBool("misc", "warn_dupl_jobs", True)
|
||||
helpfull_warnings = OptionBool("misc", "helpfull_warnings", True)
|
||||
keep_awake = OptionBool("misc", "keep_awake", True)
|
||||
win_menu = OptionBool("misc", "win_menu", True)
|
||||
allow_incomplete_nzb = OptionBool("misc", "allow_incomplete_nzb", False)
|
||||
@@ -289,10 +288,9 @@ size_limit = OptionStr("misc", "size_limit", "0")
|
||||
show_sysload = OptionNumber("misc", "show_sysload", 2, 0, 2)
|
||||
history_limit = OptionNumber("misc", "history_limit", 10, 0)
|
||||
wait_ext_drive = OptionNumber("misc", "wait_ext_drive", 5, 1, 60)
|
||||
max_foldername_length = OptionNumber("misc", "max_foldername_length", DEF_FOLDER_MAX, 20, 65000)
|
||||
marker_file = OptionStr("misc", "nomedia_marker")
|
||||
marker_file = OptionStr("misc", "nomedia_marker", "")
|
||||
ipv6_servers = OptionNumber("misc", "ipv6_servers", 1, 0, 2)
|
||||
url_base = OptionStr("misc", "url_base", "/sabnzbd", validation=validate_strip_right_slash)
|
||||
url_base = OptionStr("misc", "url_base", "/sabnzbd")
|
||||
host_whitelist = OptionList("misc", "host_whitelist", validation=all_lowercase)
|
||||
max_url_retries = OptionNumber("misc", "max_url_retries", 10, 1)
|
||||
|
||||
@@ -316,7 +314,6 @@ ncenter_enable = OptionBool("ncenter", "ncenter_enable", sabnzbd.DARWIN)
|
||||
ncenter_cats = OptionList("ncenter", "ncenter_cats", ["*"])
|
||||
ncenter_prio_startup = OptionBool("ncenter", "ncenter_prio_startup", True)
|
||||
ncenter_prio_download = OptionBool("ncenter", "ncenter_prio_download", False)
|
||||
ncenter_prio_pause_resume = OptionBool("ncenter", "ncenter_prio_pause_resume", False)
|
||||
ncenter_prio_pp = OptionBool("ncenter", "ncenter_prio_pp", False)
|
||||
ncenter_prio_complete = OptionBool("ncenter", "ncenter_prio_complete", True)
|
||||
ncenter_prio_failed = OptionBool("ncenter", "ncenter_prio_failed", True)
|
||||
@@ -332,7 +329,6 @@ acenter_enable = OptionBool("acenter", "acenter_enable", sabnzbd.WIN32)
|
||||
acenter_cats = OptionList("acenter", "acenter_cats", ["*"])
|
||||
acenter_prio_startup = OptionBool("acenter", "acenter_prio_startup", False)
|
||||
acenter_prio_download = OptionBool("acenter", "acenter_prio_download", False)
|
||||
acenter_prio_pause_resume = OptionBool("acenter", "acenter_prio_pause_resume", False)
|
||||
acenter_prio_pp = OptionBool("acenter", "acenter_prio_pp", False)
|
||||
acenter_prio_complete = OptionBool("acenter", "acenter_prio_complete", True)
|
||||
acenter_prio_failed = OptionBool("acenter", "acenter_prio_failed", True)
|
||||
@@ -348,7 +344,6 @@ ntfosd_enable = OptionBool("ntfosd", "ntfosd_enable", not sabnzbd.WIN32 and not
|
||||
ntfosd_cats = OptionList("ntfosd", "ntfosd_cats", ["*"])
|
||||
ntfosd_prio_startup = OptionBool("ntfosd", "ntfosd_prio_startup", True)
|
||||
ntfosd_prio_download = OptionBool("ntfosd", "ntfosd_prio_download", False)
|
||||
ntfosd_prio_pause_resume = OptionBool("ntfosd", "ntfosd_prio_pause_resume", False)
|
||||
ntfosd_prio_pp = OptionBool("ntfosd", "ntfosd_prio_pp", False)
|
||||
ntfosd_prio_complete = OptionBool("ntfosd", "ntfosd_prio_complete", True)
|
||||
ntfosd_prio_failed = OptionBool("ntfosd", "ntfosd_prio_failed", True)
|
||||
@@ -365,7 +360,6 @@ prowl_cats = OptionList("prowl", "prowl_cats", ["*"])
|
||||
prowl_apikey = OptionStr("prowl", "prowl_apikey")
|
||||
prowl_prio_startup = OptionNumber("prowl", "prowl_prio_startup", -3)
|
||||
prowl_prio_download = OptionNumber("prowl", "prowl_prio_download", -3)
|
||||
prowl_prio_pause_resume = OptionNumber("prowl", "prowl_prio_pause_resume", -3)
|
||||
prowl_prio_pp = OptionNumber("prowl", "prowl_prio_pp", -3)
|
||||
prowl_prio_complete = OptionNumber("prowl", "prowl_prio_complete", 0)
|
||||
prowl_prio_failed = OptionNumber("prowl", "prowl_prio_failed", 1)
|
||||
@@ -386,7 +380,6 @@ pushover_enable = OptionBool("pushover", "pushover_enable")
|
||||
pushover_cats = OptionList("pushover", "pushover_cats", ["*"])
|
||||
pushover_prio_startup = OptionNumber("pushover", "pushover_prio_startup", -3)
|
||||
pushover_prio_download = OptionNumber("pushover", "pushover_prio_download", -2)
|
||||
pushover_prio_pause_resume = OptionNumber("pushover", "pushover_prio_pause_resume", -2)
|
||||
pushover_prio_pp = OptionNumber("pushover", "pushover_prio_pp", -3)
|
||||
pushover_prio_complete = OptionNumber("pushover", "pushover_prio_complete", -1)
|
||||
pushover_prio_failed = OptionNumber("pushover", "pushover_prio_failed", -1)
|
||||
@@ -402,18 +395,17 @@ pushbullet_enable = OptionBool("pushbullet", "pushbullet_enable")
|
||||
pushbullet_cats = OptionList("pushbullet", "pushbullet_cats", ["*"])
|
||||
pushbullet_apikey = OptionStr("pushbullet", "pushbullet_apikey")
|
||||
pushbullet_device = OptionStr("pushbullet", "pushbullet_device")
|
||||
pushbullet_prio_startup = OptionBool("pushbullet", "pushbullet_prio_startup", False)
|
||||
pushbullet_prio_download = OptionBool("pushbullet", "pushbullet_prio_download", False)
|
||||
pushbullet_prio_pause_resume = OptionBool("pushbullet", "pushbullet_prio_pause_resume", False)
|
||||
pushbullet_prio_pp = OptionBool("pushbullet", "pushbullet_prio_pp", False)
|
||||
pushbullet_prio_complete = OptionBool("pushbullet", "pushbullet_prio_complete", True)
|
||||
pushbullet_prio_failed = OptionBool("pushbullet", "pushbullet_prio_failed", True)
|
||||
pushbullet_prio_disk_full = OptionBool("pushbullet", "pushbullet_prio_disk_full", True)
|
||||
pushbullet_prio_new_login = OptionBool("pushbullet", "pushbullet_prio_new_login", False)
|
||||
pushbullet_prio_warning = OptionBool("pushbullet", "pushbullet_prio_warning", False)
|
||||
pushbullet_prio_error = OptionBool("pushbullet", "pushbullet_prio_error", False)
|
||||
pushbullet_prio_queue_done = OptionBool("pushbullet", "pushbullet_prio_queue_done", False)
|
||||
pushbullet_prio_other = OptionBool("pushbullet", "pushbullet_prio_other", False)
|
||||
pushbullet_prio_startup = OptionNumber("pushbullet", "pushbullet_prio_startup", 0)
|
||||
pushbullet_prio_download = OptionNumber("pushbullet", "pushbullet_prio_download", 0)
|
||||
pushbullet_prio_pp = OptionNumber("pushbullet", "pushbullet_prio_pp", 0)
|
||||
pushbullet_prio_complete = OptionNumber("pushbullet", "pushbullet_prio_complete", 1)
|
||||
pushbullet_prio_failed = OptionNumber("pushbullet", "pushbullet_prio_failed", 1)
|
||||
pushbullet_prio_disk_full = OptionNumber("pushbullet", "pushbullet_prio_disk_full", 1)
|
||||
pushbullet_prio_new_login = OptionNumber("pushbullet", "pushbullet_prio_new_login", 0)
|
||||
pushbullet_prio_warning = OptionNumber("pushbullet", "pushbullet_prio_warning", 0)
|
||||
pushbullet_prio_error = OptionNumber("pushbullet", "pushbullet_prio_error", 0)
|
||||
pushbullet_prio_queue_done = OptionNumber("pushbullet", "pushbullet_prio_queue_done", 0)
|
||||
pushbullet_prio_other = OptionNumber("pushbullet", "pushbullet_prio_other", 0)
|
||||
|
||||
# [nscript]
|
||||
nscript_enable = OptionBool("nscript", "nscript_enable")
|
||||
@@ -422,7 +414,6 @@ nscript_script = OptionStr("nscript", "nscript_script")
|
||||
nscript_parameters = OptionStr("nscript", "nscript_parameters")
|
||||
nscript_prio_startup = OptionBool("nscript", "nscript_prio_startup", True)
|
||||
nscript_prio_download = OptionBool("nscript", "nscript_prio_download", False)
|
||||
nscript_prio_pause_resume = OptionBool("nscript", "nscript_prio_pause_resume", False)
|
||||
nscript_prio_pp = OptionBool("nscript", "nscript_prio_pp", False)
|
||||
nscript_prio_complete = OptionBool("nscript", "nscript_prio_complete", True)
|
||||
nscript_prio_failed = OptionBool("nscript", "nscript_prio_failed", True)
|
||||
|
||||
@@ -25,13 +25,12 @@ import re
|
||||
import shutil
|
||||
import threading
|
||||
import uuid
|
||||
from typing import List, Dict, Any, Callable, Optional, Union, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import configobj
|
||||
|
||||
import sabnzbd.misc
|
||||
from sabnzbd.constants import CONFIG_VERSION, NORMAL_PRIORITY, DEFAULT_PRIORITY
|
||||
from sabnzbd.constants import CONFIG_VERSION, NORMAL_PRIORITY, DEFAULT_PRIORITY, MAX_WIN_DFOLDER
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.filesystem import clip_path, real_path, create_real_path, renamer, remove_file, is_writable
|
||||
|
||||
@@ -39,7 +38,7 @@ CONFIG_LOCK = threading.Lock()
|
||||
SAVE_CONFIG_LOCK = threading.Lock()
|
||||
|
||||
|
||||
CFG: configobj.ConfigObj # Holds INI structure
|
||||
CFG = {} # Holds INI structure
|
||||
# during re-write this variable is global
|
||||
# to allow direct access to INI structure
|
||||
|
||||
@@ -48,26 +47,26 @@ database = {} # Holds the option dictionary
|
||||
modified = False # Signals a change in option dictionary
|
||||
# Should be reset after saving to settings file
|
||||
|
||||
RE_PARAMFINDER = re.compile(r"""(?:'.*?')|(?:".*?")|(?:[^'",\s][^,]*)""")
|
||||
paramfinder = re.compile(r"""(?:'.*?')|(?:".*?")|(?:[^'",\s][^,]*)""")
|
||||
|
||||
|
||||
class Option:
|
||||
""" Basic option class, basic fields """
|
||||
|
||||
def __init__(self, section: str, keyword: str, default_val: Any = None, add: bool = True, protect: bool = False):
|
||||
"""Basic option
|
||||
`section` : single section or comma-separated list of sections
|
||||
a list will be a hierarchy: "foo, bar" --> [foo][[bar]]
|
||||
`keyword` : keyword in the (last) section
|
||||
`default_val` : value returned when no value has been set
|
||||
`callback` : procedure to call when value is successfully changed
|
||||
`protect` : Do not allow setting via the API (specifically set_dict)
|
||||
def __init__(self, section, keyword, default_val=None, add=True, protect=False):
|
||||
""" Basic option
|
||||
`section` : single section or comma-separated list of sections
|
||||
a list will be a hierarchy: "foo, bar" --> [foo][[bar]]
|
||||
`keyword` : keyword in the (last) section
|
||||
`default_val` : value returned when no value has been set
|
||||
`callback` : procedure to call when value is successfully changed
|
||||
`protect` : Do not allow setting via the API (specifically set_dict)
|
||||
"""
|
||||
self.__sections = section.split(",")
|
||||
self.__keyword: str = keyword
|
||||
self.__default_val: Any = default_val
|
||||
self.__value: Any = None
|
||||
self.__callback: Optional[Callable] = None
|
||||
self.__keyword = keyword
|
||||
self.__default_val = default_val
|
||||
self.__value = None
|
||||
self.__callback = None
|
||||
self.__protect = protect
|
||||
|
||||
# Add myself to the config dictionary
|
||||
@@ -80,29 +79,34 @@ class Option:
|
||||
anchor = anchor[section]
|
||||
anchor[keyword] = self
|
||||
|
||||
def get(self) -> Any:
|
||||
def __call__(self):
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
|
||||
def get(self):
|
||||
""" Retrieve value field """
|
||||
if self.__value is not None:
|
||||
return self.__value
|
||||
else:
|
||||
return self.__default_val
|
||||
|
||||
def get_string(self) -> str:
|
||||
def get_string(self):
|
||||
return str(self.get())
|
||||
|
||||
def get_dict(self, safe: bool = False) -> Dict[str, Any]:
|
||||
def get_dict(self, safe=False):
|
||||
""" Return value a dictionary """
|
||||
return {self.__keyword: self.get()}
|
||||
|
||||
def set_dict(self, values: Dict[str, Any]):
|
||||
def set_dict(self, input_dict):
|
||||
""" Set value based on dictionary """
|
||||
if not self.__protect:
|
||||
try:
|
||||
self.set(values["value"])
|
||||
except KeyError:
|
||||
pass
|
||||
if self.__protect:
|
||||
return False
|
||||
try:
|
||||
return self.set(input_dict["value"])
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def set(self, value: Any):
|
||||
def __set(self, value):
|
||||
""" Set new value, no validation """
|
||||
global modified
|
||||
if value is not None:
|
||||
@@ -111,11 +115,15 @@ class Option:
|
||||
modified = True
|
||||
if self.__callback:
|
||||
self.__callback()
|
||||
return None
|
||||
|
||||
def default(self) -> Any:
|
||||
def set(self, value):
|
||||
return self.__set(value)
|
||||
|
||||
def default(self):
|
||||
return self.__default_val
|
||||
|
||||
def callback(self, callback: Callable):
|
||||
def callback(self, callback):
|
||||
""" Set callback function """
|
||||
self.__callback = callback
|
||||
|
||||
@@ -125,26 +133,18 @@ class Option:
|
||||
|
||||
|
||||
class OptionNumber(Option):
|
||||
"""Numeric option class, int/float is determined from default value."""
|
||||
""" Numeric option class, int/float is determined from default value """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
section: str,
|
||||
keyword: str,
|
||||
default_val: Union[int, float] = 0,
|
||||
minval: Optional[int] = None,
|
||||
maxval: Optional[int] = None,
|
||||
validation: Optional[Callable] = None,
|
||||
add: bool = True,
|
||||
protect: bool = False,
|
||||
self, section, keyword, default_val=0, minval=None, maxval=None, validation=None, add=True, protect=False
|
||||
):
|
||||
self.__minval: Optional[int] = minval
|
||||
self.__maxval: Optional[int] = maxval
|
||||
self.__validation: Optional[Callable] = validation
|
||||
self.__int: bool = isinstance(default_val, int)
|
||||
self.__minval = minval
|
||||
self.__maxval = maxval
|
||||
self.__validation = validation
|
||||
self.__int = isinstance(default_val, int)
|
||||
super().__init__(section, keyword, default_val, add=add, protect=protect)
|
||||
|
||||
def set(self, value: Any):
|
||||
def set(self, value):
|
||||
""" set new value, limited by range """
|
||||
if value is not None:
|
||||
try:
|
||||
@@ -155,7 +155,7 @@ class OptionNumber(Option):
|
||||
except ValueError:
|
||||
value = super().default()
|
||||
if self.__validation:
|
||||
_, val = self.__validation(value)
|
||||
error, val = self.__validation(value)
|
||||
super().set(val)
|
||||
else:
|
||||
if self.__maxval is not None and value > self.__maxval:
|
||||
@@ -163,49 +163,39 @@ class OptionNumber(Option):
|
||||
elif self.__minval is not None and value < self.__minval:
|
||||
value = self.__minval
|
||||
super().set(value)
|
||||
|
||||
def __call__(self) -> Union[int, float]:
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
return None
|
||||
|
||||
|
||||
class OptionBool(Option):
|
||||
""" Boolean option class, always returns 0 or 1."""
|
||||
""" Boolean option class """
|
||||
|
||||
def __init__(self, section: str, keyword: str, default_val: bool = False, add: bool = True, protect: bool = False):
|
||||
def __init__(self, section, keyword, default_val=False, add=True, protect=False):
|
||||
super().__init__(section, keyword, int(default_val), add=add, protect=protect)
|
||||
|
||||
def set(self, value: Any):
|
||||
# Store the value as integer, easier to parse when reading the config.
|
||||
super().set(sabnzbd.misc.int_conv(value))
|
||||
|
||||
def __call__(self) -> int:
|
||||
""" get() replacement """
|
||||
return int(self.get())
|
||||
def set(self, value):
|
||||
if value is None:
|
||||
value = 0
|
||||
try:
|
||||
super().set(int(value))
|
||||
except ValueError:
|
||||
super().set(0)
|
||||
return None
|
||||
|
||||
|
||||
class OptionDir(Option):
|
||||
""" Directory option class """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
section: str,
|
||||
keyword: str,
|
||||
default_val: str = "",
|
||||
apply_umask: bool = False,
|
||||
create: bool = True,
|
||||
validation: Optional[Callable] = None,
|
||||
writable: bool = True,
|
||||
add: bool = True,
|
||||
self, section, keyword, default_val="", apply_umask=False, create=True, validation=None, writable=True, add=True
|
||||
):
|
||||
self.__validation: Optional[Callable] = validation
|
||||
self.__root: str = "" # Base directory for relative paths
|
||||
self.__apply_umask: bool = apply_umask
|
||||
self.__create: bool = create
|
||||
self.__writable: bool = writable
|
||||
self.__validation = validation
|
||||
self.__root = "" # Base directory for relative paths
|
||||
self.__apply_umask = apply_umask
|
||||
self.__create = create
|
||||
self.__writable = writable
|
||||
super().__init__(section, keyword, default_val, add=add)
|
||||
|
||||
def get(self) -> str:
|
||||
def get(self):
|
||||
""" Return value, corrected for platform """
|
||||
p = super().get()
|
||||
if sabnzbd.WIN32:
|
||||
@@ -213,7 +203,7 @@ class OptionDir(Option):
|
||||
else:
|
||||
return p.replace("\\", "/") if "\\" in p else p
|
||||
|
||||
def get_path(self) -> str:
|
||||
def get_path(self):
|
||||
""" Return full absolute path """
|
||||
value = self.get()
|
||||
path = ""
|
||||
@@ -223,11 +213,11 @@ class OptionDir(Option):
|
||||
_, path, _ = create_real_path(self.ident()[1], self.__root, value, self.__apply_umask, self.__writable)
|
||||
return path
|
||||
|
||||
def get_clipped_path(self) -> str:
|
||||
def get_clipped_path(self):
|
||||
""" Return clipped full absolute path """
|
||||
return clip_path(self.get_path())
|
||||
|
||||
def test_path(self) -> bool:
|
||||
def test_path(self):
|
||||
""" Return True if path exists """
|
||||
value = self.get()
|
||||
if value:
|
||||
@@ -235,18 +225,18 @@ class OptionDir(Option):
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_root(self, root: str):
|
||||
def set_root(self, root):
|
||||
""" Set new root, is assumed to be valid """
|
||||
self.__root = root
|
||||
|
||||
def set(self, value: str, create: bool = False) -> Optional[str]:
|
||||
"""Set new dir value, validate and create if needed
|
||||
Return None when directory is accepted
|
||||
Return error-string when not accepted, value will not be changed
|
||||
'create' means try to create (but don't set permanent create flag)
|
||||
def set(self, value, create=False):
|
||||
""" Set new dir value, validate and create if needed
|
||||
Return None when directory is accepted
|
||||
Return error-string when not accepted, value will not be changed
|
||||
'create' means try to create (but don't set permanent create flag)
|
||||
"""
|
||||
error = None
|
||||
if value is not None and (value != self.get() or create):
|
||||
if value and (value != self.get() or create):
|
||||
value = value.strip()
|
||||
if self.__validation:
|
||||
error, value = self.__validation(self.__root, value, super().default())
|
||||
@@ -259,33 +249,21 @@ class OptionDir(Option):
|
||||
super().set(value)
|
||||
return error
|
||||
|
||||
def set_create(self, value: bool):
|
||||
def set_create(self, value):
|
||||
""" Set auto-creation value """
|
||||
self.__create = value
|
||||
|
||||
def __call__(self) -> str:
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
|
||||
|
||||
class OptionList(Option):
|
||||
""" List option class """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
section: str,
|
||||
keyword: str,
|
||||
default_val: Union[str, List, None] = None,
|
||||
validation: Optional[Callable] = None,
|
||||
add: bool = True,
|
||||
protect: bool = False,
|
||||
):
|
||||
self.__validation: Optional[Callable] = validation
|
||||
def __init__(self, section, keyword, default_val=None, validation=None, add=True, protect=False):
|
||||
self.__validation = validation
|
||||
if default_val is None:
|
||||
default_val = []
|
||||
super().__init__(section, keyword, default_val, add=add, protect=protect)
|
||||
|
||||
def set(self, value: Union[str, List]) -> Optional[str]:
|
||||
def set(self, value):
|
||||
""" Set the list given a comma-separated string or a list """
|
||||
error = None
|
||||
if value is not None:
|
||||
@@ -293,52 +271,47 @@ class OptionList(Option):
|
||||
if '"' not in value and "," not in value:
|
||||
value = value.split()
|
||||
else:
|
||||
value = RE_PARAMFINDER.findall(value)
|
||||
value = paramfinder.findall(value)
|
||||
if self.__validation:
|
||||
error, value = self.__validation(value)
|
||||
if not error:
|
||||
super().set(value)
|
||||
return error
|
||||
|
||||
def get_string(self) -> str:
|
||||
def get_string(self):
|
||||
""" Return the list as a comma-separated string """
|
||||
return ", ".join(self.get())
|
||||
lst = self.get()
|
||||
if isinstance(lst, str):
|
||||
return lst
|
||||
else:
|
||||
return ", ".join(lst)
|
||||
|
||||
def default_string(self) -> str:
|
||||
def default_string(self):
|
||||
""" Return the default list as a comma-separated string """
|
||||
return ", ".join(self.default())
|
||||
|
||||
def __call__(self) -> List[str]:
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
lst = self.default()
|
||||
if isinstance(lst, str):
|
||||
return lst
|
||||
else:
|
||||
return ", ".join(lst)
|
||||
|
||||
|
||||
class OptionStr(Option):
|
||||
""" String class."""
|
||||
""" String class """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
section: str,
|
||||
keyword: str,
|
||||
default_val: str = "",
|
||||
validation: Optional[Callable] = None,
|
||||
add: bool = True,
|
||||
strip: bool = True,
|
||||
protect: bool = False,
|
||||
):
|
||||
self.__validation: Optional[Callable] = validation
|
||||
self.__strip: bool = strip
|
||||
def __init__(self, section, keyword, default_val="", validation=None, add=True, strip=True, protect=False):
|
||||
self.__validation = validation
|
||||
self.__strip = strip
|
||||
super().__init__(section, keyword, default_val, add=add, protect=protect)
|
||||
|
||||
def get_float(self) -> float:
|
||||
def get_float(self):
|
||||
""" Return value converted to a float, allowing KMGT notation """
|
||||
return sabnzbd.misc.from_units(self.get())
|
||||
|
||||
def get_int(self) -> int:
|
||||
def get_int(self):
|
||||
""" Return value converted to an int, allowing KMGT notation """
|
||||
return int(self.get_float())
|
||||
|
||||
def set(self, value: Any) -> Optional[str]:
|
||||
def set(self, value):
|
||||
""" Set stripped value """
|
||||
error = None
|
||||
if isinstance(value, str) and self.__strip:
|
||||
@@ -350,43 +323,57 @@ class OptionStr(Option):
|
||||
super().set(value)
|
||||
return error
|
||||
|
||||
def __call__(self) -> str:
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
|
||||
|
||||
class OptionPassword(Option):
|
||||
""" Password class. """
|
||||
""" Password class """
|
||||
|
||||
def __init__(self, section: str, keyword: str, default_val: str = "", add: bool = True):
|
||||
def __init__(self, section, keyword, default_val="", add=True):
|
||||
self.get_string = self.get_stars
|
||||
super().__init__(section, keyword, default_val, add=add)
|
||||
|
||||
def get(self) -> Optional[str]:
|
||||
def get(self):
|
||||
""" Return decoded password """
|
||||
return decode_password(super().get(), self.ident())
|
||||
|
||||
def get_stars(self) -> Optional[str]:
|
||||
""" Return non-descript asterisk string """
|
||||
if self.get():
|
||||
return "*" * 10
|
||||
return ""
|
||||
def get_stars(self):
|
||||
""" Return decoded password as asterisk string """
|
||||
return "*" * len(self.get())
|
||||
|
||||
def get_dict(self, safe: bool = False) -> Dict[str, str]:
|
||||
def get_dict(self, safe=False):
|
||||
""" Return value a dictionary """
|
||||
if safe:
|
||||
return {self.ident()[1]: self.get_stars()}
|
||||
else:
|
||||
return {self.ident()[1]: self.get()}
|
||||
|
||||
def set(self, pw: str):
|
||||
def set(self, pw):
|
||||
""" Set password, encode it """
|
||||
if (pw is not None and pw == "") or (pw and pw.strip("*")):
|
||||
super().set(encode_password(pw))
|
||||
return None
|
||||
|
||||
def __call__(self) -> str:
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
|
||||
@synchronized(CONFIG_LOCK)
|
||||
def add_to_database(section, keyword, obj):
|
||||
""" add object as section/keyword to INI database """
|
||||
global database
|
||||
if section not in database:
|
||||
database[section] = {}
|
||||
database[section][keyword] = obj
|
||||
|
||||
|
||||
@synchronized(CONFIG_LOCK)
|
||||
def delete_from_database(section, keyword):
|
||||
""" Remove section/keyword from INI database """
|
||||
global database, CFG, modified
|
||||
del database[section][keyword]
|
||||
if section == "servers" and "[" in keyword:
|
||||
keyword = keyword.replace("[", "{").replace("]", "}")
|
||||
try:
|
||||
del CFG[section][keyword]
|
||||
except KeyError:
|
||||
pass
|
||||
modified = True
|
||||
|
||||
|
||||
class ConfigServer:
|
||||
@@ -397,28 +384,28 @@ class ConfigServer:
|
||||
self.__name = name
|
||||
name = "servers," + self.__name
|
||||
|
||||
self.displayname = OptionStr(name, "displayname", add=False)
|
||||
self.host = OptionStr(name, "host", add=False)
|
||||
self.displayname = OptionStr(name, "displayname", "", add=False)
|
||||
self.host = OptionStr(name, "host", "", add=False)
|
||||
self.port = OptionNumber(name, "port", 119, 0, 2 ** 16 - 1, add=False)
|
||||
self.timeout = OptionNumber(name, "timeout", 60, 20, 240, add=False)
|
||||
self.username = OptionStr(name, "username", add=False)
|
||||
self.password = OptionPassword(name, "password", add=False)
|
||||
self.username = OptionStr(name, "username", "", add=False)
|
||||
self.password = OptionPassword(name, "password", "", add=False)
|
||||
self.connections = OptionNumber(name, "connections", 1, 0, 100, add=False)
|
||||
self.ssl = OptionBool(name, "ssl", False, add=False)
|
||||
# 0=No, 1=Normal, 2=Strict (hostname verification)
|
||||
self.ssl_verify = OptionNumber(name, "ssl_verify", 2, add=False)
|
||||
self.ssl_ciphers = OptionStr(name, "ssl_ciphers", add=False)
|
||||
self.ssl_ciphers = OptionStr(name, "ssl_ciphers", "", add=False)
|
||||
self.enable = OptionBool(name, "enable", True, add=False)
|
||||
self.optional = OptionBool(name, "optional", False, add=False)
|
||||
self.retention = OptionNumber(name, "retention", 0, add=False)
|
||||
self.retention = OptionNumber(name, "retention", add=False)
|
||||
self.send_group = OptionBool(name, "send_group", False, add=False)
|
||||
self.priority = OptionNumber(name, "priority", 0, 0, 99, add=False)
|
||||
self.notes = OptionStr(name, "notes", add=False)
|
||||
self.notes = OptionStr(name, "notes", "", add=False)
|
||||
|
||||
self.set_dict(values)
|
||||
add_to_database("servers", self.__name, self)
|
||||
|
||||
def set_dict(self, values: Dict[str, Any]):
|
||||
def set_dict(self, values):
|
||||
""" Set one or more fields, passed as dictionary """
|
||||
for kw in (
|
||||
"displayname",
|
||||
@@ -440,13 +427,14 @@ class ConfigServer:
|
||||
):
|
||||
try:
|
||||
value = values[kw]
|
||||
getattr(self, kw).set(value)
|
||||
except KeyError:
|
||||
continue
|
||||
if not self.displayname():
|
||||
self.displayname.set(self.__name)
|
||||
exec("self.%s.set(value)" % kw)
|
||||
if not self.displayname():
|
||||
self.displayname.set(self.__name)
|
||||
return True
|
||||
|
||||
def get_dict(self, safe: bool = False) -> Dict[str, Any]:
|
||||
def get_dict(self, safe=False):
|
||||
""" Return a dictionary with all attributes """
|
||||
output_dict = {}
|
||||
output_dict["name"] = self.__name
|
||||
@@ -475,23 +463,23 @@ class ConfigServer:
|
||||
""" Remove from database """
|
||||
delete_from_database("servers", self.__name)
|
||||
|
||||
def rename(self, name: str):
|
||||
def rename(self, name):
|
||||
""" Give server new display name """
|
||||
self.displayname.set(name)
|
||||
|
||||
def ident(self) -> Tuple[str, str]:
|
||||
def ident(self):
|
||||
return "servers", self.__name
|
||||
|
||||
|
||||
class ConfigCat:
|
||||
""" Class defining a single category """
|
||||
|
||||
def __init__(self, name: str, values: Dict[str, Any]):
|
||||
def __init__(self, name, values):
|
||||
self.__name = name
|
||||
name = "categories," + name
|
||||
|
||||
self.order = OptionNumber(name, "order", 0, 0, 100, add=False)
|
||||
self.pp = OptionStr(name, "pp", add=False)
|
||||
self.pp = OptionStr(name, "pp", "", add=False)
|
||||
self.script = OptionStr(name, "script", "Default", add=False)
|
||||
self.dir = OptionDir(name, "dir", add=False, create=False)
|
||||
self.newzbin = OptionList(name, "newzbin", add=False, validation=validate_single_tag)
|
||||
@@ -500,16 +488,17 @@ class ConfigCat:
|
||||
self.set_dict(values)
|
||||
add_to_database("categories", self.__name, self)
|
||||
|
||||
def set_dict(self, values: Dict[str, Any]):
|
||||
def set_dict(self, values):
|
||||
""" Set one or more fields, passed as dictionary """
|
||||
for kw in ("order", "pp", "script", "dir", "newzbin", "priority"):
|
||||
try:
|
||||
value = values[kw]
|
||||
getattr(self, kw).set(value)
|
||||
except KeyError:
|
||||
continue
|
||||
exec("self.%s.set(value)" % kw)
|
||||
return True
|
||||
|
||||
def get_dict(self, safe: bool = False) -> Dict[str, Any]:
|
||||
def get_dict(self, safe=False):
|
||||
""" Return a dictionary with all attributes """
|
||||
output_dict = {}
|
||||
output_dict["name"] = self.__name
|
||||
@@ -533,7 +522,7 @@ class OptionFilters(Option):
|
||||
super().__init__(section, keyword, add=add)
|
||||
self.set([])
|
||||
|
||||
def move(self, current: int, new: int):
|
||||
def move(self, current, new):
|
||||
""" Move filter from position 'current' to 'new' """
|
||||
lst = self.get()
|
||||
try:
|
||||
@@ -543,9 +532,9 @@ class OptionFilters(Option):
|
||||
return
|
||||
self.set(lst)
|
||||
|
||||
def update(self, pos: int, value: Tuple):
|
||||
"""Update filter 'pos' definition, value is a list
|
||||
Append if 'pos' outside list
|
||||
def update(self, pos, value):
|
||||
""" Update filter 'pos' definition, value is a list
|
||||
Append if 'pos' outside list
|
||||
"""
|
||||
lst = self.get()
|
||||
try:
|
||||
@@ -554,7 +543,7 @@ class OptionFilters(Option):
|
||||
lst.append(value)
|
||||
self.set(lst)
|
||||
|
||||
def delete(self, pos: int):
|
||||
def delete(self, pos):
|
||||
""" Remove filter 'pos' """
|
||||
lst = self.get()
|
||||
try:
|
||||
@@ -563,27 +552,34 @@ class OptionFilters(Option):
|
||||
return
|
||||
self.set(lst)
|
||||
|
||||
def get_dict(self, safe: bool = False) -> Dict[str, str]:
|
||||
def get_dict(self, safe=False):
|
||||
""" Return filter list as a dictionary with keys 'filter[0-9]+' """
|
||||
output_dict = {}
|
||||
for n, rss_filter in enumerate(self.get()):
|
||||
output_dict[f"filter{n}"] = rss_filter
|
||||
n = 0
|
||||
for filter_name in self.get():
|
||||
output_dict["filter" + str(n)] = filter_name
|
||||
n = n + 1
|
||||
return output_dict
|
||||
|
||||
def set_dict(self, values: Dict[str, Any]):
|
||||
def set_dict(self, values):
|
||||
""" Create filter list from dictionary with keys 'filter[0-9]+' """
|
||||
filters = []
|
||||
# We don't know how many filters there are, so just assume all values are filters
|
||||
for n in range(len(values)):
|
||||
kw = f"filter{n}"
|
||||
if kw in values:
|
||||
filters.append(values[kw])
|
||||
kw = "filter%d" % n
|
||||
val = values.get(kw)
|
||||
if val is not None:
|
||||
val = values[kw]
|
||||
if isinstance(val, list):
|
||||
filters.append(val)
|
||||
else:
|
||||
filters.append(paramfinder.findall(val))
|
||||
while len(filters[-1]) < 7:
|
||||
filters[-1].append("1")
|
||||
if not filters[-1][6]:
|
||||
filters[-1][6] = "1"
|
||||
if filters:
|
||||
self.set(filters)
|
||||
|
||||
def __call__(self) -> List[List[str]]:
|
||||
""" get() replacement """
|
||||
return self.get()
|
||||
return True
|
||||
|
||||
|
||||
class ConfigRSS:
|
||||
@@ -595,7 +591,7 @@ class ConfigRSS:
|
||||
|
||||
self.uri = OptionList(name, "uri", add=False)
|
||||
self.cat = OptionStr(name, "cat", add=False)
|
||||
self.pp = OptionStr(name, "pp", add=False)
|
||||
self.pp = OptionStr(name, "pp", "", add=False)
|
||||
self.script = OptionStr(name, "script", add=False)
|
||||
self.enable = OptionBool(name, "enable", add=False)
|
||||
self.priority = OptionNumber(name, "priority", DEFAULT_PRIORITY, DEFAULT_PRIORITY, 2, add=False)
|
||||
@@ -605,17 +601,19 @@ class ConfigRSS:
|
||||
self.set_dict(values)
|
||||
add_to_database("rss", self.__name, self)
|
||||
|
||||
def set_dict(self, values: Dict[str, Any]):
|
||||
def set_dict(self, values):
|
||||
""" Set one or more fields, passed as dictionary """
|
||||
for kw in ("uri", "cat", "pp", "script", "priority", "enable"):
|
||||
try:
|
||||
value = values[kw]
|
||||
getattr(self, kw).set(value)
|
||||
except KeyError:
|
||||
continue
|
||||
self.filters.set_dict(values)
|
||||
exec("self.%s.set(value)" % kw)
|
||||
|
||||
def get_dict(self, safe: bool = False) -> Dict[str, Any]:
|
||||
self.filters.set_dict(values)
|
||||
return True
|
||||
|
||||
def get_dict(self, safe=False):
|
||||
""" Return a dictionary with all attributes """
|
||||
output_dict = {}
|
||||
output_dict["name"] = self.__name
|
||||
@@ -634,36 +632,13 @@ class ConfigRSS:
|
||||
""" Remove from database """
|
||||
delete_from_database("rss", self.__name)
|
||||
|
||||
def ident(self) -> Tuple[str, str]:
|
||||
def ident(self):
|
||||
return "rss", self.__name
|
||||
|
||||
|
||||
@synchronized(CONFIG_LOCK)
|
||||
def add_to_database(section, keyword, obj):
|
||||
""" add object as section/keyword to INI database """
|
||||
global database
|
||||
if section not in database:
|
||||
database[section] = {}
|
||||
database[section][keyword] = obj
|
||||
|
||||
|
||||
@synchronized(CONFIG_LOCK)
|
||||
def delete_from_database(section, keyword):
|
||||
""" Remove section/keyword from INI database """
|
||||
global database, CFG, modified
|
||||
del database[section][keyword]
|
||||
if section == "servers" and "[" in keyword:
|
||||
keyword = keyword.replace("[", "{").replace("]", "}")
|
||||
try:
|
||||
del CFG[section][keyword]
|
||||
except KeyError:
|
||||
pass
|
||||
modified = True
|
||||
|
||||
|
||||
def get_dconfig(section, keyword, nested=False):
|
||||
"""Return a config values dictionary,
|
||||
Single item or slices based on 'section', 'keyword'
|
||||
""" Return a config values dictionary,
|
||||
Single item or slices based on 'section', 'keyword'
|
||||
"""
|
||||
data = {}
|
||||
if not section:
|
||||
@@ -721,7 +696,7 @@ def set_config(kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def delete(section: str, keyword: str):
|
||||
def delete(section, keyword):
|
||||
""" Delete specific config item """
|
||||
try:
|
||||
database[section][keyword].delete()
|
||||
@@ -737,15 +712,15 @@ def delete(section: str, keyword: str):
|
||||
##############################################################################
|
||||
@synchronized(SAVE_CONFIG_LOCK)
|
||||
def read_config(path):
|
||||
"""Read the complete INI file and check its version number
|
||||
if OK, pass values to config-database
|
||||
""" Read the complete INI file and check its version number
|
||||
if OK, pass values to config-database
|
||||
"""
|
||||
return _read_config(path)
|
||||
|
||||
|
||||
def _read_config(path, try_backup=False):
|
||||
"""Read the complete INI file and check its version number
|
||||
if OK, pass values to config-database
|
||||
""" Read the complete INI file and check its version number
|
||||
if OK, pass values to config-database
|
||||
"""
|
||||
global CFG, database, modified
|
||||
|
||||
@@ -803,16 +778,9 @@ def _read_config(path, try_backup=False):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Define the special settings
|
||||
if "categories" in CFG:
|
||||
for cat in CFG["categories"]:
|
||||
ConfigCat(cat, CFG["categories"][cat])
|
||||
if "rss" in CFG:
|
||||
for rss_feed in CFG["rss"]:
|
||||
ConfigRSS(rss_feed, CFG["rss"][rss_feed])
|
||||
if "servers" in CFG:
|
||||
for server in CFG["servers"]:
|
||||
ConfigServer(server.replace("{", "[").replace("}", "]"), CFG["servers"][server])
|
||||
define_categories()
|
||||
define_rss()
|
||||
define_servers()
|
||||
|
||||
modified = False
|
||||
return True, ""
|
||||
@@ -857,7 +825,13 @@ def save_config(force=False):
|
||||
CFG[sec] = {}
|
||||
value = database[section][option]()
|
||||
# bool is a subclass of int, check first
|
||||
CFG[sec][kw] = value
|
||||
if isinstance(value, bool):
|
||||
# convert bool to int when saving so we store 0 or 1
|
||||
CFG[sec][kw] = str(int(value))
|
||||
elif isinstance(value, int):
|
||||
CFG[sec][kw] = str(value)
|
||||
else:
|
||||
CFG[sec][kw] = value
|
||||
|
||||
res = False
|
||||
filename = CFG.filename
|
||||
@@ -898,7 +872,27 @@ def save_config(force=False):
|
||||
return res
|
||||
|
||||
|
||||
def get_servers() -> Dict[str, ConfigServer]:
|
||||
def define_servers():
|
||||
""" Define servers listed in the Setup file
|
||||
return a list of ConfigServer instances
|
||||
"""
|
||||
global CFG
|
||||
try:
|
||||
for server in CFG["servers"]:
|
||||
svr = CFG["servers"][server]
|
||||
s = ConfigServer(server.replace("{", "[").replace("}", "]"), svr)
|
||||
|
||||
# Conversion of global SSL-Ciphers to server ones
|
||||
if sabnzbd.cfg.ssl_ciphers():
|
||||
s.ssl_ciphers.set(sabnzbd.cfg.ssl_ciphers())
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# No longer needed
|
||||
sabnzbd.cfg.ssl_ciphers.set("")
|
||||
|
||||
|
||||
def get_servers():
|
||||
global database
|
||||
try:
|
||||
return database["servers"]
|
||||
@@ -906,9 +900,22 @@ def get_servers() -> Dict[str, ConfigServer]:
|
||||
return {}
|
||||
|
||||
|
||||
def get_categories() -> Dict[str, ConfigCat]:
|
||||
"""Return link to categories section.
|
||||
This section will always contain special category '*'
|
||||
def define_categories():
|
||||
""" Define categories listed in the Setup file
|
||||
return a list of ConfigCat instances
|
||||
"""
|
||||
global CFG, categories
|
||||
try:
|
||||
for cat in CFG["categories"]:
|
||||
ConfigCat(cat, CFG["categories"][cat])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def get_categories(cat=0):
|
||||
""" Return link to categories section.
|
||||
This section will always contain special category '*'
|
||||
When 'cat' is given, a link to that category or to '*' is returned
|
||||
"""
|
||||
global database
|
||||
if "categories" not in database:
|
||||
@@ -926,21 +933,17 @@ def get_categories() -> Dict[str, ConfigCat]:
|
||||
|
||||
# Save config for future use
|
||||
save_config(True)
|
||||
if not isinstance(cat, int):
|
||||
try:
|
||||
cats = cats[cat]
|
||||
except KeyError:
|
||||
cats = cats["*"]
|
||||
return cats
|
||||
|
||||
|
||||
def get_category(cat: str = "*") -> ConfigCat:
|
||||
"""Get one specific category or if not found the default one"""
|
||||
cats = get_categories()
|
||||
try:
|
||||
return cats[cat]
|
||||
except KeyError:
|
||||
return cats["*"]
|
||||
|
||||
|
||||
def get_ordered_categories() -> List[Dict]:
|
||||
"""Return list-copy of categories section that's ordered
|
||||
by user's ordering including Default-category
|
||||
def get_ordered_categories():
|
||||
""" Return list-copy of categories section that's ordered
|
||||
by user's ordering including Default-category
|
||||
"""
|
||||
database_cats = get_categories()
|
||||
|
||||
@@ -957,10 +960,22 @@ def get_ordered_categories() -> List[Dict]:
|
||||
return categories
|
||||
|
||||
|
||||
def get_rss() -> Dict[str, ConfigRSS]:
|
||||
def define_rss():
|
||||
""" Define rss-feeds listed in the Setup file
|
||||
return a list of ConfigRSS instances
|
||||
"""
|
||||
global CFG
|
||||
try:
|
||||
for r in CFG["rss"]:
|
||||
ConfigRSS(r, CFG["rss"][r])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def get_rss():
|
||||
global database
|
||||
try:
|
||||
# We have to remove non-separator commas by detecting if they are valid URL's
|
||||
# We have to remove non-seperator commas by detecting if they are valid URL's
|
||||
for feed_key in database["rss"]:
|
||||
feed = database["rss"][feed_key]
|
||||
# Only modify if we have to, to prevent repeated config-saving
|
||||
@@ -1018,8 +1033,8 @@ def encode_password(pw):
|
||||
|
||||
|
||||
def decode_password(pw, name):
|
||||
"""Decode hexadecimal encoded password
|
||||
but only decode when prefixed
|
||||
""" Decode hexadecimal encoded password
|
||||
but only decode when prefixed
|
||||
"""
|
||||
decPW = ""
|
||||
if pw and pw.startswith(__PW_PREFIX):
|
||||
@@ -1087,8 +1102,12 @@ def validate_no_unc(root, value, default):
|
||||
|
||||
|
||||
def validate_safedir(root, value, default):
|
||||
"""Allow only when queues are empty and no UNC"""
|
||||
if not sabnzbd.__INITIALIZED__ or (sabnzbd.PostProcessor.empty() and sabnzbd.NzbQueue.is_empty()):
|
||||
""" Allow only when queues are empty and no UNC
|
||||
On Windows path should be small
|
||||
"""
|
||||
if sabnzbd.WIN32 and value and len(real_path(root, value)) >= MAX_WIN_DFOLDER:
|
||||
return T("Error: Path length should be below %s.") % MAX_WIN_DFOLDER, None
|
||||
if sabnzbd.empty_queues():
|
||||
return validate_no_unc(root, value, default)
|
||||
else:
|
||||
return T("Error: Queue not empty, cannot change folder."), None
|
||||
@@ -1102,16 +1121,9 @@ def validate_notempty(root, value, default):
|
||||
return None, default
|
||||
|
||||
|
||||
def validate_strip_right_slash(value):
|
||||
"""Strips the right slash"""
|
||||
if value:
|
||||
return None, value.rstrip("/")
|
||||
return None, value
|
||||
|
||||
|
||||
def validate_single_tag(value):
|
||||
"""Don't split single indexer tags like "TV > HD"
|
||||
into ['TV', '>', 'HD']
|
||||
""" Don't split single indexer tags like "TV > HD"
|
||||
into ['TV', '>', 'HD']
|
||||
"""
|
||||
if len(value) == 3:
|
||||
if value[1] == ">":
|
||||
|
||||
@@ -24,7 +24,7 @@ CONFIG_VERSION = 19
|
||||
QUEUE_VERSION = 10
|
||||
POSTPROC_QUEUE_VERSION = 2
|
||||
|
||||
REC_RAR_VERSION = 550
|
||||
REC_RAR_VERSION = 500
|
||||
|
||||
PNFO = namedtuple(
|
||||
"PNFO",
|
||||
@@ -37,10 +37,6 @@ QNFO = namedtuple("QNFO", "bytes bytes_left bytes_left_previous_page list q_size
|
||||
|
||||
ANFO = namedtuple("ANFO", "article_sum cache_size cache_limit")
|
||||
|
||||
# Leave some space for "_UNPACK_" which we append during post-proc
|
||||
# Or, when extra ".1", ".2" etc. are added for identically named jobs
|
||||
DEF_FOLDER_MAX = 256 - 10
|
||||
|
||||
GIGI = float(2 ** 30)
|
||||
MEBI = float(2 ** 20)
|
||||
KIBI = float(2 ** 10)
|
||||
@@ -52,7 +48,6 @@ QUEUE_FILE_NAME = QUEUE_FILE_TMPL % QUEUE_VERSION
|
||||
POSTPROC_QUEUE_FILE_NAME = "postproc%s.sab" % POSTPROC_QUEUE_VERSION
|
||||
RSS_FILE_NAME = "rss_data.sab"
|
||||
SCAN_FILE_NAME = "watched_data2.sab"
|
||||
RATING_FILE_NAME = "Rating.sab"
|
||||
FUTURE_Q_FOLDER = "future"
|
||||
JOB_ADMIN = "__ADMIN__"
|
||||
VERIFIED_FILE = "__verified__"
|
||||
@@ -88,6 +83,7 @@ DEF_ARTICLE_CACHE_MAX = "1G"
|
||||
DEF_TIMEOUT = 60
|
||||
DEF_SCANRATE = 5
|
||||
MAX_WARNINGS = 20
|
||||
MAX_WIN_DFOLDER = 60
|
||||
MAX_BAD_ARTICLES = 5
|
||||
|
||||
# Constants affecting download performance
|
||||
@@ -97,7 +93,7 @@ DIRECT_WRITE_TRIGGER = 35
|
||||
MAX_ASSEMBLER_QUEUE = 5
|
||||
|
||||
REPAIR_PRIORITY = 3
|
||||
FORCE_PRIORITY = 2
|
||||
TOP_PRIORITY = 2
|
||||
HIGH_PRIORITY = 1
|
||||
NORMAL_PRIORITY = 0
|
||||
LOW_PRIORITY = -1
|
||||
@@ -106,14 +102,6 @@ PAUSED_PRIORITY = -2
|
||||
DUP_PRIORITY = -3
|
||||
STOP_PRIORITY = -4
|
||||
|
||||
INTERFACE_PRIORITIES = {
|
||||
FORCE_PRIORITY: "Force",
|
||||
REPAIR_PRIORITY: "Repair",
|
||||
HIGH_PRIORITY: "High",
|
||||
NORMAL_PRIORITY: "Normal",
|
||||
LOW_PRIORITY: "Low",
|
||||
}
|
||||
|
||||
STAGES = {"Source": 0, "Download": 1, "Servers": 2, "Repair": 3, "Filejoin": 4, "Unpack": 5, "Script": 6}
|
||||
|
||||
VALID_ARCHIVES = (".zip", ".rar", ".7z")
|
||||
@@ -123,8 +111,6 @@ CHEETAH_DIRECTIVES = {"directiveStartToken": "<!--#", "directiveEndToken": "#-->
|
||||
|
||||
IGNORED_FOLDERS = ("@eaDir", ".appleDouble")
|
||||
|
||||
LOCALHOSTS = ("localhost", "127.0.0.1", "[::1]", "::1")
|
||||
|
||||
# (MATCHER, [EXTRA, MATCHERS])
|
||||
series_match = [
|
||||
(compile(r"( [sS]|[\d]+)x(\d+)"), [compile(r"^[-\.]+([sS]|[\d])+x(\d+)"), compile(r"^[-\.](\d+)")]), # 1x01
|
||||
@@ -153,10 +139,25 @@ class Status:
|
||||
GRABBING = "Grabbing" # Q: Getting an NZB from an external site
|
||||
MOVING = "Moving" # PP: Files are being moved
|
||||
PAUSED = "Paused" # Q: Job is paused
|
||||
QUEUED = "Queued" # Q: Job is waiting for its turn to download or post-process
|
||||
QUEUED = "Queued" # Q: Job is waiting for its turn to download
|
||||
QUICK_CHECK = "QuickCheck" # PP: QuickCheck verification is running
|
||||
REPAIRING = "Repairing" # PP: Job is being repaired (by par2)
|
||||
RUNNING = "Running" # PP: User's post processing script is running
|
||||
VERIFYING = "Verifying" # PP: Job is being verified (by par2)
|
||||
DELETED = "Deleted" # Q: Job has been deleted (and is almost gone)
|
||||
PROP = "Propagating" # Q: Delayed download
|
||||
|
||||
|
||||
NOTIFY_KEYS = (
|
||||
"startup",
|
||||
"download",
|
||||
"pp",
|
||||
"complete",
|
||||
"failed",
|
||||
"queue_done",
|
||||
"disk_full",
|
||||
"new_login",
|
||||
"warning",
|
||||
"error",
|
||||
"other",
|
||||
)
|
||||
|
||||
@@ -26,7 +26,6 @@ import logging
|
||||
import sys
|
||||
import threading
|
||||
import sqlite3
|
||||
from typing import Union, Dict
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
@@ -35,7 +34,7 @@ from sabnzbd.bpsmeter import this_week, this_month
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.encoding import ubtou, utob
|
||||
from sabnzbd.misc import int_conv, caller_name, opts_to_pp
|
||||
from sabnzbd.filesystem import remove_file, clip_path
|
||||
from sabnzbd.filesystem import remove_file
|
||||
|
||||
DB_LOCK = threading.RLock()
|
||||
|
||||
@@ -62,10 +61,10 @@ def convert_search(search):
|
||||
|
||||
|
||||
class HistoryDB:
|
||||
"""Class to access the History database
|
||||
Each class-instance will create an access channel that
|
||||
can be used in one thread.
|
||||
Each thread needs its own class-instance!
|
||||
""" Class to access the History database
|
||||
Each class-instance will create an access channel that
|
||||
can be used in one thread.
|
||||
Each thread needs its own class-instance!
|
||||
"""
|
||||
|
||||
# These class attributes will be accessed directly because
|
||||
@@ -85,7 +84,7 @@ class HistoryDB:
|
||||
""" Create a connection to the database """
|
||||
create_table = not os.path.exists(HistoryDB.db_path)
|
||||
self.con = sqlite3.connect(HistoryDB.db_path)
|
||||
self.con.row_factory = sqlite3.Row
|
||||
self.con.row_factory = dict_factory
|
||||
self.c = self.con.cursor()
|
||||
if create_table:
|
||||
self.create_history_db()
|
||||
@@ -99,7 +98,7 @@ class HistoryDB:
|
||||
self.execute("PRAGMA user_version;")
|
||||
try:
|
||||
version = self.c.fetchone()["user_version"]
|
||||
except IndexError:
|
||||
except TypeError:
|
||||
version = 0
|
||||
if version < 1:
|
||||
# Add any missing columns added since first DB version
|
||||
@@ -221,7 +220,7 @@ class HistoryDB:
|
||||
"""SELECT path FROM history WHERE name LIKE ? AND status = ?""", (search, Status.FAILED)
|
||||
)
|
||||
if fetch_ok:
|
||||
return [item["path"] for item in self.c.fetchall()]
|
||||
return [item.get("path") for item in self.c.fetchall()]
|
||||
else:
|
||||
return []
|
||||
|
||||
@@ -276,15 +275,15 @@ class HistoryDB:
|
||||
save=True,
|
||||
)
|
||||
|
||||
def add_history_db(self, nzo, storage="", postproc_time=0, script_output="", script_line=""):
|
||||
def add_history_db(self, nzo, storage="", path="", postproc_time=0, script_output="", script_line=""):
|
||||
""" Add a new job entry to the database """
|
||||
t = build_history_info(nzo, storage, postproc_time, script_output, script_line, series_info=True)
|
||||
t = build_history_info(nzo, storage, path, postproc_time, script_output, script_line, series_info=True)
|
||||
|
||||
self.execute(
|
||||
"""INSERT INTO history (completed, name, nzb_name, category, pp, script, report,
|
||||
url, status, nzo_id, storage, path, script_log, script_line, download_time, postproc_time, stage_log,
|
||||
downloaded, fail_message, url_info, bytes, series, md5sum, password)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
downloaded, completeness, fail_message, url_info, bytes, series, md5sum, password)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
t,
|
||||
save=True,
|
||||
)
|
||||
@@ -310,8 +309,8 @@ class HistoryDB:
|
||||
total_items = -1
|
||||
if res:
|
||||
try:
|
||||
total_items = self.c.fetchone()["COUNT(*)"]
|
||||
except IndexError:
|
||||
total_items = self.c.fetchone().get("COUNT(*)")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not start:
|
||||
@@ -343,39 +342,36 @@ class HistoryDB:
|
||||
if series and season and episode:
|
||||
pattern = "%s/%s/%s" % (series, season, episode)
|
||||
res = self.execute(
|
||||
"""SELECT COUNT(*) FROM History WHERE series = ? AND STATUS != ?""", (pattern, Status.FAILED)
|
||||
"select count(*) from History WHERE series = ? AND STATUS != ?", (pattern, Status.FAILED)
|
||||
)
|
||||
if res:
|
||||
try:
|
||||
total = self.c.fetchone()["COUNT(*)"]
|
||||
except IndexError:
|
||||
total = self.c.fetchone().get("count(*)")
|
||||
except AttributeError:
|
||||
pass
|
||||
return total > 0
|
||||
|
||||
def have_name_or_md5sum(self, name, md5sum):
|
||||
""" Check whether this name or md5sum is already in History """
|
||||
total = 0
|
||||
res = self.execute(
|
||||
"""SELECT COUNT(*) FROM History WHERE ( LOWER(name) = LOWER(?) OR md5sum = ? ) AND STATUS != ?""",
|
||||
(name, md5sum, Status.FAILED),
|
||||
)
|
||||
res = self.execute("select count(*) from History WHERE md5sum = ? AND STATUS != ?", (md5sum, Status.FAILED))
|
||||
if res:
|
||||
try:
|
||||
total = self.c.fetchone()["COUNT(*)"]
|
||||
except IndexError:
|
||||
total = self.c.fetchone().get("count(*)")
|
||||
except AttributeError:
|
||||
pass
|
||||
return total > 0
|
||||
|
||||
def get_history_size(self):
|
||||
"""Returns the total size of the history and
|
||||
amounts downloaded in the last month and week
|
||||
""" Returns the total size of the history and
|
||||
amounts downloaded in the last month and week
|
||||
"""
|
||||
# Total Size of the history
|
||||
total = 0
|
||||
if self.execute("""SELECT sum(bytes) FROM history"""):
|
||||
try:
|
||||
total = self.c.fetchone()["sum(bytes)"]
|
||||
except IndexError:
|
||||
total = self.c.fetchone().get("sum(bytes)")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Amount downloaded this month
|
||||
@@ -386,8 +382,8 @@ class HistoryDB:
|
||||
month = 0
|
||||
if self.execute("""SELECT sum(bytes) FROM history WHERE completed > ?""", (month_timest,)):
|
||||
try:
|
||||
month = self.c.fetchone()["sum(bytes)"]
|
||||
except IndexError:
|
||||
month = self.c.fetchone().get("sum(bytes)")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Amount downloaded this week
|
||||
@@ -396,8 +392,8 @@ class HistoryDB:
|
||||
week = 0
|
||||
if self.execute("""SELECT sum(bytes) FROM history WHERE completed > ?""", (week_timest,)):
|
||||
try:
|
||||
week = self.c.fetchone()["sum(bytes)"]
|
||||
except IndexError:
|
||||
week = self.c.fetchone().get("sum(bytes)")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return total, month, week
|
||||
@@ -406,9 +402,9 @@ class HistoryDB:
|
||||
""" Return decompressed log file """
|
||||
data = ""
|
||||
t = (nzo_id,)
|
||||
if self.execute("""SELECT script_log FROM history WHERE nzo_id = ?""", t):
|
||||
if self.execute("SELECT script_log FROM history WHERE nzo_id = ?", t):
|
||||
try:
|
||||
data = ubtou(zlib.decompress(self.c.fetchone()["script_log"]))
|
||||
data = ubtou(zlib.decompress(self.c.fetchone().get("script_log")))
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
@@ -417,10 +413,10 @@ class HistoryDB:
|
||||
""" Return name of the job `nzo_id` """
|
||||
t = (nzo_id,)
|
||||
name = ""
|
||||
if self.execute("""SELECT name FROM history WHERE nzo_id = ?""", t):
|
||||
if self.execute("SELECT name FROM history WHERE nzo_id = ?", t):
|
||||
try:
|
||||
name = self.c.fetchone()["name"]
|
||||
except IndexError:
|
||||
name = self.c.fetchone().get("name")
|
||||
except AttributeError:
|
||||
pass
|
||||
return name
|
||||
|
||||
@@ -428,9 +424,9 @@ class HistoryDB:
|
||||
""" Return the `incomplete` path of the job `nzo_id` if it is still there """
|
||||
t = (nzo_id,)
|
||||
path = ""
|
||||
if self.execute("""SELECT path FROM history WHERE nzo_id = ?""", t):
|
||||
if self.execute("SELECT path FROM history WHERE nzo_id = ?", t):
|
||||
try:
|
||||
path = self.c.fetchone()["path"]
|
||||
path = self.c.fetchone().get("path")
|
||||
except AttributeError:
|
||||
pass
|
||||
if os.path.exists(path):
|
||||
@@ -440,29 +436,38 @@ class HistoryDB:
|
||||
def get_other(self, nzo_id):
|
||||
""" Return additional data for job `nzo_id` """
|
||||
t = (nzo_id,)
|
||||
if self.execute("""SELECT * FROM history WHERE nzo_id = ?""", t):
|
||||
if self.execute("SELECT * FROM history WHERE nzo_id = ?", t):
|
||||
try:
|
||||
item = self.c.fetchone()
|
||||
return item["report"], item["url"], item["pp"], item["script"], item["category"]
|
||||
items = self.c.fetchone()
|
||||
dtype = items.get("report")
|
||||
url = items.get("url")
|
||||
pp = items.get("pp")
|
||||
script = items.get("script")
|
||||
cat = items.get("category")
|
||||
return dtype, url, pp, script, cat
|
||||
except (AttributeError, IndexError):
|
||||
pass
|
||||
return "", "", "", "", ""
|
||||
|
||||
def __enter__(self):
|
||||
""" For context manager support """
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
""" For context manager support, ignore any exception """
|
||||
self.close()
|
||||
def dict_factory(cursor, row):
|
||||
""" Return a dictionary for the current database position """
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
return d
|
||||
|
||||
|
||||
_PP_LOOKUP = {0: "", 1: "R", 2: "U", 3: "D"}
|
||||
|
||||
|
||||
def build_history_info(nzo, workdir_complete="", postproc_time=0, script_output="", script_line="", series_info=False):
|
||||
def build_history_info(
|
||||
nzo, storage="", downpath="", postproc_time=0, script_output="", script_line="", series_info=False
|
||||
):
|
||||
""" Collects all the information needed for the database """
|
||||
completed = int(time.time())
|
||||
if not downpath:
|
||||
downpath = nzo.downpath
|
||||
pp = _PP_LOOKUP.get(opts_to_pp(*nzo.repair_opts), "X")
|
||||
|
||||
if script_output:
|
||||
@@ -470,6 +475,7 @@ def build_history_info(nzo, workdir_complete="", postproc_time=0, script_output=
|
||||
script_output = sqlite3.Binary(zlib.compress(utob(script_output)))
|
||||
|
||||
download_time = nzo.nzo_info.get("download_time", 0)
|
||||
completeness = 0
|
||||
url_info = nzo.nzo_info.get("details", "") or nzo.nzo_info.get("more_info", "")
|
||||
|
||||
# Get the dictionary containing the stages and their unpack process
|
||||
@@ -501,14 +507,15 @@ def build_history_info(nzo, workdir_complete="", postproc_time=0, script_output=
|
||||
nzo.url,
|
||||
nzo.status,
|
||||
nzo.nzo_id,
|
||||
clip_path(workdir_complete),
|
||||
clip_path(nzo.download_path),
|
||||
storage,
|
||||
downpath,
|
||||
script_output,
|
||||
script_line,
|
||||
download_time,
|
||||
postproc_time,
|
||||
stage_log,
|
||||
nzo.bytes_downloaded,
|
||||
completeness,
|
||||
nzo.fail_msg,
|
||||
url_info,
|
||||
nzo.bytes_downloaded,
|
||||
@@ -518,42 +525,44 @@ def build_history_info(nzo, workdir_complete="", postproc_time=0, script_output=
|
||||
)
|
||||
|
||||
|
||||
def unpack_history_info(item: Union[Dict, sqlite3.Row]):
|
||||
"""Expands the single line stage_log from the DB
|
||||
into a python dictionary for use in the history display
|
||||
def unpack_history_info(item):
|
||||
""" Expands the single line stage_log from the DB
|
||||
into a python dictionary for use in the history display
|
||||
"""
|
||||
# Convert result to dictionary
|
||||
if isinstance(item, sqlite3.Row):
|
||||
item = dict(item)
|
||||
|
||||
# Stage Name is separated by ::: stage lines by ; and stages by \r\n
|
||||
lst = item["stage_log"]
|
||||
if lst:
|
||||
parsed_stage_log = []
|
||||
try:
|
||||
all_stages_lines = lst.split("\r\n")
|
||||
lines = lst.split("\r\n")
|
||||
except:
|
||||
logging.error(T("Invalid stage logging in history for %s"), item["name"])
|
||||
logging.error(T("Invalid stage logging in history for %s") + " (\\r\\n)", item["name"])
|
||||
logging.debug("Lines: %s", lst)
|
||||
all_stages_lines = []
|
||||
|
||||
for stage_lines in all_stages_lines:
|
||||
lines = []
|
||||
lst = [None for x in STAGES]
|
||||
for line in lines:
|
||||
stage = {}
|
||||
try:
|
||||
key, logs = stage_lines.split(":::")
|
||||
key, logs = line.split(":::")
|
||||
except:
|
||||
logging.info('Missing key:::logs "%s"', stage_lines)
|
||||
continue
|
||||
stage = {"name": key, "actions": []}
|
||||
logging.debug('Missing key:::logs "%s"', line)
|
||||
key = line
|
||||
logs = ""
|
||||
stage["name"] = key
|
||||
stage["actions"] = []
|
||||
try:
|
||||
stage["actions"] = logs.split(";")
|
||||
logs = logs.split(";")
|
||||
except:
|
||||
logging.error(T("Invalid stage logging in history for %s"), item["name"])
|
||||
logging.error(T("Invalid stage logging in history for %s") + " (;)", item["name"])
|
||||
logging.debug("Logs: %s", logs)
|
||||
parsed_stage_log.append(stage)
|
||||
|
||||
# Sort it so it is more logical
|
||||
parsed_stage_log.sort(key=lambda stage_log: STAGES.get(stage_log["name"], 100))
|
||||
item["stage_log"] = parsed_stage_log
|
||||
logs = []
|
||||
for log in logs:
|
||||
stage["actions"].append(log)
|
||||
try:
|
||||
lst[STAGES[key]] = stage
|
||||
except KeyError:
|
||||
lst.append(stage)
|
||||
# Remove unused stages
|
||||
item["stage_log"] = [x for x in lst if x is not None]
|
||||
|
||||
if item["script_log"]:
|
||||
item["script_log"] = ""
|
||||
@@ -565,5 +574,6 @@ def unpack_history_info(item: Union[Dict, sqlite3.Row]):
|
||||
|
||||
def midnight_history_purge():
|
||||
logging.info("Scheduled history purge")
|
||||
with HistoryDB() as history_db:
|
||||
history_db.auto_history_purge()
|
||||
history_db = HistoryDB()
|
||||
history_db.auto_history_purge()
|
||||
history_db.close()
|
||||
|
||||
@@ -23,12 +23,13 @@ import logging
|
||||
import hashlib
|
||||
import queue
|
||||
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.nzbstuff import Article
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
from sabnzbd.downloader import Downloader
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.misc import match_str
|
||||
|
||||
# Check for correct SABYenc version
|
||||
@@ -61,6 +62,8 @@ class BadYenc(Exception):
|
||||
class Decoder:
|
||||
""" Implement thread-like coordinator for the decoders """
|
||||
|
||||
do = None
|
||||
|
||||
def __init__(self):
|
||||
logging.debug("Initializing decoders")
|
||||
# Initialize queue and servers
|
||||
@@ -70,12 +73,13 @@ class Decoder:
|
||||
self.decoder_workers = []
|
||||
for i in range(cfg.num_decoders()):
|
||||
self.decoder_workers.append(DecoderWorker(self.decoder_queue))
|
||||
Decoder.do = self
|
||||
|
||||
def start(self):
|
||||
for decoder_worker in self.decoder_workers:
|
||||
decoder_worker.start()
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
def is_alive(self):
|
||||
# Check all workers
|
||||
for decoder_worker in self.decoder_workers:
|
||||
if not decoder_worker.is_alive():
|
||||
@@ -85,7 +89,7 @@ class Decoder:
|
||||
def stop(self):
|
||||
# Put multiple to stop all decoders
|
||||
for _ in self.decoder_workers:
|
||||
self.decoder_queue.put((None, None))
|
||||
self.decoder_queue.put(None)
|
||||
|
||||
def join(self):
|
||||
# Wait for all decoders to finish
|
||||
@@ -95,14 +99,14 @@ class Decoder:
|
||||
except:
|
||||
pass
|
||||
|
||||
def process(self, article: Article, raw_data: List[bytes]):
|
||||
def process(self, article, raw_data):
|
||||
# We use reported article-size, just like sabyenc does
|
||||
sabnzbd.ArticleCache.reserve_space(article.bytes)
|
||||
ArticleCache.do.reserve_space(article.bytes)
|
||||
self.decoder_queue.put((article, raw_data))
|
||||
|
||||
def queue_full(self) -> bool:
|
||||
def queue_full(self):
|
||||
# Check if the queue size exceeds the limits
|
||||
return self.decoder_queue.qsize() >= sabnzbd.ArticleCache.decoder_cache_article_limit
|
||||
return self.decoder_queue.qsize() >= ArticleCache.do.decoder_cache_article_limit
|
||||
|
||||
|
||||
class DecoderWorker(Thread):
|
||||
@@ -112,25 +116,30 @@ class DecoderWorker(Thread):
|
||||
Thread.__init__(self)
|
||||
logging.debug("Initializing decoder %s", self.name)
|
||||
|
||||
self.decoder_queue: queue.Queue[Tuple[Optional[Article], Optional[List[bytes]]]] = decoder_queue
|
||||
self.decoder_queue = decoder_queue
|
||||
|
||||
def stop(self):
|
||||
# Put multiple to stop all decoders
|
||||
self.decoder_queue.put(None)
|
||||
self.decoder_queue.put(None)
|
||||
|
||||
def run(self):
|
||||
while 1:
|
||||
# Set Article and NzbObject objects to None so references from this
|
||||
# thread do not keep the parent objects alive (see #1628)
|
||||
decoded_data = raw_data = article = nzo = None
|
||||
article, raw_data = self.decoder_queue.get()
|
||||
if not article:
|
||||
# Let's get to work!
|
||||
art_tup = self.decoder_queue.get()
|
||||
if not art_tup:
|
||||
logging.info("Shutting down decoder %s", self.name)
|
||||
break
|
||||
|
||||
article, raw_data = art_tup
|
||||
nzo = article.nzf.nzo
|
||||
art_id = article.article
|
||||
|
||||
# Free space in the decoder-queue
|
||||
sabnzbd.ArticleCache.free_reserved_space(article.bytes)
|
||||
ArticleCache.do.free_reserved_space(article.bytes)
|
||||
|
||||
# Keeping track
|
||||
decoded_data = None
|
||||
article_success = False
|
||||
|
||||
try:
|
||||
@@ -146,12 +155,12 @@ class DecoderWorker(Thread):
|
||||
except MemoryError:
|
||||
logging.warning(T("Decoder failure: Out of memory"))
|
||||
logging.info("Decoder-Queue: %d", self.decoder_queue.qsize())
|
||||
logging.info("Cache: %d, %d, %d", *sabnzbd.ArticleCache.cache_info())
|
||||
logging.info("Cache: %d, %d, %d", *ArticleCache.do.cache_info())
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
sabnzbd.Downloader.pause()
|
||||
Downloader.do.pause()
|
||||
|
||||
# This article should be fetched again
|
||||
sabnzbd.NzbQueue.reset_try_lists(article)
|
||||
NzbQueue.do.reset_try_lists(article)
|
||||
continue
|
||||
|
||||
except CrcError:
|
||||
@@ -184,7 +193,7 @@ class DecoderWorker(Thread):
|
||||
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)
|
||||
NzbQueue.do.end_job(nzo)
|
||||
break
|
||||
|
||||
# Pre-check, proper article found so just register
|
||||
@@ -210,12 +219,12 @@ class DecoderWorker(Thread):
|
||||
if decoded_data:
|
||||
# If the data needs to be written to disk due to full cache, this will be slow
|
||||
# Causing the decoder-queue to fill up and delay the downloader
|
||||
sabnzbd.ArticleCache.save_article(article, decoded_data)
|
||||
ArticleCache.do.save_article(article, decoded_data)
|
||||
|
||||
sabnzbd.NzbQueue.register_article(article, article_success)
|
||||
NzbQueue.do.register_article(article, article_success)
|
||||
|
||||
|
||||
def decode(article: Article, raw_data: List[bytes]) -> bytes:
|
||||
def decode(article, raw_data):
|
||||
# Let SABYenc do all the heavy lifting
|
||||
decoded_data, yenc_filename, crc, crc_expected, crc_correct = sabyenc3.decode_usenet_chunks(raw_data, article.bytes)
|
||||
|
||||
@@ -242,7 +251,7 @@ def decode(article: Article, raw_data: List[bytes]) -> bytes:
|
||||
return decoded_data
|
||||
|
||||
|
||||
def search_new_server(article: Article) -> bool:
|
||||
def search_new_server(article):
|
||||
""" Shorthand for searching new server or else increasing bad_articles """
|
||||
# Continue to the next one if we found new server
|
||||
if not article.search_new_server():
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2020 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.
|
||||
|
||||
"""
|
||||
|
||||
Deobfuscation post-processing script:
|
||||
|
||||
Will check in the completed job folder if maybe there are par2 files,
|
||||
for example "rename.par2", and use those to rename the files.
|
||||
If there is no "rename.par2" available, it will rename large, not-excluded
|
||||
files to the job-name in the queue if the filename looks obfuscated
|
||||
|
||||
Based on work by P1nGu1n
|
||||
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from sabnzbd.filesystem import get_unique_filename, renamer, get_ext
|
||||
from sabnzbd.par2file import is_parfile, parse_par2_file
|
||||
|
||||
# Files to exclude and minimal file size for renaming
|
||||
EXCLUDED_FILE_EXTS = (".vob", ".rar", ".par2", ".mts", ".m2ts", ".cpi", ".clpi", ".mpl", ".mpls", ".bdm", ".bdmv")
|
||||
MIN_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
|
||||
def decode_par2(parfile):
|
||||
""" Parse a par2 file and rename files listed in the par2 to their real name """
|
||||
# Check if really a par2 file
|
||||
if not is_parfile(parfile):
|
||||
logging.info("Par2 file %s was not really a par2 file")
|
||||
return False
|
||||
|
||||
# Parse the par2 file
|
||||
md5of16k = {}
|
||||
parse_par2_file(parfile, md5of16k)
|
||||
|
||||
# Parse all files in the folder
|
||||
dirname = os.path.dirname(parfile)
|
||||
result = False
|
||||
for fn in os.listdir(dirname):
|
||||
filepath = os.path.join(dirname, fn)
|
||||
# Only check files
|
||||
if os.path.isfile(filepath):
|
||||
with open(filepath, "rb") as fileToMatch:
|
||||
first16k_data = fileToMatch.read(16384)
|
||||
|
||||
# Check if we have this hash
|
||||
file_md5of16k = hashlib.md5(first16k_data).digest()
|
||||
if file_md5of16k in md5of16k:
|
||||
new_path = os.path.join(dirname, md5of16k[file_md5of16k])
|
||||
# Make sure it's a unique name
|
||||
renamer(filepath, get_unique_filename(new_path))
|
||||
result = True
|
||||
return result
|
||||
|
||||
|
||||
def is_probably_obfuscated(myinputfilename):
|
||||
"""Returns boolean if filename is likely obfuscated. Default: True
|
||||
myinputfilename (string) can be a plain file name, or a full path"""
|
||||
|
||||
# Find filebasename
|
||||
path, filename = os.path.split(myinputfilename)
|
||||
filebasename, fileextension = os.path.splitext(filename)
|
||||
|
||||
# First fixed patterns that we know of:
|
||||
|
||||
# ...blabla.H.264/b082fa0beaa644d3aa01045d5b8d0b36.mkv is certainly obfuscated
|
||||
if re.findall(r"^[a-f0-9]{32}$", filebasename):
|
||||
logging.debug("Obfuscated: 32 hex digit")
|
||||
# exactly 32 hex digits, so:
|
||||
return True
|
||||
|
||||
# /some/thing/abc.xyz.a4c567edbcbf27.BLA is certainly obfuscated
|
||||
if re.findall(r"^abc\.xyz", filebasename):
|
||||
logging.debug("Obfuscated: starts with 'abc.xyz'")
|
||||
# ... which we consider as obfuscated:
|
||||
return True
|
||||
|
||||
# these are signals for the obfuscation versus non-obfuscation
|
||||
decimals = sum(1 for c in filebasename if c.isnumeric())
|
||||
upperchars = sum(1 for c in filebasename if c.isupper())
|
||||
lowerchars = sum(1 for c in filebasename if c.islower())
|
||||
spacesdots = sum(1 for c in filebasename if c == " " or c == ".")
|
||||
|
||||
# Example: "Great Distro"
|
||||
if upperchars >= 2 and lowerchars >= 2 and spacesdots >= 1:
|
||||
logging.debug("Not obfuscated: upperchars >= 2 and lowerchars >= 2 and spacesdots >= 1")
|
||||
return False
|
||||
|
||||
# Example: "this is a download"
|
||||
if spacesdots >= 3:
|
||||
logging.debug("Not obfuscated: spacesdots >= 3")
|
||||
return False
|
||||
|
||||
# Example: "Beast 2020"
|
||||
if (upperchars + lowerchars >= 4) and decimals >= 4 and spacesdots >= 1:
|
||||
logging.debug("Not obfuscated: (upperchars + lowerchars >= 4) and decimals > 3 and spacesdots > 1")
|
||||
return False
|
||||
|
||||
# Example: "Catullus", starts with a capital, and most letters are lower case
|
||||
if filebasename[0].isupper() and lowerchars > 2 and upperchars / lowerchars <= 0.25:
|
||||
logging.debug("Not obfuscated: starts with a capital, and most letters are lower case")
|
||||
return False
|
||||
|
||||
# If we get here, no trigger for a clear name was found, so let's default to obfuscated
|
||||
logging.debug("Obfuscated (default)")
|
||||
return True # default not obfuscated
|
||||
|
||||
|
||||
def deobfuscate_list(filelist, usefulname):
|
||||
""" Check all files in filelist, and if wanted, deobfuscate """
|
||||
|
||||
# to be sure, only keep really exsiting files:
|
||||
filelist = [f for f in filelist if os.path.exists(f)]
|
||||
|
||||
# Search for par2 files in the filelist
|
||||
par2_files = [f for f in filelist if f.endswith(".par2")]
|
||||
|
||||
# Found any par2 files we can use?
|
||||
run_renamer = True
|
||||
if not par2_files:
|
||||
logging.debug("No par2 files found to process, running renamer.")
|
||||
else:
|
||||
# Run par2 from SABnzbd on them
|
||||
for par2_file in par2_files:
|
||||
# Analyse data and analyse result
|
||||
logging.debug("Deobfuscate par2: handling %s", par2_file)
|
||||
if decode_par2(par2_file):
|
||||
logging.debug("Deobfuscate par2 repair/verify finished.")
|
||||
run_renamer = False
|
||||
else:
|
||||
logging.debug("Deobfuscate par2 repair/verify did not find anything to rename.")
|
||||
|
||||
# No par2 files? Then we try to rename qualifying (big, not-excluded, obfuscated) files to the job-name
|
||||
if run_renamer:
|
||||
logging.debug("Trying to see if there are qualifying files to be deobfuscated")
|
||||
for filename in filelist:
|
||||
logging.debug("Deobfuscate inspecting %s", filename)
|
||||
file_size = os.path.getsize(filename)
|
||||
# Do we need to rename this file?
|
||||
# Criteria: big, not-excluded extension, obfuscated (in that order)
|
||||
if (
|
||||
file_size > MIN_FILE_SIZE
|
||||
and get_ext(filename) not in EXCLUDED_FILE_EXTS
|
||||
and is_probably_obfuscated(filename) # this as last test to avoid unnecessary analysis
|
||||
):
|
||||
# OK, rename
|
||||
path, file = os.path.split(filename)
|
||||
new_name = get_unique_filename("%s%s" % (os.path.join(path, usefulname), get_ext(filename)))
|
||||
logging.info("Deobfuscate renaming %s to %s", filename, new_name)
|
||||
# Rename and make sure the new filename is unique
|
||||
renamer(filename, new_name)
|
||||
else:
|
||||
logging.info("No qualifying files found to deobfuscate")
|
||||
@@ -21,20 +21,19 @@ sabnzbd.directunpacker
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import threading
|
||||
import subprocess
|
||||
import logging
|
||||
from typing import Optional, Dict, Tuple, List
|
||||
from subprocess import Popen
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.misc import format_time_string, build_and_run_command
|
||||
from sabnzbd.filesystem import long_path, remove_all, real_path, remove_file, analyze_rar_filename
|
||||
from sabnzbd.nzbstuff import NzbObject, NzbFile
|
||||
from sabnzbd.misc import int_conv, format_time_string
|
||||
from sabnzbd.filesystem import clip_path, long_path, remove_all, real_path, remove_file
|
||||
from sabnzbd.encoding import platform_btou
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.newsunpack import EXTRACTFROM_RE, EXTRACTED_RE, rar_volumelist
|
||||
from sabnzbd.newsunpack import build_command, EXTRACTFROM_RE, EXTRACTED_RE, rar_volumelist
|
||||
from sabnzbd.postproc import prepare_extraction_path
|
||||
from sabnzbd.utils.rarfile import RarFile
|
||||
from sabnzbd.utils.diskspeed import diskspeedmeasure
|
||||
@@ -45,24 +44,26 @@ START_STOP_LOCK = threading.RLock()
|
||||
|
||||
ACTIVE_UNPACKERS = []
|
||||
|
||||
RAR_NR = re.compile(r"(.*?)(\.part(\d*).rar|\.r(\d*))$", re.IGNORECASE)
|
||||
|
||||
|
||||
class DirectUnpacker(threading.Thread):
|
||||
def __init__(self, nzo: NzbObject):
|
||||
def __init__(self, nzo):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.nzo: NzbObject = nzo
|
||||
self.active_instance: Optional[subprocess.Popen] = None
|
||||
self.nzo = nzo
|
||||
self.active_instance = None
|
||||
self.killed = False
|
||||
self.next_file_lock = threading.Condition(threading.RLock())
|
||||
|
||||
self.unpack_dir_info = None
|
||||
self.rarfile_nzf: Optional[NzbFile] = None
|
||||
self.rarfile_nzf = None
|
||||
self.cur_setname = None
|
||||
self.cur_volume = 0
|
||||
self.total_volumes = {}
|
||||
self.unpack_time = 0.0
|
||||
|
||||
self.success_sets: Dict[str, Tuple[List[str], List[str]]] = {}
|
||||
self.success_sets = {}
|
||||
self.next_sets = []
|
||||
|
||||
self.duplicate_lines = 0
|
||||
@@ -76,8 +77,9 @@ class DirectUnpacker(threading.Thread):
|
||||
pass
|
||||
|
||||
def reset_active(self):
|
||||
# make sure the process and file handlers are closed nicely:
|
||||
# make sure the process and filehandles are closed nicely:
|
||||
try:
|
||||
# Creation was done via "self.active_instance = Popen()", so:
|
||||
if self.active_instance:
|
||||
self.active_instance.stdout.close()
|
||||
self.active_instance.stdin.close()
|
||||
@@ -85,7 +87,6 @@ class DirectUnpacker(threading.Thread):
|
||||
except:
|
||||
logging.debug("Exception in reset_active()", exc_info=True)
|
||||
pass
|
||||
|
||||
self.active_instance = None
|
||||
self.cur_setname = None
|
||||
self.cur_volume = 0
|
||||
@@ -95,7 +96,6 @@ class DirectUnpacker(threading.Thread):
|
||||
if (
|
||||
not cfg.direct_unpack()
|
||||
or self.killed
|
||||
or self.nzo.first_articles
|
||||
or not self.nzo.unpack
|
||||
or self.nzo.bad_articles
|
||||
or sabnzbd.newsunpack.RAR_PROBLEM
|
||||
@@ -123,12 +123,12 @@ class DirectUnpacker(threading.Thread):
|
||||
self.total_volumes = {}
|
||||
|
||||
@synchronized(START_STOP_LOCK)
|
||||
def add(self, nzf: NzbFile):
|
||||
def add(self, nzf):
|
||||
""" Add jobs and start instance of DirectUnpack """
|
||||
if not cfg.direct_unpack_tested():
|
||||
test_disk_performance()
|
||||
|
||||
# Stop if something is wrong or we shouldn't start yet
|
||||
# Stop if something is wrong
|
||||
if not self.check_requirements():
|
||||
return
|
||||
|
||||
@@ -163,9 +163,8 @@ class DirectUnpacker(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
# Input and output
|
||||
linebuf = b""
|
||||
linebuf_encoded = ""
|
||||
last_volume_linebuf = b""
|
||||
linebuf = ""
|
||||
last_volume_linebuf = ""
|
||||
unrar_log = []
|
||||
rarfiles = []
|
||||
extracted = []
|
||||
@@ -177,107 +176,99 @@ class DirectUnpacker(threading.Thread):
|
||||
with START_STOP_LOCK:
|
||||
if not self.active_instance or not self.active_instance.stdout:
|
||||
break
|
||||
char = self.active_instance.stdout.read(1)
|
||||
char = platform_btou(self.active_instance.stdout.read(1))
|
||||
|
||||
if not char:
|
||||
# End of program
|
||||
break
|
||||
linebuf += char
|
||||
|
||||
# Continue if it's not a space or end of line
|
||||
if char not in (b" ", b"\n"):
|
||||
continue
|
||||
# Error? Let PP-handle it
|
||||
if linebuf.endswith(
|
||||
(
|
||||
"ERROR: ",
|
||||
"Cannot create",
|
||||
"in the encrypted file",
|
||||
"CRC failed",
|
||||
"checksum failed",
|
||||
"You need to start extraction from a previous volume",
|
||||
"password is incorrect",
|
||||
"Incorrect password",
|
||||
"Write error",
|
||||
"checksum error",
|
||||
"Cannot open",
|
||||
"start extraction from a previous volume",
|
||||
"Unexpected end of archive",
|
||||
)
|
||||
):
|
||||
logging.info("Error in DirectUnpack of %s: %s", self.cur_setname, linebuf.strip())
|
||||
self.abort()
|
||||
|
||||
# Handle whole lines
|
||||
if char == b"\n":
|
||||
# When reaching end-of-line, we can safely convert and add to the log
|
||||
linebuf_encoded = platform_btou(linebuf.strip())
|
||||
unrar_log.append(linebuf_encoded)
|
||||
linebuf = b""
|
||||
|
||||
# Error? Let PP-handle this job
|
||||
if any(
|
||||
error_text in linebuf_encoded
|
||||
for error_text in (
|
||||
"ERROR: ",
|
||||
"Cannot create",
|
||||
"in the encrypted file",
|
||||
"CRC failed",
|
||||
"checksum failed",
|
||||
"You need to start extraction from a previous volume",
|
||||
"password is incorrect",
|
||||
"Incorrect password",
|
||||
"Write error",
|
||||
"checksum error",
|
||||
"Cannot open",
|
||||
"start extraction from a previous volume",
|
||||
"Unexpected end of archive",
|
||||
)
|
||||
):
|
||||
logging.info("Error in DirectUnpack of %s: %s", self.cur_setname, platform_btou(linebuf.strip()))
|
||||
self.abort()
|
||||
|
||||
elif linebuf_encoded.startswith("All OK"):
|
||||
# Did we reach the end?
|
||||
# Stop timer and finish
|
||||
self.unpack_time += time.time() - start_time
|
||||
ACTIVE_UNPACKERS.remove(self)
|
||||
|
||||
# Add to success
|
||||
rarfile_path = os.path.join(self.nzo.download_path, self.rarfile_nzf.filename)
|
||||
self.success_sets[self.cur_setname] = (
|
||||
rar_volumelist(rarfile_path, self.nzo.password, rarfiles),
|
||||
extracted,
|
||||
)
|
||||
logging.info("DirectUnpack completed for %s", self.cur_setname)
|
||||
self.nzo.set_action_line(T("Direct Unpack"), T("Completed"))
|
||||
|
||||
# List success in history-info
|
||||
msg = T("Unpacked %s files/folders in %s") % (len(extracted), format_time_string(self.unpack_time))
|
||||
msg = "%s - %s" % (T("Direct Unpack"), msg)
|
||||
self.nzo.set_unpack_info("Unpack", msg, self.cur_setname)
|
||||
|
||||
# Write current log and clear
|
||||
logging.debug("DirectUnpack Unrar output %s", "\n".join(unrar_log))
|
||||
unrar_log = []
|
||||
rarfiles = []
|
||||
extracted = []
|
||||
|
||||
# Are there more files left?
|
||||
while self.nzo.files and not self.next_sets:
|
||||
with self.next_file_lock:
|
||||
self.next_file_lock.wait()
|
||||
|
||||
# Is there another set to do?
|
||||
if self.next_sets:
|
||||
# Start new instance
|
||||
nzf = self.next_sets.pop(0)
|
||||
self.reset_active()
|
||||
self.cur_setname = nzf.setname
|
||||
# Wait for the 1st volume to appear
|
||||
self.wait_for_next_volume()
|
||||
self.create_unrar_instance()
|
||||
start_time = time.time()
|
||||
else:
|
||||
self.killed = True
|
||||
break
|
||||
|
||||
elif linebuf_encoded.startswith("Extracting from"):
|
||||
# List files we used
|
||||
filename = re.search(EXTRACTFROM_RE, linebuf_encoded).group(1)
|
||||
if linebuf.endswith("\n"):
|
||||
# List files we used
|
||||
if linebuf.startswith("Extracting from"):
|
||||
filename = re.search(EXTRACTFROM_RE, linebuf.strip()).group(1)
|
||||
if filename not in rarfiles:
|
||||
rarfiles.append(filename)
|
||||
else:
|
||||
# List files we extracted
|
||||
m = re.search(EXTRACTED_RE, linebuf_encoded)
|
||||
if m:
|
||||
# In case of flat-unpack, UnRar still prints the whole path (?!)
|
||||
unpacked_file = m.group(2)
|
||||
if cfg.flat_unpack():
|
||||
unpacked_file = os.path.basename(unpacked_file)
|
||||
extracted.append(real_path(self.unpack_dir_info[0], unpacked_file))
|
||||
|
||||
if linebuf.endswith(b"[C]ontinue, [Q]uit "):
|
||||
# List files we extracted
|
||||
m = re.search(EXTRACTED_RE, linebuf)
|
||||
if m:
|
||||
# In case of flat-unpack, UnRar still prints the whole path (?!)
|
||||
unpacked_file = m.group(2)
|
||||
if cfg.flat_unpack():
|
||||
unpacked_file = os.path.basename(unpacked_file)
|
||||
extracted.append(real_path(self.unpack_dir_info[0], unpacked_file))
|
||||
|
||||
# Did we reach the end?
|
||||
if linebuf.endswith("All OK"):
|
||||
# Stop timer and finish
|
||||
self.unpack_time += time.time() - start_time
|
||||
ACTIVE_UNPACKERS.remove(self)
|
||||
|
||||
# Add to success
|
||||
rarfile_path = os.path.join(self.nzo.downpath, self.rarfile_nzf.filename)
|
||||
self.success_sets[self.cur_setname] = (
|
||||
rar_volumelist(rarfile_path, self.nzo.password, rarfiles),
|
||||
extracted,
|
||||
)
|
||||
logging.info("DirectUnpack completed for %s", self.cur_setname)
|
||||
self.nzo.set_action_line(T("Direct Unpack"), T("Completed"))
|
||||
|
||||
# List success in history-info
|
||||
msg = T("Unpacked %s files/folders in %s") % (len(extracted), format_time_string(self.unpack_time))
|
||||
msg = "%s - %s" % (T("Direct Unpack"), msg)
|
||||
self.nzo.set_unpack_info("Unpack", msg, self.cur_setname)
|
||||
|
||||
# Write current log and clear
|
||||
unrar_log.append(linebuf.strip())
|
||||
linebuf = ""
|
||||
last_volume_linebuf = ""
|
||||
logging.debug("DirectUnpack Unrar output %s", "\n".join(unrar_log))
|
||||
unrar_log = []
|
||||
rarfiles = []
|
||||
extracted = []
|
||||
|
||||
# Are there more files left?
|
||||
while self.nzo.files and not self.next_sets:
|
||||
with self.next_file_lock:
|
||||
self.next_file_lock.wait()
|
||||
|
||||
# Is there another set to do?
|
||||
if self.next_sets:
|
||||
# Start new instance
|
||||
nzf = self.next_sets.pop(0)
|
||||
self.reset_active()
|
||||
self.cur_setname = nzf.setname
|
||||
# Wait for the 1st volume to appear
|
||||
self.wait_for_next_volume()
|
||||
self.create_unrar_instance()
|
||||
start_time = time.time()
|
||||
else:
|
||||
self.killed = True
|
||||
break
|
||||
|
||||
if linebuf.endswith("[C]ontinue, [Q]uit "):
|
||||
# Stop timer
|
||||
self.unpack_time += time.time() - start_time
|
||||
|
||||
@@ -310,16 +301,20 @@ class DirectUnpacker(threading.Thread):
|
||||
logging.info("DirectUnpack failed due to missing files %s", self.cur_setname)
|
||||
self.abort()
|
||||
else:
|
||||
logging.debug('Duplicate output line detected: "%s"', platform_btou(last_volume_linebuf))
|
||||
logging.debug('Duplicate output line detected: "%s"', last_volume_linebuf)
|
||||
self.duplicate_lines += 1
|
||||
else:
|
||||
self.duplicate_lines = 0
|
||||
last_volume_linebuf = linebuf
|
||||
|
||||
# Add last line and write any new output
|
||||
if linebuf:
|
||||
unrar_log.append(platform_btou(linebuf.strip()))
|
||||
logging.debug("DirectUnpack Unrar output %s", "\n".join(unrar_log))
|
||||
# Show the log
|
||||
if linebuf.endswith("\n"):
|
||||
unrar_log.append(linebuf.strip())
|
||||
linebuf = ""
|
||||
|
||||
# Add last line
|
||||
unrar_log.append(linebuf.strip())
|
||||
logging.debug("DirectUnpack Unrar output %s", "\n".join(unrar_log))
|
||||
|
||||
# Make more space
|
||||
self.reset_active()
|
||||
@@ -330,9 +325,9 @@ class DirectUnpacker(threading.Thread):
|
||||
self.killed = True
|
||||
|
||||
def have_next_volume(self):
|
||||
"""Check if next volume of set is available, start
|
||||
from the end of the list where latest completed files are
|
||||
Make sure that files are 100% written to disk by checking md5sum
|
||||
""" Check if next volume of set is available, start
|
||||
from the end of the list where latest completed files are
|
||||
Make sure that files are 100% written to disk by checking md5sum
|
||||
"""
|
||||
for nzf_search in reversed(self.nzo.finished_files):
|
||||
if nzf_search.setname == self.cur_setname and nzf_search.vol == (self.cur_volume + 1) and nzf_search.md5sum:
|
||||
@@ -340,8 +335,8 @@ class DirectUnpacker(threading.Thread):
|
||||
return False
|
||||
|
||||
def wait_for_next_volume(self):
|
||||
"""Wait for the correct volume to appear
|
||||
But stop if it was killed or the NZB is done
|
||||
""" Wait for the correct volume to appear
|
||||
But stop if it was killed or the NZB is done
|
||||
"""
|
||||
while not self.have_next_volume() and not self.killed and self.nzo.files:
|
||||
with self.next_file_lock:
|
||||
@@ -382,32 +377,29 @@ class DirectUnpacker(threading.Thread):
|
||||
return
|
||||
|
||||
# Generate command
|
||||
rarfile_path = os.path.join(self.nzo.download_path, self.rarfile_nzf.filename)
|
||||
rarfile_path = os.path.join(self.nzo.downpath, self.rarfile_nzf.filename)
|
||||
if sabnzbd.WIN32:
|
||||
# For Unrar to support long-path, we need to circumvent Python's list2cmdline
|
||||
# For Unrar to support long-path, we need to cricumvent Python's list2cmdline
|
||||
# See: https://github.com/sabnzbd/sabnzbd/issues/1043
|
||||
# The -scf forces the output to be UTF8
|
||||
command = [
|
||||
"%s" % sabnzbd.newsunpack.RAR_COMMAND,
|
||||
action,
|
||||
"-vp",
|
||||
"-idp",
|
||||
"-scf",
|
||||
"-o+",
|
||||
"-ai",
|
||||
password_command,
|
||||
rarfile_path,
|
||||
"%s" % clip_path(rarfile_path),
|
||||
"%s\\" % long_path(extraction_path),
|
||||
]
|
||||
|
||||
else:
|
||||
# Don't use "-ai" (not needed for non-Windows)
|
||||
# The -scf forces the output to be UTF8
|
||||
command = [
|
||||
"%s" % sabnzbd.newsunpack.RAR_COMMAND,
|
||||
action,
|
||||
"-vp",
|
||||
"-idp",
|
||||
"-scf",
|
||||
"-o+",
|
||||
password_command,
|
||||
"%s" % rarfile_path,
|
||||
@@ -419,10 +411,19 @@ class DirectUnpacker(threading.Thread):
|
||||
|
||||
# Let's start from the first one!
|
||||
self.cur_volume = 1
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command, flatten_command=True)
|
||||
logging.debug("Running unrar for DirectUnpack %s", command)
|
||||
# Need to disable buffer to have direct feedback
|
||||
self.active_instance = build_and_run_command(command, flatten_command=True, bufsize=0)
|
||||
|
||||
self.active_instance = Popen(
|
||||
command,
|
||||
shell=False,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
bufsize=0,
|
||||
)
|
||||
# Add to runners
|
||||
ACTIVE_UNPACKERS.append(self)
|
||||
|
||||
@@ -472,7 +473,7 @@ class DirectUnpacker(threading.Thread):
|
||||
# RarFile can fail for mysterious reasons
|
||||
try:
|
||||
rar_contents = RarFile(
|
||||
os.path.join(self.nzo.download_path, rarfile_nzf.filename), single_file_check=True
|
||||
os.path.join(self.nzo.downpath, rarfile_nzf.filename), single_file_check=True
|
||||
).filelist()
|
||||
for rm_file in rar_contents:
|
||||
# Flat-unpack, so remove foldername from RarFile output
|
||||
@@ -501,6 +502,23 @@ class DirectUnpacker(threading.Thread):
|
||||
return self.cur_volume
|
||||
|
||||
|
||||
def analyze_rar_filename(filename):
|
||||
""" Extract volume number and setname from rar-filenames
|
||||
Both ".part01.rar" and ".r01"
|
||||
"""
|
||||
m = RAR_NR.search(filename)
|
||||
if m:
|
||||
if m.group(4):
|
||||
# Special since starts with ".rar", ".r00"
|
||||
return m.group(1), int_conv(m.group(4)) + 2
|
||||
return m.group(1), int_conv(m.group(3))
|
||||
else:
|
||||
# Detect if first of "rxx" set
|
||||
if filename.endswith(".rar"):
|
||||
return os.path.splitext(filename)[0], 1
|
||||
return None, None
|
||||
|
||||
|
||||
def abort_all():
|
||||
""" Abort all running DirectUnpackers """
|
||||
logging.info("Aborting all DirectUnpackers")
|
||||
@@ -509,8 +527,8 @@ def abort_all():
|
||||
|
||||
|
||||
def test_disk_performance():
|
||||
"""Test the incomplete-dir performance and enable
|
||||
Direct Unpack if good enough (> 40MB/s)
|
||||
""" Test the incomplete-dir performance and enable
|
||||
Direct Unpack if good enough (> 40MB/s)
|
||||
"""
|
||||
if diskspeedmeasure(sabnzbd.cfg.download_dir.get_path()) > 40:
|
||||
cfg.direct_unpack.set(True)
|
||||
|
||||
@@ -46,7 +46,7 @@ def compare_stat_tuple(tup1, tup2):
|
||||
|
||||
def clean_file_list(inp_list, folder, files):
|
||||
""" Remove elements of "inp_list" not found in "files" """
|
||||
for path in sorted(inp_list):
|
||||
for path in sorted(inp_list.keys()):
|
||||
fld, name = os.path.split(path)
|
||||
if fld == folder:
|
||||
present = False
|
||||
@@ -59,12 +59,14 @@ def clean_file_list(inp_list, folder, files):
|
||||
|
||||
|
||||
class DirScanner(threading.Thread):
|
||||
"""Thread that periodically scans a given directory and picks up any
|
||||
valid NZB, NZB.GZ ZIP-with-only-NZB and even NZB.GZ named as .NZB
|
||||
Candidates which turned out wrong, will be remembered and skipped in
|
||||
subsequent scans, unless changed.
|
||||
""" Thread that periodically scans a given directory and picks up any
|
||||
valid NZB, NZB.GZ ZIP-with-only-NZB and even NZB.GZ named as .NZB
|
||||
Candidates which turned out wrong, will be remembered and skipped in
|
||||
subsequent scans, unless changed.
|
||||
"""
|
||||
|
||||
do = None # Access to instance of DirScanner
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
@@ -87,6 +89,7 @@ class DirScanner(threading.Thread):
|
||||
self.trigger = False
|
||||
cfg.dirscan_dir.callback(self.newdir)
|
||||
cfg.dirscan_speed.callback(self.newspeed)
|
||||
DirScanner.do = self
|
||||
|
||||
def newdir(self):
|
||||
""" We're notified of a dir change """
|
||||
@@ -102,6 +105,7 @@ class DirScanner(threading.Thread):
|
||||
|
||||
def stop(self):
|
||||
""" Stop the dir scanner """
|
||||
logging.info("Dirscanner shutting down")
|
||||
self.shutdown = True
|
||||
|
||||
def save(self):
|
||||
@@ -209,3 +213,9 @@ class DirScanner(threading.Thread):
|
||||
if os.path.isdir(dpath) and dd.lower() in cats:
|
||||
run_dir(dpath, dd.lower())
|
||||
self.busy = False
|
||||
|
||||
|
||||
def dirscan():
|
||||
""" Wrapper required for scheduler """
|
||||
logging.info("Scheduled or manual watched folder scan")
|
||||
DirScanner.do.scan()
|
||||
|
||||
@@ -27,14 +27,15 @@ from nntplib import NNTPPermanentError
|
||||
import socket
|
||||
import random
|
||||
import sys
|
||||
from typing import List, Dict
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized, NzbQueueLocker, DOWNLOADER_CV
|
||||
from sabnzbd.newswrapper import NewsWrapper, request_server_info
|
||||
import sabnzbd.notifier
|
||||
import sabnzbd.notifier as notifier
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
import sabnzbd.scheduler
|
||||
from sabnzbd.misc import from_units, nntp_to_msg, int_conv
|
||||
from sabnzbd.utils.happyeyeballs import happyeyeballs
|
||||
|
||||
@@ -78,10 +79,10 @@ class Server:
|
||||
self.restart = False
|
||||
self.displayname = displayname
|
||||
self.host = host
|
||||
self.port: int = port
|
||||
self.port = port
|
||||
self.timeout = timeout
|
||||
self.threads = threads
|
||||
self.priority: int = priority
|
||||
self.priority = priority
|
||||
self.ssl = ssl
|
||||
self.ssl_verify = ssl_verify
|
||||
self.ssl_ciphers = ssl_ciphers
|
||||
@@ -109,12 +110,12 @@ class Server:
|
||||
|
||||
@property
|
||||
def hostip(self):
|
||||
"""In case a server still has active connections, we use the same IP again
|
||||
If new connection then based on value of load_balancing() and self.info:
|
||||
0 - return the first entry, so all threads use the same IP
|
||||
1 - and self.info has more than 1 entry (read: IP address): Return a random entry from the possible IPs
|
||||
2 - and self.info has more than 1 entry (read: IP address): Return the quickest IP based on the happyeyeballs algorithm
|
||||
In case of problems: return the host name itself
|
||||
""" In case a server still has active connections, we use the same IP again
|
||||
If new connection then based on value of load_balancing() and self.info:
|
||||
0 - return the first entry, so all threads use the same IP
|
||||
1 - and self.info has more than 1 entry (read: IP address): Return a random entry from the possible IPs
|
||||
2 - and self.info has more than 1 entry (read: IP address): Return the quickest IP based on the happyeyeballs algorithm
|
||||
In case of problems: return the host name itself
|
||||
"""
|
||||
# Check if already a successful ongoing connection
|
||||
if self.busy_threads and self.busy_threads[0].nntp:
|
||||
@@ -161,12 +162,14 @@ class Server:
|
||||
self.idle_threads = []
|
||||
|
||||
def __repr__(self):
|
||||
return "<Server: %s:%s>" % (self.host, self.port)
|
||||
return "%s:%s" % (self.host, self.port)
|
||||
|
||||
|
||||
class Downloader(Thread):
|
||||
""" Singleton Downloader Thread """
|
||||
|
||||
do = None
|
||||
|
||||
def __init__(self, paused=False):
|
||||
Thread.__init__(self)
|
||||
|
||||
@@ -196,21 +199,23 @@ class Downloader(Thread):
|
||||
|
||||
self.force_disconnect = False
|
||||
|
||||
self.read_fds: Dict[int, NewsWrapper] = {}
|
||||
self.write_fds: Dict[int, NewsWrapper] = {}
|
||||
self.read_fds = {}
|
||||
self.write_fds = {}
|
||||
|
||||
self.servers: List[Server] = []
|
||||
self.server_dict: Dict[str, Server] = {} # For faster lookups, but is not updated later!
|
||||
self.servers = []
|
||||
self.server_dict = {} # For faster lookups, but is not updated later!
|
||||
self.server_nr = 0
|
||||
self._timers = {}
|
||||
|
||||
for server in config.get_servers():
|
||||
self.init_server(None, server)
|
||||
|
||||
Downloader.do = self
|
||||
|
||||
def init_server(self, oldserver, newserver):
|
||||
"""Setup or re-setup single server
|
||||
When oldserver is defined and in use, delay startup.
|
||||
Note that the server names are "host:port" strings!
|
||||
""" Setup or re-setup single server
|
||||
When oldserver is defined and in use, delay startup.
|
||||
Note that the server names are "host:port" strings!
|
||||
"""
|
||||
|
||||
create = False
|
||||
@@ -269,6 +274,8 @@ class Downloader(Thread):
|
||||
# Update server-count
|
||||
self.server_nr = len(self.servers)
|
||||
|
||||
return
|
||||
|
||||
@NzbQueueLocker
|
||||
def set_paused_state(self, state):
|
||||
""" Set downloader to specified paused state """
|
||||
@@ -279,7 +286,7 @@ class Downloader(Thread):
|
||||
# Do not notify when SABnzbd is still starting
|
||||
if self.paused and sabnzbd.WEB_DIR:
|
||||
logging.info("Resuming")
|
||||
sabnzbd.notifier.send_notification("SABnzbd", T("Resuming"), "pause_resume")
|
||||
notifier.send_notification("SABnzbd", T("Resuming"), "download")
|
||||
self.paused = False
|
||||
|
||||
@NzbQueueLocker
|
||||
@@ -288,9 +295,9 @@ class Downloader(Thread):
|
||||
if not self.paused:
|
||||
self.paused = True
|
||||
logging.info("Pausing")
|
||||
sabnzbd.notifier.send_notification("SABnzbd", T("Paused"), "pause_resume")
|
||||
notifier.send_notification("SABnzbd", T("Paused"), "download")
|
||||
if self.is_paused():
|
||||
sabnzbd.BPSMeter.reset()
|
||||
BPSMeter.do.reset()
|
||||
if cfg.autodisconnect():
|
||||
self.disconnect()
|
||||
|
||||
@@ -307,9 +314,9 @@ class Downloader(Thread):
|
||||
self.force_disconnect = True
|
||||
|
||||
def limit_speed(self, value):
|
||||
"""Set the actual download speed in Bytes/sec
|
||||
When 'value' ends with a '%' sign or is within 1-100, it is interpreted as a pecentage of the maximum bandwidth
|
||||
When no '%' is found, it is interpreted as an absolute speed (including KMGT notation).
|
||||
""" Set the actual download speed in Bytes/sec
|
||||
When 'value' ends with a '%' sign or is within 1-100, it is interpreted as a pecentage of the maximum bandwidth
|
||||
When no '%' is found, it is interpreted as an absolute speed (including KMGT notation).
|
||||
"""
|
||||
if value:
|
||||
mx = cfg.bandwidth_max.get_int()
|
||||
@@ -319,7 +326,7 @@ class Downloader(Thread):
|
||||
if mx:
|
||||
self.bandwidth_limit = mx * self.bandwidth_perc / 100
|
||||
else:
|
||||
logging.warning_helpful(T("You must set a maximum bandwidth before you can set a bandwidth limit"))
|
||||
logging.warning(T("You must set a maximum bandwidth before you can set a bandwidth limit"))
|
||||
else:
|
||||
self.bandwidth_limit = from_units(value)
|
||||
if mx:
|
||||
@@ -350,14 +357,14 @@ class Downloader(Thread):
|
||||
if not self.paused:
|
||||
return False
|
||||
else:
|
||||
if sabnzbd.NzbQueue.has_forced_items():
|
||||
if sabnzbd.nzbqueue.NzbQueue.do.has_forced_items():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def highest_server(self, me: Server):
|
||||
"""Return True when this server has the highest priority of the active ones
|
||||
0 is the highest priority
|
||||
def highest_server(self, me):
|
||||
""" Return True when this server has the highest priority of the active ones
|
||||
0 is the highest priority
|
||||
"""
|
||||
for server in self.servers:
|
||||
if server is not me and server.active and server.priority < me.priority:
|
||||
@@ -396,30 +403,32 @@ class Downloader(Thread):
|
||||
# Make sure server address resolution is refreshed
|
||||
server.info = None
|
||||
|
||||
sabnzbd.NzbQueue.reset_all_try_lists()
|
||||
sabnzbd.nzbqueue.NzbQueue.do.reset_all_try_lists()
|
||||
|
||||
def decode(self, article, raw_data):
|
||||
"""Decode article and check the status of
|
||||
the decoder and the assembler
|
||||
""" Decode article and check the status of
|
||||
the decoder and the assembler
|
||||
"""
|
||||
# Handle broken articles directly
|
||||
if not raw_data:
|
||||
if not article.search_new_server():
|
||||
sabnzbd.NzbQueue.register_article(article, success=False)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.register_article(article, success=False)
|
||||
return
|
||||
|
||||
# Send to decoder-queue
|
||||
sabnzbd.Decoder.process(article, raw_data)
|
||||
sabnzbd.decoder.Decoder.do.process(article, raw_data)
|
||||
|
||||
# See if we need to delay because the queues are full
|
||||
logged = False
|
||||
while not self.shutdown and (sabnzbd.Decoder.queue_full() or sabnzbd.Assembler.queue_full()):
|
||||
while not self.shutdown and (
|
||||
sabnzbd.decoder.Decoder.do.queue_full() or sabnzbd.assembler.Assembler.do.queue_full()
|
||||
):
|
||||
if not logged:
|
||||
# Only log once, to not waste any CPU-cycles
|
||||
logging.debug(
|
||||
"Delaying - Decoder queue: %s - Assembler queue: %s",
|
||||
sabnzbd.Decoder.decoder_queue.qsize(),
|
||||
sabnzbd.Assembler.queue.qsize(),
|
||||
sabnzbd.decoder.Decoder.do.decoder_queue.qsize(),
|
||||
sabnzbd.assembler.Assembler.do.queue.qsize(),
|
||||
)
|
||||
logged = True
|
||||
time.sleep(0.05)
|
||||
@@ -434,29 +443,12 @@ class Downloader(Thread):
|
||||
logging.debug("SSL verification test: %s", sabnzbd.CERTIFICATE_VALIDATION)
|
||||
|
||||
# Kick BPS-Meter to check quota
|
||||
sabnzbd.BPSMeter.update()
|
||||
|
||||
# Store when each server last searched for articles
|
||||
last_searched = {}
|
||||
|
||||
# Store when each server last downloaded anything or found an article
|
||||
last_busy = {}
|
||||
BPSMeter.do.update()
|
||||
|
||||
while 1:
|
||||
now = time.time()
|
||||
for server in self.servers:
|
||||
serverid = server.id
|
||||
if server.busy_threads:
|
||||
last_busy[serverid] = now
|
||||
else:
|
||||
# Skip this server if idle for 1 second and it has already been searched less than 0.5 seconds ago
|
||||
if last_busy.get(serverid, 0) + 1 < now and last_searched.get(serverid, 0) + 0.5 > now:
|
||||
continue
|
||||
|
||||
last_searched[serverid] = now
|
||||
|
||||
for nw in server.busy_threads[:]:
|
||||
if (nw.nntp and nw.nntp.error_msg) or (nw.timeout and now > nw.timeout):
|
||||
if (nw.nntp and nw.nntp.error_msg) or (nw.timeout and time.time() > nw.timeout):
|
||||
if nw.nntp and nw.nntp.error_msg:
|
||||
self.__reset_nw(nw, "", warn=False)
|
||||
else:
|
||||
@@ -471,7 +463,7 @@ class Downloader(Thread):
|
||||
if newid:
|
||||
self.init_server(None, newid)
|
||||
self.__restart -= 1
|
||||
sabnzbd.NzbQueue.reset_all_try_lists()
|
||||
sabnzbd.nzbqueue.NzbQueue.do.reset_all_try_lists()
|
||||
# Have to leave this loop, because we removed element
|
||||
break
|
||||
else:
|
||||
@@ -486,26 +478,24 @@ class Downloader(Thread):
|
||||
|
||||
for nw in server.idle_threads[:]:
|
||||
if nw.timeout:
|
||||
if now < nw.timeout:
|
||||
if time.time() < nw.timeout:
|
||||
continue
|
||||
else:
|
||||
nw.timeout = None
|
||||
|
||||
if not server.info:
|
||||
# Only request info if there's stuff in the queue
|
||||
if not sabnzbd.NzbQueue.is_empty():
|
||||
if not sabnzbd.nzbqueue.NzbQueue.do.is_empty():
|
||||
self.maybe_block_server(server)
|
||||
request_server_info(server)
|
||||
break
|
||||
|
||||
article = sabnzbd.NzbQueue.get_article(server, self.servers)
|
||||
article = sabnzbd.nzbqueue.NzbQueue.do.get_article(server, self.servers)
|
||||
|
||||
if not article:
|
||||
break
|
||||
|
||||
last_busy[serverid] = now
|
||||
|
||||
if server.retention and article.nzf.nzo.avg_stamp < now - server.retention:
|
||||
if server.retention and article.nzf.nzo.avg_stamp < time.time() - server.retention:
|
||||
# Let's get rid of all the articles for this server at once
|
||||
logging.info("Job %s too old for %s, moving on", article.nzf.nzo.final_name, server.host)
|
||||
while article:
|
||||
@@ -572,26 +562,26 @@ class Downloader(Thread):
|
||||
# Need to initialize the check during first 20 seconds
|
||||
if self.can_be_slowed is None or self.can_be_slowed_timer:
|
||||
# Wait for stable speed to start testing
|
||||
if not self.can_be_slowed_timer and sabnzbd.BPSMeter.get_stable_speed(timespan=10):
|
||||
self.can_be_slowed_timer = now
|
||||
if not self.can_be_slowed_timer and BPSMeter.do.get_stable_speed(timespan=10):
|
||||
self.can_be_slowed_timer = time.time()
|
||||
|
||||
# Check 10 seconds after enabling slowdown
|
||||
if self.can_be_slowed_timer and now > self.can_be_slowed_timer + 10:
|
||||
if self.can_be_slowed_timer and time.time() > self.can_be_slowed_timer + 10:
|
||||
# Now let's check if it was stable in the last 10 seconds
|
||||
self.can_be_slowed = sabnzbd.BPSMeter.get_stable_speed(timespan=10)
|
||||
self.can_be_slowed = BPSMeter.do.get_stable_speed(timespan=10)
|
||||
self.can_be_slowed_timer = 0
|
||||
logging.debug("Downloader-slowdown: %r", self.can_be_slowed)
|
||||
|
||||
else:
|
||||
read, write, error = ([], [], [])
|
||||
|
||||
sabnzbd.BPSMeter.reset()
|
||||
BPSMeter.do.reset()
|
||||
|
||||
time.sleep(1.0)
|
||||
|
||||
DOWNLOADER_CV.acquire()
|
||||
while (
|
||||
(sabnzbd.NzbQueue.is_empty() or self.is_paused() or self.postproc)
|
||||
(sabnzbd.nzbqueue.NzbQueue.do.is_empty() or self.is_paused() or self.postproc)
|
||||
and not self.shutdown
|
||||
and not self.__restart
|
||||
):
|
||||
@@ -612,7 +602,7 @@ class Downloader(Thread):
|
||||
self.write_fds.pop(fileno)
|
||||
|
||||
if not read:
|
||||
sabnzbd.BPSMeter.update()
|
||||
BPSMeter.do.update()
|
||||
continue
|
||||
|
||||
for selected in read:
|
||||
@@ -620,13 +610,16 @@ class Downloader(Thread):
|
||||
article = nw.article
|
||||
server = nw.server
|
||||
|
||||
if article:
|
||||
nzo = article.nzf.nzo
|
||||
|
||||
try:
|
||||
bytes_received, done, skip = nw.recv_chunk()
|
||||
except:
|
||||
bytes_received, done, skip = (0, False, False)
|
||||
|
||||
if skip:
|
||||
sabnzbd.BPSMeter.update()
|
||||
BPSMeter.do.update()
|
||||
continue
|
||||
|
||||
if bytes_received < 1:
|
||||
@@ -636,12 +629,12 @@ class Downloader(Thread):
|
||||
else:
|
||||
if self.bandwidth_limit:
|
||||
limit = self.bandwidth_limit
|
||||
if bytes_received + sabnzbd.BPSMeter.bps > limit:
|
||||
while sabnzbd.BPSMeter.bps > limit:
|
||||
if bytes_received + BPSMeter.do.bps > limit:
|
||||
while BPSMeter.do.bps > limit:
|
||||
time.sleep(0.05)
|
||||
sabnzbd.BPSMeter.update()
|
||||
sabnzbd.BPSMeter.update(server.id, bytes_received)
|
||||
article.nzf.nzo.update_download_stats(sabnzbd.BPSMeter.bps, server.id, bytes_received)
|
||||
BPSMeter.do.update()
|
||||
BPSMeter.do.update(server.id, bytes_received)
|
||||
nzo.update_download_stats(BPSMeter.do.bps, server.id, bytes_received)
|
||||
|
||||
if not done and nw.status_code != 222:
|
||||
if not nw.connected or nw.status_code == 480:
|
||||
@@ -723,7 +716,7 @@ class Downloader(Thread):
|
||||
server.active = False
|
||||
if penalty and (block or server.optional):
|
||||
self.plan_server(server, penalty)
|
||||
sabnzbd.NzbQueue.reset_all_try_lists()
|
||||
sabnzbd.nzbqueue.NzbQueue.do.reset_all_try_lists()
|
||||
self.__reset_nw(nw, None, warn=False, send_quit=True)
|
||||
continue
|
||||
except:
|
||||
@@ -763,7 +756,7 @@ class Downloader(Thread):
|
||||
nw.clear_data()
|
||||
|
||||
elif nw.status_code == 500:
|
||||
if article.nzf.nzo.precheck:
|
||||
if nzo.precheck:
|
||||
# Assume "STAT" command is not supported
|
||||
server.have_stat = False
|
||||
logging.debug("Server %s does not support STAT", server.host)
|
||||
@@ -830,7 +823,7 @@ class Downloader(Thread):
|
||||
self.decode(article, None)
|
||||
else:
|
||||
# Allow all servers to iterate over each nzo/nzf again
|
||||
sabnzbd.NzbQueue.reset_try_lists(article)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.reset_try_lists(article)
|
||||
|
||||
if destroy:
|
||||
nw.terminate(quit=send_quit)
|
||||
@@ -840,7 +833,7 @@ class Downloader(Thread):
|
||||
# Empty SSL info, it might change on next connect
|
||||
server.ssl_info = ""
|
||||
|
||||
def __request_article(self, nw: NewsWrapper):
|
||||
def __request_article(self, nw):
|
||||
try:
|
||||
nzo = nw.article.nzf.nzo
|
||||
if nw.server.send_group and nzo.group != nw.group:
|
||||
@@ -884,7 +877,7 @@ class Downloader(Thread):
|
||||
stamp = time.time() + 60.0 * interval
|
||||
self._timers[server.id].append(stamp)
|
||||
if interval:
|
||||
sabnzbd.Scheduler.plan_server(self.trigger_server, [server.id, stamp], interval)
|
||||
sabnzbd.scheduler.plan_server(self.trigger_server, [server.id, stamp], interval)
|
||||
|
||||
@synchronized(TIMER_LOCK)
|
||||
def trigger_server(self, server_id, timestamp):
|
||||
@@ -921,8 +914,7 @@ class Downloader(Thread):
|
||||
# Clean expired timers
|
||||
now = time.time()
|
||||
kicked = []
|
||||
# Create a copy so we can remove during iteration
|
||||
for server_id in list(self._timers):
|
||||
for server_id in self._timers.keys():
|
||||
if not [stamp for stamp in self._timers[server_id] if stamp >= now]:
|
||||
logging.debug("Forcing re-evaluation of server-id %s", server_id)
|
||||
del self._timers[server_id]
|
||||
@@ -936,8 +928,8 @@ class Downloader(Thread):
|
||||
self.init_server(server.id, server.id)
|
||||
|
||||
def update_server(self, oldserver, newserver):
|
||||
"""Update the server and make sure we trigger
|
||||
the update in the loop to do housekeeping"""
|
||||
""" Update the server and make sure we trigger
|
||||
the update in the loop to do housekeeping """
|
||||
self.init_server(oldserver, newserver)
|
||||
self.wakeup()
|
||||
|
||||
@@ -948,18 +940,18 @@ class Downloader(Thread):
|
||||
|
||||
def stop(self):
|
||||
self.shutdown = True
|
||||
sabnzbd.notifier.send_notification("SABnzbd", T("Shutting down"), "startup")
|
||||
notifier.send_notification("SABnzbd", T("Shutting down"), "startup")
|
||||
|
||||
|
||||
def stop():
|
||||
DOWNLOADER_CV.acquire()
|
||||
try:
|
||||
sabnzbd.Downloader.stop()
|
||||
Downloader.do.stop()
|
||||
finally:
|
||||
DOWNLOADER_CV.notify_all()
|
||||
DOWNLOADER_CV.release()
|
||||
try:
|
||||
sabnzbd.Downloader.join()
|
||||
Downloader.do.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import glob
|
||||
|
||||
from Cheetah.Template import Template
|
||||
from email.message import EmailMessage
|
||||
from email import policy
|
||||
|
||||
from sabnzbd.constants import *
|
||||
import sabnzbd
|
||||
@@ -297,4 +296,4 @@ def _prepare_message(txt):
|
||||
msg[keyword] = value
|
||||
|
||||
msg.set_content("\n".join(payload))
|
||||
return msg.as_bytes(policy=msg.policy.clone(linesep="\r\n"))
|
||||
return msg.as_bytes()
|
||||
|
||||
@@ -22,29 +22,29 @@ sabnzbd.encoding - Unicode/byte translation functions
|
||||
import locale
|
||||
import chardet
|
||||
from xml.sax.saxutils import escape
|
||||
from typing import AnyStr
|
||||
|
||||
|
||||
CODEPAGE = locale.getpreferredencoding()
|
||||
|
||||
|
||||
def utob(str_in: AnyStr) -> bytes:
|
||||
""" Shorthand for converting UTF-8 string to bytes """
|
||||
def utob(str_in):
|
||||
""" Shorthand for converting UTF-8 to bytes """
|
||||
if isinstance(str_in, bytes):
|
||||
return str_in
|
||||
return str_in.encode("utf-8")
|
||||
|
||||
|
||||
def ubtou(str_in: AnyStr) -> str:
|
||||
""" Shorthand for converting unicode bytes to UTF-8 string """
|
||||
def ubtou(str_in):
|
||||
""" Shorthand for converting unicode bytes to UTF-8 """
|
||||
if not isinstance(str_in, bytes):
|
||||
return str_in
|
||||
return str_in.decode("utf-8")
|
||||
|
||||
|
||||
def platform_btou(str_in: AnyStr) -> str:
|
||||
"""Return Unicode string, if not already Unicode, decode with locale encoding.
|
||||
NOTE: Used for POpen because universal_newlines/text parameter doesn't
|
||||
always work! We cannot use encoding-parameter because it's Python 3.7+
|
||||
def platform_btou(str_in):
|
||||
""" Return Unicode, if not already Unicode, decode with locale encoding.
|
||||
NOTE: Used for POpen because universal_newlines/text parameter doesn't
|
||||
always work! We cannot use encoding-parameter because it's Python 3.7+
|
||||
"""
|
||||
if isinstance(str_in, bytes):
|
||||
try:
|
||||
@@ -55,11 +55,11 @@ def platform_btou(str_in: AnyStr) -> str:
|
||||
return str_in
|
||||
|
||||
|
||||
def correct_unknown_encoding(str_or_bytes_in: AnyStr) -> str:
|
||||
"""Files created on Windows but unpacked/repaired on
|
||||
linux can result in invalid filenames. Try to fix this
|
||||
encoding by going to bytes and then back to unicode again.
|
||||
Last resort we use chardet package
|
||||
def correct_unknown_encoding(str_or_bytes_in):
|
||||
""" Files created on Windows but unpacked/repaired on
|
||||
linux can result in invalid filenames. Try to fix this
|
||||
encoding by going to bytes and then back to unicode again.
|
||||
Last resort we use chardet package
|
||||
"""
|
||||
# If already string, back to bytes
|
||||
if not isinstance(str_or_bytes_in, bytes):
|
||||
|
||||
@@ -29,12 +29,6 @@ import time
|
||||
import fnmatch
|
||||
import stat
|
||||
import zipfile
|
||||
from typing import Union, List, Tuple, Any, Dict, Optional
|
||||
|
||||
try:
|
||||
import win32api
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized
|
||||
@@ -43,7 +37,7 @@ from sabnzbd.encoding import correct_unknown_encoding
|
||||
from sabnzbd.utils import rarfile
|
||||
|
||||
|
||||
def get_ext(filename: str) -> str:
|
||||
def get_ext(filename):
|
||||
""" Return lowercased file extension """
|
||||
try:
|
||||
return os.path.splitext(filename)[1].lower()
|
||||
@@ -51,7 +45,7 @@ def get_ext(filename: str) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def get_filename(path: str) -> str:
|
||||
def get_filename(path):
|
||||
""" Return path without the file extension """
|
||||
try:
|
||||
return os.path.split(path)[1]
|
||||
@@ -59,33 +53,12 @@ def get_filename(path: str) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def setname_from_path(path: str) -> str:
|
||||
def setname_from_path(path):
|
||||
""" Get the setname from a path """
|
||||
return os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
|
||||
RAR_NR = re.compile(r"(.*?)(\.part(\d*).rar|\.r(\d*))$", re.IGNORECASE)
|
||||
|
||||
|
||||
def analyze_rar_filename(filename_or_path: str) -> Tuple[Optional[str], Optional[int]]:
|
||||
"""Extract volume number and setname from rar-filenames or paths
|
||||
Both ".part01.rar" and ".r01" work
|
||||
"""
|
||||
filename = os.path.basename(filename_or_path)
|
||||
m = RAR_NR.search(filename)
|
||||
if m:
|
||||
if m.group(4):
|
||||
# Special since starts with ".rar", ".r00"
|
||||
return m.group(1), sabnzbd.misc.int_conv(m.group(4)) + 2
|
||||
return m.group(1), sabnzbd.misc.int_conv(m.group(3))
|
||||
else:
|
||||
# Detect if first of "rxx" set
|
||||
if filename.endswith(".rar"):
|
||||
return os.path.splitext(filename)[0], 1
|
||||
return None, None
|
||||
|
||||
|
||||
def is_writable(path: str) -> bool:
|
||||
def is_writable(path):
|
||||
""" Return True is file is writable (also when non-existent) """
|
||||
if os.path.isfile(path):
|
||||
return bool(os.stat(path).st_mode & stat.S_IWUSR)
|
||||
@@ -119,10 +92,10 @@ _DEVICES = (
|
||||
)
|
||||
|
||||
|
||||
def replace_win_devices(name: str) -> str:
|
||||
"""Remove reserved Windows device names from a name.
|
||||
aux.txt ==> _aux.txt
|
||||
txt.aux ==> txt.aux
|
||||
def replace_win_devices(name):
|
||||
""" Remove reserved Windows device names from a name.
|
||||
aux.txt ==> _aux.txt
|
||||
txt.aux ==> txt.aux
|
||||
"""
|
||||
if name:
|
||||
lname = name.lower()
|
||||
@@ -138,13 +111,13 @@ def replace_win_devices(name: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def has_win_device(filename: str) -> bool:
|
||||
"""Return True if filename part contains forbidden name
|
||||
Before and after sanitizing
|
||||
def has_win_device(p):
|
||||
""" Return True if filename part contains forbidden name
|
||||
Before and after sanitizing
|
||||
"""
|
||||
filename = os.path.split(filename)[1].lower()
|
||||
p = os.path.split(p)[1].lower()
|
||||
for dev in _DEVICES:
|
||||
if filename == dev or filename.startswith(dev + ".") or filename.startswith("_" + dev + "."):
|
||||
if p == dev or p.startswith(dev + ".") or p.startswith("_" + dev + "."):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -155,9 +128,9 @@ CH_ILLEGAL_WIN = '\\/<>?*|"\t:'
|
||||
CH_LEGAL_WIN = "++{}!@#'+-"
|
||||
|
||||
|
||||
def sanitize_filename(name: str) -> str:
|
||||
"""Return filename with illegal chars converted to legal ones
|
||||
and with the par2 extension always in lowercase
|
||||
def sanitize_filename(name):
|
||||
""" Return filename with illegal chars converted to legal ones
|
||||
and with the par2 extension always in lowercase
|
||||
"""
|
||||
if not name:
|
||||
return name
|
||||
@@ -171,7 +144,7 @@ def sanitize_filename(name: str) -> str:
|
||||
legal += CH_LEGAL_WIN
|
||||
|
||||
if ":" in name and sabnzbd.DARWIN:
|
||||
# Compensate for the foolish way par2 on macOS handles a colon character
|
||||
# Compensate for the foolish way par2 on OSX handles a colon character
|
||||
name = name[name.rfind(":") + 1 :]
|
||||
|
||||
lst = []
|
||||
@@ -194,9 +167,9 @@ def sanitize_filename(name: str) -> str:
|
||||
return name + ext
|
||||
|
||||
|
||||
def sanitize_foldername(name: str) -> str:
|
||||
"""Return foldername with dodgy chars converted to safe ones
|
||||
Remove any leading and trailing dot and space characters
|
||||
def sanitize_foldername(name):
|
||||
""" Return foldername with dodgy chars converted to safe ones
|
||||
Remove any leading and trailing dot and space characters
|
||||
"""
|
||||
if not name:
|
||||
return name
|
||||
@@ -224,9 +197,6 @@ def sanitize_foldername(name: str) -> str:
|
||||
if sabnzbd.WIN32 or sabnzbd.cfg.sanitize_safe():
|
||||
name = replace_win_devices(name)
|
||||
|
||||
if len(name) >= sabnzbd.cfg.max_foldername_length():
|
||||
name = name[: sabnzbd.cfg.max_foldername_length()]
|
||||
|
||||
# And finally, make sure it doesn't end in a dot
|
||||
if name != "." and name != "..":
|
||||
name = name.rstrip(".")
|
||||
@@ -236,7 +206,7 @@ def sanitize_foldername(name: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def sanitize_and_trim_path(path: str) -> str:
|
||||
def sanitize_and_trim_path(path):
|
||||
""" Remove illegal characters and trim element size """
|
||||
path = path.strip()
|
||||
new_path = ""
|
||||
@@ -263,7 +233,8 @@ def sanitize_and_trim_path(path: str) -> str:
|
||||
|
||||
|
||||
def sanitize_files_in_folder(folder):
|
||||
"""Sanitize each file in the folder, return list of new names"""
|
||||
""" Sanitize each file in the folder, return list of new names
|
||||
"""
|
||||
lst = []
|
||||
for root, _, files in os.walk(folder):
|
||||
for file_ in files:
|
||||
@@ -279,17 +250,17 @@ def sanitize_files_in_folder(folder):
|
||||
return lst
|
||||
|
||||
|
||||
def is_obfuscated_filename(filename: str) -> bool:
|
||||
"""Check if this file has an extension, if not, it's
|
||||
probably obfuscated and we don't use it
|
||||
def is_obfuscated_filename(filename):
|
||||
""" Check if this file has an extension, if not, it's
|
||||
probably obfuscated and we don't use it
|
||||
"""
|
||||
return len(get_ext(filename)) < 2
|
||||
|
||||
|
||||
def real_path(loc: str, path: str) -> str:
|
||||
"""When 'path' is relative, return normalized join of 'loc' and 'path'
|
||||
When 'path' is absolute, return normalized path
|
||||
A path starting with ~ will be located in the user's Home folder
|
||||
def real_path(loc, path):
|
||||
""" When 'path' is relative, return normalized join of 'loc' and 'path'
|
||||
When 'path' is absolute, return normalized path
|
||||
A path starting with ~ will be located in the user's Home folder
|
||||
"""
|
||||
# The Windows part is a bit convoluted because
|
||||
# C: and C:\ are 2 different things
|
||||
@@ -301,9 +272,6 @@ def real_path(loc: str, path: str) -> str:
|
||||
if not sabnzbd.WIN32 and path.startswith("~/"):
|
||||
path = path.replace("~", os.environ.get("HOME", sabnzbd.DIR_HOME), 1)
|
||||
if sabnzbd.WIN32:
|
||||
# The Windows-functions work differently on long-path
|
||||
# So we bring it back to normal and make it long-path at the end
|
||||
loc = clip_path(loc)
|
||||
path = path.replace("/", "\\")
|
||||
if len(path) > 1 and path[0].isalpha() and path[1] == ":":
|
||||
if len(path) == 2 or path[2] != "\\":
|
||||
@@ -323,15 +291,13 @@ def real_path(loc: str, path: str) -> str:
|
||||
return long_path(os.path.normpath(os.path.abspath(path)))
|
||||
|
||||
|
||||
def create_real_path(
|
||||
name: str, loc: str, path: str, umask: bool = False, writable: bool = True
|
||||
) -> Tuple[bool, str, Optional[str]]:
|
||||
"""When 'path' is relative, create join of 'loc' and 'path'
|
||||
When 'path' is absolute, create normalized path
|
||||
'name' is used for logging.
|
||||
Optional 'umask' will be applied.
|
||||
'writable' means that an existing folder should be writable
|
||||
Returns ('success', 'full path', 'error_msg')
|
||||
def create_real_path(name, loc, path, umask=False, writable=True):
|
||||
""" When 'path' is relative, create join of 'loc' and 'path'
|
||||
When 'path' is absolute, create normalized path
|
||||
'name' is used for logging.
|
||||
Optional 'umask' will be applied.
|
||||
'writable' means that an existing folder should be writable
|
||||
Returns ('success', 'full path', 'error_msg')
|
||||
"""
|
||||
if path:
|
||||
my_dir = real_path(loc, path)
|
||||
@@ -353,10 +319,10 @@ def create_real_path(
|
||||
return False, path, None
|
||||
|
||||
|
||||
def same_file(a: str, b: str) -> int:
|
||||
"""Return 0 if A and B have nothing in common
|
||||
return 1 if A and B are actually the same path
|
||||
return 2 if B is a subfolder of A
|
||||
def same_file(a, b):
|
||||
""" Return 0 if A and B have nothing in common
|
||||
return 1 if A and B are actually the same path
|
||||
return 2 if B is a subfolder of A
|
||||
"""
|
||||
if sabnzbd.WIN32 or sabnzbd.DARWIN:
|
||||
a = clip_path(a.lower())
|
||||
@@ -382,8 +348,8 @@ def same_file(a: str, b: str) -> int:
|
||||
return is_subfolder
|
||||
|
||||
|
||||
def is_archive(path: str) -> Tuple[int, Any, str]:
|
||||
"""Check if file in path is an ZIP, RAR or 7z file
|
||||
def is_archive(path):
|
||||
""" Check if file in path is an ZIP, RAR or 7z file
|
||||
:param path: path to file
|
||||
:return: (zf, status, expected_extension)
|
||||
status: -1==Error/Retry, 0==OK, 1==Ignore
|
||||
@@ -416,9 +382,9 @@ def is_archive(path: str) -> Tuple[int, Any, str]:
|
||||
return 1, None, ""
|
||||
|
||||
|
||||
def check_mount(path: str) -> bool:
|
||||
"""Return False if volume isn't mounted on Linux or macOS
|
||||
Retry 6 times with an interval of 1 sec.
|
||||
def check_mount(path):
|
||||
""" Return False if volume isn't mounted on Linux or OSX
|
||||
Retry 6 times with an interval of 1 sec.
|
||||
"""
|
||||
if sabnzbd.DARWIN:
|
||||
m = re.search(r"^(/Volumes/[^/]+)", path, re.I)
|
||||
@@ -436,9 +402,9 @@ def check_mount(path: str) -> bool:
|
||||
return not m
|
||||
|
||||
|
||||
def safe_fnmatch(f: str, pattern: str) -> bool:
|
||||
"""fnmatch will fail if the pattern contains any of it's
|
||||
key characters, like [, ] or !.
|
||||
def safe_fnmatch(f, pattern):
|
||||
""" fnmatch will fail if the pattern contains any of it's
|
||||
key characters, like [, ] or !.
|
||||
"""
|
||||
try:
|
||||
return fnmatch.fnmatch(f, pattern)
|
||||
@@ -446,7 +412,7 @@ def safe_fnmatch(f: str, pattern: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def globber(path: str, pattern: str = "*") -> List[str]:
|
||||
def globber(path, pattern="*"):
|
||||
""" Return matching base file/folder names in folder `path` """
|
||||
# Cannot use glob.glob() because it doesn't support Windows long name notation
|
||||
if os.path.exists(path):
|
||||
@@ -454,7 +420,7 @@ def globber(path: str, pattern: str = "*") -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
def globber_full(path: str, pattern: str = "*") -> List[str]:
|
||||
def globber_full(path, pattern="*"):
|
||||
""" Return matching full file/folder names in folder `path` """
|
||||
# Cannot use glob.glob() because it doesn't support Windows long name notation
|
||||
if os.path.exists(path):
|
||||
@@ -462,10 +428,21 @@ def globber_full(path: str, pattern: str = "*") -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
def fix_unix_encoding(folder: str):
|
||||
"""Fix bad name encoding for Unix systems
|
||||
This happens for example when files are created
|
||||
on Windows but unpacked/repaired on linux
|
||||
def trim_win_path(path):
|
||||
""" Make sure Windows path stays below 70 by trimming last part """
|
||||
if sabnzbd.WIN32 and len(path) > 69:
|
||||
path, folder = os.path.split(path)
|
||||
maxlen = 69 - len(path)
|
||||
if len(folder) > maxlen:
|
||||
folder = folder[:maxlen]
|
||||
path = os.path.join(path, folder).rstrip(". ")
|
||||
return path
|
||||
|
||||
|
||||
def fix_unix_encoding(folder):
|
||||
""" Fix bad name encoding for Unix systems
|
||||
This happens for example when files are created
|
||||
on Windows but unpacked/repaired on linux
|
||||
"""
|
||||
if not sabnzbd.WIN32 and not sabnzbd.DARWIN:
|
||||
for root, dirs, files in os.walk(folder):
|
||||
@@ -478,7 +455,7 @@ def fix_unix_encoding(folder: str):
|
||||
logging.info("Cannot correct name of %s", os.path.join(root, name))
|
||||
|
||||
|
||||
def make_script_path(script: str) -> Optional[str]:
|
||||
def make_script_path(script):
|
||||
""" Return full script path, if any valid script exists, else None """
|
||||
script_path = None
|
||||
script_dir = sabnzbd.cfg.script_dir.get_path()
|
||||
@@ -493,9 +470,9 @@ def make_script_path(script: str) -> Optional[str]:
|
||||
return script_path
|
||||
|
||||
|
||||
def get_admin_path(name: str, future: bool):
|
||||
"""Return news-style full path to job-admin folder of names job
|
||||
or else the old cache path
|
||||
def get_admin_path(name, future):
|
||||
""" Return news-style full path to job-admin folder of names job
|
||||
or else the old cache path
|
||||
"""
|
||||
if future:
|
||||
return os.path.join(sabnzbd.cfg.admin_dir.get_path(), FUTURE_Q_FOLDER)
|
||||
@@ -503,7 +480,7 @@ def get_admin_path(name: str, future: bool):
|
||||
return os.path.join(os.path.join(sabnzbd.cfg.download_dir.get_path(), name), JOB_ADMIN)
|
||||
|
||||
|
||||
def set_chmod(path: str, permissions: int, report: bool):
|
||||
def set_chmod(path, permissions, report):
|
||||
""" Set 'permissions' on 'path', report any errors when 'report' is True """
|
||||
try:
|
||||
logging.debug("Applying permissions %s (octal) to %s", oct(permissions), path)
|
||||
@@ -515,7 +492,7 @@ def set_chmod(path: str, permissions: int, report: bool):
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
|
||||
|
||||
def set_permissions(path: str, recursive: bool = True):
|
||||
def set_permissions(path, recursive=True):
|
||||
""" Give folder tree and its files their proper permissions """
|
||||
if not sabnzbd.WIN32:
|
||||
umask = sabnzbd.cfg.umask()
|
||||
@@ -546,28 +523,14 @@ def set_permissions(path: str, recursive: bool = True):
|
||||
set_chmod(path, umask_file, report)
|
||||
|
||||
|
||||
def userxbit(filename: str) -> bool:
|
||||
"""Returns boolean if the x-bit for user is set on the given file.
|
||||
This is a workaround: os.access(filename, os.X_OK) does not work
|
||||
on certain mounted file systems. Does not work at all on Windows.
|
||||
"""
|
||||
# rwx rwx rwx
|
||||
# 876 543 210 # we want bit 6 from the right, counting from 0
|
||||
userxbit = 1 << 6 # bit 6
|
||||
rwxbits = os.stat(filename)[0] # the first element of os.stat() is "mode"
|
||||
# do logical AND, check if it is not 0:
|
||||
xbitset = (rwxbits & userxbit) > 0
|
||||
return xbitset
|
||||
|
||||
|
||||
def clip_path(path: str) -> str:
|
||||
def clip_path(path):
|
||||
r""" Remove \\?\ or \\?\UNC\ prefix from Windows path """
|
||||
if sabnzbd.WIN32 and path and "?" in path:
|
||||
path = path.replace("\\\\?\\UNC\\", "\\\\", 1).replace("\\\\?\\", "", 1)
|
||||
return path
|
||||
|
||||
|
||||
def long_path(path: str) -> str:
|
||||
def long_path(path):
|
||||
""" For Windows, convert to long style path; others, return same path """
|
||||
if sabnzbd.WIN32 and path and not path.startswith("\\\\?\\"):
|
||||
if path.startswith("\\\\"):
|
||||
@@ -586,18 +549,15 @@ DIR_LOCK = threading.RLock()
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def create_all_dirs(path: str, apply_umask: bool = False) -> Union[str, bool]:
|
||||
"""Create all required path elements and set umask on all
|
||||
The umask argument is ignored on Windows
|
||||
Return path if elements could be made or exists
|
||||
def create_all_dirs(path, apply_umask=False):
|
||||
""" Create all required path elements and set umask on all
|
||||
The umask argument is ignored on Windows
|
||||
Return path if elements could be made or exists
|
||||
"""
|
||||
try:
|
||||
logging.info("Creating directories: %s", path)
|
||||
if sabnzbd.WIN32:
|
||||
# On Windows it can fail on UNC-paths in long-path notation
|
||||
# https://bugs.python.org/issue41705
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
else:
|
||||
# We need to build the directory recursively so we can
|
||||
# apply permissions to only the newly created folders
|
||||
@@ -624,7 +584,7 @@ def create_all_dirs(path: str, apply_umask: bool = False) -> Union[str, bool]:
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def get_unique_path(dirpath: str, n: int = 0, create_dir: bool = True) -> str:
|
||||
def get_unique_path(dirpath, n=0, create_dir=True):
|
||||
""" Determine a unique folder or filename """
|
||||
|
||||
if not check_mount(dirpath):
|
||||
@@ -644,9 +604,9 @@ def get_unique_path(dirpath: str, n: int = 0, create_dir: bool = True) -> str:
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def get_unique_filename(path: str) -> str:
|
||||
"""Check if path is unique.
|
||||
If not, add number like: "/path/name.NUM.ext".
|
||||
def get_unique_filename(path):
|
||||
""" Check if path is unique.
|
||||
If not, add number like: "/path/name.NUM.ext".
|
||||
"""
|
||||
num = 1
|
||||
new_path, fname = os.path.split(path)
|
||||
@@ -659,7 +619,7 @@ def get_unique_filename(path: str) -> str:
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def listdir_full(input_dir: str, recursive: bool = True) -> List[str]:
|
||||
def listdir_full(input_dir, recursive=True):
|
||||
""" List all files in dirs and sub-dirs """
|
||||
filelist = []
|
||||
for root, dirs, files in os.walk(input_dir):
|
||||
@@ -673,9 +633,9 @@ def listdir_full(input_dir: str, recursive: bool = True) -> List[str]:
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def move_to_path(path: str, new_path: str) -> Tuple[bool, Optional[str]]:
|
||||
"""Move a file to a new path, optionally give unique filename
|
||||
Return (ok, new_path)
|
||||
def move_to_path(path, new_path):
|
||||
""" Move a file to a new path, optionally give unique filename
|
||||
Return (ok, new_path)
|
||||
"""
|
||||
ok = True
|
||||
overwrite = sabnzbd.cfg.overwrite_files()
|
||||
@@ -713,7 +673,7 @@ def move_to_path(path: str, new_path: str) -> Tuple[bool, Optional[str]]:
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def cleanup_empty_directories(path: str):
|
||||
def cleanup_empty_directories(path):
|
||||
""" Remove all empty folders inside (and including) 'path' """
|
||||
path = os.path.normpath(path)
|
||||
while 1:
|
||||
@@ -737,7 +697,7 @@ def cleanup_empty_directories(path: str):
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def get_filepath(path: str, nzo, filename: str):
|
||||
def get_filepath(path, nzo, filename):
|
||||
""" Create unique filepath """
|
||||
# This procedure is only used by the Assembler thread
|
||||
# It does no umask setting
|
||||
@@ -774,7 +734,7 @@ def get_filepath(path: str, nzo, filename: str):
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def renamer(old: str, new: str):
|
||||
def renamer(old, new):
|
||||
""" Rename file/folder with retries for Win32 """
|
||||
# Sanitize last part of new name
|
||||
path, name = os.path.split(new)
|
||||
@@ -786,11 +746,11 @@ def renamer(old: str, new: str):
|
||||
|
||||
logging.debug('Renaming "%s" to "%s"', old, new)
|
||||
if sabnzbd.WIN32:
|
||||
retries = 10
|
||||
retries = 15
|
||||
while retries > 0:
|
||||
try:
|
||||
# First we try 3 times with os.rename
|
||||
if retries > 7:
|
||||
if retries > 12:
|
||||
os.rename(old, new)
|
||||
else:
|
||||
# Now we try the back-up method
|
||||
@@ -803,12 +763,11 @@ def renamer(old: str, new: str):
|
||||
# Error 17 - Rename can't move to different disk
|
||||
# Jump to moving with shutil.move
|
||||
retries -= 3
|
||||
elif err.winerror == 32 or err.winerror == 5:
|
||||
elif err.winerror == 32:
|
||||
# Error 32 - Used by another process
|
||||
# Error 5 - Access is denied (virus scanners)
|
||||
logging.debug("File busy, retrying rename %s to %s", old, new)
|
||||
retries -= 1
|
||||
# Wait for the other process
|
||||
# Wait for the other process to finish
|
||||
time.sleep(2)
|
||||
else:
|
||||
raise
|
||||
@@ -817,14 +776,14 @@ def renamer(old: str, new: str):
|
||||
shutil.move(old, new)
|
||||
|
||||
|
||||
def remove_file(path: str):
|
||||
def remove_file(path):
|
||||
""" Wrapper function so any file removal is logged """
|
||||
logging.debug("[%s] Deleting file %s", sabnzbd.misc.caller_name(), path)
|
||||
os.remove(path)
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def remove_dir(path: str):
|
||||
def remove_dir(path):
|
||||
""" Remove directory with retries for Win32 """
|
||||
logging.debug("[%s] Removing dir %s", sabnzbd.misc.caller_name(), path)
|
||||
if sabnzbd.WIN32:
|
||||
@@ -847,7 +806,7 @@ def remove_dir(path: str):
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def remove_all(path: str, pattern: str = "*", keep_folder: bool = False, recursive: bool = False):
|
||||
def remove_all(path, pattern="*", keep_folder=False, recursive=False):
|
||||
""" Remove folder and all its content (optionally recursive) """
|
||||
if path and os.path.exists(path):
|
||||
# Fast-remove the whole tree if recursive
|
||||
@@ -881,47 +840,67 @@ def remove_all(path: str, pattern: str = "*", keep_folder: bool = False, recursi
|
||||
##############################################################################
|
||||
# Diskfree
|
||||
##############################################################################
|
||||
def diskspace_base(dir_to_check: str) -> Tuple[float, float]:
|
||||
""" Return amount of free and used diskspace in GBytes """
|
||||
# Find first folder level that exists in the path
|
||||
def find_dir(p):
|
||||
""" Return first folder level that exists in this path """
|
||||
x = "x"
|
||||
while x and not os.path.exists(dir_to_check):
|
||||
dir_to_check, x = os.path.split(dir_to_check)
|
||||
while x and not os.path.exists(p):
|
||||
p, x = os.path.split(p)
|
||||
return p
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
# windows diskfree
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
# windows diskfree
|
||||
try:
|
||||
# Careful here, because win32api test hasn't been done yet!
|
||||
import win32api
|
||||
except:
|
||||
pass
|
||||
|
||||
def diskspace_base(_dir):
|
||||
""" Return amount of free and used diskspace in GBytes """
|
||||
_dir = find_dir(_dir)
|
||||
try:
|
||||
available, disk_size, total_free = win32api.GetDiskFreeSpaceEx(dir_to_check)
|
||||
available, disk_size, total_free = win32api.GetDiskFreeSpaceEx(_dir)
|
||||
return disk_size / GIGI, available / GIGI
|
||||
except:
|
||||
return 0.0, 0.0
|
||||
elif hasattr(os, "statvfs"):
|
||||
|
||||
|
||||
else:
|
||||
try:
|
||||
os.statvfs
|
||||
# posix diskfree
|
||||
try:
|
||||
s = os.statvfs(dir_to_check)
|
||||
if s.f_blocks < 0:
|
||||
disk_size = float(sys.maxsize) * float(s.f_frsize)
|
||||
else:
|
||||
disk_size = float(s.f_blocks) * float(s.f_frsize)
|
||||
if s.f_bavail < 0:
|
||||
available = float(sys.maxsize) * float(s.f_frsize)
|
||||
else:
|
||||
available = float(s.f_bavail) * float(s.f_frsize)
|
||||
return disk_size / GIGI, available / GIGI
|
||||
except:
|
||||
return 0.0, 0.0
|
||||
else:
|
||||
return 20.0, 10.0
|
||||
def diskspace_base(_dir):
|
||||
""" Return amount of free and used diskspace in GBytes """
|
||||
_dir = find_dir(_dir)
|
||||
try:
|
||||
s = os.statvfs(_dir)
|
||||
if s.f_blocks < 0:
|
||||
disk_size = float(sys.maxsize) * float(s.f_frsize)
|
||||
else:
|
||||
disk_size = float(s.f_blocks) * float(s.f_frsize)
|
||||
if s.f_bavail < 0:
|
||||
available = float(sys.maxsize) * float(s.f_frsize)
|
||||
else:
|
||||
available = float(s.f_bavail) * float(s.f_frsize)
|
||||
return disk_size / GIGI, available / GIGI
|
||||
except:
|
||||
return 0.0, 0.0
|
||||
|
||||
except ImportError:
|
||||
|
||||
def diskspace_base(_dir):
|
||||
return 20.0, 10.0
|
||||
|
||||
|
||||
# Store all results to speed things up
|
||||
__DIRS_CHECKED = []
|
||||
__DISKS_SAME = None
|
||||
__LAST_DISK_RESULT = {"download_dir": (0.0, 0.0), "complete_dir": (0.0, 0.0)}
|
||||
__LAST_DISK_RESULT = {"download_dir": [], "complete_dir": []}
|
||||
__LAST_DISK_CALL = 0
|
||||
|
||||
|
||||
def diskspace(force: bool = False) -> Dict[str, Tuple[float, float]]:
|
||||
def diskspace(force=False):
|
||||
""" Wrapper to cache results """
|
||||
global __DIRS_CHECKED, __DISKS_SAME, __LAST_DISK_RESULT, __LAST_DISK_CALL
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ def localipv4():
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
|
||||
# Option: use 100.64.1.1 (IANA-Reserved IPv4 Prefix for Shared Address Space)
|
||||
s_ipv4.connect(("10.255.255.255", 80))
|
||||
s_ipv4.connect(("1.2.3.4", 80))
|
||||
ipv4 = s_ipv4.getsockname()[0]
|
||||
except socket.error:
|
||||
ipv4 = None
|
||||
@@ -76,9 +76,9 @@ def localipv4():
|
||||
|
||||
|
||||
def publicipv4():
|
||||
"""Because of dual IPv4/IPv6 clients, finding the
|
||||
public ipv4 needs special attention, meaning forcing
|
||||
IPv4 connections, and not allowing IPv6 connections
|
||||
""" Because of dual IPv4/IPv6 clients, finding the
|
||||
public ipv4 needs special attention, meaning forcing
|
||||
IPv4 connections, and not allowing IPv6 connections
|
||||
"""
|
||||
public_ipv4 = None
|
||||
try:
|
||||
|
||||
@@ -33,10 +33,12 @@ import functools
|
||||
from threading import Thread
|
||||
from random import randint
|
||||
from xml.sax.saxutils import escape
|
||||
from Cheetah.Template import Template
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.rss
|
||||
import sabnzbd.scheduler as scheduler
|
||||
|
||||
from Cheetah.Template import Template
|
||||
from sabnzbd.misc import (
|
||||
to_units,
|
||||
from_units,
|
||||
@@ -50,18 +52,24 @@ from sabnzbd.misc import (
|
||||
)
|
||||
from sabnzbd.filesystem import real_path, long_path, globber, globber_full, remove_all, clip_path, same_file
|
||||
from sabnzbd.newswrapper import GetServerParms
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
from sabnzbd.encoding import xml_name, utob
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
import sabnzbd.notifier as notifier
|
||||
import sabnzbd.newsunpack
|
||||
from sabnzbd.downloader import Downloader
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
from sabnzbd.utils.servertests import test_nntp_server_dict
|
||||
from sabnzbd.decoder import SABYENC_ENABLED
|
||||
from sabnzbd.utils.diskspeed import diskspeedmeasure
|
||||
from sabnzbd.utils.getperformance import getpystone
|
||||
from sabnzbd.utils.internetspeed import internetspeed
|
||||
import sabnzbd.utils.ssdp
|
||||
|
||||
from sabnzbd.constants import MEBI, DEF_SKIN_COLORS, DEF_STDCONFIG, DEF_MAIN_TMPL, DEFAULT_PRIORITY, CHEETAH_DIRECTIVES
|
||||
|
||||
from sabnzbd.lang import list_languages
|
||||
|
||||
from sabnzbd.api import (
|
||||
list_scripts,
|
||||
list_cats,
|
||||
@@ -72,11 +80,18 @@ from sabnzbd.api import (
|
||||
retry_job,
|
||||
build_header,
|
||||
build_history,
|
||||
format_bytes,
|
||||
report,
|
||||
del_hist_job,
|
||||
Ttemplate,
|
||||
build_queue_header,
|
||||
)
|
||||
|
||||
##############################################################################
|
||||
# Global constants
|
||||
##############################################################################
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Security functions
|
||||
##############################################################################
|
||||
@@ -126,12 +141,12 @@ def secured_expose(wrap_func=None, check_configlock=False, check_api_key=False):
|
||||
|
||||
|
||||
def check_access(access_type=4):
|
||||
"""Check if external address is allowed given access_type:
|
||||
1=nzb
|
||||
2=api
|
||||
3=full_api
|
||||
4=webui
|
||||
5=webui with login for external
|
||||
""" Check if external address is allowed given access_type:
|
||||
1=nzb
|
||||
2=api
|
||||
3=full_api
|
||||
4=webui
|
||||
5=webui with login for external
|
||||
"""
|
||||
referrer = cherrypy.request.remote.ip
|
||||
|
||||
@@ -147,9 +162,9 @@ def check_access(access_type=4):
|
||||
|
||||
|
||||
def check_hostname():
|
||||
"""Check if hostname is allowed, to mitigate DNS-rebinding attack.
|
||||
Similar to CVE-2019-5702, we need to add protection even
|
||||
if only allowed to be accessed via localhost.
|
||||
""" Check if hostname is allowed, to mitigate DNS-rebinding attack.
|
||||
Similar to CVE-2019-5702, we need to add protection even
|
||||
if only allowed to be accessed via localhost.
|
||||
"""
|
||||
# If login is enabled, no API-key can be deducted
|
||||
if cfg.username() and cfg.password():
|
||||
@@ -187,10 +202,10 @@ COOKIE_SECRET = str(randint(1000, 100000) * os.getpid())
|
||||
|
||||
|
||||
def set_login_cookie(remove=False, remember_me=False):
|
||||
"""We try to set a cookie as unique as possible
|
||||
to the current user. Based on it's IP and the
|
||||
current process ID of the SAB instance and a random
|
||||
number, so cookies cannot be re-used
|
||||
""" We try to set a cookie as unique as possible
|
||||
to the current user. Based on it's IP and the
|
||||
current process ID of the SAB instance and a random
|
||||
number, so cookies cannot be re-used
|
||||
"""
|
||||
salt = randint(1, 1000)
|
||||
cookie_str = utob(str(salt) + cherrypy.request.remote.ip + COOKIE_SECRET)
|
||||
@@ -253,18 +268,15 @@ def set_auth(conf):
|
||||
}
|
||||
)
|
||||
conf.update(
|
||||
{
|
||||
"/api": {"tools.auth_basic.on": False},
|
||||
"%s/api" % cfg.url_base(): {"tools.auth_basic.on": False},
|
||||
}
|
||||
{"/api": {"tools.auth_basic.on": False}, "%s/api" % cfg.url_base(): {"tools.auth_basic.on": False},}
|
||||
)
|
||||
else:
|
||||
conf.update({"tools.auth_basic.on": False})
|
||||
|
||||
|
||||
def check_apikey(kwargs):
|
||||
"""Check API-key or NZB-key
|
||||
Return None when OK, otherwise an error message
|
||||
""" Check API-key or NZB-key
|
||||
Return None when OK, otherwise an error message
|
||||
"""
|
||||
mode = kwargs.get("mode", "")
|
||||
name = kwargs.get("name", "")
|
||||
@@ -395,7 +407,7 @@ class MainPage:
|
||||
)
|
||||
)
|
||||
|
||||
bytespersec_list = sabnzbd.BPSMeter.get_bps_list()
|
||||
bytespersec_list = BPSMeter.do.get_bps_list()
|
||||
info["bytespersec_list"] = ",".join([str(bps) for bps in bytespersec_list])
|
||||
|
||||
template = Template(
|
||||
@@ -418,13 +430,13 @@ class MainPage:
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def pause(self, **kwargs):
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
sabnzbd.Downloader.pause()
|
||||
scheduler.plan_resume(0)
|
||||
Downloader.do.pause()
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def resume(self, **kwargs):
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
scheduler.plan_resume(0)
|
||||
sabnzbd.unpause_all()
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@@ -469,13 +481,6 @@ class MainPage:
|
||||
cherrypy.response.headers["Content-Type"] = "text/plain"
|
||||
return "User-agent: *\nDisallow: /\n"
|
||||
|
||||
@secured_expose
|
||||
def description_xml(self, **kwargs):
|
||||
""" Keep web crawlers out """
|
||||
logging.debug("description.xml was requested by %s", cherrypy.request.remote.ip)
|
||||
cherrypy.response.headers["Content-Type"] = "application/xml"
|
||||
return utob(sabnzbd.utils.ssdp.server_ssdp_xml())
|
||||
|
||||
|
||||
##############################################################################
|
||||
class Wizard:
|
||||
@@ -523,7 +528,7 @@ class Wizard:
|
||||
else:
|
||||
# Sort servers to get the first enabled one
|
||||
server_names = sorted(
|
||||
servers,
|
||||
servers.keys(),
|
||||
key=lambda svr: "%d%02d%s"
|
||||
% (int(not servers[svr].enable()), servers[svr].priority(), servers[svr].displayname().lower()),
|
||||
)
|
||||
@@ -575,12 +580,13 @@ class Wizard:
|
||||
|
||||
def get_access_info():
|
||||
""" Build up a list of url's that sabnzbd can be accessed from """
|
||||
# Access_url is used to provide the user a link to SABnzbd depending on the host
|
||||
# Access_url is used to provide the user a link to sabnzbd depending on the host
|
||||
access_uri = "localhost"
|
||||
cherryhost = cfg.cherryhost()
|
||||
host = socket.gethostname().lower()
|
||||
socks = [host]
|
||||
|
||||
if cherryhost == "0.0.0.0":
|
||||
host = socket.gethostname()
|
||||
socks = [host]
|
||||
# Grab a list of all ips for the hostname
|
||||
try:
|
||||
addresses = socket.getaddrinfo(host, None)
|
||||
@@ -591,8 +597,17 @@ def get_access_info():
|
||||
# Filter out ipv6 addresses (should not be allowed)
|
||||
if ":" not in address and address not in socks:
|
||||
socks.append(address)
|
||||
socks.insert(0, "localhost")
|
||||
if "host" in cherrypy.request.headers:
|
||||
host = cherrypy.request.headers["host"]
|
||||
host = host.rsplit(":")[0]
|
||||
access_uri = host
|
||||
socks.insert(0, host)
|
||||
else:
|
||||
socks.insert(0, "localhost")
|
||||
|
||||
elif cherryhost == "::":
|
||||
host = socket.gethostname()
|
||||
socks = [host]
|
||||
# Grab a list of all ips for the hostname
|
||||
addresses = socket.getaddrinfo(host, None)
|
||||
for addr in addresses:
|
||||
@@ -602,14 +617,22 @@ def get_access_info():
|
||||
address = "[%s]" % address
|
||||
if address not in socks:
|
||||
socks.append(address)
|
||||
socks.insert(0, "localhost")
|
||||
elif cherryhost:
|
||||
if "host" in cherrypy.request.headers:
|
||||
host = cherrypy.request.headers["host"]
|
||||
host = host.rsplit(":")[0]
|
||||
access_uri = host
|
||||
socks.insert(0, host)
|
||||
else:
|
||||
socks.insert(0, "localhost")
|
||||
|
||||
elif not cherryhost:
|
||||
socks = [socket.gethostname()]
|
||||
access_uri = socket.gethostname()
|
||||
else:
|
||||
socks = [cherryhost]
|
||||
access_uri = cherryhost
|
||||
|
||||
# Add the current requested URL as the base
|
||||
access_url = urllib.parse.urljoin(cherrypy.request.base, cfg.url_base())
|
||||
|
||||
urls = [access_url]
|
||||
urls = []
|
||||
for sock in socks:
|
||||
if sock:
|
||||
if cfg.enable_https() and cfg.https_port():
|
||||
@@ -618,10 +641,17 @@ def get_access_info():
|
||||
url = "https://%s:%s%s" % (sock, cfg.cherryport(), cfg.url_base())
|
||||
else:
|
||||
url = "http://%s:%s%s" % (sock, cfg.cherryport(), cfg.url_base())
|
||||
|
||||
urls.append(url)
|
||||
|
||||
# Return a unique list
|
||||
return access_url, set(urls)
|
||||
if cfg.enable_https() and cfg.https_port():
|
||||
access_url = "https://%s:%s%s" % (sock, cfg.https_port(), cfg.url_base())
|
||||
elif cfg.enable_https():
|
||||
access_url = "https://%s:%s%s" % (access_uri, cfg.cherryport(), cfg.url_base())
|
||||
else:
|
||||
access_url = "http://%s:%s%s" % (access_uri, cfg.cherryport(), cfg.url_base())
|
||||
|
||||
return access_url, urls
|
||||
|
||||
|
||||
##############################################################################
|
||||
@@ -692,7 +722,7 @@ class NzoPage:
|
||||
nzo_id = a
|
||||
break
|
||||
|
||||
nzo = sabnzbd.NzbQueue.get_nzo(nzo_id)
|
||||
nzo = NzbQueue.do.get_nzo(nzo_id)
|
||||
if nzo_id and nzo:
|
||||
info, pnfo_list, bytespersec, q_size, bytes_left_previous_page = build_queue_header()
|
||||
|
||||
@@ -731,7 +761,7 @@ class NzoPage:
|
||||
n = 0
|
||||
for pnfo in pnfo_list:
|
||||
if pnfo.nzo_id == nzo_id:
|
||||
nzo = sabnzbd.NzbQueue.get_nzo(nzo_id)
|
||||
nzo = NzbQueue.do.get_nzo(nzo_id)
|
||||
repair = pnfo.repair
|
||||
unpack = pnfo.unpack
|
||||
delete = pnfo.delete
|
||||
@@ -764,7 +794,7 @@ class NzoPage:
|
||||
|
||||
def nzo_files(self, info, nzo_id):
|
||||
active = []
|
||||
nzo = sabnzbd.NzbQueue.get_nzo(nzo_id)
|
||||
nzo = NzbQueue.do.get_nzo(nzo_id)
|
||||
if nzo:
|
||||
pnfo = nzo.gather_info(full=True)
|
||||
info["nzo_id"] = pnfo.nzo_id
|
||||
@@ -779,8 +809,8 @@ class NzoPage:
|
||||
"filename": nzf.filename if nzf.filename else nzf.subject,
|
||||
"mbleft": "%.2f" % (nzf.bytes_left / MEBI),
|
||||
"mb": "%.2f" % (nzf.bytes / MEBI),
|
||||
"size": to_units(nzf.bytes, "B"),
|
||||
"sizeleft": to_units(nzf.bytes_left, "B"),
|
||||
"size": format_bytes(nzf.bytes),
|
||||
"sizeleft": format_bytes(nzf.bytes_left),
|
||||
"nzf_id": nzf.nzf_id,
|
||||
"age": calc_age(nzf.date),
|
||||
"checked": checked,
|
||||
@@ -800,15 +830,15 @@ class NzoPage:
|
||||
script = kwargs.get("script", None)
|
||||
cat = kwargs.get("cat", None)
|
||||
priority = kwargs.get("priority", None)
|
||||
nzo = sabnzbd.NzbQueue.get_nzo(nzo_id)
|
||||
nzo = NzbQueue.do.get_nzo(nzo_id)
|
||||
|
||||
if index is not None:
|
||||
sabnzbd.NzbQueue.switch(nzo_id, index)
|
||||
NzbQueue.do.switch(nzo_id, index)
|
||||
if name is not None:
|
||||
sabnzbd.NzbQueue.change_name(nzo_id, name, password)
|
||||
NzbQueue.do.change_name(nzo_id, name, password)
|
||||
|
||||
if cat is not None and nzo.cat is not cat and not (nzo.cat == "*" and cat == "Default"):
|
||||
sabnzbd.NzbQueue.change_cat(nzo_id, cat, priority)
|
||||
NzbQueue.do.change_cat(nzo_id, cat, priority)
|
||||
# Category changed, so make sure "Default" attributes aren't set again
|
||||
if script == "Default":
|
||||
script = None
|
||||
@@ -818,11 +848,11 @@ class NzoPage:
|
||||
pp = None
|
||||
|
||||
if script is not None and nzo.script != script:
|
||||
sabnzbd.NzbQueue.change_script(nzo_id, script)
|
||||
NzbQueue.do.change_script(nzo_id, script)
|
||||
if pp is not None and nzo.pp != pp:
|
||||
sabnzbd.NzbQueue.change_opts(nzo_id, pp)
|
||||
NzbQueue.do.change_opts(nzo_id, pp)
|
||||
if priority is not None and nzo.priority != int(priority):
|
||||
sabnzbd.NzbQueue.set_priority(nzo_id, priority)
|
||||
NzbQueue.do.set_priority(nzo_id, priority)
|
||||
|
||||
raise Raiser(urllib.parse.urljoin(self.__root, "../queue/"))
|
||||
|
||||
@@ -831,7 +861,7 @@ class NzoPage:
|
||||
if kwargs["action_key"] == "Delete":
|
||||
for key in kwargs:
|
||||
if kwargs[key] == "on":
|
||||
sabnzbd.NzbQueue.remove_nzf(nzo_id, key, force_delete=True)
|
||||
NzbQueue.do.remove_nzf(nzo_id, key, force_delete=True)
|
||||
|
||||
elif kwargs["action_key"] in ("Top", "Up", "Down", "Bottom"):
|
||||
nzf_ids = []
|
||||
@@ -840,15 +870,15 @@ class NzoPage:
|
||||
nzf_ids.append(key)
|
||||
size = int_conv(kwargs.get("action_size", 1))
|
||||
if kwargs["action_key"] == "Top":
|
||||
sabnzbd.NzbQueue.move_top_bulk(nzo_id, nzf_ids)
|
||||
NzbQueue.do.move_top_bulk(nzo_id, nzf_ids)
|
||||
elif kwargs["action_key"] == "Up":
|
||||
sabnzbd.NzbQueue.move_up_bulk(nzo_id, nzf_ids, size)
|
||||
NzbQueue.do.move_up_bulk(nzo_id, nzf_ids, size)
|
||||
elif kwargs["action_key"] == "Down":
|
||||
sabnzbd.NzbQueue.move_down_bulk(nzo_id, nzf_ids, size)
|
||||
NzbQueue.do.move_down_bulk(nzo_id, nzf_ids, size)
|
||||
elif kwargs["action_key"] == "Bottom":
|
||||
sabnzbd.NzbQueue.move_bottom_bulk(nzo_id, nzf_ids)
|
||||
NzbQueue.do.move_bottom_bulk(nzo_id, nzf_ids)
|
||||
|
||||
if sabnzbd.NzbQueue.get_nzo(nzo_id):
|
||||
if NzbQueue.do.get_nzo(nzo_id):
|
||||
url = urllib.parse.urljoin(self.__root, nzo_id)
|
||||
else:
|
||||
url = urllib.parse.urljoin(self.__root, "../queue")
|
||||
@@ -879,18 +909,18 @@ class QueuePage:
|
||||
uid = kwargs.get("uid")
|
||||
del_files = int_conv(kwargs.get("del_files"))
|
||||
if uid:
|
||||
sabnzbd.NzbQueue.remove(uid, delete_all_data=del_files)
|
||||
NzbQueue.do.remove(uid, add_to_history=False, delete_all_data=del_files)
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def purge(self, **kwargs):
|
||||
sabnzbd.NzbQueue.remove_all(kwargs.get("search"))
|
||||
NzbQueue.do.remove_all(kwargs.get("search"))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def change_queue_complete_action(self, **kwargs):
|
||||
"""Action or script to be performed once the queue has been completed
|
||||
Scripts are prefixed with 'script_'
|
||||
""" Action or script to be performed once the queue has been completed
|
||||
Scripts are prefixed with 'script_'
|
||||
"""
|
||||
action = kwargs.get("action")
|
||||
sabnzbd.change_queue_complete_action(action)
|
||||
@@ -901,7 +931,7 @@ class QueuePage:
|
||||
uid1 = kwargs.get("uid1")
|
||||
uid2 = kwargs.get("uid2")
|
||||
if uid1 and uid2:
|
||||
sabnzbd.NzbQueue.switch(uid1, uid2)
|
||||
NzbQueue.do.switch(uid1, uid2)
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
@@ -909,7 +939,7 @@ class QueuePage:
|
||||
nzo_id = kwargs.get("nzo_id")
|
||||
pp = kwargs.get("pp", "")
|
||||
if nzo_id and pp and pp.isdigit():
|
||||
sabnzbd.NzbQueue.change_opts(nzo_id, int(pp))
|
||||
NzbQueue.do.change_opts(nzo_id, int(pp))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
@@ -919,7 +949,7 @@ class QueuePage:
|
||||
if nzo_id and script:
|
||||
if script == "None":
|
||||
script = None
|
||||
sabnzbd.NzbQueue.change_script(nzo_id, script)
|
||||
NzbQueue.do.change_script(nzo_id, script)
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
@@ -929,7 +959,7 @@ class QueuePage:
|
||||
if nzo_id and cat:
|
||||
if cat == "None":
|
||||
cat = None
|
||||
sabnzbd.NzbQueue.change_cat(nzo_id, cat)
|
||||
NzbQueue.do.change_cat(nzo_id, cat)
|
||||
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@@ -940,46 +970,46 @@ class QueuePage:
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def pause(self, **kwargs):
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
sabnzbd.Downloader.pause()
|
||||
scheduler.plan_resume(0)
|
||||
Downloader.do.pause()
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def resume(self, **kwargs):
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
scheduler.plan_resume(0)
|
||||
sabnzbd.unpause_all()
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def pause_nzo(self, **kwargs):
|
||||
uid = kwargs.get("uid", "")
|
||||
sabnzbd.NzbQueue.pause_multiple_nzo(uid.split(","))
|
||||
NzbQueue.do.pause_multiple_nzo(uid.split(","))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def resume_nzo(self, **kwargs):
|
||||
uid = kwargs.get("uid", "")
|
||||
sabnzbd.NzbQueue.resume_multiple_nzo(uid.split(","))
|
||||
NzbQueue.do.resume_multiple_nzo(uid.split(","))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def set_priority(self, **kwargs):
|
||||
sabnzbd.NzbQueue.set_priority(kwargs.get("nzo_id"), kwargs.get("priority"))
|
||||
NzbQueue.do.set_priority(kwargs.get("nzo_id"), kwargs.get("priority"))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def sort_by_avg_age(self, **kwargs):
|
||||
sabnzbd.NzbQueue.sort_queue("avg_age", kwargs.get("dir"))
|
||||
NzbQueue.do.sort_queue("avg_age", kwargs.get("dir"))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def sort_by_name(self, **kwargs):
|
||||
sabnzbd.NzbQueue.sort_queue("name", kwargs.get("dir"))
|
||||
NzbQueue.do.sort_queue("name", kwargs.get("dir"))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def sort_by_size(self, **kwargs):
|
||||
sabnzbd.NzbQueue.sort_queue("size", kwargs.get("dir"))
|
||||
NzbQueue.do.sort_queue("size", kwargs.get("dir"))
|
||||
raise queueRaiser(self.__root, kwargs)
|
||||
|
||||
|
||||
@@ -987,20 +1017,23 @@ class QueuePage:
|
||||
class HistoryPage:
|
||||
def __init__(self, root):
|
||||
self.__root = root
|
||||
self.__failed_only = False
|
||||
|
||||
@secured_expose
|
||||
def index(self, **kwargs):
|
||||
start = int_conv(kwargs.get("start"))
|
||||
limit = int_conv(kwargs.get("limit"))
|
||||
search = kwargs.get("search")
|
||||
failed_only = int_conv(kwargs.get("failed_only"))
|
||||
failed_only = kwargs.get("failed_only")
|
||||
if failed_only is None:
|
||||
failed_only = self.__failed_only
|
||||
|
||||
history = build_header()
|
||||
history["failed_only"] = failed_only
|
||||
history["rating_enable"] = bool(cfg.rating_enable())
|
||||
|
||||
postfix = T("B") # : Abbreviation for bytes, as in GB
|
||||
grand, month, week, day = sabnzbd.BPSMeter.get_sums()
|
||||
grand, month, week, day = BPSMeter.do.get_sums()
|
||||
history["total_size"], history["month_size"], history["week_size"], history["day_size"] = (
|
||||
to_units(grand, postfix=postfix),
|
||||
to_units(month, postfix=postfix),
|
||||
@@ -1009,7 +1042,7 @@ class HistoryPage:
|
||||
)
|
||||
|
||||
history["lines"], history["fetched"], history["noofslots"] = build_history(
|
||||
start=start, limit=limit, search=search, failed_only=failed_only
|
||||
limit=limit, start=start, search=search, failed_only=failed_only
|
||||
)
|
||||
|
||||
if search:
|
||||
@@ -1079,7 +1112,7 @@ class ConfigPage:
|
||||
|
||||
conf["have_unzip"] = bool(sabnzbd.newsunpack.ZIP_COMMAND)
|
||||
conf["have_7zip"] = bool(sabnzbd.newsunpack.SEVEN_COMMAND)
|
||||
conf["have_sabyenc"] = sabnzbd.decoder.SABYENC_ENABLED
|
||||
conf["have_sabyenc"] = SABYENC_ENABLED
|
||||
conf["have_mt_par2"] = sabnzbd.newsunpack.PAR2_MT
|
||||
|
||||
conf["certificate_validation"] = sabnzbd.CERTIFICATE_VALIDATION
|
||||
@@ -1090,7 +1123,7 @@ class ConfigPage:
|
||||
new[svr] = {}
|
||||
conf["servers"] = new
|
||||
|
||||
conf["folders"] = sabnzbd.NzbQueue.scan_jobs(all_jobs=False, action=False)
|
||||
conf["folders"] = NzbQueue.do.scan_jobs(all_jobs=False, action=False)
|
||||
|
||||
template = Template(
|
||||
file=os.path.join(sabnzbd.WEB_DIR_CONFIG, "config.tmpl"),
|
||||
@@ -1167,11 +1200,7 @@ class ConfigFolders:
|
||||
# return sabnzbd.api.report('json', error=msg)
|
||||
return badParameterResponse(msg, kwargs.get("ajax"))
|
||||
|
||||
if not sabnzbd.check_incomplete_vs_complete():
|
||||
return badParameterResponse(
|
||||
T("The Completed Download Folder cannot be the same or a subfolder of the Temporary Download Folder"),
|
||||
kwargs.get("ajax"),
|
||||
)
|
||||
sabnzbd.check_incomplete_vs_complete()
|
||||
config.save_config()
|
||||
if kwargs.get("ajax"):
|
||||
return sabnzbd.api.report("json")
|
||||
@@ -1201,7 +1230,6 @@ SWITCH_LIST = (
|
||||
"pre_script",
|
||||
"pause_on_pwrar",
|
||||
"sfv_check",
|
||||
"deobfuscate_final_filenames",
|
||||
"folder_rename",
|
||||
"load_balancing",
|
||||
"quota_size",
|
||||
@@ -1297,11 +1325,11 @@ SPECIAL_BOOL_LIST = (
|
||||
"start_paused",
|
||||
"no_penalties",
|
||||
"fast_fail",
|
||||
"ignore_wrong_unrar",
|
||||
"overwrite_files",
|
||||
"enable_par_cleanup",
|
||||
"queue_complete_pers",
|
||||
"api_warnings",
|
||||
"helpfull_warnings",
|
||||
"ampm",
|
||||
"enable_unrar",
|
||||
"enable_unzip",
|
||||
@@ -1320,6 +1348,7 @@ SPECIAL_BOOL_LIST = (
|
||||
"html_login",
|
||||
"wait_for_dfolder",
|
||||
"max_art_opt",
|
||||
"warn_empty_nzb",
|
||||
"enable_bonjour",
|
||||
"warn_dupl_jobs",
|
||||
"replace_illegal",
|
||||
@@ -1336,7 +1365,6 @@ SPECIAL_VALUE_LIST = (
|
||||
"max_url_retries",
|
||||
"req_completion_rate",
|
||||
"wait_ext_drive",
|
||||
"max_foldername_length",
|
||||
"show_sysload",
|
||||
"url_base",
|
||||
"direct_unpack_threads",
|
||||
@@ -1508,7 +1536,7 @@ class ConfigGeneral:
|
||||
cfg.bandwidth_perc.set(bandwidth_perc)
|
||||
bandwidth_perc = cfg.bandwidth_perc()
|
||||
if bandwidth_perc and not bandwidth_max:
|
||||
logging.warning_helpful(T("You must set a maximum bandwidth before you can set a bandwidth limit"))
|
||||
logging.warning(T("You must set a maximum bandwidth before you can set a bandwidth limit"))
|
||||
|
||||
config.save_config()
|
||||
|
||||
@@ -1549,13 +1577,13 @@ class ConfigServer:
|
||||
new = []
|
||||
servers = config.get_servers()
|
||||
server_names = sorted(
|
||||
servers,
|
||||
list(servers.keys()),
|
||||
key=lambda svr: "%d%02d%s"
|
||||
% (int(not servers[svr].enable()), servers[svr].priority(), servers[svr].displayname().lower()),
|
||||
)
|
||||
for svr in server_names:
|
||||
new.append(servers[svr].get_dict(safe=True))
|
||||
t, m, w, d, timeline = sabnzbd.BPSMeter.amounts(svr)
|
||||
t, m, w, d, timeline = BPSMeter.do.amounts(svr)
|
||||
if t:
|
||||
new[-1]["amounts"] = to_units(t), to_units(m), to_units(w), to_units(d), timeline
|
||||
conf["servers"] = new
|
||||
@@ -1592,7 +1620,7 @@ class ConfigServer:
|
||||
def clrServer(self, **kwargs):
|
||||
server = kwargs.get("server")
|
||||
if server:
|
||||
sabnzbd.BPSMeter.clear_server(server)
|
||||
BPSMeter.do.clear_server(server)
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
@@ -1603,7 +1631,7 @@ class ConfigServer:
|
||||
if svr:
|
||||
svr.enable.set(not svr.enable())
|
||||
config.save_config()
|
||||
sabnzbd.Downloader.update_server(server, server)
|
||||
Downloader.do.update_server(server, server)
|
||||
raise Raiser(self.__root)
|
||||
|
||||
|
||||
@@ -1681,7 +1709,7 @@ def handle_server(kwargs, root=None, new_svr=False):
|
||||
config.ConfigServer(server, kwargs)
|
||||
|
||||
config.save_config()
|
||||
sabnzbd.Downloader.update_server(old_server, server)
|
||||
Downloader.do.update_server(old_server, server)
|
||||
if root:
|
||||
if ajax:
|
||||
return sabnzbd.api.report("json")
|
||||
@@ -1736,7 +1764,7 @@ class ConfigRss:
|
||||
active_feed = kwargs.get("feed", "")
|
||||
conf["active_feed"] = active_feed
|
||||
conf["rss"] = rss
|
||||
conf["rss_next"] = time.strftime(time_format("%H:%M"), time.localtime(sabnzbd.RSSReader.next_run))
|
||||
conf["rss_next"] = time.strftime(time_format("%H:%M"), time.localtime(sabnzbd.rss.next_run()))
|
||||
|
||||
if active_feed:
|
||||
readout = bool(self.__refresh_readout)
|
||||
@@ -1746,7 +1774,7 @@ class ConfigRss:
|
||||
self.__refresh_force = False
|
||||
self.__refresh_ignore = False
|
||||
if self.__evaluate:
|
||||
msg = sabnzbd.RSSReader.run_feed(
|
||||
msg = sabnzbd.rss.run_feed(
|
||||
active_feed,
|
||||
download=self.__refresh_download,
|
||||
force=self.__refresh_force,
|
||||
@@ -1757,7 +1785,7 @@ class ConfigRss:
|
||||
msg = ""
|
||||
self.__evaluate = False
|
||||
if readout:
|
||||
sabnzbd.RSSReader.save()
|
||||
sabnzbd.rss.save()
|
||||
self.__last_msg = msg
|
||||
else:
|
||||
msg = self.__last_msg
|
||||
@@ -1788,13 +1816,13 @@ class ConfigRss:
|
||||
""" Save changed RSS automatic readout rate """
|
||||
cfg.rss_rate.set(kwargs.get("rss_rate"))
|
||||
config.save_config()
|
||||
sabnzbd.Scheduler.restart()
|
||||
scheduler.restart()
|
||||
raise rssRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
def upd_rss_feed(self, **kwargs):
|
||||
"""Update Feed level attributes,
|
||||
legacy version: ignores 'enable' parameter
|
||||
""" Update Feed level attributes,
|
||||
legacy version: ignores 'enable' parameter
|
||||
"""
|
||||
if kwargs.get("enable") is not None:
|
||||
del kwargs["enable"]
|
||||
@@ -1860,7 +1888,7 @@ class ConfigRss:
|
||||
config.ConfigRSS(feed, kwargs)
|
||||
# Clear out any existing reference to this feed name
|
||||
# Otherwise first-run detection can fail
|
||||
sabnzbd.RSSReader.clear_feed(feed)
|
||||
sabnzbd.rss.clear_feed(feed)
|
||||
config.save_config()
|
||||
self.__refresh_readout = feed
|
||||
self.__refresh_download = False
|
||||
@@ -1916,7 +1944,7 @@ class ConfigRss:
|
||||
kwargs["section"] = "rss"
|
||||
kwargs["keyword"] = kwargs.get("feed")
|
||||
del_from_section(kwargs)
|
||||
sabnzbd.RSSReader.clear_feed(kwargs.get("feed"))
|
||||
sabnzbd.rss.clear_feed(kwargs.get("feed"))
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
@@ -1952,7 +1980,7 @@ class ConfigRss:
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
def clean_rss_jobs(self, *args, **kwargs):
|
||||
""" Remove processed RSS jobs from UI """
|
||||
sabnzbd.RSSReader.clear_downloaded(kwargs["feed"])
|
||||
sabnzbd.rss.clear_downloaded(kwargs["feed"])
|
||||
self.__evaluate = True
|
||||
raise rssRaiser(self.__root, kwargs)
|
||||
|
||||
@@ -1987,7 +2015,7 @@ class ConfigRss:
|
||||
feed = kwargs.get("feed")
|
||||
url = kwargs.get("url")
|
||||
nzbname = kwargs.get("nzbname")
|
||||
att = sabnzbd.RSSReader.lookup_url(feed, url)
|
||||
att = sabnzbd.rss.lookup_url(feed, url)
|
||||
if att:
|
||||
pp = att.get("pp")
|
||||
cat = att.get("cat")
|
||||
@@ -1997,13 +2025,13 @@ class ConfigRss:
|
||||
if url:
|
||||
sabnzbd.add_url(url, pp, script, cat, prio, nzbname)
|
||||
# Need to pass the title instead
|
||||
sabnzbd.RSSReader.flag_downloaded(feed, url)
|
||||
sabnzbd.rss.flag_downloaded(feed, url)
|
||||
raise rssRaiser(self.__root, kwargs)
|
||||
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
def rss_now(self, *args, **kwargs):
|
||||
""" Run an automatic RSS run now """
|
||||
sabnzbd.Scheduler.force_rss()
|
||||
scheduler.force_rss()
|
||||
raise rssRaiser(self.__root, kwargs)
|
||||
|
||||
|
||||
@@ -2082,7 +2110,7 @@ class ConfigScheduling:
|
||||
snum = 1
|
||||
conf["schedlines"] = []
|
||||
conf["taskinfo"] = []
|
||||
for ev in sabnzbd.scheduler.sort_schedules(all_events=False):
|
||||
for ev in scheduler.sort_schedules(all_events=False):
|
||||
line = ev[3]
|
||||
conf["schedlines"].append(line)
|
||||
try:
|
||||
@@ -2198,7 +2226,7 @@ class ConfigScheduling:
|
||||
cfg.schedules.set(sched)
|
||||
|
||||
config.save_config()
|
||||
sabnzbd.Scheduler.restart()
|
||||
scheduler.restart(force=True)
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
@@ -2209,7 +2237,7 @@ class ConfigScheduling:
|
||||
schedules.remove(line)
|
||||
cfg.schedules.set(schedules)
|
||||
config.save_config()
|
||||
sabnzbd.Scheduler.restart()
|
||||
scheduler.restart(force=True)
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True, check_configlock=True)
|
||||
@@ -2226,7 +2254,7 @@ class ConfigScheduling:
|
||||
break
|
||||
cfg.schedules.set(schedules)
|
||||
config.save_config()
|
||||
sabnzbd.Scheduler.restart()
|
||||
scheduler.restart(force=True)
|
||||
raise Raiser(self.__root)
|
||||
|
||||
|
||||
@@ -2386,12 +2414,12 @@ class Status:
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def reset_quota(self, **kwargs):
|
||||
sabnzbd.BPSMeter.reset_quota(force=True)
|
||||
BPSMeter.do.reset_quota(force=True)
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def disconnect(self, **kwargs):
|
||||
sabnzbd.Downloader.disconnect()
|
||||
Downloader.do.disconnect()
|
||||
raise Raiser(self.__root)
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
@@ -2453,7 +2481,7 @@ class Status:
|
||||
|
||||
@secured_expose(check_api_key=True)
|
||||
def unblock_server(self, **kwargs):
|
||||
sabnzbd.Downloader.unblock(kwargs.get("server"))
|
||||
Downloader.do.unblock(kwargs.get("server"))
|
||||
# Short sleep so that UI shows new server status
|
||||
time.sleep(1.0)
|
||||
raise Raiser(self.__root)
|
||||
@@ -2516,7 +2544,7 @@ def orphan_delete(kwargs):
|
||||
|
||||
|
||||
def orphan_delete_all():
|
||||
paths = sabnzbd.NzbQueue.scan_jobs(all_jobs=False, action=False)
|
||||
paths = NzbQueue.do.scan_jobs(all_jobs=False, action=False)
|
||||
for path in paths:
|
||||
kwargs = {"name": path}
|
||||
orphan_delete(kwargs)
|
||||
@@ -2527,11 +2555,11 @@ def orphan_add(kwargs):
|
||||
if path:
|
||||
path = os.path.join(long_path(cfg.download_dir.get_path()), path)
|
||||
logging.info("Re-adding orphaned job %s", path)
|
||||
sabnzbd.NzbQueue.repair_job(path, None, None)
|
||||
NzbQueue.do.repair_job(path, None, None)
|
||||
|
||||
|
||||
def orphan_add_all():
|
||||
paths = sabnzbd.NzbQueue.scan_jobs(all_jobs=False, action=False)
|
||||
paths = NzbQueue.do.scan_jobs(all_jobs=False, action=False)
|
||||
for path in paths:
|
||||
kwargs = {"name": path}
|
||||
orphan_add(kwargs)
|
||||
@@ -2632,7 +2660,7 @@ def GetRssLog(feed):
|
||||
|
||||
return job
|
||||
|
||||
jobs = sabnzbd.RSSReader.show_result(feed).values()
|
||||
jobs = list(sabnzbd.rss.show_result(feed).values())
|
||||
good, bad, done = ([], [], [])
|
||||
for job in jobs:
|
||||
if job["status"][0] == "G":
|
||||
@@ -2671,8 +2699,6 @@ LIST_NCENTER = (
|
||||
"ncenter_cats",
|
||||
"ncenter_prio_startup",
|
||||
"ncenter_prio_download",
|
||||
"ncenter_prio_pause_resume",
|
||||
"ncenter_prio_pp",
|
||||
"ncenter_prio_pp",
|
||||
"ncenter_prio_complete",
|
||||
"ncenter_prio_failed",
|
||||
@@ -2688,7 +2714,6 @@ LIST_ACENTER = (
|
||||
"acenter_cats",
|
||||
"acenter_prio_startup",
|
||||
"acenter_prio_download",
|
||||
"acenter_prio_pause_resume",
|
||||
"acenter_prio_pp",
|
||||
"acenter_prio_complete",
|
||||
"acenter_prio_failed",
|
||||
@@ -2704,7 +2729,6 @@ LIST_NTFOSD = (
|
||||
"ntfosd_cats",
|
||||
"ntfosd_prio_startup",
|
||||
"ntfosd_prio_download",
|
||||
"ntfosd_prio_pause_resume",
|
||||
"ntfosd_prio_pp",
|
||||
"ntfosd_prio_complete",
|
||||
"ntfosd_prio_failed",
|
||||
@@ -2721,7 +2745,6 @@ LIST_PROWL = (
|
||||
"prowl_apikey",
|
||||
"prowl_prio_startup",
|
||||
"prowl_prio_download",
|
||||
"prowl_prio_pause_resume",
|
||||
"prowl_prio_pp",
|
||||
"prowl_prio_complete",
|
||||
"prowl_prio_failed",
|
||||
@@ -2740,7 +2763,6 @@ LIST_PUSHOVER = (
|
||||
"pushover_device",
|
||||
"pushover_prio_startup",
|
||||
"pushover_prio_download",
|
||||
"pushover_prio_pause_resume",
|
||||
"pushover_prio_pp",
|
||||
"pushover_prio_complete",
|
||||
"pushover_prio_failed",
|
||||
@@ -2760,7 +2782,6 @@ LIST_PUSHBULLET = (
|
||||
"pushbullet_device",
|
||||
"pushbullet_prio_startup",
|
||||
"pushbullet_prio_download",
|
||||
"pushbullet_prio_pause_resume",
|
||||
"pushbullet_prio_pp",
|
||||
"pushbullet_prio_complete",
|
||||
"pushbullet_prio_failed",
|
||||
@@ -2778,7 +2799,6 @@ LIST_NSCRIPT = (
|
||||
"nscript_parameters",
|
||||
"nscript_prio_startup",
|
||||
"nscript_prio_download",
|
||||
"nscript_prio_pause_resume",
|
||||
"nscript_prio_pp",
|
||||
"nscript_prio_complete",
|
||||
"nscript_prio_failed",
|
||||
@@ -2822,7 +2842,8 @@ class ConfigNotify:
|
||||
conf[kw] = config.get_config("ntfosd", kw)()
|
||||
for kw in LIST_NSCRIPT:
|
||||
conf[kw] = config.get_config("nscript", kw)()
|
||||
conf["notify_types"] = sabnzbd.notifier.NOTIFICATION
|
||||
conf["notify_keys"] = sabnzbd.constants.NOTIFY_KEYS
|
||||
conf["notify_texts"] = sabnzbd.notifier.NOTIFICATION
|
||||
|
||||
template = Template(
|
||||
file=os.path.join(sabnzbd.WEB_DIR_CONFIG, "config_notify.tmpl"),
|
||||
|
||||
@@ -62,10 +62,10 @@ def set_language(language=None):
|
||||
|
||||
|
||||
def list_languages():
|
||||
"""Return sorted list of (lang-code, lang-string) pairs,
|
||||
representing the available languages.
|
||||
When any language file is found, the default tuple ('en', 'English')
|
||||
will be included. Otherwise an empty list is returned.
|
||||
""" Return sorted list of (lang-code, lang-string) pairs,
|
||||
representing the available languages.
|
||||
When any language file is found, the default tuple ('en', 'English')
|
||||
will be included. Otherwise an empty list is returned.
|
||||
"""
|
||||
# Find all the MO files.
|
||||
lst = []
|
||||
|
||||
258
sabnzbd/misc.py
258
sabnzbd/misc.py
@@ -30,40 +30,22 @@ import time
|
||||
import datetime
|
||||
import inspect
|
||||
import ctypes
|
||||
import ipaddress
|
||||
from typing import Union, Tuple, Any, Optional, List
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import DEFAULT_PRIORITY, MEBI, DEF_ARTICLE_CACHE_DEFAULT, DEF_ARTICLE_CACHE_MAX
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.encoding import ubtou, platform_btou
|
||||
from sabnzbd.filesystem import userxbit
|
||||
|
||||
TAB_UNITS = ("", "K", "M", "G", "T", "P")
|
||||
RE_UNITS = re.compile(r"(\d+\.*\d*)\s*([KMGTP]?)", re.I)
|
||||
RE_UNITS = re.compile(r"(\d+\.*\d*)\s*([KMGTP]{0,1})", re.I)
|
||||
RE_VERSION = re.compile(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)")
|
||||
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)
|
||||
RE_IP4 = re.compile(r"inet\s+(addr:\s*){0,1}(\d+\.\d+\.\d+\.\d+)")
|
||||
RE_IP6 = re.compile(r"inet6\s+(addr:\s*){0,1}([0-9a-f:]+)", re.I)
|
||||
|
||||
# Check if strings are defined for AM and PM
|
||||
HAVE_AMPM = bool(time.strftime("%p", time.localtime()))
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
try:
|
||||
import win32process
|
||||
import win32con
|
||||
|
||||
# Define scheduling priorities
|
||||
WIN_SCHED_PRIOS = {
|
||||
1: win32process.IDLE_PRIORITY_CLASS,
|
||||
2: win32process.BELOW_NORMAL_PRIORITY_CLASS,
|
||||
3: win32process.NORMAL_PRIORITY_CLASS,
|
||||
4: win32process.ABOVE_NORMAL_PRIORITY_CLASS,
|
||||
}
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def time_format(fmt):
|
||||
""" Return time-format string adjusted for 12/24 hour clock setting """
|
||||
@@ -73,10 +55,10 @@ def time_format(fmt):
|
||||
return fmt
|
||||
|
||||
|
||||
def calc_age(date: datetime.datetime, trans=False) -> str:
|
||||
"""Calculate the age difference between now and date.
|
||||
Value is returned as either days, hours, or minutes.
|
||||
When 'trans' is True, time symbols will be translated.
|
||||
def calc_age(date, trans=False):
|
||||
""" Calculate the age difference between now and date.
|
||||
Value is returned as either days, hours, or minutes.
|
||||
When 'trans' is True, time symbols will be translated.
|
||||
"""
|
||||
if trans:
|
||||
d = T("d") # : Single letter abbreviation of day
|
||||
@@ -107,7 +89,16 @@ def calc_age(date: datetime.datetime, trans=False) -> str:
|
||||
return age
|
||||
|
||||
|
||||
def safe_lower(txt: Any) -> str:
|
||||
def monthrange(start, finish):
|
||||
""" Calculate months between 2 dates, used in the Config template """
|
||||
months = (finish.year - start.year) * 12 + finish.month + 1
|
||||
for i in range(start.month, months):
|
||||
year = (i - 1) / 12 + start.year
|
||||
month = (i - 1) % 12 + 1
|
||||
yield datetime.date(int(year), int(month), 1)
|
||||
|
||||
|
||||
def safe_lower(txt):
|
||||
""" Return lowercased string. Return '' for None """
|
||||
if txt:
|
||||
return txt.lower()
|
||||
@@ -139,19 +130,20 @@ def name_to_cat(fname, cat=None):
|
||||
return fname, cat
|
||||
|
||||
|
||||
def cat_to_opts(cat, pp=None, script=None, priority=None) -> Tuple[str, int, str, int]:
|
||||
"""Derive options from category, if options not already defined.
|
||||
Specified options have priority over category-options.
|
||||
If no valid category is given, special category '*' will supply default values
|
||||
def cat_to_opts(cat, pp=None, script=None, priority=None):
|
||||
""" Derive options from category, if options not already defined.
|
||||
Specified options have priority over category-options.
|
||||
If no valid category is given, special category '*' will supply default values
|
||||
"""
|
||||
def_cat = config.get_category()
|
||||
def_cat = config.get_categories("*")
|
||||
cat = safe_lower(cat)
|
||||
if cat in ("", "none", "default"):
|
||||
cat = "*"
|
||||
my_cat = config.get_category(cat)
|
||||
# Ignore the input category if we don't know it
|
||||
if my_cat == def_cat:
|
||||
try:
|
||||
my_cat = config.get_categories()[cat]
|
||||
except KeyError:
|
||||
cat = "*"
|
||||
my_cat = def_cat
|
||||
|
||||
if pp is None:
|
||||
pp = my_cat.pp()
|
||||
@@ -168,11 +160,11 @@ def cat_to_opts(cat, pp=None, script=None, priority=None) -> Tuple[str, int, str
|
||||
if priority == DEFAULT_PRIORITY:
|
||||
priority = def_cat.priority()
|
||||
|
||||
logging.debug("Parsing category %s to attributes: pp=%s script=%s prio=%s", cat, pp, script, priority)
|
||||
logging.debug("Cat->Attrib cat=%s pp=%s script=%s prio=%s", cat, pp, script, priority)
|
||||
return cat, pp, script, priority
|
||||
|
||||
|
||||
def pp_to_opts(pp: int) -> Tuple[bool, bool, bool]:
|
||||
def pp_to_opts(pp):
|
||||
""" Convert numeric processing options to (repair, unpack, delete) """
|
||||
# Convert the pp to an int
|
||||
pp = sabnzbd.interface.int_conv(pp)
|
||||
@@ -185,8 +177,10 @@ def pp_to_opts(pp: int) -> Tuple[bool, bool, bool]:
|
||||
return True, True, True
|
||||
|
||||
|
||||
def opts_to_pp(repair: bool, unpack: bool, delete: bool) -> int:
|
||||
def opts_to_pp(repair, unpack, delete):
|
||||
""" Convert (repair, unpack, delete) to numeric process options """
|
||||
if repair is None:
|
||||
return None
|
||||
pp = 0
|
||||
if repair:
|
||||
pp = 1
|
||||
@@ -221,10 +215,10 @@ def wildcard_to_re(text):
|
||||
|
||||
|
||||
def cat_convert(cat):
|
||||
"""Convert indexer's category/group-name to user categories.
|
||||
If no match found, but indexer-cat equals user-cat, then return user-cat
|
||||
If no match found, but the indexer-cat starts with the user-cat, return user-cat
|
||||
If no match found, return None
|
||||
""" Convert indexer's category/group-name to user categories.
|
||||
If no match found, but indexer-cat equals user-cat, then return user-cat
|
||||
If no match found, but the indexer-cat starts with the user-cat, return user-cat
|
||||
If no match found, return None
|
||||
"""
|
||||
if cat and cat.lower() != "none":
|
||||
cats = config.get_ordered_categories()
|
||||
@@ -261,8 +255,8 @@ def cat_convert(cat):
|
||||
|
||||
|
||||
def windows_variant():
|
||||
"""Determine Windows variant
|
||||
Return vista_plus, x64
|
||||
""" Determine Windows variant
|
||||
Return vista_plus, x64
|
||||
"""
|
||||
from win32api import GetVersionEx
|
||||
from win32con import VER_PLATFORM_WIN32_NT
|
||||
@@ -335,7 +329,7 @@ def set_serv_parms(service, args):
|
||||
return True
|
||||
|
||||
|
||||
def get_from_url(url: str) -> Optional[str]:
|
||||
def get_from_url(url):
|
||||
""" Retrieve URL and return content """
|
||||
try:
|
||||
req = urllib.request.Request(url)
|
||||
@@ -366,26 +360,26 @@ def convert_version(text):
|
||||
|
||||
|
||||
def check_latest_version():
|
||||
"""Do an online check for the latest version
|
||||
""" Do an online check for the latest version
|
||||
|
||||
Perform an online version check
|
||||
Syntax of online version file:
|
||||
<current-final-release>
|
||||
<url-of-current-final-release>
|
||||
<latest-alpha/beta-or-rc>
|
||||
<url-of-latest-alpha/beta/rc-release>
|
||||
The latter two lines are only present when an alpha/beta/rc is available.
|
||||
Formula for the version numbers (line 1 and 3).
|
||||
<major>.<minor>.<bugfix>[rc|beta|alpha]<cand>
|
||||
Perform an online version check
|
||||
Syntax of online version file:
|
||||
<current-final-release>
|
||||
<url-of-current-final-release>
|
||||
<latest-alpha/beta-or-rc>
|
||||
<url-of-latest-alpha/beta/rc-release>
|
||||
The latter two lines are only present when an alpha/beta/rc is available.
|
||||
Formula for the version numbers (line 1 and 3).
|
||||
<major>.<minor>.<bugfix>[rc|beta|alpha]<cand>
|
||||
|
||||
The <cand> value for a final version is assumned to be 99.
|
||||
The <cand> value for the beta/rc version is 1..98, with RC getting
|
||||
a boost of 80 and Beta of 40.
|
||||
This is done to signal alpha/beta/rc users of availability of the final
|
||||
version (which is implicitly 99).
|
||||
People will only be informed to upgrade to a higher alpha/beta/rc version, if
|
||||
they are already using an alpha/beta/rc.
|
||||
RC's are valued higher than Beta's, which are valued higher than Alpha's.
|
||||
The <cand> value for a final version is assumned to be 99.
|
||||
The <cand> value for the beta/rc version is 1..98, with RC getting
|
||||
a boost of 80 and Beta of 40.
|
||||
This is done to signal alpha/beta/rc users of availability of the final
|
||||
version (which is implicitly 99).
|
||||
People will only be informed to upgrade to a higher alpha/beta/rc version, if
|
||||
they are already using an alpha/beta/rc.
|
||||
RC's are valued higher than Beta's, which are valued higher than Alpha's.
|
||||
"""
|
||||
|
||||
if not cfg.version_check():
|
||||
@@ -437,7 +431,7 @@ def check_latest_version():
|
||||
url = url_beta
|
||||
|
||||
if testver and current < latest:
|
||||
# This is a test version, but user hasn't seen the
|
||||
# This is a test version, but user has't seen the
|
||||
# "Final" of this one yet, so show the Final
|
||||
sabnzbd.NEW_VERSION = (latest_label, url)
|
||||
elif current < latest:
|
||||
@@ -465,15 +459,15 @@ def upload_file_to_sabnzbd(url, fp):
|
||||
url = "%s&ma_username=%s&ma_password=%s" % (url, username, password)
|
||||
get_from_url(url)
|
||||
except:
|
||||
logging.error(T("Failed to upload file: %s"), fp)
|
||||
logging.error("Failed to upload file: %s", fp)
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
|
||||
|
||||
def from_units(val: str) -> float:
|
||||
def from_units(val):
|
||||
""" Convert K/M/G/T/P notation to float """
|
||||
val = str(val).strip().upper()
|
||||
if val == "-1":
|
||||
return float(val)
|
||||
return val
|
||||
m = RE_UNITS.search(val)
|
||||
if m:
|
||||
if m.group(2):
|
||||
@@ -493,9 +487,9 @@ def from_units(val: str) -> float:
|
||||
return 0.0
|
||||
|
||||
|
||||
def to_units(val: Union[int, float], postfix="") -> str:
|
||||
"""Convert number to K/M/G/T/P notation
|
||||
Show single decimal for M and higher
|
||||
def to_units(val, postfix=""):
|
||||
""" Convert number to K/M/G/T/P notation
|
||||
Show single decimal for M and higher
|
||||
"""
|
||||
dec_limit = 1
|
||||
if val < 0:
|
||||
@@ -524,8 +518,8 @@ def to_units(val: Union[int, float], postfix="") -> str:
|
||||
|
||||
def caller_name(skip=2):
|
||||
"""Get a name of a caller in the format module.method
|
||||
Originally used: https://gist.github.com/techtonik/2151727
|
||||
Adapted for speed by using sys calls directly
|
||||
Originally used: https://gist.github.com/techtonik/2151727
|
||||
Adapted for speed by using sys calls directly
|
||||
"""
|
||||
# Only do the tracing on Debug (function is always called)
|
||||
if cfg.log_level() != 2:
|
||||
@@ -534,7 +528,7 @@ def caller_name(skip=2):
|
||||
parentframe = sys._getframe(skip)
|
||||
function_name = parentframe.f_code.co_name
|
||||
|
||||
# Module name is not available in the binaries, we can use the filename instead
|
||||
# Modulename not available in the binaries, we can use the filename instead
|
||||
if hasattr(sys, "frozen"):
|
||||
module_name = inspect.getfile(parentframe)
|
||||
else:
|
||||
@@ -582,9 +576,9 @@ def split_host(srv):
|
||||
|
||||
|
||||
def get_cache_limit():
|
||||
"""Depending on OS, calculate cache limits.
|
||||
In ArticleCache it will make sure we stay
|
||||
within system limits for 32/64 bit
|
||||
""" Depending on OS, calculate cache limits.
|
||||
In ArticleCache it will make sure we stay
|
||||
within system limits for 32/64 bit
|
||||
"""
|
||||
# Calculate, if possible
|
||||
try:
|
||||
@@ -647,7 +641,7 @@ def get_windows_memory():
|
||||
|
||||
def get_darwin_memory():
|
||||
""" Use system-call to extract total memory on macOS """
|
||||
system_output = run_command(["sysctl", "hw.memsize"])
|
||||
system_output = sabnzbd.newsunpack.run_simple(["sysctl", "hw.memsize"])
|
||||
return float(system_output.split()[1])
|
||||
|
||||
|
||||
@@ -739,7 +733,7 @@ def format_time_string(seconds):
|
||||
return " ".join(completestr)
|
||||
|
||||
|
||||
def int_conv(value: Any) -> int:
|
||||
def int_conv(value):
|
||||
""" Safe conversion to int (can handle None) """
|
||||
try:
|
||||
value = int(value)
|
||||
@@ -798,13 +792,13 @@ def get_all_passwords(nzo):
|
||||
|
||||
# Check size
|
||||
if len(pws) > 30:
|
||||
logging.warning_helpful(
|
||||
logging.warning(
|
||||
T(
|
||||
"Your password file contains more than 30 passwords, testing all these passwords takes a lot of time. Try to only list useful passwords."
|
||||
)
|
||||
)
|
||||
except:
|
||||
logging.warning(T("Failed to read the password file %s"), pw_file)
|
||||
logging.warning("Failed to read the passwords file %s", pw_file)
|
||||
|
||||
if nzo.password:
|
||||
# If an explicit password was set, add a retry without password, just in case.
|
||||
@@ -835,26 +829,19 @@ def find_on_path(targets):
|
||||
|
||||
|
||||
def probablyipv4(ip):
|
||||
try:
|
||||
return ipaddress.ip_address(ip).version == 4
|
||||
except:
|
||||
if ip.count(".") == 3 and re.sub("[0123456789.]", "", ip) == "":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def probablyipv6(ip):
|
||||
# Returns True if the given input is probably an IPv6 address
|
||||
# Square Brackets like '[2001::1]' are OK
|
||||
try:
|
||||
# Check for plain IPv6 address
|
||||
return ipaddress.ip_address(ip).version == 6
|
||||
except:
|
||||
try:
|
||||
# Remove '[' and ']' and test again:
|
||||
ip = re.search(r"^\[(.*)\]$", ip).group(1)
|
||||
return ipaddress.ip_address(ip).version == 6
|
||||
except:
|
||||
# No, not an IPv6 address
|
||||
return False
|
||||
if ip.count(":") >= 2 and re.sub(r"[0123456789abcdefABCDEF:\[\]]", "", ip) == "":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def ip_extract():
|
||||
@@ -877,7 +864,17 @@ def ip_extract():
|
||||
for item in info:
|
||||
ips.append(item[4][0])
|
||||
else:
|
||||
output = run_command(program)
|
||||
p = subprocess.Popen(
|
||||
program,
|
||||
shell=False,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=None,
|
||||
creationflags=0,
|
||||
)
|
||||
output = platform_btou(p.stdout.read())
|
||||
p.wait()
|
||||
for line in output.split("\n"):
|
||||
m = RE_IP4.search(line)
|
||||
if not (m and m.group(2)):
|
||||
@@ -888,8 +885,8 @@ def ip_extract():
|
||||
|
||||
|
||||
def get_base_url(url):
|
||||
"""Return only the true root domain for the favicon, so api.oznzb.com -> oznzb.com
|
||||
But also api.althub.co.za -> althub.co.za
|
||||
""" Return only the true root domain for the favicon, so api.oznzb.com -> oznzb.com
|
||||
But also api.althub.co.za -> althub.co.za
|
||||
"""
|
||||
url_host = urllib.parse.urlparse(url).hostname
|
||||
if url_host:
|
||||
@@ -922,72 +919,3 @@ def nntp_to_msg(text):
|
||||
else:
|
||||
lines = text.split(b"\r\n")
|
||||
return ubtou(lines[0])
|
||||
|
||||
|
||||
def build_and_run_command(command: List[str], flatten_command=False, **kwargs):
|
||||
"""Builds and then runs command with nessecary flags and optional
|
||||
IONice and Nice commands. Optional Popen arguments can be supplied.
|
||||
On Windows we need to run our own list2cmdline for Unrar.
|
||||
Returns the Popen-instance.
|
||||
"""
|
||||
# command[0] should be set, and thus not None
|
||||
if not command[0]:
|
||||
logging.error(T("[%s] The command in build_command is undefined."), caller_name())
|
||||
raise IOError
|
||||
|
||||
if not sabnzbd.WIN32:
|
||||
if command[0].endswith(".py"):
|
||||
with open(command[0], "r") as script_file:
|
||||
if not userxbit(command[0]):
|
||||
# Inform user that Python scripts need x-bit and then stop
|
||||
logging.error(T('Python script "%s" does not have execute (+x) permission set'), command[0])
|
||||
raise IOError
|
||||
elif script_file.read(2) != "#!":
|
||||
# No shebang (#!) defined, add default python
|
||||
command.insert(0, "python")
|
||||
|
||||
if sabnzbd.newsunpack.IONICE_COMMAND and cfg.ionice():
|
||||
ionice = cfg.ionice().split()
|
||||
command = ionice + command
|
||||
command.insert(0, sabnzbd.newsunpack.IONICE_COMMAND)
|
||||
if sabnzbd.newsunpack.NICE_COMMAND and cfg.nice():
|
||||
nice = cfg.nice().split()
|
||||
command = nice + command
|
||||
command.insert(0, sabnzbd.newsunpack.NICE_COMMAND)
|
||||
creationflags = 0
|
||||
startupinfo = None
|
||||
else:
|
||||
# For Windows we always need to add python interpreter
|
||||
if command[0].endswith(".py"):
|
||||
command.insert(0, "python.exe")
|
||||
if flatten_command:
|
||||
command = sabnzbd.newsunpack.list2cmdline(command)
|
||||
# On some Windows platforms we need to supress a quick pop-up of the command window
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags = win32process.STARTF_USESHOWWINDOW
|
||||
startupinfo.wShowWindow = win32con.SW_HIDE
|
||||
creationflags = WIN_SCHED_PRIOS[cfg.win_process_prio()]
|
||||
|
||||
# Set the basic Popen arguments
|
||||
popen_kwargs = {
|
||||
"stdin": subprocess.PIPE,
|
||||
"stdout": subprocess.PIPE,
|
||||
"stderr": subprocess.STDOUT,
|
||||
"startupinfo": startupinfo,
|
||||
"creationflags": creationflags,
|
||||
}
|
||||
# Update with the supplied ones
|
||||
popen_kwargs.update(kwargs)
|
||||
|
||||
# Run the command
|
||||
logging.info("[%s] Running external command: %s", caller_name(), command)
|
||||
logging.debug("Popen arguments: %s", popen_kwargs)
|
||||
return subprocess.Popen(command, **popen_kwargs)
|
||||
|
||||
|
||||
def run_command(cmd: List[str], **kwargs):
|
||||
""" Run simple external command and return output as a string. """
|
||||
with build_and_run_command(cmd, **kwargs) as p:
|
||||
txt = platform_btou(p.stdout.read())
|
||||
p.wait()
|
||||
return txt
|
||||
|
||||
@@ -28,21 +28,12 @@ import time
|
||||
import zlib
|
||||
import shutil
|
||||
import functools
|
||||
from typing import List
|
||||
from subprocess import Popen
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.encoding import platform_btou, correct_unknown_encoding, ubtou
|
||||
import sabnzbd.utils.rarfile as rarfile
|
||||
from sabnzbd.misc import (
|
||||
format_time_string,
|
||||
find_on_path,
|
||||
int_conv,
|
||||
get_all_passwords,
|
||||
calc_age,
|
||||
cmp,
|
||||
run_command,
|
||||
build_and_run_command,
|
||||
)
|
||||
from sabnzbd.misc import format_time_string, find_on_path, int_conv, get_all_passwords, calc_age, cmp, caller_name
|
||||
from sabnzbd.filesystem import (
|
||||
make_script_path,
|
||||
real_path,
|
||||
@@ -56,15 +47,31 @@ from sabnzbd.filesystem import (
|
||||
setname_from_path,
|
||||
get_ext,
|
||||
get_filename,
|
||||
analyze_rar_filename,
|
||||
)
|
||||
from sabnzbd.nzbstuff import NzbObject, NzbFile
|
||||
from sabnzbd.sorting import SeriesSorter
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.constants import Status
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
try:
|
||||
import win32api
|
||||
import win32con
|
||||
import win32process
|
||||
|
||||
# Define scheduling priorities
|
||||
WIN_SCHED_PRIOS = {
|
||||
1: win32process.IDLE_PRIORITY_CLASS,
|
||||
2: win32process.BELOW_NORMAL_PRIORITY_CLASS,
|
||||
3: win32process.NORMAL_PRIORITY_CLASS,
|
||||
4: win32process.ABOVE_NORMAL_PRIORITY_CLASS,
|
||||
}
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
LOADING_RE = re.compile(r'^Loading "(.+)"')
|
||||
TARGET_RE = re.compile(r'^(?:File|Target): "(.+)" -')
|
||||
@@ -117,14 +124,7 @@ def find_programs(curdir):
|
||||
if not sabnzbd.newsunpack.PAR2_COMMAND:
|
||||
sabnzbd.newsunpack.PAR2_COMMAND = find_on_path("par2")
|
||||
if not sabnzbd.newsunpack.RAR_COMMAND:
|
||||
sabnzbd.newsunpack.RAR_COMMAND = find_on_path(
|
||||
(
|
||||
"unrar",
|
||||
"rar",
|
||||
"unrar3",
|
||||
"rar3",
|
||||
)
|
||||
)
|
||||
sabnzbd.newsunpack.RAR_COMMAND = find_on_path(("unrar", "rar", "unrar3", "rar3",))
|
||||
sabnzbd.newsunpack.NICE_COMMAND = find_on_path("nice")
|
||||
sabnzbd.newsunpack.IONICE_COMMAND = find_on_path("ionice")
|
||||
if not sabnzbd.newsunpack.ZIP_COMMAND:
|
||||
@@ -169,7 +169,7 @@ ENV_NZO_FIELDS = [
|
||||
]
|
||||
|
||||
|
||||
def external_processing(extern_proc, nzo: NzbObject, complete_dir, nicename, status):
|
||||
def external_processing(extern_proc, nzo, complete_dir, nicename, status):
|
||||
""" Run a user postproc script, return console output and exit value """
|
||||
failure_url = nzo.nzo_info.get("failure", "")
|
||||
# Items can be bool or null, causing POpen to fail
|
||||
@@ -186,7 +186,7 @@ def external_processing(extern_proc, nzo: NzbObject, complete_dir, nicename, sta
|
||||
]
|
||||
|
||||
# Add path to original NZB
|
||||
nzb_paths = globber_full(nzo.admin_path, "*.gz")
|
||||
nzb_paths = globber_full(nzo.workpath, "*.gz")
|
||||
|
||||
# Fields not in the NZO directly
|
||||
extra_env_fields = {
|
||||
@@ -200,8 +200,31 @@ def external_processing(extern_proc, nzo: NzbObject, complete_dir, nicename, sta
|
||||
}
|
||||
|
||||
try:
|
||||
p = build_and_run_command(command, env=create_env(nzo, extra_env_fields))
|
||||
sabnzbd.PostProcessor.external_process = p
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
env = create_env(nzo, extra_env_fields)
|
||||
|
||||
logging.info(
|
||||
"Running external script %s(%s, %s, %s, %s, %s, %s, %s, %s)",
|
||||
extern_proc,
|
||||
complete_dir,
|
||||
nzo.filename,
|
||||
nicename,
|
||||
"",
|
||||
nzo.cat,
|
||||
nzo.group,
|
||||
status,
|
||||
failure_url,
|
||||
)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
env=env,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
# Follow the output, so we can abort it
|
||||
proc = p.stdout
|
||||
@@ -218,6 +241,14 @@ def external_processing(extern_proc, nzo: NzbObject, complete_dir, nicename, sta
|
||||
|
||||
# Show current line in history
|
||||
nzo.set_action_line(T("Running script"), line)
|
||||
|
||||
# Check if we should still continue
|
||||
if not nzo.pp_active:
|
||||
p.kill()
|
||||
lines.append(T("PostProcessing was aborted (%s)") % T("Script"))
|
||||
# Print at least what we got
|
||||
output = "\n".join(lines)
|
||||
return output, 1
|
||||
except:
|
||||
logging.debug("Failed script %s, Traceback: ", extern_proc, exc_info=True)
|
||||
return "Cannot run script %s\r\n" % extern_proc, -1
|
||||
@@ -227,9 +258,34 @@ def external_processing(extern_proc, nzo: NzbObject, complete_dir, nicename, sta
|
||||
return output, ret
|
||||
|
||||
|
||||
def unpack_magic(
|
||||
nzo: NzbObject, workdir, workdir_complete, dele, one_folder, joinables, zips, rars, sevens, ts, depth=0
|
||||
):
|
||||
def external_script(script, p1, p2, p3=None, p4=None):
|
||||
""" Run a user script with two parameters, return console output and exit value """
|
||||
command = [script, p1, p2, p3, p4]
|
||||
|
||||
try:
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
env = create_env()
|
||||
logging.info("Running user script %s(%s, %s)", script, p1, p2)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
env=env,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
except:
|
||||
logging.debug("Failed script %s, Traceback: ", script, exc_info=True)
|
||||
return "Cannot run script %s\r\n" % script, -1
|
||||
|
||||
output = platform_btou(p.stdout.read())
|
||||
ret = p.wait()
|
||||
return output, ret
|
||||
|
||||
|
||||
def unpack_magic(nzo, workdir, workdir_complete, dele, one_folder, joinables, zips, rars, sevens, ts, depth=0):
|
||||
""" Do a recursive unpack from all archives in 'workdir' to 'workdir_complete' """
|
||||
if depth > 5:
|
||||
logging.warning(T("Unpack nesting too deep [%s]"), nzo.final_name)
|
||||
@@ -377,9 +433,9 @@ def get_seq_number(name):
|
||||
return 0
|
||||
|
||||
|
||||
def file_join(nzo: NzbObject, workdir, workdir_complete, delete, joinables):
|
||||
"""Join and joinable files in 'workdir' to 'workdir_complete' and
|
||||
when successful, delete originals
|
||||
def file_join(nzo, workdir, workdir_complete, delete, joinables):
|
||||
""" Join and joinable files in 'workdir' to 'workdir_complete' and
|
||||
when successful, delete originals
|
||||
"""
|
||||
newfiles = []
|
||||
bufsize = 24 * 1024 * 1024
|
||||
@@ -468,71 +524,28 @@ def file_join(nzo: NzbObject, workdir, workdir_complete, delete, joinables):
|
||||
##############################################################################
|
||||
# (Un)Rar Functions
|
||||
##############################################################################
|
||||
def rar_unpack(nzo: NzbObject, workdir: str, workdir_complete: str, delete: bool, one_folder: bool, rars: List[str]):
|
||||
"""Unpack multiple sets 'rars' of RAR files from 'workdir' to 'workdir_complete.
|
||||
When 'delete' is set, originals will be deleted.
|
||||
When 'one_folder' is set, all files will be in a single folder
|
||||
def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
|
||||
""" Unpack multiple sets 'rars' of RAR files from 'workdir' to 'workdir_complete.
|
||||
When 'delete' is set, originals will be deleted.
|
||||
When 'one_folder' is set, all files will be in a single folder
|
||||
"""
|
||||
fail = False
|
||||
newfiles = extracted_files = []
|
||||
|
||||
# Is the direct-unpacker still running? We wait for it
|
||||
if nzo.direct_unpacker:
|
||||
wait_count = 0
|
||||
last_stats = nzo.direct_unpacker.get_formatted_stats()
|
||||
while nzo.direct_unpacker.is_alive():
|
||||
logging.debug("DirectUnpacker still alive for %s: %s", nzo.final_name, last_stats)
|
||||
|
||||
# Bump the file-lock in case it's stuck
|
||||
with nzo.direct_unpacker.next_file_lock:
|
||||
nzo.direct_unpacker.next_file_lock.notify()
|
||||
time.sleep(2)
|
||||
|
||||
# Did something change? Might be stuck
|
||||
if last_stats == nzo.direct_unpacker.get_formatted_stats():
|
||||
wait_count += 1
|
||||
if wait_count > 60:
|
||||
# We abort after 2 minutes of no changes
|
||||
nzo.direct_unpacker.abort()
|
||||
else:
|
||||
wait_count = 0
|
||||
last_stats = nzo.direct_unpacker.get_formatted_stats()
|
||||
|
||||
# Process everything already extracted by Direct Unpack
|
||||
for rar_set in nzo.direct_unpacker.success_sets:
|
||||
logging.info("Set %s completed by DirectUnpack", rar_set)
|
||||
unpacked_rars, newfiles = nzo.direct_unpacker.success_sets[rar_set]
|
||||
logging.debug("Rars: %s", unpacked_rars)
|
||||
logging.debug("Newfiles: %s", newfiles)
|
||||
extracted_files.extend(newfiles)
|
||||
|
||||
# Remove all source files from the list and from the disk (if requested)
|
||||
# so they don't get parsed by the regular unpack
|
||||
for rar in unpacked_rars:
|
||||
if rar in rars:
|
||||
rars.remove(rar)
|
||||
if delete:
|
||||
remove_file(rar)
|
||||
|
||||
# Clear all sets
|
||||
nzo.direct_unpacker.success_sets = []
|
||||
|
||||
# See which sets are left
|
||||
rar_sets = {}
|
||||
for rar in rars:
|
||||
# Skip any files that were already removed
|
||||
if not os.path.exists(rar):
|
||||
continue
|
||||
rar_set, _ = analyze_rar_filename(rar)
|
||||
rar_set = setname_from_path(rar)
|
||||
if RAR_RE_V3.search(rar_set):
|
||||
# Remove the ".partXX" part
|
||||
rar_set = os.path.splitext(rar_set)[0]
|
||||
if rar_set not in rar_sets:
|
||||
rar_sets[rar_set] = []
|
||||
rar_sets[rar_set].append(rar)
|
||||
|
||||
logging.debug("Remaining rar sets: %s", rar_sets)
|
||||
logging.debug("Rar_sets: %s", rar_sets)
|
||||
|
||||
for rar_set in rar_sets:
|
||||
# Run the RAR extractor
|
||||
rar_sets[rar_set].sort(key=functools.cmp_to_key(rar_sort))
|
||||
|
||||
rarpath = rar_sets[rar_set][0]
|
||||
|
||||
if workdir_complete and rarpath.startswith(workdir):
|
||||
@@ -540,31 +553,67 @@ def rar_unpack(nzo: NzbObject, workdir: str, workdir_complete: str, delete: bool
|
||||
else:
|
||||
extraction_path = os.path.split(rarpath)[0]
|
||||
|
||||
logging.info("Extracting rarfile %s (belonging to %s) to %s", rarpath, rar_set, extraction_path)
|
||||
try:
|
||||
fail, newfiles, rars = rar_extract(
|
||||
rarpath, len(rar_sets[rar_set]), one_folder, nzo, rar_set, extraction_path
|
||||
)
|
||||
except:
|
||||
fail = True
|
||||
msg = sys.exc_info()[1]
|
||||
nzo.fail_msg = T("Unpacking failed, %s") % msg
|
||||
setname = nzo.final_name
|
||||
nzo.set_unpack_info("Unpack", T('[%s] Error "%s" while unpacking RAR files') % (setname, msg))
|
||||
# Is the direct-unpacker still running? We wait for it
|
||||
if nzo.direct_unpacker:
|
||||
wait_count = 0
|
||||
last_stats = nzo.direct_unpacker.get_formatted_stats()
|
||||
while nzo.direct_unpacker.is_alive():
|
||||
logging.debug("DirectUnpacker still alive for %s: %s", nzo.final_name, last_stats)
|
||||
|
||||
logging.error(T('Error "%s" while running rar_unpack on %s'), msg, setname)
|
||||
logging.debug("Traceback: ", exc_info=True)
|
||||
# Bump the file-lock in case it's stuck
|
||||
with nzo.direct_unpacker.next_file_lock:
|
||||
nzo.direct_unpacker.next_file_lock.notify()
|
||||
time.sleep(2)
|
||||
|
||||
if not fail:
|
||||
logging.debug("Rars: %s", rars)
|
||||
logging.debug("Newfiles: %s", newfiles)
|
||||
# Did something change? Might be stuck
|
||||
if last_stats == nzo.direct_unpacker.get_formatted_stats():
|
||||
wait_count += 1
|
||||
if wait_count > 60:
|
||||
# We abort after 2 minutes of no changes
|
||||
nzo.direct_unpacker.abort()
|
||||
else:
|
||||
wait_count = 0
|
||||
last_stats = nzo.direct_unpacker.get_formatted_stats()
|
||||
|
||||
# Did we already direct-unpack it? Not when recursive-unpacking
|
||||
if nzo.direct_unpacker and rar_set in nzo.direct_unpacker.success_sets:
|
||||
logging.info("Set %s completed by DirectUnpack", rar_set)
|
||||
fail = False
|
||||
success = True
|
||||
rars, newfiles = nzo.direct_unpacker.success_sets.pop(rar_set)
|
||||
else:
|
||||
logging.info("Extracting rarfile %s (belonging to %s) to %s", rarpath, rar_set, extraction_path)
|
||||
try:
|
||||
fail, newfiles, rars = rar_extract(
|
||||
rarpath, len(rar_sets[rar_set]), one_folder, nzo, rar_set, extraction_path
|
||||
)
|
||||
# Was it aborted?
|
||||
if not nzo.pp_active:
|
||||
fail = True
|
||||
break
|
||||
success = not fail
|
||||
except:
|
||||
success = False
|
||||
fail = True
|
||||
msg = sys.exc_info()[1]
|
||||
nzo.fail_msg = T("Unpacking failed, %s") % msg
|
||||
setname = nzo.final_name
|
||||
nzo.set_unpack_info("Unpack", T('[%s] Error "%s" while unpacking RAR files') % (setname, msg))
|
||||
|
||||
logging.error(T('Error "%s" while running rar_unpack on %s'), msg, setname)
|
||||
logging.debug("Traceback: ", exc_info=True)
|
||||
|
||||
if success:
|
||||
logging.debug("rar_unpack(): Rars: %s", rars)
|
||||
logging.debug("rar_unpack(): Newfiles: %s", newfiles)
|
||||
extracted_files.extend(newfiles)
|
||||
|
||||
# Do not fail if this was a recursive unpack
|
||||
if fail and rarpath.startswith(workdir_complete):
|
||||
# Do not delete the files, leave it to user!
|
||||
logging.info("Ignoring failure to do recursive unpack of %s", rarpath)
|
||||
fail = False
|
||||
fail = 0
|
||||
success = True
|
||||
newfiles = []
|
||||
|
||||
# Do not fail if this was maybe just some duplicate fileset
|
||||
@@ -572,11 +621,12 @@ def rar_unpack(nzo: NzbObject, workdir: str, workdir_complete: str, delete: bool
|
||||
if fail and rar_set.endswith((".1", ".2")):
|
||||
# Just in case, we leave the raw files
|
||||
logging.info("Ignoring failure of unpack for possible duplicate file %s", rarpath)
|
||||
fail = False
|
||||
fail = 0
|
||||
success = True
|
||||
newfiles = []
|
||||
|
||||
# Delete the old files if we have to
|
||||
if not fail and delete and newfiles:
|
||||
if success and delete and newfiles:
|
||||
for rar in rars:
|
||||
try:
|
||||
remove_file(rar)
|
||||
@@ -597,10 +647,10 @@ def rar_unpack(nzo: NzbObject, workdir: str, workdir_complete: str, delete: bool
|
||||
return fail, extracted_files
|
||||
|
||||
|
||||
def rar_extract(rarfile_path, numrars, one_folder, nzo: NzbObject, setname, extraction_path):
|
||||
"""Unpack single rar set 'rarfile' to 'extraction_path',
|
||||
with password tries
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, rars
|
||||
def rar_extract(rarfile_path, numrars, one_folder, nzo, setname, extraction_path):
|
||||
""" Unpack single rar set 'rarfile' to 'extraction_path',
|
||||
with password tries
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, rars
|
||||
"""
|
||||
fail = 0
|
||||
new_files = None
|
||||
@@ -624,9 +674,9 @@ def rar_extract(rarfile_path, numrars, one_folder, nzo: NzbObject, setname, extr
|
||||
return fail, new_files, rars
|
||||
|
||||
|
||||
def rar_extract_core(rarfile_path, numrars, one_folder, nzo: NzbObject, setname, extraction_path, password):
|
||||
"""Unpack single rar set 'rarfile_path' to 'extraction_path'
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password)/fail==3(crc-error), new_files, rars
|
||||
def rar_extract_core(rarfile_path, numrars, one_folder, nzo, setname, extraction_path, password):
|
||||
""" Unpack single rar set 'rarfile_path' to 'extraction_path'
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password)/fail==3(crc-error), new_files, rars
|
||||
"""
|
||||
start = time.time()
|
||||
|
||||
@@ -651,25 +701,22 @@ def rar_extract_core(rarfile_path, numrars, one_folder, nzo: NzbObject, setname,
|
||||
rename = "-or" # Auto renaming
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
# For Unrar to support long-path, we need to circumvent Python's list2cmdline
|
||||
# For Unrar to support long-path, we need to cricumvent Python's list2cmdline
|
||||
# See: https://github.com/sabnzbd/sabnzbd/issues/1043
|
||||
# The -scf forces the output to be UTF8
|
||||
command = [
|
||||
"%s" % RAR_COMMAND,
|
||||
action,
|
||||
"-idp",
|
||||
"-scf",
|
||||
overwrite,
|
||||
rename,
|
||||
"-ai",
|
||||
password_command,
|
||||
rarfile_path,
|
||||
"%s" % clip_path(rarfile_path),
|
||||
"%s\\" % long_path(extraction_path),
|
||||
]
|
||||
|
||||
elif RAR_PROBLEM:
|
||||
# Use only oldest options, specifically no "-or" or "-scf"
|
||||
# Luckily platform_btou has a fallback for non-UTF-8
|
||||
# Use only oldest options (specifically no "-or")
|
||||
command = [
|
||||
"%s" % RAR_COMMAND,
|
||||
action,
|
||||
@@ -681,12 +728,10 @@ def rar_extract_core(rarfile_path, numrars, one_folder, nzo: NzbObject, setname,
|
||||
]
|
||||
else:
|
||||
# Don't use "-ai" (not needed for non-Windows)
|
||||
# The -scf forces the output to be UTF8
|
||||
command = [
|
||||
"%s" % RAR_COMMAND,
|
||||
action,
|
||||
"-idp",
|
||||
"-scf",
|
||||
overwrite,
|
||||
rename,
|
||||
password_command,
|
||||
@@ -697,10 +742,20 @@ def rar_extract_core(rarfile_path, numrars, one_folder, nzo: NzbObject, setname,
|
||||
if cfg.ignore_unrar_dates():
|
||||
command.insert(3, "-tsm-")
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command, flatten_command=True)
|
||||
|
||||
# Get list of all the volumes part of this set
|
||||
logging.debug("Analyzing rar file ... %s found", rarfile.is_rarfile(rarfile_path))
|
||||
p = build_and_run_command(command, flatten_command=True)
|
||||
sabnzbd.PostProcessor.external_process = p
|
||||
logging.debug("Running unrar %s", command)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
proc = p.stdout
|
||||
if p.stdin:
|
||||
@@ -721,6 +776,15 @@ def rar_extract_core(rarfile_path, numrars, one_folder, nzo: NzbObject, setname,
|
||||
if not line:
|
||||
break
|
||||
|
||||
# Check if we should still continue
|
||||
if not nzo.pp_active:
|
||||
p.kill()
|
||||
msg = T("PostProcessing was aborted (%s)") % T("Unpack")
|
||||
nzo.fail_msg = msg
|
||||
nzo.set_unpack_info("Unpack", msg, setname)
|
||||
nzo.status = Status.FAILED
|
||||
return fail, (), ()
|
||||
|
||||
line = line.strip()
|
||||
lines.append(line)
|
||||
|
||||
@@ -856,9 +920,9 @@ def rar_extract_core(rarfile_path, numrars, one_folder, nzo: NzbObject, setname,
|
||||
##############################################################################
|
||||
# (Un)Zip Functions
|
||||
##############################################################################
|
||||
def unzip(nzo: NzbObject, workdir, workdir_complete, delete, one_folder, zips):
|
||||
"""Unpack multiple sets 'zips' of ZIP files from 'workdir' to 'workdir_complete.
|
||||
When 'delete' is ste, originals will be deleted.
|
||||
def unzip(nzo, workdir, workdir_complete, delete, one_folder, zips):
|
||||
""" Unpack multiple sets 'zips' of ZIP files from 'workdir' to 'workdir_complete.
|
||||
When 'delete' is ste, originals will be deleted.
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -924,19 +988,32 @@ def ZIP_Extract(zipfile, extraction_path, one_folder):
|
||||
if one_folder or cfg.flat_unpack():
|
||||
command.insert(3, "-j") # Unpack without folders
|
||||
|
||||
p = build_and_run_command(command)
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
logging.debug("Starting unzip: %s", command)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
output = platform_btou(p.stdout.read())
|
||||
ret = p.wait()
|
||||
logging.debug("unzip output: \n%s", output)
|
||||
|
||||
ret = p.wait()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
##############################################################################
|
||||
# 7Zip Functions
|
||||
##############################################################################
|
||||
def unseven(nzo: NzbObject, workdir, workdir_complete, delete, one_folder, sevens):
|
||||
"""Unpack multiple sets '7z' of 7Zip files from 'workdir' to 'workdir_complete.
|
||||
When 'delete' is set, originals will be deleted.
|
||||
def unseven(nzo, workdir, workdir_complete, delete, one_folder, sevens):
|
||||
""" Unpack multiple sets '7z' of 7Zip files from 'workdir' to 'workdir_complete.
|
||||
When 'delete' is set, originals will be deleted.
|
||||
"""
|
||||
i = 0
|
||||
unseven_failed = False
|
||||
@@ -982,9 +1059,9 @@ def unseven(nzo: NzbObject, workdir, workdir_complete, delete, one_folder, seven
|
||||
return unseven_failed, new_files
|
||||
|
||||
|
||||
def seven_extract(nzo: NzbObject, sevenset, extensions, extraction_path, one_folder, delete):
|
||||
"""Unpack single set 'sevenset' to 'extraction_path', with password tries
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, sevens
|
||||
def seven_extract(nzo, sevenset, extensions, extraction_path, one_folder, delete):
|
||||
""" Unpack single set 'sevenset' to 'extraction_path', with password tries
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, sevens
|
||||
"""
|
||||
# Before we start, make sure the 7z binary SEVEN_COMMAND is defined
|
||||
if not SEVEN_COMMAND:
|
||||
@@ -1016,8 +1093,8 @@ def seven_extract(nzo: NzbObject, sevenset, extensions, extraction_path, one_fol
|
||||
|
||||
|
||||
def seven_extract_core(sevenset, extensions, extraction_path, one_folder, delete, password):
|
||||
"""Unpack single 7Z set 'sevenset' to 'extraction_path'
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, message
|
||||
""" Unpack single 7Z set 'sevenset' to 'extraction_path'
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, message
|
||||
"""
|
||||
if one_folder:
|
||||
method = "e" # Unpack without folders
|
||||
@@ -1050,8 +1127,19 @@ def seven_extract_core(sevenset, extensions, extraction_path, one_folder, delete
|
||||
orig_dir_content = listdir_full(extraction_path)
|
||||
|
||||
command = [SEVEN_COMMAND, method, "-y", overwrite, parm, case, password, "-o%s" % extraction_path, name]
|
||||
p = build_and_run_command(command)
|
||||
sabnzbd.PostProcessor.external_process = p
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
logging.debug("Starting 7za: %s", command)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
output = platform_btou(p.stdout.read())
|
||||
logging.debug("7za output: %s", output)
|
||||
|
||||
@@ -1090,7 +1178,7 @@ def seven_extract_core(sevenset, extensions, extraction_path, one_folder, delete
|
||||
##############################################################################
|
||||
# PAR2 Functions
|
||||
##############################################################################
|
||||
def par2_repair(parfile_nzf: NzbFile, nzo: NzbObject, workdir, setname, single):
|
||||
def par2_repair(parfile_nzf, nzo, workdir, setname, single):
|
||||
""" Try to repair a set, return readd or correctness """
|
||||
# Check if file exists, otherwise see if another is done
|
||||
parfile_path = os.path.join(workdir, parfile_nzf.filename)
|
||||
@@ -1216,7 +1304,7 @@ _RE_LOADING_PAR2 = re.compile(r'Loading "([^"]+)"\.')
|
||||
_RE_LOADED_PAR2 = re.compile(r"Loaded (\d+) new packets")
|
||||
|
||||
|
||||
def PAR_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
def PAR_Verify(parfile, nzo, setname, joinables, single=False):
|
||||
""" Run par2 on par-set """
|
||||
used_joinables = []
|
||||
used_for_repair = []
|
||||
@@ -1247,7 +1335,7 @@ def PAR_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
# Or the one that complains about basepath
|
||||
# Only if we're not doing multicore
|
||||
if not sabnzbd.WIN32 and not sabnzbd.DARWIN:
|
||||
par2text = run_command([command[0], "-h"])
|
||||
par2text = run_simple([command[0], "-h"])
|
||||
if "No data skipping" in par2text:
|
||||
logging.info("Detected par2cmdline version that skips blocks, adding -N parameter")
|
||||
command.insert(2, "-N")
|
||||
@@ -1256,14 +1344,24 @@ def PAR_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
command.insert(2, "-B")
|
||||
command.insert(3, parfolder)
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
|
||||
# par2multicore wants to see \\.\ paths on Windows
|
||||
# See: https://github.com/sabnzbd/sabnzbd/pull/771
|
||||
if sabnzbd.WIN32:
|
||||
command = [clip_path(x) if x.startswith("\\\\?\\") else x for x in command]
|
||||
|
||||
# Run the external command
|
||||
p = build_and_run_command(command)
|
||||
sabnzbd.PostProcessor.external_process = p
|
||||
logging.info("Starting par2: %s", command)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
proc = p.stdout
|
||||
|
||||
if p.stdin:
|
||||
@@ -1299,6 +1397,16 @@ def PAR_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
line = linebuf.strip()
|
||||
linebuf = ""
|
||||
|
||||
# Check if we should still continue
|
||||
if not nzo.pp_active:
|
||||
p.kill()
|
||||
msg = T("PostProcessing was aborted (%s)") % T("Repair")
|
||||
nzo.fail_msg = msg
|
||||
nzo.set_unpack_info("Repair", msg, setname)
|
||||
nzo.status = Status.FAILED
|
||||
readd = False
|
||||
break
|
||||
|
||||
# Skip empty lines
|
||||
if line == "":
|
||||
continue
|
||||
@@ -1342,7 +1450,7 @@ def PAR_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
block_table[nzf.blocks] = nzf
|
||||
|
||||
if block_table:
|
||||
nzf = block_table[min(block_table)]
|
||||
nzf = block_table[min(block_table.keys())]
|
||||
logging.info("Found new par2file %s", nzf.filename)
|
||||
|
||||
# Move from extrapar list to files to be downloaded
|
||||
@@ -1528,7 +1636,7 @@ def PAR_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
_RE_FILENAME = re.compile(r'"([^"]+)"')
|
||||
|
||||
|
||||
def MultiPar_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
def MultiPar_Verify(parfile, nzo, setname, joinables, single=False):
|
||||
""" Run par2 on par-set """
|
||||
parfolder = os.path.split(parfile)[0]
|
||||
used_joinables = []
|
||||
@@ -1538,10 +1646,9 @@ def MultiPar_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
nzo.status = Status.VERIFYING
|
||||
start = time.time()
|
||||
|
||||
# Caching of verification implemented by adding -vs/-vd
|
||||
# Force output of utf-8 by adding -uo
|
||||
# Caching of verification implemented by adding:
|
||||
# But not really required due to prospective-par2
|
||||
command = [str(MULTIPAR_COMMAND), "r", "-uo", "-vs2", "-vd%s" % parfolder, parfile]
|
||||
command = [str(MULTIPAR_COMMAND), "r", "-vs2", "-vd%s" % parfolder, parfile]
|
||||
|
||||
# Check if there are maybe par2cmdline/par2tbb commands supplied
|
||||
if "-t" in cfg.par_option() or "-p" in cfg.par_option():
|
||||
@@ -1564,20 +1671,31 @@ def MultiPar_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
wildcard = setname + "*"
|
||||
command.append(os.path.join(parfolder, wildcard))
|
||||
|
||||
# Run MultiPar
|
||||
p = build_and_run_command(command)
|
||||
sabnzbd.PostProcessor.external_process = p
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
logging.info("Starting MultiPar: %s", command)
|
||||
|
||||
lines = []
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
proc = p.stdout
|
||||
|
||||
if p.stdin:
|
||||
p.stdin.close()
|
||||
|
||||
# Set up our variables
|
||||
lines = []
|
||||
datafiles = []
|
||||
renames = {}
|
||||
reconstructed = []
|
||||
|
||||
linebuf = b""
|
||||
linebuf = ""
|
||||
finished = 0
|
||||
readd = False
|
||||
|
||||
@@ -1593,17 +1711,27 @@ def MultiPar_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
|
||||
# Loop over the output, whee
|
||||
while 1:
|
||||
char = proc.read(1)
|
||||
char = platform_btou(proc.read(1))
|
||||
if not char:
|
||||
break
|
||||
|
||||
# Line not complete yet
|
||||
if char not in (b"\n", b"\r"):
|
||||
if char not in ("\n", "\r"):
|
||||
linebuf += char
|
||||
continue
|
||||
|
||||
line = ubtou(linebuf).strip()
|
||||
linebuf = b""
|
||||
line = linebuf.strip()
|
||||
linebuf = ""
|
||||
|
||||
# Check if we should still continue
|
||||
if not nzo.pp_active:
|
||||
p.kill()
|
||||
msg = T("PostProcessing was aborted (%s)") % T("Repair")
|
||||
nzo.fail_msg = msg
|
||||
nzo.set_unpack_info("Repair", msg, setname)
|
||||
nzo.status = Status.FAILED
|
||||
readd = False
|
||||
break
|
||||
|
||||
# Skip empty lines
|
||||
if line == "":
|
||||
@@ -1634,7 +1762,7 @@ def MultiPar_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
block_table[nzf.blocks] = nzf
|
||||
|
||||
if block_table:
|
||||
nzf = block_table[min(block_table)]
|
||||
nzf = block_table[min(block_table.keys())]
|
||||
logging.info("Found new par2file %s", nzf.filename)
|
||||
|
||||
# Move from extrapar list to files to be downloaded
|
||||
@@ -1900,9 +2028,9 @@ def MultiPar_Verify(parfile, nzo: NzbObject, setname, joinables, single=False):
|
||||
|
||||
|
||||
def create_env(nzo=None, extra_env_fields={}):
|
||||
"""Modify the environment for pp-scripts with extra information
|
||||
macOS: Return copy of environment without PYTHONPATH and PYTHONHOME
|
||||
other: return None
|
||||
""" Modify the environment for pp-scripts with extra information
|
||||
OSX: Return copy of environment without PYTHONPATH and PYTHONHOME
|
||||
other: return None
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
|
||||
@@ -1959,10 +2087,76 @@ def create_env(nzo=None, extra_env_fields={}):
|
||||
return env
|
||||
|
||||
|
||||
def userxbit(filename):
|
||||
# Returns boolean if the x-bit for user is set on the given file
|
||||
# This is a workaround: os.access(filename, os.X_OK) does not work on certain mounted file systems
|
||||
# Does not work on Windows, but it is not called on Windows
|
||||
|
||||
# rwx rwx rwx
|
||||
# 876 543 210 # we want bit 6 from the right, counting from 0
|
||||
userxbit = 1 << 6 # bit 6
|
||||
rwxbits = os.stat(filename)[0] # the first element of os.stat() is "mode"
|
||||
# do logical AND, check if it is not 0:
|
||||
xbitset = (rwxbits & userxbit) > 0
|
||||
return xbitset
|
||||
|
||||
|
||||
def build_command(command, flatten_command=False):
|
||||
""" Prepare list from running an external program
|
||||
On Windows we need to run our own list2cmdline for Unrar
|
||||
"""
|
||||
# command[0] should be set, and thus not None
|
||||
if not command[0]:
|
||||
logging.error(T("[%s] The command in build_command is undefined."), caller_name())
|
||||
raise IOError
|
||||
|
||||
if not sabnzbd.WIN32:
|
||||
if command[0].endswith(".py"):
|
||||
with open(command[0], "r") as script_file:
|
||||
if not userxbit(command[0]):
|
||||
# Inform user that Python scripts need x-bit and then stop
|
||||
logging.error(T('Python script "%s" does not have execute (+x) permission set'), command[0])
|
||||
raise IOError
|
||||
elif script_file.read(2) != "#!":
|
||||
# No shebang (#!) defined, add default python
|
||||
command.insert(0, "python")
|
||||
|
||||
if IONICE_COMMAND and cfg.ionice().strip():
|
||||
lst = cfg.ionice().split()
|
||||
lst.reverse()
|
||||
for arg in lst:
|
||||
command.insert(0, arg)
|
||||
command.insert(0, IONICE_COMMAND)
|
||||
if NICE_COMMAND and cfg.nice().strip():
|
||||
lst = cfg.nice().split()
|
||||
lst.reverse()
|
||||
for arg in lst:
|
||||
command.insert(0, arg)
|
||||
command.insert(0, NICE_COMMAND)
|
||||
need_shell = False
|
||||
stup = None
|
||||
creationflags = 0
|
||||
|
||||
else:
|
||||
# For Windows we always need to add python interpreter
|
||||
if command[0].endswith(".py"):
|
||||
command.insert(0, "python")
|
||||
|
||||
need_shell = os.path.splitext(command[0])[1].lower() not in (".exe", ".com")
|
||||
stup = subprocess.STARTUPINFO()
|
||||
stup.dwFlags = win32process.STARTF_USESHOWWINDOW
|
||||
stup.wShowWindow = win32con.SW_HIDE
|
||||
creationflags = WIN_SCHED_PRIOS[cfg.win_process_prio()]
|
||||
|
||||
if need_shell or flatten_command:
|
||||
command = list2cmdline(command)
|
||||
|
||||
return stup, need_shell, command, creationflags
|
||||
|
||||
|
||||
def rar_volumelist(rarfile_path, password, known_volumes):
|
||||
"""List volumes that are part of this rarset
|
||||
and merge them with parsed paths list, removing duplicates.
|
||||
We assume RarFile is right and use parsed paths as backup.
|
||||
""" Extract volumes that are part of this rarset
|
||||
and merge them with existing list, removing duplicates
|
||||
"""
|
||||
# UnRar is required to read some RAR files
|
||||
# RarFile can fail in special cases
|
||||
@@ -1981,12 +2175,12 @@ def rar_volumelist(rarfile_path, password, known_volumes):
|
||||
zf_volumes = []
|
||||
|
||||
# Remove duplicates
|
||||
zf_volumes_base = [os.path.basename(vol) for vol in zf_volumes]
|
||||
for known_volume in known_volumes:
|
||||
if os.path.basename(known_volume) not in zf_volumes_base:
|
||||
known_volumes_base = [os.path.basename(vol) for vol in known_volumes]
|
||||
for zf_volume in zf_volumes:
|
||||
if os.path.basename(zf_volume) not in known_volumes_base:
|
||||
# Long-path notation just to be sure
|
||||
zf_volumes.append(long_path(known_volume))
|
||||
return zf_volumes
|
||||
known_volumes.append(long_path(zf_volume))
|
||||
return known_volumes
|
||||
|
||||
|
||||
# Sort the various RAR filename formats properly :\
|
||||
@@ -2006,9 +2200,9 @@ def rar_sort(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
|
||||
""" 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 = ([], [], [], [], [], [])
|
||||
|
||||
@@ -2088,7 +2282,7 @@ def quick_check_set(set, nzo):
|
||||
if nzf.md5sum == md5pack[file]:
|
||||
try:
|
||||
logging.debug("Quick-check will rename %s to %s", nzf.filename, file)
|
||||
renamer(os.path.join(nzo.download_path, nzf.filename), os.path.join(nzo.download_path, file))
|
||||
renamer(os.path.join(nzo.downpath, nzf.filename), os.path.join(nzo.downpath, file))
|
||||
renames[file] = nzf.filename
|
||||
nzf.filename = file
|
||||
result &= True
|
||||
@@ -2115,15 +2309,15 @@ def quick_check_set(set, nzo):
|
||||
|
||||
|
||||
def unrar_check(rar):
|
||||
"""Return version number of unrar, where "5.01" returns 501
|
||||
Also return whether an original version is found
|
||||
(version, original)
|
||||
""" Return version number of unrar, where "5.01" returns 501
|
||||
Also return whether an original version is found
|
||||
(version, original)
|
||||
"""
|
||||
version = 0
|
||||
original = ""
|
||||
if rar:
|
||||
try:
|
||||
version = run_command([rar])
|
||||
version = run_simple(rar)
|
||||
except:
|
||||
return version, original
|
||||
original = "Alexander Roshal" in version
|
||||
@@ -2138,7 +2332,7 @@ def unrar_check(rar):
|
||||
def par2_mt_check(par2_path):
|
||||
""" Detect if we have multicore par2 variants """
|
||||
try:
|
||||
par2_version = run_command([par2_path, "-h"])
|
||||
par2_version = run_simple([par2_path, "-h"])
|
||||
# Look for a threads option
|
||||
if "-t<" in par2_version:
|
||||
return True
|
||||
@@ -2191,7 +2385,7 @@ def is_sfv_file(myfile):
|
||||
return sfv_info_line_counter >= 1
|
||||
|
||||
|
||||
def sfv_check(sfvs, nzo: NzbObject, workdir):
|
||||
def sfv_check(sfvs, nzo, workdir):
|
||||
""" Verify files using SFV files """
|
||||
# Update status
|
||||
nzo.status = Status.VERIFYING
|
||||
@@ -2248,7 +2442,7 @@ def sfv_check(sfvs, nzo: NzbObject, workdir):
|
||||
if nzf.filename in calculated_crc32 and calculated_crc32[nzf.filename] == sfv_parse_results[file]:
|
||||
try:
|
||||
logging.debug("SFV-check will rename %s to %s", nzf.filename, file)
|
||||
renamer(os.path.join(nzo.download_path, nzf.filename), os.path.join(nzo.download_path, file))
|
||||
renamer(os.path.join(nzo.downpath, nzf.filename), os.path.join(nzo.downpath, file))
|
||||
renames[file] = nzf.filename
|
||||
nzf.filename = file
|
||||
result &= True
|
||||
@@ -2314,9 +2508,9 @@ def analyse_show(name):
|
||||
return show_name, info.get("season_num", ""), info.get("episode_num", ""), info.get("ep_name", "")
|
||||
|
||||
|
||||
def pre_queue(nzo: NzbObject, pp, cat):
|
||||
"""Run pre-queue script (if any) and process results.
|
||||
pp and cat are supplied seperate since they can change.
|
||||
def pre_queue(nzo, pp, cat):
|
||||
""" Run pre-queue script (if any) and process results.
|
||||
pp and cat are supplied seperate since they can change.
|
||||
"""
|
||||
|
||||
def fix(p):
|
||||
@@ -2352,7 +2546,19 @@ def pre_queue(nzo: NzbObject, pp, cat):
|
||||
}
|
||||
|
||||
try:
|
||||
p = build_and_run_command(command, env=create_env(nzo, extra_env_fields))
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
env = create_env(nzo, extra_env_fields)
|
||||
logging.info("Running pre-queue script %s", command)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
env=env,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
except:
|
||||
logging.debug("Failed script %s, Traceback: ", script_path, exc_info=True)
|
||||
return values
|
||||
@@ -2405,8 +2611,20 @@ class SevenZip:
|
||||
names = []
|
||||
# Future extension: use '-sccUTF-8' to get names in UTF8 encoding
|
||||
command = [SEVEN_COMMAND, "l", "-p", "-y", "-slt", self.path]
|
||||
output = run_command(command)
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
output = platform_btou(p.stdout.read())
|
||||
_ = p.wait()
|
||||
re_path = re.compile("^Path = (.+)")
|
||||
for line in output.split("\n"):
|
||||
m = re_path.search(line)
|
||||
@@ -2420,9 +2638,30 @@ class SevenZip:
|
||||
def read(self, name):
|
||||
""" Read named file from 7Zip and return data """
|
||||
command = [SEVEN_COMMAND, "e", "-p", "-y", "-so", self.path, name]
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
|
||||
# Ignore diagnostic output, otherwise it will be appended to content
|
||||
return run_command(command, stderr=subprocess.DEVNULL)
|
||||
p = Popen(
|
||||
command,
|
||||
shell=need_shell,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
startupinfo=stup,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
output = platform_btou(p.stdout.read())
|
||||
_ = p.wait()
|
||||
return output
|
||||
|
||||
def close(self):
|
||||
""" Close file """
|
||||
pass
|
||||
|
||||
|
||||
def run_simple(cmd):
|
||||
""" Run simple external command and return output """
|
||||
with Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
|
||||
txt = platform_btou(p.stdout.read())
|
||||
p.wait()
|
||||
return txt
|
||||
|
||||
@@ -26,12 +26,11 @@ from nntplib import NNTPPermanentError
|
||||
import time
|
||||
import logging
|
||||
import ssl
|
||||
from typing import List, Optional
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.constants import DEF_TIMEOUT
|
||||
from sabnzbd.constants import *
|
||||
from sabnzbd.encoding import utob
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.misc import nntp_to_msg, probablyipv4, probablyipv6
|
||||
|
||||
# Set pre-defined socket timeout
|
||||
@@ -51,7 +50,7 @@ def _retrieve_info(server):
|
||||
else:
|
||||
server.bad_cons = 0
|
||||
(server.info, server.request) = (info, False)
|
||||
sabnzbd.Downloader.wakeup()
|
||||
sabnzbd.downloader.Downloader.do.wakeup()
|
||||
|
||||
|
||||
def request_server_info(server):
|
||||
@@ -140,7 +139,7 @@ class NNTP:
|
||||
def __init__(self, host, port, info, sslenabled, nw, block=False, write_fds=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.nw: NewsWrapper = nw
|
||||
self.nw = nw
|
||||
self.blocking = block
|
||||
self.error_msg = None
|
||||
|
||||
@@ -166,21 +165,21 @@ class NNTP:
|
||||
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:
|
||||
if nw.server.ssl_verify < 2:
|
||||
ctx.check_hostname = False
|
||||
# Certificates optional
|
||||
if self.nw.server.ssl_verify == 0:
|
||||
if nw.server.ssl_verify == 0:
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Did the user set a custom cipher-string?
|
||||
if self.nw.server.ssl_ciphers:
|
||||
if 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)
|
||||
ctx.set_ciphers(nw.server.ssl_ciphers)
|
||||
|
||||
self.sock = ctx.wrap_socket(socket.socket(af, socktype, proto), server_hostname=self.nw.server.host)
|
||||
self.sock = ctx.wrap_socket(socket.socket(af, socktype, proto), server_hostname=str(nw.server.host))
|
||||
else:
|
||||
# Use a regular wrapper, no certificate validation
|
||||
self.sock = ssl.wrap_socket(socket.socket(af, socktype, proto))
|
||||
self.sock = ssl.wrap_socket(socket.socket(af, socktype, proto), ciphers=sabnzbd.cfg.ssl_ciphers())
|
||||
else:
|
||||
self.sock = socket.socket(af, socktype, proto)
|
||||
|
||||
@@ -261,9 +260,6 @@ class NNTP:
|
||||
logging.info(msg)
|
||||
self.nw.server.warning = msg
|
||||
|
||||
def __repr__(self):
|
||||
return "<NNTP: %s:%s>" % (self.host, self.port)
|
||||
|
||||
|
||||
class NewsWrapper:
|
||||
# Pre-define attributes to save memory
|
||||
@@ -287,16 +283,16 @@ class NewsWrapper:
|
||||
)
|
||||
|
||||
def __init__(self, server, thrdnum, block=False):
|
||||
self.server: sabnzbd.downloader.Server = server
|
||||
self.server = server
|
||||
self.thrdnum = thrdnum
|
||||
self.blocking = block
|
||||
|
||||
self.timeout = None
|
||||
self.article: Optional[sabnzbd.nzbstuff.Article] = None
|
||||
self.data: List[bytes] = []
|
||||
self.article = None
|
||||
self.data = []
|
||||
self.last_line = ""
|
||||
|
||||
self.nntp: Optional[NNTP] = None
|
||||
self.nntp = None
|
||||
self.recv = None
|
||||
|
||||
self.connected = False
|
||||
@@ -434,16 +430,16 @@ class NewsWrapper:
|
||||
# Official end-of-article is ".\r\n" but sometimes it can get lost between 2 chunks
|
||||
chunk_len = len(chunk)
|
||||
if chunk[-5:] == b"\r\n.\r\n":
|
||||
return chunk_len, True, False
|
||||
return (chunk_len, True, False)
|
||||
elif chunk_len < 5 and len(self.data) > 1:
|
||||
# We need to make sure the end is not split over 2 chunks
|
||||
# This is faster than join()
|
||||
combine_chunk = self.data[-2][-5:] + chunk
|
||||
if combine_chunk[-5:] == b"\r\n.\r\n":
|
||||
return chunk_len, True, False
|
||||
return (chunk_len, True, False)
|
||||
|
||||
# Still in middle of data, so continue!
|
||||
return chunk_len, False, False
|
||||
return (chunk_len, False, False)
|
||||
|
||||
def soft_reset(self):
|
||||
self.timeout = None
|
||||
@@ -485,11 +481,3 @@ class NewsWrapper:
|
||||
except:
|
||||
pass
|
||||
del self.nntp
|
||||
|
||||
def __repr__(self):
|
||||
return "<NewsWrapper: server=%s:%s, thread=%s, connected=%s>" % (
|
||||
self.server.host,
|
||||
self.server.port,
|
||||
self.thrdnum,
|
||||
self.connected,
|
||||
)
|
||||
|
||||
@@ -30,10 +30,8 @@ from threading import Thread
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.encoding import platform_btou
|
||||
from sabnzbd.filesystem import make_script_path
|
||||
from sabnzbd.misc import build_and_run_command
|
||||
from sabnzbd.newsunpack import create_env
|
||||
from sabnzbd.newsunpack import external_script
|
||||
|
||||
if sabnzbd.FOUNDATION:
|
||||
import Foundation
|
||||
@@ -58,7 +56,6 @@ except:
|
||||
TT = lambda x: x
|
||||
NOTIFICATION = {
|
||||
"startup": TT("Startup/Shutdown"), #: Notification
|
||||
"pause_resume": TT("Pause") + "/" + TT("Resume"), #: Notification
|
||||
"download": TT("Added NZB"), #: Notification
|
||||
"pp": TT("Post-processing started"), # : Notification
|
||||
"complete": TT("Job finished"), #: Notification
|
||||
@@ -102,8 +99,8 @@ def get_prio(gtype, section):
|
||||
|
||||
|
||||
def check_cat(section, job_cat, keyword=None):
|
||||
"""Check if `job_cat` is enabled in `section`.
|
||||
* = All, if no other categories selected.
|
||||
""" Check if `job_cat` is enabled in `section`.
|
||||
* = All, if no other categories selected.
|
||||
"""
|
||||
if not job_cat:
|
||||
return True
|
||||
@@ -119,7 +116,6 @@ def check_cat(section, job_cat, keyword=None):
|
||||
|
||||
def send_notification(title, msg, gtype, job_cat=None):
|
||||
""" Send Notification message """
|
||||
logging.info("Sending notification: %s - %s (type=%s, job_cat=%s)", title, msg, gtype, job_cat)
|
||||
# Notification Center
|
||||
if sabnzbd.DARWIN and sabnzbd.cfg.ncenter_enable():
|
||||
if check_classes(gtype, "ncenter") and check_cat("ncenter", job_cat):
|
||||
@@ -174,7 +170,7 @@ def send_notify_osd(title, message):
|
||||
# Wrap notify2.init to prevent blocking in dbus
|
||||
# when there's no active notification daemon
|
||||
try:
|
||||
_NTFOSD = _NTFOSD or notify2.init("SABnzbd")
|
||||
_NTFOSD = _NTFOSD or notify2.init("icon-summary-body")
|
||||
except:
|
||||
_NTFOSD = False
|
||||
|
||||
@@ -349,11 +345,10 @@ def send_nscript(title, msg, gtype, force=False, test=None):
|
||||
""" Run user's notification script """
|
||||
if test:
|
||||
script = test.get("nscript_script")
|
||||
nscript_parameters = test.get("nscript_parameters")
|
||||
parameters = test.get("nscript_parameters")
|
||||
else:
|
||||
script = sabnzbd.cfg.nscript_script()
|
||||
nscript_parameters = sabnzbd.cfg.nscript_parameters()
|
||||
nscript_parameters = nscript_parameters.split()
|
||||
parameters = sabnzbd.cfg.nscript_parameters()
|
||||
if not script:
|
||||
return T("Cannot send, missing required data")
|
||||
title = "SABnzbd: " + T(NOTIFICATION.get(gtype, "other"))
|
||||
@@ -361,20 +356,12 @@ def send_nscript(title, msg, gtype, force=False, test=None):
|
||||
if force or check_classes(gtype, "nscript"):
|
||||
script_path = make_script_path(script)
|
||||
if script_path:
|
||||
ret = -1
|
||||
output = None
|
||||
try:
|
||||
p = build_and_run_command([script_path, gtype, title, msg] + nscript_parameters, env=create_env())
|
||||
output = platform_btou(p.stdout.read())
|
||||
ret = p.wait()
|
||||
except:
|
||||
logging.info("Failed script %s, Traceback: ", script, exc_info=True)
|
||||
|
||||
output, ret = external_script(script_path, gtype, title, msg, parameters)
|
||||
if ret:
|
||||
logging.error(T('Script returned exit code %s and output "%s"'), ret, output)
|
||||
return T('Script returned exit code %s and output "%s"') % (ret, output)
|
||||
else:
|
||||
logging.info("Successfully executed notification script %s", script_path)
|
||||
logging.info("Successfully executed notification script " + script_path)
|
||||
else:
|
||||
return T('Notification script "%s" does not exist') % script_path
|
||||
return ""
|
||||
|
||||
@@ -118,7 +118,7 @@ def nzbfile_parser(raw_data, nzo):
|
||||
pass
|
||||
|
||||
# Sort the articles by part number, compatible with Python 3.5
|
||||
raw_article_db_sorted = [raw_article_db[partnum] for partnum in sorted(raw_article_db)]
|
||||
raw_article_db_sorted = [raw_article_db[partnum] for partnum in sorted(raw_article_db.keys())]
|
||||
|
||||
# Create NZF
|
||||
nzf = sabnzbd.nzbstuff.NzbFile(file_date, file_name, raw_article_db_sorted, file_bytes, nzo)
|
||||
@@ -139,7 +139,7 @@ def nzbfile_parser(raw_data, nzo):
|
||||
else:
|
||||
logging.info("Error importing %s, skipping", file_name)
|
||||
if nzf.nzf_id:
|
||||
sabnzbd.remove_data(nzf.nzf_id, nzo.admin_path)
|
||||
sabnzbd.remove_data(nzf.nzf_id, nzo.workpath)
|
||||
skipped_files += 1
|
||||
|
||||
# Final bookkeeping
|
||||
@@ -169,10 +169,10 @@ def process_nzb_archive_file(
|
||||
password=None,
|
||||
nzo_id=None,
|
||||
):
|
||||
"""Analyse ZIP file and create job(s).
|
||||
Accepts ZIP files with ONLY nzb/nfo/folder files in it.
|
||||
returns (status, nzo_ids)
|
||||
status: -1==Error, 0==OK, 1==Ignore
|
||||
""" Analyse ZIP file and create job(s).
|
||||
Accepts ZIP files with ONLY nzb/nfo/folder files in it.
|
||||
returns (status, nzo_ids)
|
||||
status: -1==Error, 0==OK, 1==Ignore
|
||||
"""
|
||||
nzo_ids = []
|
||||
if catdir is None:
|
||||
@@ -234,10 +234,10 @@ def process_nzb_archive_file(
|
||||
if nzo:
|
||||
if nzo_id:
|
||||
# Re-use existing nzo_id, when a "future" job gets it payload
|
||||
sabnzbd.NzbQueue.remove(nzo_id, delete_all_data=False)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo_id, add_to_history=False, delete_all_data=False)
|
||||
nzo.nzo_id = nzo_id
|
||||
nzo_id = None
|
||||
nzo_ids.append(sabnzbd.NzbQueue.add(nzo))
|
||||
nzo_ids.append(sabnzbd.nzbqueue.NzbQueue.do.add(nzo))
|
||||
nzo.update_rating()
|
||||
zf.close()
|
||||
try:
|
||||
@@ -270,10 +270,10 @@ def process_single_nzb(
|
||||
password=None,
|
||||
nzo_id=None,
|
||||
):
|
||||
"""Analyze file and create a job from it
|
||||
Supports NZB, NZB.BZ2, NZB.GZ and GZ.NZB-in-disguise
|
||||
returns (status, nzo_ids)
|
||||
status: -2==Error/retry, -1==Error, 0==OK
|
||||
""" Analyze file and create a job from it
|
||||
Supports NZB, NZB.BZ2, NZB.GZ and GZ.NZB-in-disguise
|
||||
returns (status, nzo_ids)
|
||||
status: -2==Error/retry, -1==Error, 0==OK
|
||||
"""
|
||||
nzo_ids = []
|
||||
if catdir is None:
|
||||
@@ -329,7 +329,7 @@ def process_single_nzb(
|
||||
except TypeError:
|
||||
# Duplicate, ignore
|
||||
if nzo_id:
|
||||
sabnzbd.NzbQueue.remove(nzo_id)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo_id, add_to_history=False)
|
||||
nzo = None
|
||||
except ValueError:
|
||||
# Empty
|
||||
@@ -346,9 +346,9 @@ def process_single_nzb(
|
||||
if nzo:
|
||||
if nzo_id:
|
||||
# Re-use existing nzo_id, when a "future" job gets it payload
|
||||
sabnzbd.NzbQueue.remove(nzo_id, delete_all_data=False)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo_id, add_to_history=False, delete_all_data=False)
|
||||
nzo.nzo_id = nzo_id
|
||||
nzo_ids.append(sabnzbd.NzbQueue.add(nzo, quiet=reuse))
|
||||
nzo_ids.append(sabnzbd.nzbqueue.NzbQueue.do.add(nzo, quiet=reuse))
|
||||
nzo.update_rating()
|
||||
|
||||
try:
|
||||
|
||||
@@ -24,10 +24,9 @@ import logging
|
||||
import time
|
||||
import datetime
|
||||
import functools
|
||||
from typing import List, Dict, Union, Tuple, Optional
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.nzbstuff import NzbObject, Article
|
||||
from sabnzbd.nzbstuff import NzbObject
|
||||
from sabnzbd.misc import exit_sab, cat_to_opts, int_conv, caller_name, cmp, safe_lower
|
||||
from sabnzbd.filesystem import get_admin_path, remove_all, globber_full, remove_file
|
||||
from sabnzbd.nzbparser import process_single_nzb
|
||||
@@ -43,7 +42,7 @@ from sabnzbd.constants import (
|
||||
LOW_PRIORITY,
|
||||
NORMAL_PRIORITY,
|
||||
HIGH_PRIORITY,
|
||||
FORCE_PRIORITY,
|
||||
TOP_PRIORITY,
|
||||
REPAIR_PRIORITY,
|
||||
STOP_PRIORITY,
|
||||
VERIFIED_FILE,
|
||||
@@ -54,24 +53,29 @@ from sabnzbd.constants import (
|
||||
)
|
||||
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.downloader import Server
|
||||
from sabnzbd.assembler import file_has_articles
|
||||
import sabnzbd.downloader
|
||||
from sabnzbd.assembler import Assembler, file_has_articles
|
||||
import sabnzbd.notifier as notifier
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
|
||||
|
||||
class NzbQueue:
|
||||
""" Singleton NzbQueue """
|
||||
|
||||
do = None
|
||||
|
||||
def __init__(self):
|
||||
self.__top_only: bool = cfg.top_only()
|
||||
self.__nzo_list: List[NzbObject] = []
|
||||
self.__nzo_table: Dict[str, NzbObject] = {}
|
||||
self.__top_only = cfg.top_only()
|
||||
self.__nzo_list = []
|
||||
self.__nzo_table = {}
|
||||
|
||||
NzbQueue.do = self
|
||||
|
||||
def read_queue(self, repair):
|
||||
"""Read queue from disk, supporting repair modes
|
||||
0 = no repairs
|
||||
1 = use existing queue, add missing "incomplete" folders
|
||||
2 = Discard all queue admin, reconstruct from "incomplete" folders
|
||||
""" Read queue from disk, supporting repair modes
|
||||
0 = no repairs
|
||||
1 = use existing queue, add missing "incomplete" folders
|
||||
2 = Discard all queue admin, reconstruct from "incomplete" folders
|
||||
"""
|
||||
nzo_ids = []
|
||||
if repair < 2:
|
||||
@@ -111,7 +115,6 @@ class NzbQueue:
|
||||
|
||||
# Scan for any folders in "incomplete" that are not yet in the queue
|
||||
if repair:
|
||||
logging.info("Starting queue repair")
|
||||
self.scan_jobs(not folders)
|
||||
# Handle any lost future jobs
|
||||
for item in globber_full(os.path.join(cfg.admin_dir.get_path(), FUTURE_Q_FOLDER)):
|
||||
@@ -129,10 +132,10 @@ class NzbQueue:
|
||||
|
||||
@NzbQueueLocker
|
||||
def scan_jobs(self, all_jobs=False, action=True):
|
||||
"""Scan "incomplete" for missing folders,
|
||||
'all' is True: Include active folders
|
||||
'action' is True, do the recovery action
|
||||
returns list of orphaned folders
|
||||
""" Scan "incomplete" for missing folders,
|
||||
'all' is True: Include active folders
|
||||
'action' is True, do the recovery action
|
||||
returns list of orphaned folders
|
||||
"""
|
||||
result = []
|
||||
# Folders from the download queue
|
||||
@@ -142,7 +145,7 @@ class NzbQueue:
|
||||
registered = [nzo.work_name for nzo in self.__nzo_list]
|
||||
|
||||
# Retryable folders from History
|
||||
items = sabnzbd.api.build_history()[0]
|
||||
items = sabnzbd.api.build_history(output=True)[0]
|
||||
# Anything waiting or active or retryable is a known item
|
||||
registered.extend(
|
||||
[
|
||||
@@ -179,6 +182,7 @@ class NzbQueue:
|
||||
remove_all(admin_path, "*.gz", keep_folder=True)
|
||||
logging.debug("Repair job %s with new NZB (%s)", name, new_nzb.filename)
|
||||
_, nzo_ids = sabnzbd.add_nzbfile(new_nzb, nzbname=name, reuse=repair_folder, password=password)
|
||||
nzo_id = nzo_ids[0]
|
||||
else:
|
||||
# Was this file already post-processed?
|
||||
verified = sabnzbd.load_data(VERIFIED_FILE, admin_path, remove=False)
|
||||
@@ -189,47 +193,34 @@ class NzbQueue:
|
||||
if filenames:
|
||||
logging.debug("Repair job %s by re-parsing stored NZB", name)
|
||||
_, nzo_ids = sabnzbd.add_nzbfile(filenames[0], nzbname=name, reuse=repair_folder, password=password)
|
||||
nzo_id = nzo_ids[0]
|
||||
else:
|
||||
try:
|
||||
logging.debug("Repair job %s without stored NZB", name)
|
||||
nzo = NzbObject(name, nzbname=name, reuse=repair_folder)
|
||||
nzo.password = password
|
||||
self.add(nzo)
|
||||
nzo_ids = [nzo.nzo_id]
|
||||
except:
|
||||
# NzoObject can throw exceptions if duplicate or unwanted etc
|
||||
logging.info("Skipping %s due to exception", name, exc_info=True)
|
||||
nzo_ids = []
|
||||
logging.debug("Repair job %s without stored NZB", name)
|
||||
nzo = NzbObject(name, nzbname=name, reuse=repair_folder)
|
||||
nzo.password = password
|
||||
self.add(nzo)
|
||||
nzo_id = nzo.nzo_id
|
||||
|
||||
# Return None if we could not add anything
|
||||
if nzo_ids:
|
||||
return nzo_ids[0]
|
||||
return None
|
||||
return nzo_id
|
||||
|
||||
@NzbQueueLocker
|
||||
def send_back(self, old_nzo: NzbObject):
|
||||
def send_back(self, nzo):
|
||||
""" Send back job to queue after successful pre-check """
|
||||
try:
|
||||
nzb_path = globber_full(old_nzo.admin_path, "*.gz")[0]
|
||||
nzb_path = globber_full(nzo.workpath, "*.gz")[0]
|
||||
except:
|
||||
logging.info("Failed to find NZB file after pre-check (%s)", old_nzo.nzo_id)
|
||||
logging.info("Failed to find NZB file after pre-check (%s)", nzo.nzo_id)
|
||||
return
|
||||
|
||||
# Store old position and create new NZO
|
||||
old_position = self.__nzo_list.index(old_nzo)
|
||||
res, nzo_ids = process_single_nzb(
|
||||
old_nzo.filename, nzb_path, keep=True, reuse=old_nzo.download_path, nzo_id=old_nzo.nzo_id
|
||||
)
|
||||
# Need to remove it first, otherwise it might still be downloading
|
||||
self.remove(nzo, add_to_history=False, cleanup=False)
|
||||
res, nzo_ids = process_single_nzb(nzo.filename, nzb_path, keep=True, reuse=nzo.downpath, nzo_id=nzo.nzo_id)
|
||||
if res == 0 and nzo_ids:
|
||||
# Swap to old position
|
||||
new_nzo = self.get_nzo(nzo_ids[0])
|
||||
self.__nzo_list.remove(new_nzo)
|
||||
self.__nzo_list.insert(old_position, new_nzo)
|
||||
# Reset reuse flag to make pause/abort on encryption possible
|
||||
self.__nzo_table[nzo_ids[0]].reuse = None
|
||||
|
||||
@NzbQueueLocker
|
||||
def save(self, save_nzo: Union[NzbObject, None, bool] = None):
|
||||
def save(self, save_nzo=None):
|
||||
""" Save queue, all nzo's or just the specified one """
|
||||
logging.info("Saving queue")
|
||||
|
||||
@@ -243,7 +234,7 @@ class NzbQueue:
|
||||
# Also includes save_data for NZO
|
||||
nzo.save_to_disk()
|
||||
else:
|
||||
sabnzbd.save_data(nzo, nzo.nzo_id, nzo.admin_path)
|
||||
sabnzbd.save_data(nzo, nzo.nzo_id, nzo.workpath)
|
||||
|
||||
sabnzbd.save_admin((QUEUE_VERSION, nzo_ids, []), QUEUE_FILE_NAME)
|
||||
|
||||
@@ -267,7 +258,7 @@ class NzbQueue:
|
||||
self.add(future_nzo)
|
||||
return future_nzo
|
||||
|
||||
def change_opts(self, nzo_ids: str, pp: int) -> int:
|
||||
def change_opts(self, nzo_ids, pp):
|
||||
result = 0
|
||||
for nzo_id in [item.strip() for item in nzo_ids.split(",")]:
|
||||
if nzo_id in self.__nzo_table:
|
||||
@@ -275,7 +266,7 @@ class NzbQueue:
|
||||
result += 1
|
||||
return result
|
||||
|
||||
def change_script(self, nzo_ids: str, script: str) -> int:
|
||||
def change_script(self, nzo_ids, script):
|
||||
result = 0
|
||||
for nzo_id in [item.strip() for item in nzo_ids.split(",")]:
|
||||
if nzo_id in self.__nzo_table:
|
||||
@@ -284,7 +275,7 @@ class NzbQueue:
|
||||
result += 1
|
||||
return result
|
||||
|
||||
def change_cat(self, nzo_ids: str, cat: str, explicit_priority=None):
|
||||
def change_cat(self, nzo_ids, cat, explicit_priority=None):
|
||||
result = 0
|
||||
for nzo_id in [item.strip() for item in nzo_ids.split(",")]:
|
||||
if nzo_id in self.__nzo_table:
|
||||
@@ -299,7 +290,7 @@ class NzbQueue:
|
||||
result += 1
|
||||
return result
|
||||
|
||||
def change_name(self, nzo_id: str, name: str, password: str = None):
|
||||
def change_name(self, nzo_id, name, password=None):
|
||||
if nzo_id in self.__nzo_table:
|
||||
nzo = self.__nzo_table[nzo_id]
|
||||
logging.info("Renaming %s to %s", nzo.final_name, name)
|
||||
@@ -315,61 +306,63 @@ class NzbQueue:
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_nzo(self, nzo_id) -> Optional[NzbObject]:
|
||||
def get_nzo(self, nzo_id):
|
||||
if nzo_id in self.__nzo_table:
|
||||
return self.__nzo_table[nzo_id]
|
||||
else:
|
||||
return None
|
||||
|
||||
@NzbQueueLocker
|
||||
def add(self, nzo: NzbObject, save=True, quiet=False) -> str:
|
||||
def add(self, nzo, save=True, quiet=False):
|
||||
if not nzo.nzo_id:
|
||||
nzo.nzo_id = sabnzbd.get_new_id("nzo", nzo.admin_path, self.__nzo_table)
|
||||
nzo.nzo_id = sabnzbd.get_new_id("nzo", nzo.workpath, self.__nzo_table)
|
||||
|
||||
# If no files are to be downloaded anymore, send to postproc
|
||||
if not nzo.files and not nzo.futuretype:
|
||||
self.end_job(nzo)
|
||||
return nzo.nzo_id
|
||||
|
||||
# Reset try_lists, markers and evaluate the scheduling settings
|
||||
# Reset try_lists
|
||||
nzo.reset_try_list()
|
||||
nzo.deleted = False
|
||||
priority = nzo.priority
|
||||
if sabnzbd.Scheduler.analyse(False, priority):
|
||||
nzo.status = Status.PAUSED
|
||||
|
||||
self.__nzo_table[nzo.nzo_id] = nzo
|
||||
if priority > HIGH_PRIORITY:
|
||||
# Top and repair priority items are added to the top of the queue
|
||||
self.__nzo_list.insert(0, nzo)
|
||||
elif priority == LOW_PRIORITY:
|
||||
self.__nzo_list.append(nzo)
|
||||
else:
|
||||
# for high priority we need to add the item at the bottom
|
||||
# of any other high priority items above the normal priority
|
||||
# for normal priority we need to add the item at the bottom
|
||||
# of the normal priority items above the low priority
|
||||
if self.__nzo_list:
|
||||
pos = 0
|
||||
added = False
|
||||
for position in self.__nzo_list:
|
||||
if position.priority < priority:
|
||||
self.__nzo_list.insert(pos, nzo)
|
||||
added = True
|
||||
break
|
||||
pos += 1
|
||||
if not added:
|
||||
# if there are no other items classed as a lower priority
|
||||
# then it will be added to the bottom of the queue
|
||||
self.__nzo_list.append(nzo)
|
||||
else:
|
||||
# if the queue is empty then simple append the item to the bottom
|
||||
if nzo.nzo_id:
|
||||
nzo.deleted = False
|
||||
priority = nzo.priority
|
||||
if sabnzbd.scheduler.analyse(False, priority):
|
||||
nzo.status = Status.PAUSED
|
||||
|
||||
self.__nzo_table[nzo.nzo_id] = nzo
|
||||
if priority > HIGH_PRIORITY:
|
||||
# Top and repair priority items are added to the top of the queue
|
||||
self.__nzo_list.insert(0, nzo)
|
||||
elif priority == LOW_PRIORITY:
|
||||
self.__nzo_list.append(nzo)
|
||||
if save:
|
||||
self.save(nzo)
|
||||
else:
|
||||
# for high priority we need to add the item at the bottom
|
||||
# of any other high priority items above the normal priority
|
||||
# for normal priority we need to add the item at the bottom
|
||||
# of the normal priority items above the low priority
|
||||
if self.__nzo_list:
|
||||
pos = 0
|
||||
added = False
|
||||
for position in self.__nzo_list:
|
||||
if position.priority < priority:
|
||||
self.__nzo_list.insert(pos, nzo)
|
||||
added = True
|
||||
break
|
||||
pos += 1
|
||||
if not added:
|
||||
# if there are no other items classed as a lower priority
|
||||
# then it will be added to the bottom of the queue
|
||||
self.__nzo_list.append(nzo)
|
||||
else:
|
||||
# if the queue is empty then simple append the item to the bottom
|
||||
self.__nzo_list.append(nzo)
|
||||
if save:
|
||||
self.save(nzo)
|
||||
|
||||
if not (quiet or nzo.status == Status.FETCHING):
|
||||
notifier.send_notification(T("NZB added to queue"), nzo.filename, "download", nzo.cat)
|
||||
if not (quiet or nzo.status == Status.FETCHING):
|
||||
notifier.send_notification(T("NZB added to queue"), nzo.filename, "download", nzo.cat)
|
||||
|
||||
if not quiet and cfg.auto_sort():
|
||||
try:
|
||||
@@ -380,10 +373,10 @@ class NzbQueue:
|
||||
return nzo.nzo_id
|
||||
|
||||
@NzbQueueLocker
|
||||
def remove(self, nzo_id: str, cleanup=True, delete_all_data=True):
|
||||
"""Remove NZO from queue.
|
||||
It can be added to history directly.
|
||||
Or, we do some clean-up, sometimes leaving some data.
|
||||
def remove(self, nzo_id, add_to_history=True, cleanup=True, delete_all_data=True):
|
||||
""" Remove NZO from queue.
|
||||
It can be added to history directly.
|
||||
Or, we do some clean-up, sometimes leaving some data.
|
||||
"""
|
||||
if nzo_id in self.__nzo_table:
|
||||
nzo = self.__nzo_table.pop(nzo_id)
|
||||
@@ -395,28 +388,36 @@ class NzbQueue:
|
||||
nzo.status = Status.DELETED
|
||||
self.__nzo_list.remove(nzo)
|
||||
|
||||
if cleanup:
|
||||
if add_to_history:
|
||||
# Create the history DB instance
|
||||
history_db = database.HistoryDB()
|
||||
# Add the nzo to the database. Only the path, script and time taken is passed
|
||||
# Other information is obtained from the nzo
|
||||
history_db.add_history_db(nzo)
|
||||
history_db.close()
|
||||
sabnzbd.history_updated()
|
||||
elif cleanup:
|
||||
nzo.purge_data(delete_all_data=delete_all_data)
|
||||
self.save(False)
|
||||
return nzo_id
|
||||
return None
|
||||
|
||||
@NzbQueueLocker
|
||||
def remove_multiple(self, nzo_ids: List[str], delete_all_data=True) -> List[str]:
|
||||
def remove_multiple(self, nzo_ids, delete_all_data=True):
|
||||
removed = []
|
||||
for nzo_id in nzo_ids:
|
||||
if self.remove(nzo_id, delete_all_data=delete_all_data):
|
||||
if self.remove(nzo_id, add_to_history=False, delete_all_data=delete_all_data):
|
||||
removed.append(nzo_id)
|
||||
|
||||
# Any files left? Otherwise let's disconnect
|
||||
if self.actives(grabs=False) == 0 and cfg.autodisconnect():
|
||||
# This was the last job, close server connections
|
||||
sabnzbd.Downloader.disconnect()
|
||||
sabnzbd.downloader.Downloader.do.disconnect()
|
||||
|
||||
return removed
|
||||
|
||||
@NzbQueueLocker
|
||||
def remove_all(self, search: str = "") -> List[str]:
|
||||
def remove_all(self, search=None):
|
||||
""" Remove NZO's that match the search-pattern """
|
||||
nzo_ids = []
|
||||
search = safe_lower(search)
|
||||
@@ -425,7 +426,7 @@ class NzbQueue:
|
||||
nzo_ids.append(nzo_id)
|
||||
return self.remove_multiple(nzo_ids)
|
||||
|
||||
def remove_nzf(self, nzo_id: str, nzf_id: str, force_delete=False) -> List[str]:
|
||||
def remove_nzf(self, nzo_id, nzf_id, force_delete=False):
|
||||
removed = []
|
||||
if nzo_id in self.__nzo_table:
|
||||
nzo = self.__nzo_table[nzo_id]
|
||||
@@ -439,26 +440,24 @@ class NzbQueue:
|
||||
if nzo.finished_files:
|
||||
self.end_job(nzo)
|
||||
else:
|
||||
self.remove(nzo_id)
|
||||
self.remove(nzo_id, add_to_history=False, keep_basic=False)
|
||||
elif force_delete:
|
||||
# Force-remove all trace and update counters
|
||||
# Force-remove all trace
|
||||
nzo.bytes -= nzf.bytes
|
||||
nzo.bytes_tried -= nzf.bytes - nzf.bytes_left
|
||||
if nzf.is_par2 or sabnzbd.par2file.is_parfile(nzf.filename):
|
||||
nzo.bytes_par2 -= nzf.bytes
|
||||
del nzo.files_table[nzf_id]
|
||||
nzo.finished_files.remove(nzf)
|
||||
logging.info("Removed NZFs %s from job %s", removed, nzo.final_name)
|
||||
return removed
|
||||
|
||||
def pause_multiple_nzo(self, nzo_ids: List[str]) -> List[str]:
|
||||
def pause_multiple_nzo(self, nzo_ids):
|
||||
handled = []
|
||||
for nzo_id in nzo_ids:
|
||||
self.pause_nzo(nzo_id)
|
||||
handled.append(nzo_id)
|
||||
return handled
|
||||
|
||||
def pause_nzo(self, nzo_id: str) -> List[str]:
|
||||
def pause_nzo(self, nzo_id):
|
||||
handled = []
|
||||
if nzo_id in self.__nzo_table:
|
||||
nzo = self.__nzo_table[nzo_id]
|
||||
@@ -467,7 +466,7 @@ class NzbQueue:
|
||||
handled.append(nzo_id)
|
||||
return handled
|
||||
|
||||
def resume_multiple_nzo(self, nzo_ids: List[str]) -> List[str]:
|
||||
def resume_multiple_nzo(self, nzo_ids):
|
||||
handled = []
|
||||
for nzo_id in nzo_ids:
|
||||
self.resume_nzo(nzo_id)
|
||||
@@ -475,7 +474,7 @@ class NzbQueue:
|
||||
return handled
|
||||
|
||||
@NzbQueueLocker
|
||||
def resume_nzo(self, nzo_id: str) -> List[str]:
|
||||
def resume_nzo(self, nzo_id):
|
||||
handled = []
|
||||
if nzo_id in self.__nzo_table:
|
||||
nzo = self.__nzo_table[nzo_id]
|
||||
@@ -486,7 +485,7 @@ class NzbQueue:
|
||||
return handled
|
||||
|
||||
@NzbQueueLocker
|
||||
def switch(self, item_id_1: str, item_id_2: str) -> Tuple[int, int]:
|
||||
def switch(self, item_id_1, item_id_2):
|
||||
try:
|
||||
# Allow an index as second parameter, easier for some skins
|
||||
i = int(item_id_2)
|
||||
@@ -578,8 +577,8 @@ class NzbQueue:
|
||||
self.__nzo_list = sort_queue_function(self.__nzo_list, _nzo_size_cmp, reverse)
|
||||
|
||||
def sort_queue(self, field, reverse=None):
|
||||
"""Sort queue by field: "name", "size" or "avg_age"
|
||||
Direction is specified as "desc"/True or "asc"/False
|
||||
""" Sort queue by field: "name", "size" or "avg_age"
|
||||
Direction is specified as "desc"/True or "asc"/False
|
||||
"""
|
||||
if isinstance(reverse, str):
|
||||
if reverse.lower() == "desc":
|
||||
@@ -593,7 +592,7 @@ class NzbQueue:
|
||||
elif field.lower() == "size" or field.lower() == "bytes":
|
||||
self.sort_by_size(reverse)
|
||||
elif field.lower() == "avg_age":
|
||||
self.sort_by_avg_age(not reverse)
|
||||
self.sort_by_avg_age(reverse)
|
||||
else:
|
||||
logging.debug("Sort: %s not recognized", field)
|
||||
|
||||
@@ -622,7 +621,7 @@ class NzbQueue:
|
||||
return nzo_id_pos1
|
||||
|
||||
nzo.set_priority(priority)
|
||||
if sabnzbd.Scheduler.analyse(False, priority) and nzo.status in (
|
||||
if sabnzbd.scheduler.analyse(False, priority) and nzo.status in (
|
||||
Status.CHECKING,
|
||||
Status.DOWNLOADING,
|
||||
Status.QUEUED,
|
||||
@@ -634,7 +633,7 @@ class NzbQueue:
|
||||
|
||||
if nzo_id_pos1 != -1:
|
||||
del self.__nzo_list[nzo_id_pos1]
|
||||
if priority == FORCE_PRIORITY:
|
||||
if priority == TOP_PRIORITY:
|
||||
# A top priority item (usually a completed download fetching pars)
|
||||
# is added to the top of the queue
|
||||
self.__nzo_list.insert(0, nzo)
|
||||
@@ -685,8 +684,7 @@ class NzbQueue:
|
||||
except:
|
||||
return -1
|
||||
|
||||
@staticmethod
|
||||
def reset_try_lists(article: Article, article_reset=True):
|
||||
def reset_try_lists(self, article, article_reset=True):
|
||||
""" Let article get new fetcher and reset trylists """
|
||||
article.fetcher = None
|
||||
if article_reset:
|
||||
@@ -699,27 +697,27 @@ class NzbQueue:
|
||||
nzo.reset_all_try_lists()
|
||||
|
||||
def has_forced_items(self):
|
||||
"""Check if the queue contains any Forced
|
||||
Priority items to download while paused
|
||||
""" Check if the queue contains any Forced
|
||||
Priority items to download while paused
|
||||
"""
|
||||
for nzo in self.__nzo_list:
|
||||
if nzo.priority == FORCE_PRIORITY and nzo.status not in (Status.PAUSED, Status.GRABBING):
|
||||
if nzo.priority == TOP_PRIORITY and nzo.status not in (Status.PAUSED, Status.GRABBING):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_article(self, server: Server, servers: List[Server]) -> Optional[Article]:
|
||||
"""Get next article for jobs in the queue
|
||||
Not locked for performance, since it only reads the queue
|
||||
def get_article(self, server, servers):
|
||||
""" Get next article for jobs in the queue
|
||||
Not locked for performance, since it only reads the queue
|
||||
"""
|
||||
# Pre-calculate propagation delay
|
||||
propagation_delay = float(cfg.propagation_delay() * 60)
|
||||
for nzo in self.__nzo_list:
|
||||
# Not when queue paused and not a forced item
|
||||
if nzo.status not in (Status.PAUSED, Status.GRABBING) or nzo.priority == FORCE_PRIORITY:
|
||||
if nzo.status not in (Status.PAUSED, Status.GRABBING) or nzo.priority == TOP_PRIORITY:
|
||||
# Check if past propagation delay, or forced
|
||||
if (
|
||||
not propagation_delay
|
||||
or nzo.priority == FORCE_PRIORITY
|
||||
or nzo.priority == TOP_PRIORITY
|
||||
or (nzo.avg_stamp + propagation_delay) < time.time()
|
||||
):
|
||||
if not nzo.server_in_try_list(server):
|
||||
@@ -730,9 +728,9 @@ class NzbQueue:
|
||||
if self.__top_only:
|
||||
return
|
||||
|
||||
def register_article(self, article: Article, success=True):
|
||||
"""Register the articles we tried
|
||||
Not locked for performance, since it only modifies individual NZOs
|
||||
def register_article(self, article, success=True):
|
||||
""" Register the articles we tried
|
||||
Not locked for performance, since it only modifies individual NZOs
|
||||
"""
|
||||
nzf = article.nzf
|
||||
nzo = nzf.nzo
|
||||
@@ -752,7 +750,7 @@ class NzbQueue:
|
||||
# Only start decoding if we have a filename and type
|
||||
# The type is only set if sabyenc could decode the article
|
||||
if nzf.filename and nzf.type:
|
||||
sabnzbd.Assembler.process(nzo, nzf, file_done)
|
||||
Assembler.do.process((nzo, nzf, file_done))
|
||||
elif nzf.filename.lower().endswith(".par2"):
|
||||
# Broken par2 file, try to get another one
|
||||
nzo.promote_par2(nzf)
|
||||
@@ -763,7 +761,7 @@ class NzbQueue:
|
||||
# Save bookkeeping in case of crash
|
||||
if file_done and (nzo.next_save is None or time.time() > nzo.next_save):
|
||||
nzo.save_to_disk()
|
||||
sabnzbd.BPSMeter.save()
|
||||
BPSMeter.do.save()
|
||||
if nzo.save_timeout is None:
|
||||
nzo.next_save = None
|
||||
else:
|
||||
@@ -771,19 +769,19 @@ class NzbQueue:
|
||||
|
||||
# Remove post from Queue
|
||||
if post_done:
|
||||
nzo.set_download_report()
|
||||
self.end_job(nzo)
|
||||
|
||||
def end_job(self, nzo: NzbObject):
|
||||
def end_job(self, nzo):
|
||||
""" Send NZO to the post-processing queue """
|
||||
logging.info("[%s] Ending job %s", caller_name(), nzo.final_name)
|
||||
|
||||
# Notify assembler to call postprocessor
|
||||
if not nzo.deleted:
|
||||
logging.info("[%s] Ending job %s", caller_name(), nzo.final_name)
|
||||
nzo.deleted = True
|
||||
if nzo.precheck:
|
||||
nzo.save_to_disk()
|
||||
# Check result
|
||||
enough, _ = nzo.check_availability_ratio()
|
||||
enough, _ratio = nzo.check_availability_ratio()
|
||||
if enough:
|
||||
# Enough data present, do real download
|
||||
self.send_back(nzo)
|
||||
@@ -791,11 +789,11 @@ class NzbQueue:
|
||||
else:
|
||||
# Not enough data, let postprocessor show it as failed
|
||||
pass
|
||||
sabnzbd.Assembler.process(nzo)
|
||||
Assembler.do.process((nzo, None, None))
|
||||
|
||||
def actives(self, grabs=True) -> int:
|
||||
"""Return amount of non-paused jobs, optionally with 'grabbing' items
|
||||
Not locked for performance, only reads the queue
|
||||
def actives(self, grabs=True):
|
||||
""" Return amount of non-paused jobs, optionally with 'grabbing' items
|
||||
Not locked for performance, only reads the queue
|
||||
"""
|
||||
n = 0
|
||||
for nzo in self.__nzo_list:
|
||||
@@ -807,9 +805,9 @@ class NzbQueue:
|
||||
return n
|
||||
|
||||
def queue_info(self, search=None, start=0, limit=0):
|
||||
"""Return list of queued jobs,
|
||||
optionally filtered by 'search' and limited by start and limit.
|
||||
Not locked for performance, only reads the queue
|
||||
""" Return list of queued jobs,
|
||||
optionally filtered by 'search' and limited by start and limit.
|
||||
Not locked for performance, only reads the queue
|
||||
"""
|
||||
if search:
|
||||
search = search.lower()
|
||||
@@ -821,7 +819,7 @@ class NzbQueue:
|
||||
n = 0
|
||||
|
||||
for nzo in self.__nzo_list:
|
||||
if nzo.status not in (Status.PAUSED, Status.CHECKING) or nzo.priority == FORCE_PRIORITY:
|
||||
if nzo.status not in (Status.PAUSED, Status.CHECKING) or nzo.priority == TOP_PRIORITY:
|
||||
b_left = nzo.remaining
|
||||
bytes_total += nzo.bytes
|
||||
bytes_left += b_left
|
||||
@@ -840,8 +838,8 @@ class NzbQueue:
|
||||
return QNFO(bytes_total, bytes_left, bytes_left_previous_page, pnfo_list, q_size, n)
|
||||
|
||||
def remaining(self):
|
||||
"""Return bytes left in the queue by non-paused items
|
||||
Not locked for performance, only reads the queue
|
||||
""" Return bytes left in the queue by non-paused items
|
||||
Not locked for performance, only reads the queue
|
||||
"""
|
||||
bytes_left = 0
|
||||
for nzo in self.__nzo_list:
|
||||
@@ -867,10 +865,10 @@ class NzbQueue:
|
||||
|
||||
# Stall prevention by checking if all servers are in the trylist
|
||||
# This is a CPU-cheaper alternative to prevent stalling
|
||||
if len(nzo.try_list) == sabnzbd.Downloader.server_nr:
|
||||
if len(nzo.try_list) == sabnzbd.downloader.Downloader.do.server_nr:
|
||||
# Maybe the NZF's need a reset too?
|
||||
for nzf in nzo.files:
|
||||
if len(nzf.try_list) == sabnzbd.Downloader.server_nr:
|
||||
if len(nzf.try_list) == sabnzbd.downloader.Downloader.do.server_nr:
|
||||
# We do not want to reset all article trylists, they are good
|
||||
logging.info("Resetting bad trylist for file %s in job %s", nzf.filename, nzo.final_name)
|
||||
nzf.reset_try_list()
|
||||
@@ -882,25 +880,25 @@ class NzbQueue:
|
||||
for nzo in empty:
|
||||
self.end_job(nzo)
|
||||
|
||||
def pause_on_prio(self, priority: int):
|
||||
def pause_on_prio(self, priority):
|
||||
for nzo in self.__nzo_list:
|
||||
if nzo.priority == priority:
|
||||
nzo.pause()
|
||||
|
||||
@NzbQueueLocker
|
||||
def resume_on_prio(self, priority: int):
|
||||
def resume_on_prio(self, priority):
|
||||
for nzo in self.__nzo_list:
|
||||
if nzo.priority == priority:
|
||||
# Don't use nzo.resume() to avoid resetting job warning flags
|
||||
nzo.status = Status.QUEUED
|
||||
|
||||
def pause_on_cat(self, cat: str):
|
||||
def pause_on_cat(self, cat):
|
||||
for nzo in self.__nzo_list:
|
||||
if nzo.cat == cat:
|
||||
nzo.pause()
|
||||
|
||||
@NzbQueueLocker
|
||||
def resume_on_cat(self, cat: str):
|
||||
def resume_on_cat(self, cat):
|
||||
for nzo in self.__nzo_list:
|
||||
if nzo.cat == cat:
|
||||
# Don't use nzo.resume() to avoid resetting job warning flags
|
||||
@@ -921,7 +919,7 @@ class NzbQueue:
|
||||
return "<NzbQueue>"
|
||||
|
||||
|
||||
def _nzo_date_cmp(nzo1: NzbObject, nzo2: NzbObject):
|
||||
def _nzo_date_cmp(nzo1, nzo2):
|
||||
avg_date1 = nzo1.avg_date
|
||||
avg_date2 = nzo2.avg_date
|
||||
|
||||
@@ -944,9 +942,9 @@ def _nzo_size_cmp(nzo1, nzo2):
|
||||
return cmp(nzo1.bytes, nzo2.bytes)
|
||||
|
||||
|
||||
def sort_queue_function(nzo_list: List[NzbObject], method, reverse: bool) -> List[NzbObject]:
|
||||
def sort_queue_function(nzo_list, method, reverse):
|
||||
ultra_high_priority = [nzo for nzo in nzo_list if nzo.priority == REPAIR_PRIORITY]
|
||||
super_high_priority = [nzo for nzo in nzo_list if nzo.priority == FORCE_PRIORITY]
|
||||
super_high_priority = [nzo for nzo in nzo_list if nzo.priority == TOP_PRIORITY]
|
||||
high_priority = [nzo for nzo in nzo_list if nzo.priority == HIGH_PRIORITY]
|
||||
normal_priority = [nzo for nzo in nzo_list if nzo.priority == NORMAL_PRIORITY]
|
||||
low_priority = [nzo for nzo in nzo_list if nzo.priority == LOW_PRIORITY]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
sabnzbd.osxmenu - macOS Top Menu
|
||||
sabnzbd.osxmenu - OSX Top Menu
|
||||
"""
|
||||
|
||||
import objc
|
||||
@@ -41,8 +41,11 @@ from sabnzbd.panic import launch_a_browser
|
||||
import sabnzbd.notifier as notifier
|
||||
|
||||
from sabnzbd.api import fast_queue
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.scheduler as scheduler
|
||||
import sabnzbd.downloader
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
|
||||
status_icons = {
|
||||
"idle": "icons/sabnzbd_osx_idle.tiff",
|
||||
@@ -110,7 +113,7 @@ class SABnzbdDelegate(NSObject):
|
||||
# Variables
|
||||
self.state = "Idle"
|
||||
try:
|
||||
self.speed = sabnzbd.Downloader.get_limit()
|
||||
self.speed = sabnzbd.downloader.Downloader.do.get_limit()
|
||||
except:
|
||||
self.speed = 0
|
||||
self.version_notify = 1
|
||||
@@ -231,7 +234,7 @@ class SABnzbdDelegate(NSObject):
|
||||
100: "100%",
|
||||
}
|
||||
|
||||
for speed in sorted(speeds):
|
||||
for speed in sorted(speeds.keys()):
|
||||
menu_speed_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
|
||||
"%s" % (speeds[speed]), "speedlimitAction:", ""
|
||||
)
|
||||
@@ -383,7 +386,7 @@ class SABnzbdDelegate(NSObject):
|
||||
|
||||
def queueUpdate(self):
|
||||
try:
|
||||
qnfo = sabnzbd.NzbQueue.queue_info(start=0, limit=10)
|
||||
qnfo = NzbQueue.do.queue_info(start=0, limit=10)
|
||||
pnfo_list = qnfo.list
|
||||
|
||||
bytesleftprogess = 0
|
||||
@@ -404,7 +407,7 @@ class SABnzbdDelegate(NSObject):
|
||||
bytesleftprogess += pnfo.bytes_left
|
||||
bytes_total = pnfo.bytes / MEBI
|
||||
nzo_id = pnfo.nzo_id
|
||||
timeleft = self.calc_timeleft_(bytesleftprogess, sabnzbd.BPSMeter.bps)
|
||||
timeleft = self.calc_timeleft_(bytesleftprogess, BPSMeter.do.bps)
|
||||
|
||||
job = "%s\t(%d/%d MB) %s" % (pnfo.filename, bytesleft, bytes_total, timeleft)
|
||||
menu_queue_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(job, "", "")
|
||||
@@ -506,8 +509,8 @@ class SABnzbdDelegate(NSObject):
|
||||
|
||||
if paused:
|
||||
self.state = T("Paused")
|
||||
if sabnzbd.Scheduler.pause_int() != "0":
|
||||
self.setMenuTitle_("\n\n%s\n" % (sabnzbd.Scheduler.pause_int()))
|
||||
if sabnzbd.scheduler.pause_int() != "0":
|
||||
self.setMenuTitle_("\n\n%s\n" % (sabnzbd.scheduler.pause_int()))
|
||||
else:
|
||||
self.setMenuTitle_("")
|
||||
elif bytes_left > 0:
|
||||
@@ -543,7 +546,7 @@ class SABnzbdDelegate(NSObject):
|
||||
|
||||
def iconUpdate(self):
|
||||
try:
|
||||
if sabnzbd.Downloader.paused:
|
||||
if sabnzbd.downloader.Downloader.do.paused:
|
||||
self.status_item.setImage_(self.icons["pause"])
|
||||
else:
|
||||
self.status_item.setImage_(self.icons["idle"])
|
||||
@@ -552,7 +555,7 @@ class SABnzbdDelegate(NSObject):
|
||||
|
||||
def pauseUpdate(self):
|
||||
try:
|
||||
if sabnzbd.Downloader.paused:
|
||||
if sabnzbd.downloader.Downloader.do.paused:
|
||||
if self.isLeopard:
|
||||
self.resume_menu_item.setHidden_(NO)
|
||||
self.pause_menu_item.setHidden_(YES)
|
||||
@@ -571,7 +574,7 @@ class SABnzbdDelegate(NSObject):
|
||||
|
||||
def speedlimitUpdate(self):
|
||||
try:
|
||||
speed = int(sabnzbd.Downloader.get_limit())
|
||||
speed = int(sabnzbd.downloader.Downloader.do.get_limit())
|
||||
if self.speed != speed:
|
||||
self.speed = speed
|
||||
speedsValues = self.menu_speed.numberOfItems()
|
||||
@@ -732,14 +735,14 @@ class SABnzbdDelegate(NSObject):
|
||||
# logging.info("[osx] speed limit to %s" % (sender.representedObject()))
|
||||
speed = int(sender.representedObject())
|
||||
if speed != self.speed:
|
||||
sabnzbd.Downloader.limit_speed("%s%%" % speed)
|
||||
sabnzbd.downloader.Downloader.do.limit_speed("%s%%" % speed)
|
||||
self.speedlimitUpdate()
|
||||
|
||||
def purgeAction_(self, sender):
|
||||
mode = sender.representedObject()
|
||||
# logging.info("[osx] purge %s" % (mode))
|
||||
if mode == "queue":
|
||||
sabnzbd.NzbQueue.remove_all()
|
||||
NzbQueue.do.remove_all()
|
||||
elif mode == "history":
|
||||
if not self.history_db:
|
||||
self.history_db = sabnzbd.database.HistoryDB()
|
||||
@@ -749,18 +752,18 @@ class SABnzbdDelegate(NSObject):
|
||||
minutes = int(sender.representedObject())
|
||||
# logging.info("[osx] pause for %s" % (minutes))
|
||||
if minutes:
|
||||
sabnzbd.Scheduler.plan_resume(minutes)
|
||||
scheduler.plan_resume(minutes)
|
||||
else:
|
||||
sabnzbd.Downloader.pause()
|
||||
sabnzbd.downloader.Downloader.do.pause()
|
||||
|
||||
def resumeAction_(self, sender):
|
||||
sabnzbd.Scheduler.plan_resume(0)
|
||||
scheduler.plan_resume(0)
|
||||
|
||||
def watchedFolderAction_(self, sender):
|
||||
sabnzbd.DirScanner.scan()
|
||||
sabnzbd.dirscanner.dirscan()
|
||||
|
||||
def rssAction_(self, sender):
|
||||
sabnzbd.Scheduler.force_rss()
|
||||
scheduler.force_rss()
|
||||
|
||||
def openFolderAction_(self, sender):
|
||||
folder2open = sender.representedObject()
|
||||
@@ -799,7 +802,7 @@ class SABnzbdDelegate(NSObject):
|
||||
# logging.info('[osx] file open')
|
||||
# logging.info('[osx] file : %s' % (filenames))
|
||||
for filename in filenames:
|
||||
logging.info("[osx] receiving from macOS : %s", filename)
|
||||
logging.info("[osx] receiving from OSX : %s", filename)
|
||||
if os.path.exists(filename):
|
||||
if sabnzbd.filesystem.get_ext(filename) in VALID_ARCHIVES + VALID_NZB_FILES:
|
||||
sabnzbd.add_nzbfile(filename, keep=True)
|
||||
|
||||
@@ -250,8 +250,8 @@ def launch_a_browser(url, force=False):
|
||||
|
||||
|
||||
def show_error_dialog(msg):
|
||||
"""Show a pop-up when program cannot start
|
||||
Windows-only, otherwise only print to console
|
||||
""" Show a pop-up when program cannot start
|
||||
Windows-only, otherwise only print to console
|
||||
"""
|
||||
if sabnzbd.WIN32:
|
||||
ctypes.windll.user32.MessageBoxW(0, msg, T("Fatal error"), 0)
|
||||
|
||||
@@ -18,42 +18,35 @@
|
||||
"""
|
||||
sabnzbd.par2file - All par2-related functionality
|
||||
"""
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import hashlib
|
||||
import struct
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from sabnzbd.encoding import correct_unknown_encoding
|
||||
|
||||
PROBABLY_PAR2_RE = re.compile(r"(.*)\.vol(\d*)[+\-](\d*)\.par2", re.I)
|
||||
PROBABLY_PAR2_RE = re.compile(r"(.*)\.vol(\d*)[\+\-](\d*)\.par2", re.I)
|
||||
PAR_PKT_ID = b"PAR2\x00PKT"
|
||||
PAR_FILE_ID = b"PAR 2.0\x00FileDesc"
|
||||
PAR_CREATOR_ID = b"PAR 2.0\x00Creator"
|
||||
PAR_RECOVERY_ID = b"RecvSlic"
|
||||
|
||||
|
||||
def is_parfile(filename: str) -> bool:
|
||||
"""Check quickly whether file has par2 signature
|
||||
or if the filename has '.par2' in it
|
||||
"""
|
||||
if os.path.exists(filename):
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
buf = f.read(8)
|
||||
return buf.startswith(PAR_PKT_ID)
|
||||
except:
|
||||
pass
|
||||
elif ".par2" in filename.lower():
|
||||
return True
|
||||
def is_parfile(filename):
|
||||
""" Check quickly whether file has par2 signature """
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
buf = f.read(8)
|
||||
return buf.startswith(PAR_PKT_ID)
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def analyse_par2(name: str, filepath: Optional[str] = None) -> Tuple[str, int, int]:
|
||||
"""Check if file is a par2-file and determine vol/block
|
||||
return setname, vol, block
|
||||
setname is empty when not a par2 file
|
||||
def analyse_par2(name, filepath=None):
|
||||
""" Check if file is a par2-file and determine vol/block
|
||||
return setname, vol, block
|
||||
setname is empty when not a par2 file
|
||||
"""
|
||||
name = name.strip()
|
||||
vol = block = 0
|
||||
@@ -83,13 +76,11 @@ def analyse_par2(name: str, filepath: Optional[str] = None) -> Tuple[str, int, i
|
||||
return setname, vol, block
|
||||
|
||||
|
||||
def parse_par2_file(fname: str, md5of16k: Dict[bytes, str]) -> Dict[str, bytes]:
|
||||
"""Get the hash table and the first-16k hash table from a PAR2 file
|
||||
Return as dictionary, indexed on names or hashes for the first-16 table
|
||||
The input md5of16k is modified in place and thus not returned!
|
||||
|
||||
For a full description of the par2 specification, visit:
|
||||
http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
|
||||
def parse_par2_file(nzf, fname):
|
||||
""" Get the hash table and the first-16k hash table from a PAR2 file
|
||||
Return as dictionary, indexed on names or hashes for the first-16 table
|
||||
For a full description of the par2 specification, visit:
|
||||
http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
|
||||
"""
|
||||
table = {}
|
||||
duplicates16k = []
|
||||
@@ -101,9 +92,9 @@ def parse_par2_file(fname: str, md5of16k: Dict[bytes, str]) -> Dict[str, bytes]:
|
||||
name, filehash, hash16k = parse_par2_file_packet(f, header)
|
||||
if name:
|
||||
table[name] = filehash
|
||||
if hash16k not in md5of16k:
|
||||
md5of16k[hash16k] = name
|
||||
elif md5of16k[hash16k] != name:
|
||||
if hash16k not in nzf.nzo.md5of16k:
|
||||
nzf.nzo.md5of16k[hash16k] = name
|
||||
elif nzf.nzo.md5of16k[hash16k] != name:
|
||||
# Not unique and not already linked to this file
|
||||
# Remove to avoid false-renames
|
||||
duplicates16k.append(hash16k)
|
||||
@@ -122,14 +113,14 @@ def parse_par2_file(fname: str, md5of16k: Dict[bytes, str]) -> Dict[str, bytes]:
|
||||
# Have to remove duplicates at the end to make sure
|
||||
# no trace is left in case of multi-duplicates
|
||||
for hash16k in duplicates16k:
|
||||
if hash16k in md5of16k:
|
||||
old_name = md5of16k.pop(hash16k)
|
||||
if hash16k in nzf.nzo.md5of16k:
|
||||
old_name = nzf.nzo.md5of16k.pop(hash16k)
|
||||
logging.debug("Par2-16k signature of %s not unique, discarding", old_name)
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def parse_par2_file_packet(f, header) -> Tuple[Optional[str], Optional[bytes], Optional[bytes]]:
|
||||
def parse_par2_file_packet(f, header):
|
||||
""" Look up and analyze a FileDesc package """
|
||||
|
||||
nothing = None, None, None
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user