mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-25 08:38:05 -05:00
Compare commits
23 Commits
4.6.0Alpha
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a637d218c4 | ||
|
|
63c03b42a9 | ||
|
|
4539837fad | ||
|
|
a0cd48e3f5 | ||
|
|
ceeb7cb162 | ||
|
|
f9f4e1b028 | ||
|
|
6487944c6c | ||
|
|
239fddf39c | ||
|
|
8ada8b2fd9 | ||
|
|
b19bd65495 | ||
|
|
e3ea5fdd64 | ||
|
|
4fdb89701a | ||
|
|
9165c4f304 | ||
|
|
4152f0ba6a | ||
|
|
3eaab17739 | ||
|
|
578bfd083d | ||
|
|
dd464456e4 | ||
|
|
e7a0255359 | ||
|
|
2e1281d9e8 | ||
|
|
efecefdd3b | ||
|
|
a91e718ef5 | ||
|
|
b420975267 | ||
|
|
c4211df8dc |
58
.github/workflows/build_release.yml
vendored
58
.github/workflows/build_release.yml
vendored
@@ -8,8 +8,18 @@ env:
|
||||
|
||||
jobs:
|
||||
build_windows:
|
||||
name: Build Windows binary
|
||||
runs-on: windows-2022
|
||||
name: Build Windows binary (${{ matrix.architecture }})
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- architecture: x64
|
||||
runs-on: windows-2022
|
||||
- architecture: arm64
|
||||
runs-on: windows-11-arm
|
||||
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -17,7 +27,7 @@ jobs:
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
architecture: "x64"
|
||||
architecture: ${{ matrix.architecture }}
|
||||
cache: pip
|
||||
cache-dependency-path: "**/requirements.txt"
|
||||
- name: Install Python dependencies
|
||||
@@ -31,11 +41,11 @@ jobs:
|
||||
id: windows_binary
|
||||
run: python builder/package.py binary
|
||||
- name: Upload Windows standalone binary (unsigned)
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
id: upload-unsigned-binary
|
||||
with:
|
||||
path: "*-win64-bin.zip"
|
||||
name: Windows standalone binary
|
||||
path: "*-win*-bin.zip"
|
||||
name: Windows standalone binary (${{ matrix.architecture }})
|
||||
- name: Sign Windows standalone binary
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
if: contains(github.ref, 'refs/tags/')
|
||||
@@ -49,22 +59,24 @@ jobs:
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: "signed"
|
||||
- name: Upload Windows standalone binary (signed)
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
if: contains(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: Windows standalone binary (signed)
|
||||
name: Windows standalone binary (${{ matrix.architecture }}, signed)
|
||||
path: "signed"
|
||||
- name: Build Windows installer
|
||||
if: matrix.architecture == 'x64'
|
||||
run: python builder/package.py installer
|
||||
- name: Upload Windows installer
|
||||
uses: actions/upload-artifact@v5
|
||||
if: matrix.architecture == 'x64'
|
||||
uses: actions/upload-artifact@v6
|
||||
id: upload-unsigned-installer
|
||||
with:
|
||||
path: "*-win-setup.exe"
|
||||
name: Windows installer
|
||||
name: Windows installer (${{ matrix.architecture }})
|
||||
- name: Sign Windows installer
|
||||
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
if: contains(github.ref, 'refs/tags/')
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
|
||||
@@ -75,10 +87,10 @@ jobs:
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: "signed"
|
||||
- name: Upload Windows installer (signed)
|
||||
if: contains(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v5
|
||||
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Windows installer (signed)
|
||||
name: Windows installer (${{ matrix.architecture }}, signed)
|
||||
path: "signed/*-win-setup.exe"
|
||||
|
||||
build_macos:
|
||||
@@ -105,7 +117,7 @@ jobs:
|
||||
cache-dependency-path: "**/requirements.txt"
|
||||
- name: Cache Python download
|
||||
id: cache-python-download
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/python.pkg
|
||||
key: cache-macOS-Python-${{ env.PYTHON_VERSION }}
|
||||
@@ -140,7 +152,7 @@ jobs:
|
||||
# Run this on macOS so the line endings are correct by default
|
||||
run: python builder/package.py source
|
||||
- name: Upload source distribution
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
path: "*-src.tar.gz"
|
||||
name: Source distribution
|
||||
@@ -153,7 +165,7 @@ jobs:
|
||||
python3 builder/package.py app
|
||||
python3 builder/make_dmg.py
|
||||
- name: Upload macOS binary
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
path: "*-macos.dmg"
|
||||
name: macOS binary
|
||||
@@ -167,14 +179,14 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
linux_arch: amd64
|
||||
linux_arch: x64
|
||||
- os: ubuntu-24.04-arm
|
||||
linux_arch: arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache par2cmdline-turbo tarball
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: cache-par2cmdline
|
||||
# Clearing the cache in case of new version requires manual clearing in GitHub!
|
||||
with:
|
||||
@@ -196,7 +208,7 @@ jobs:
|
||||
timeout 10s snap run sabnzbd --help || true
|
||||
sudo snap remove sabnzbd
|
||||
- name: Upload snap
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Snap package (${{ matrix.linux_arch }})
|
||||
path: ${{ steps.snapcraft.outputs.snap }}
|
||||
@@ -223,15 +235,15 @@ jobs:
|
||||
cache: pip
|
||||
cache-dependency-path: "builder/release-requirements.txt"
|
||||
- name: Download Source distribution artifact
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: Source distribution
|
||||
- name: Download macOS artifact
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: macOS binary
|
||||
- name: Download Windows artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: ${{ (contains(github.ref, 'refs/tags/')) && '*signed*' || '*Windows*' }}
|
||||
merge-multiple: true
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
if: github.repository_owner == 'sabnzbd'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
- uses: dessant/lock-threads@v6
|
||||
with:
|
||||
log-output: true
|
||||
issue-inactive-days: 60
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
run: |
|
||||
python3 tools/make_mo.py
|
||||
- name: Push translatable and translated texts back to repo
|
||||
uses: stefanzweifel/git-auto-commit-action@v7.0.0
|
||||
uses: stefanzweifel/git-auto-commit-action@v7.1.0
|
||||
if: env.TX_TOKEN
|
||||
with:
|
||||
commit_message: |
|
||||
|
||||
12
README.mkd
12
README.mkd
@@ -1,12 +1,12 @@
|
||||
Release Notes - SABnzbd 4.6.0 Alpha 2
|
||||
Release Notes - SABnzbd 4.6.0 Beta 2
|
||||
=========================================================
|
||||
|
||||
This is the second test release of version 4.6.
|
||||
This is the second beta release of version 4.6.
|
||||
|
||||
## New features in 4.6.0
|
||||
|
||||
* Added default support for NNTP Pipelining which eliminates idle waiting
|
||||
between requests, significantly improving speeds on high-latency connections.
|
||||
* Added support for NNTP Pipelining which eliminates idle waiting between
|
||||
requests, significantly improving speeds on high-latency connections.
|
||||
Read more here: https://sabnzbd.org/wiki/advanced/nntp-pipelining
|
||||
* Dynamically increase Assembler limits on faster connections.
|
||||
* Improved disk speed measurement in Status window.
|
||||
@@ -15,13 +15,15 @@ This is the second test release of version 4.6.
|
||||
* If a download only has `.nzb` files inside, the new downloads
|
||||
will include the name of the original download.
|
||||
* Dropped support for Python 3.8.
|
||||
* Windows: Added Windows ARM (portable) release.
|
||||
|
||||
## Bug fixes since 4.5.0
|
||||
|
||||
* `Check before download` could get stuck or fail to reject.
|
||||
* Windows: Tray icon disappears after Explorer restart.
|
||||
* No error was shown in case NZB upload failed.
|
||||
* Correct mobile layout if `Full Width` is enabled.
|
||||
* Aborted Direct Unpack could result in no files being unpacked.
|
||||
* Windows: Tray icon disappears after Explorer restart.
|
||||
* macOS: Slow to start on some network setups.
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
|
||||
# Constants
|
||||
@@ -43,11 +44,17 @@ RELEASE_VERSION_BASE = f"{RELEASE_VERSION_TUPLE[0]}.{RELEASE_VERSION_TUPLE[1]}.{
|
||||
RELEASE_NAME = "SABnzbd-%s" % RELEASE_VERSION
|
||||
RELEASE_TITLE = "SABnzbd %s" % RELEASE_VERSION
|
||||
RELEASE_SRC = RELEASE_NAME + "-src.tar.gz"
|
||||
RELEASE_BINARY = RELEASE_NAME + "-win64-bin.zip"
|
||||
RELEASE_INSTALLER = RELEASE_NAME + "-win-setup.exe"
|
||||
RELEASE_WIN_BIN_X64 = RELEASE_NAME + "-win64-bin.zip"
|
||||
RELEASE_WIN_BIN_ARM64 = RELEASE_NAME + "-win-arm64-bin.zip"
|
||||
RELEASE_WIN_INSTALLER = RELEASE_NAME + "-win-setup.exe"
|
||||
RELEASE_MACOS = RELEASE_NAME + "-macos.dmg"
|
||||
RELEASE_README = "README.mkd"
|
||||
|
||||
# Detect architecture
|
||||
RELEASE_WIN_BIN = RELEASE_WIN_BIN_X64
|
||||
if platform.machine() == "ARM64":
|
||||
RELEASE_WIN_BIN = RELEASE_WIN_BIN_ARM64
|
||||
|
||||
# Used in package.py and SABnzbd.spec
|
||||
EXTRA_FILES = [
|
||||
RELEASE_README,
|
||||
|
||||
@@ -35,8 +35,8 @@ from constants import (
|
||||
VERSION_FILE,
|
||||
RELEASE_README,
|
||||
RELEASE_NAME,
|
||||
RELEASE_BINARY,
|
||||
RELEASE_INSTALLER,
|
||||
RELEASE_WIN_BIN,
|
||||
RELEASE_WIN_INSTALLER,
|
||||
ON_GITHUB_ACTIONS,
|
||||
RELEASE_THIS,
|
||||
RELEASE_SRC,
|
||||
@@ -257,7 +257,7 @@ if __name__ == "__main__":
|
||||
|
||||
# Remove any leftovers
|
||||
safe_remove(RELEASE_NAME)
|
||||
safe_remove(RELEASE_BINARY)
|
||||
safe_remove(RELEASE_WIN_BIN)
|
||||
|
||||
# Run PyInstaller and check output
|
||||
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
|
||||
@@ -275,8 +275,8 @@ if __name__ == "__main__":
|
||||
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
|
||||
|
||||
# Create the archive
|
||||
run_external_command(["win/7zip/7za.exe", "a", RELEASE_BINARY, "SABnzbd"], cwd="dist")
|
||||
shutil.move(f"dist/{RELEASE_BINARY}", RELEASE_BINARY)
|
||||
run_external_command(["win/7zip/7za.exe", "a", RELEASE_WIN_BIN, "SABnzbd"], cwd="dist")
|
||||
shutil.move(f"dist/{RELEASE_WIN_BIN}", RELEASE_WIN_BIN)
|
||||
|
||||
if "installer" in sys.argv:
|
||||
# Check if we have the dist folder
|
||||
@@ -284,10 +284,10 @@ if __name__ == "__main__":
|
||||
raise FileNotFoundError("SABnzbd executable not found, run binary creation first")
|
||||
|
||||
# Check if we have a signed version
|
||||
if os.path.exists(f"signed/{RELEASE_BINARY}"):
|
||||
if os.path.exists(f"signed/{RELEASE_WIN_BIN}"):
|
||||
print("Using signed version of SABnzbd binaries")
|
||||
safe_remove("dist/SABnzbd")
|
||||
run_external_command(["win/7zip/7za.exe", "x", "-odist", f"signed/{RELEASE_BINARY}"])
|
||||
run_external_command(["win/7zip/7za.exe", "x", "-odist", f"signed/{RELEASE_WIN_BIN}"])
|
||||
|
||||
# Make sure it exists
|
||||
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
|
||||
@@ -310,7 +310,7 @@ if __name__ == "__main__":
|
||||
"/V3",
|
||||
"/DSAB_VERSION=%s" % RELEASE_VERSION,
|
||||
"/DSAB_VERSIONKEY=%s" % ".".join(map(str, RELEASE_VERSION_TUPLE)),
|
||||
"/DSAB_FILE=%s" % RELEASE_INSTALLER,
|
||||
"/DSAB_FILE=%s" % RELEASE_WIN_INSTALLER,
|
||||
"NSIS_Installer.nsi.tmp",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -29,8 +29,9 @@ from constants import (
|
||||
RELEASE_VERSION_BASE,
|
||||
PRERELEASE,
|
||||
RELEASE_SRC,
|
||||
RELEASE_BINARY,
|
||||
RELEASE_INSTALLER,
|
||||
RELEASE_WIN_BIN_X64,
|
||||
RELEASE_WIN_BIN_ARM64,
|
||||
RELEASE_WIN_INSTALLER,
|
||||
RELEASE_MACOS,
|
||||
RELEASE_README,
|
||||
RELEASE_THIS,
|
||||
@@ -42,8 +43,9 @@ from constants import (
|
||||
# Verify we have all assets
|
||||
files_to_check = (
|
||||
RELEASE_SRC,
|
||||
RELEASE_BINARY,
|
||||
RELEASE_INSTALLER,
|
||||
RELEASE_WIN_BIN_X64,
|
||||
RELEASE_WIN_BIN_ARM64,
|
||||
RELEASE_WIN_INSTALLER,
|
||||
RELEASE_MACOS,
|
||||
RELEASE_README,
|
||||
)
|
||||
|
||||
@@ -117,6 +117,12 @@
|
||||
<input type="checkbox" name="optional" id="optional" value="1" />
|
||||
<span class="desc">$T('explain-optional')</span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="pipelining_requests">$T('srv-pipelining_requests')</label>
|
||||
<input type="number" name="pipelining_requests" id="pipelining_requests" min="1" max="20" value="1" />
|
||||
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
|
||||
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="expire_date">$T('srv-expire_date')</label>
|
||||
<input type="date" name="expire_date" id="expire_date" />
|
||||
@@ -248,6 +254,12 @@
|
||||
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-optional')</span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="pipelining_requests$cur">$T('srv-pipelining_requests')</label>
|
||||
<input type="number" name="pipelining_requests" id="pipelining_requests$cur" value="$server['pipelining_requests']" min="1" max="20" required />
|
||||
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
|
||||
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
|
||||
</div>
|
||||
<div class="field-pair advanced-settings">
|
||||
<label class="config" for="expire_date$cur">$T('srv-expire_date')</label>
|
||||
<input type="date" name="expire_date" id="expire_date$cur" value="$server['expire_date']" />
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
<span class="glyphicon glyphicon-open"></span> $T('Glitter-notification-uploading') <span class="main-notification-box-file-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-uploading-failed">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span> $T('Glitter-notification-upload-failed').replace('%s', '') <span class="main-notification-box-file-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-queue-repair">
|
||||
<span class="glyphicon glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
|
||||
<span class="glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
|
||||
</div>
|
||||
|
||||
<div class="main-notification-box-disconnect">
|
||||
|
||||
@@ -726,6 +726,9 @@ function ViewModel() {
|
||||
$('#nzbname').val('')
|
||||
$('.btn-file em').html(glitterTranslate.chooseFile + '…')
|
||||
}
|
||||
}).fail(function(xhr, status, error) {
|
||||
// Update the uploading notification text to show error
|
||||
showNotification('.main-notification-box-uploading-failed', 0, error)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,10 @@ legend,
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.main-notification-box-uploading-failed {
|
||||
color: #F95151;
|
||||
}
|
||||
|
||||
.container,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<name>SABnzbd</name>
|
||||
<summary>Free and easy binary newsreader</summary>
|
||||
<branding>
|
||||
<color type="primary" scheme_preference="light">#e7e7e7</color>
|
||||
<color type="primary" scheme_preference="dark">#444444</color>
|
||||
</branding>
|
||||
<description>
|
||||
<p>
|
||||
SABnzbd is a free and Open Source web-based binary newsreader,
|
||||
@@ -17,6 +21,13 @@
|
||||
and services that help automate the download process.
|
||||
</p>
|
||||
</description>
|
||||
<keywords>
|
||||
<keyword>usenet</keyword>
|
||||
<keyword>nzb</keyword>
|
||||
<keyword>download</keyword>
|
||||
<keyword>newsreader</keyword>
|
||||
<keyword>binary</keyword>
|
||||
</keywords>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
<category>FileTransfer</category>
|
||||
@@ -24,34 +35,49 @@
|
||||
<url type="homepage">https://sabnzbd.org</url>
|
||||
<url type="bugtracker">https://github.com/sabnzbd/sabnzbd/issues</url>
|
||||
<url type="vcs-browser">https://github.com/sabnzbd/sabnzbd</url>
|
||||
<url type="contribute">https://github.com/sabnzbd/sabnzbd</url>
|
||||
<url type="translate">https://sabnzbd.org/wiki/translate</url>
|
||||
<url type="donation">https://sabnzbd.org/donate</url>
|
||||
<url type="help">https://sabnzbd.org/wiki/</url>
|
||||
<url type="faq">https://sabnzbd.org/wiki/faq</url>
|
||||
<url type="contact">https://sabnzbd.org/live-chat.html</url>
|
||||
<releases>
|
||||
<release version="4.6.0" date="2025-12-24" type="stable"/>
|
||||
<release version="4.5.5" date="2025-10-24" type="stable"/>
|
||||
<release version="4.5.4" date="2025-10-22" type="stable"/>
|
||||
<release version="4.5.3" date="2025-08-25" type="stable"/>
|
||||
<release version="4.5.2" date="2025-07-09" type="stable"/>
|
||||
<release version="4.5.1" date="2025-04-11" type="stable"/>
|
||||
<release version="4.5.0" date="2025-04-01" type="stable"/>
|
||||
<release version="4.4.1" date="2024-12-23" type="stable"/>
|
||||
<release version="4.4.0" date="2024-12-09" type="stable"/>
|
||||
<release version="4.3.3" date="2024-08-01" type="stable"/>
|
||||
<release version="4.3.2" date="2024-05-30" type="stable"/>
|
||||
<release version="4.3.1" date="2024-05-03" type="stable"/>
|
||||
<release version="4.3.0" date="2024-05-01" type="stable"/>
|
||||
<release version="4.2.2" date="2024-02-01" type="stable"/>
|
||||
<release version="4.2.1" date="2024-01-05" type="stable"/>
|
||||
<release version="4.2.0" date="2024-01-03" type="stable"/>
|
||||
<release version="4.1.0" date="2023-09-26" type="stable"/>
|
||||
<release version="4.0.3" date="2023-06-16" type="stable"/>
|
||||
<release version="4.0.2" date="2023-06-09" type="stable"/>
|
||||
<release version="4.0.1" date="2023-05-01" type="stable"/>
|
||||
<release version="4.0.0" date="2023-04-28" type="stable"/>
|
||||
<release version="3.7.2" date="2023-02-05" type="stable"/>
|
||||
<release version="4.6.0" date="2025-12-24" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.6.0</url>
|
||||
</release>
|
||||
<release version="4.5.5" date="2025-10-24" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.5</url>
|
||||
</release>
|
||||
<release version="4.5.4" date="2025-10-22" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.4</url>
|
||||
</release>
|
||||
<release version="4.5.3" date="2025-08-25" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.3</url>
|
||||
</release>
|
||||
<release version="4.5.2" date="2025-07-09" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.2</url>
|
||||
</release>
|
||||
<release version="4.5.1" date="2025-04-11" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.1</url>
|
||||
</release>
|
||||
<release version="4.5.0" date="2025-04-01" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.0</url>
|
||||
</release>
|
||||
<release version="4.4.1" date="2024-12-23" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.1</url>
|
||||
</release>
|
||||
<release version="4.4.0" date="2024-12-09" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.0</url>
|
||||
</release>
|
||||
<release version="4.3.3" date="2024-08-01" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.3</url>
|
||||
</release>
|
||||
<release version="4.3.2" date="2024-05-30" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.2</url>
|
||||
</release>
|
||||
<release version="4.3.1" date="2024-05-03" type="stable">
|
||||
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.1</url>
|
||||
</release>
|
||||
</releases>
|
||||
<launchable type="desktop-id">sabnzbd.desktop</launchable>
|
||||
<provides>
|
||||
@@ -74,11 +100,59 @@
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/interface.png</image>
|
||||
<caption>Web interface</caption>
|
||||
<caption>Intuitive interface</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/night-mode.png</image>
|
||||
<caption>Night mode</caption>
|
||||
<caption>Also comes in Night-mode</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/add-nzb.png</image>
|
||||
<caption>Add NZB's or use drag-and-drop!</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/phone-interface.png</image>
|
||||
<caption>Scales to any screen size</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/history-details.png</image>
|
||||
<caption>Easy overview of all history details</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/phone-extra.png</image>
|
||||
<caption>Every option, on every screen size</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/file-lists.png</image>
|
||||
<caption>Manage a job's individual files</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/set-speedlimit.png</image>
|
||||
<caption>Easy speed limiting</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/set-options.png</image>
|
||||
<caption>Quickly change settings</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/dashboard.png</image>
|
||||
<caption>Easy system check</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/connections-overview.png</image>
|
||||
<caption>See active connections</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/skin-settings.png</image>
|
||||
<caption>Customize the interface</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/tabbed.png</image>
|
||||
<caption>Tabbed-mode</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/set-custom-pause.png</image>
|
||||
<caption>Specify any pause duration</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://sabnzbd.org/images/landing/screenshots/config.png</image>
|
||||
|
||||
@@ -125,6 +125,11 @@ msgstr ""
|
||||
msgid "Current umask (%o) might deny SABnzbd access to the files and folders it creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Completed Download Folder %s is on FAT file system, limiting maximum file size to 4GB"
|
||||
@@ -279,7 +284,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr ""
|
||||
|
||||
@@ -884,7 +889,7 @@ msgid "Update Available!"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1300,103 +1305,18 @@ msgstr ""
|
||||
msgid "NZB added to queue"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr ""
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr ""
|
||||
@@ -3482,6 +3402,14 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Request multiple articles per connection without waiting for each response first.<br />This can improve download speeds, especially on connections with higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4189,6 +4117,11 @@ msgstr ""
|
||||
msgid "Filename"
|
||||
msgstr ""
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr ""
|
||||
@@ -4598,6 +4531,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/cs.po
120
po/main/cs.po
@@ -144,6 +144,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -316,7 +321,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Neočekávaná přípona v rar souboru %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Přerušeno, nalezena neočekávaná připona"
|
||||
|
||||
@@ -955,7 +960,7 @@ msgid "Update Available!"
|
||||
msgstr "Dostupná aktualizace!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Nezdařilo se nahrát soubor: %s"
|
||||
|
||||
@@ -1388,103 +1393,18 @@ msgstr "Nelze nahrát %s, detekován porušený soubor"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB přidáno do fronty"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignoruji duplikátní NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Nezdařilo se duplikovat NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Duplikátní NZB"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Prázdný NZB soubor %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Nechtěná přípona v souboru %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Zrušeno, nelze dokončit"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Chyba při importu %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLIKÁT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ŠIFROVANÉ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "PŘÍLIŠ VELKÝ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "NEKOMPLETNÍ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "NECHTĚNÝ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "ČEKÁNÍ %s s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "PROPAGUJI %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Staženo do %s s průměrnou rychlostí %s B/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Stáří"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Pozastavuji duplikátní NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problém s"
|
||||
@@ -3672,6 +3592,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4413,6 +4344,11 @@ msgstr ""
|
||||
msgid "Filename"
|
||||
msgstr ""
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Stáří"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr ""
|
||||
@@ -4832,6 +4768,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Prázdný NZB soubor %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/da.po
120
po/main/da.po
@@ -147,6 +147,11 @@ msgid ""
|
||||
msgstr ""
|
||||
"Aktuel umask (%o) kan nægte SABnzbd adgang til filer og mapper den opretter."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -331,7 +336,7 @@ msgstr "I \"%s\" uønsket extension i RAR fil. Uønsket fil er \"%s\" "
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Uønsket extension i rar fil %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Afbrudt, uønsket extension fundet"
|
||||
|
||||
@@ -996,7 +1001,7 @@ msgid "Update Available!"
|
||||
msgstr "Opdatering tilgængelig!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Kunne ikke uploade fil: %s"
|
||||
|
||||
@@ -1432,103 +1437,18 @@ msgstr "Downloadnings fejl %s, ødelagt fil fundet"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB tilføjet i køen"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorerer identiske NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Fejler dublet NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Dublet NZB"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "Ødelagt NZB fil %s, springer over (årsag=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tom NZB fil %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "Før-kø script job markeret som mislykkedet"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Uønsket filtype i fil %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Afbrudt, kan ikke afsluttes"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Det lykkedes ikke at importere %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLIKERE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATIV"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "KRYPTEREDE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "FOR STOR"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "UFULDSTÆNDIG"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "UØNSKET"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "VENT %s sekunder"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "PROPAGATING %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Hentede i %s med et gennemsnit på %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Alder"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artikler misdannede"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artikler manglede"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artikler havde ikke-matchende dubletter"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Pause duplikeret NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problem med"
|
||||
@@ -3845,6 +3765,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Aktivere"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4620,6 +4551,11 @@ msgstr "Slet"
|
||||
msgid "Filename"
|
||||
msgstr "Filnavn"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Alder"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Ledig diskplads"
|
||||
@@ -5053,6 +4989,10 @@ msgstr "Fil ikke på server"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "Serveren kunne ikke fuldføre anmodningen"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tom NZB fil %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
123
po/main/de.po
123
po/main/de.po
@@ -167,6 +167,11 @@ msgstr ""
|
||||
"Die aktuellen Zugriffseinstellungen (%o) könnte SABnzbd den Zugriff auf die "
|
||||
"erstellten Dateien und Ordner von SABnzbd verweigern."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -357,7 +362,7 @@ msgstr "Unerwünschter Typ \"%s\" in RAR Datei. Unerwünschte Datei ist %s "
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Unerwünschter Dateityp im RAR-Archiv %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Abgebrochen, unerwünschte Dateieindung gefunden"
|
||||
|
||||
@@ -1036,7 +1041,7 @@ msgid "Update Available!"
|
||||
msgstr "Neue Version verfügbar!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Hochladen fehlgeschlagen: %s"
|
||||
|
||||
@@ -1479,106 +1484,18 @@ msgstr "Fehler beim Laden von %s. Beschädigte Datei gefunden."
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB zur Warteschlange hinzugefügt"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Doppelte NZB \"%s\" wird ignoriert"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "kopieren der NZB \"%s\" fehlgeschlagen"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Doppelte NZB"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "Ungültige NZB-Datei %s wird übersprungen (Fehler: %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Leere NZB-Datei %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
"Das Vorwarteschlangen (pre-queue) Skript hat die Downloadaufgabe als "
|
||||
"gescheitert markiert"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Ungewollte Dateiendung in der Datei %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Abgebrochen, kann nicht fertiggestellt werden"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Fehler beim Importieren von %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLIKAT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATIVE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "VERSCHLÜSSELT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "ZU GROSS"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "UNVOLLSTÄNDIG"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "UNERWÜNSCHT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "WARTE %s Sek"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "AUSBREITUNG %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr ""
|
||||
"Heruntergeladen in %s mit einer Durchschnittsgeschwindigkeit von %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Alter"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s Artikel hatten ein ungültiges Format"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s Artikel fehlten"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s Artikel hatten nicht übereinstimmende Duplikate"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Doppelt vorhandene NZB \"%s\" angehalten"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problem mit"
|
||||
@@ -3954,6 +3871,17 @@ msgstr "Für unzuverlässige Server, wird bei Fehlern länger ignoriert"
|
||||
msgid "Enable"
|
||||
msgstr "Aktivieren"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4737,6 +4665,11 @@ msgstr "Löschen"
|
||||
msgid "Filename"
|
||||
msgstr "Dateiname"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Alter"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Freier Speicherplatz"
|
||||
@@ -5171,6 +5104,10 @@ msgstr "Datei nicht auf dem Server"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "Server konnte nicht vollständig antworten"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Leere NZB-Datei %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
122
po/main/es.po
122
po/main/es.po
@@ -156,6 +156,11 @@ msgstr ""
|
||||
"La umask actual (%o) podría denegarle acceso a SABnzbd a los archivos y "
|
||||
"carpetas que este crea."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -342,7 +347,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Se ha encontrado una extensión desconocida en el fichero rar %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Se interrumpió la acción porque se detectó una extensión no deseada"
|
||||
|
||||
@@ -1020,7 +1025,7 @@ msgid "Update Available!"
|
||||
msgstr "¡Actualización Disponible!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Error al subir archivo: %s"
|
||||
|
||||
@@ -1471,105 +1476,18 @@ msgstr "Error al cargar %s, archivo corrupto"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB añadido a la cola"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorando NZB Duplicado \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Fallo al duplicar NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Duplicar NZB"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "Fichero NBZ inválido: %s, omitiendo (razón=%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fichero NZB vacío: %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
"La secuencia de comandos de la cola preestablecida ha marcado la tarea como "
|
||||
"fallida"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Extensión no deseada en el archivo %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Abortado, No puede ser completado"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Error importando %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLICADO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATIVO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ENCRIPTADO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "DEMASIADO GRANDE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "INCOMPLETO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "NO DESEADO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "ESPERAR %s seg"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "PROPAGANDO %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Descargado en %s a una media de %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Edad"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artículos estaban mal formados."
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artículos no encontrados"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artículos contenían duplicados inconexos"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Pausando NZB duplicados \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problema con"
|
||||
@@ -3924,6 +3842,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Habilitar"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4706,6 +4635,11 @@ msgstr "Eliminar"
|
||||
msgid "Filename"
|
||||
msgstr "Nombre de archivo"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Edad"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Espacio libre"
|
||||
@@ -5142,6 +5076,10 @@ msgstr "El fichero no se encuentra en el servidor"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "El servidor no ha podido completar la solicitud"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fichero NZB vacío: %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/fi.po
120
po/main/fi.po
@@ -146,6 +146,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -315,7 +320,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Ei toivottu tiedostopääte on rar arkistossa %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Peruutettu, ei toivottu tiedostopääte havaittu"
|
||||
|
||||
@@ -961,7 +966,7 @@ msgid "Update Available!"
|
||||
msgstr "Päivitys saatavilla!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1387,103 +1392,18 @@ msgstr "Virhe ladattaessa %s, korruptoitunut tiedosto havaittu"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB lisätty jonoon"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ohitetaan kaksoiskappale NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tyhjä NZB tiedosto %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Peruutettu, ei voi valmistua"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Virhe tuotaessa %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "KAKSOISKAPPALE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "SALATTU"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "LIIAN SUURI"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "KESKENERÄINEN"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "EI TOIVOTTU"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "ODOTA %s sekuntia"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "LEVITETÄÄN %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Ladattiin ajassa %s keskilatausnopeudella %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Ikä"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artikkelia oli väärin muotoiltuja"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artikkelia puuttui"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artikkelissa oli ei-vastaavia kaksoiskappaleita"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Keskeytetään kaksoiskappale NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Ongelma"
|
||||
@@ -3763,6 +3683,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Ota käyttöön"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4519,6 +4450,11 @@ msgstr "Poista"
|
||||
msgid "Filename"
|
||||
msgstr "Tiedostonimi"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Ikä"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Vapaa tila"
|
||||
@@ -4949,6 +4885,10 @@ msgstr "Tiedostoa ei ole palvelimella"
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tyhjä NZB tiedosto %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
122
po/main/fr.po
122
po/main/fr.po
@@ -157,6 +157,11 @@ msgstr ""
|
||||
"L'umask actuel (%o) pourrait refuser à SABnzbd l'accès aux fichiers et "
|
||||
"dossiers qu'il crée."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -347,7 +352,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "L'extension indésirable est dans le fichier rar %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Interrompu, extension indésirable détectée"
|
||||
|
||||
@@ -1025,7 +1030,7 @@ msgid "Update Available!"
|
||||
msgstr "Mise à Jour disponible!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Échec de l'upload du fichier : %s"
|
||||
|
||||
@@ -1466,103 +1471,18 @@ msgstr "Erreur lors du chargement de %s, fichier corrompu détecté"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB ajouté à la file d'attente"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Doublon NZB ignoré \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Échec de duplication du NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Dupliquer NZB"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "Fichier NZB %s invalide, sera ignoré (erreur : %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fichier NZB %s vide"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "Le script de pré-file d'attente a marqué la tâche comme échouée"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Extension non souhaitée dans le fichier %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Interrompu, ne peut être achevé"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Erreur lors de l'importation de %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DOUBLON"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATIVE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "CHIFFRÉ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "TROP VOLUMINEUX"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "INCOMPLET"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "INDÉSIRABLE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "PATIENTER %s sec"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "PROPAGATION %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Téléchargé en %s à %sB/s de moyenne"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Âge"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s articles malformés"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s articles manquants"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s articles avec doublons sans correspondance"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Mise en pause du doublon NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problème avec"
|
||||
@@ -3505,6 +3425,8 @@ msgstr "Activer les contrôles SFV"
|
||||
msgid ""
|
||||
"If no par2 files are available, use sfv files (if present) to verify files"
|
||||
msgstr ""
|
||||
"Si aucun fichier par2 n'est disponible, utiliser les fichiers sfv (si "
|
||||
"présents) pour vérifier les fichiers"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "User script can flag job as failed"
|
||||
@@ -3935,6 +3857,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Activer"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4720,6 +4653,11 @@ msgstr "Supprimer"
|
||||
msgid "Filename"
|
||||
msgstr "Nom de fichier"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Âge"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Espace libre"
|
||||
@@ -5158,6 +5096,10 @@ msgstr "Fichier introuvable sur le serveur"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "Le serveur n'a pas pu terminer la requête"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fichier NZB %s vide"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/he.po
120
po/main/he.po
@@ -143,6 +143,11 @@ msgstr ""
|
||||
"פקודת umask נוכחית (%o) עשויה לדחות גישה מן SABnzbd אל הקבצים והתיקיות שהוא "
|
||||
"יוצר."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -320,7 +325,7 @@ msgstr "בעבודה \"%s\" יש סיומת בלתי רצויה בתוך קוב
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "סיומת בלתי רצויה בקובץ rar %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "בוטל, סיומת בלתי רצויה התגלתה"
|
||||
|
||||
@@ -973,7 +978,7 @@ msgid "Update Available!"
|
||||
msgstr "עדכון זמין!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "כישלון בהעלאת קובץ: %s"
|
||||
|
||||
@@ -1404,103 +1409,18 @@ msgstr "שגיאה בטעינת %s, קובץ פגום התגלה"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB התווסף לתור"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "מתעלם מן NZB כפול \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "מכשיל NZB כפול \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "NZB כפול"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "קובץ NZB בלתי תקף %s, מדלג (שגיאה: %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "קובץ NZB ריק %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "תסריט קדם־תור סומן כנכשל"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "סיומת בלתי רצויה בקובץ %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "בוטל, לא יכול להיות שלם"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "שגיאה ביבוא %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "כפול"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "חלופה"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "מוצפן"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "גדול מדי"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "בלתי שלם"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "בלתי רצוי"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "המתן %s שניות"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "מפיץ %s דקות"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "ירד תוך %s בממוצע של %s ב/ש"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "גיל"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s מאמרים עוותו"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s מאמרים היו חסרים"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "אל %s מאמרים יש כפילויות בלתי תואמות"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "משהה NZB כפול \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "בעיה עם"
|
||||
@@ -3781,6 +3701,17 @@ msgstr "עבור שרתים בלתי מהימנים, ייתקל בהתעלמות
|
||||
msgid "Enable"
|
||||
msgstr "אפשר"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4546,6 +4477,11 @@ msgstr "מחק"
|
||||
msgid "Filename"
|
||||
msgstr "שם קובץ"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "גיל"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "שטח פנוי"
|
||||
@@ -4979,6 +4915,10 @@ msgstr "קובץ לא על השרת"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "השרת לא היה יכול להשלים בקשה"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "קובץ NZB ריק %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/it.po
120
po/main/it.po
@@ -150,6 +150,11 @@ msgstr ""
|
||||
"L'umask corrente (%o) potrebbe negare a SABnzbd l'accesso ai file e alle "
|
||||
"cartelle che crea."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -340,7 +345,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "L'estensione non desiderata è nel file rar %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Annullato, rilevata estensione non desiderata"
|
||||
|
||||
@@ -1016,7 +1021,7 @@ msgid "Update Available!"
|
||||
msgstr "Aggiornamento disponibile!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Caricamento del file %s fallito"
|
||||
|
||||
@@ -1454,103 +1459,18 @@ msgstr "Errore durante il caricamento di %s, rilevato file corrotto"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB aggiunto alla coda"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorando NZB duplicato \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Fallimento NZB duplicato \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "NZB duplicato"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "File NZB non valido %s, saltato (errore: %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "File NZB vuoto %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "Lo script pre-coda ha contrassegnato il processo come fallito"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Estensione non desiderata nel file %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Annullato, non può essere completato"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Errore durante l'importazione di %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLICATO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATIVO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "CRITTOGRAFATO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "TROPPO GRANDE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "INCOMPLETO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "NON DESIDERATO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "ATTENDI %s sec"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "PROPAGAZIONE %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Scaricato in %s a una media di %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Età"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s articoli erano malformati"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s articoli erano mancanti"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s articoli avevano duplicati non corrispondenti"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Messa in pausa NZB duplicato \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problema con"
|
||||
@@ -3894,6 +3814,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Abilita"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4675,6 +4606,11 @@ msgstr "Elimina"
|
||||
msgid "Filename"
|
||||
msgstr "Nome file"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Età"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Spazio libero"
|
||||
@@ -5111,6 +5047,10 @@ msgstr "File non presente sul server"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "Il server non ha potuto completare la richiesta"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "File NZB vuoto %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/nb.po
120
po/main/nb.po
@@ -142,6 +142,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -313,7 +318,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Uønsket forlenging finnes i rar fil %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Avbryt, uønsket forlenging oppdaget"
|
||||
|
||||
@@ -958,7 +963,7 @@ msgid "Update Available!"
|
||||
msgstr "Oppdatering tilgjengelig"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1385,103 +1390,18 @@ msgstr "Lastingsfeil %s, feilaktig fil oppdaget"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB er lagt til i køen"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorerer duplikatfil \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tom NZB-fil %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Avbrutt, kan ikke fullføres"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Kunne ikke importere %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLIKAT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "KRYPTERT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "FOR STOR"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "UFULLSTENDIG"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "UØNSKET"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "VENT %s sek"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Hentet filer på %s med gjenomsnitts hastighet på %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Tid"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artikler var korrupte"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artikler manglet"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artikler hadde ulike duplikater"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Stanser duplikatfil \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problem med"
|
||||
@@ -3742,6 +3662,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Aktivere"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4493,6 +4424,11 @@ msgstr "Fjern"
|
||||
msgid "Filename"
|
||||
msgstr "Filnavn"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Tid"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Ledig plass"
|
||||
@@ -4921,6 +4857,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Tom NZB-fil %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/nl.po
120
po/main/nl.po
@@ -152,6 +152,11 @@ msgstr ""
|
||||
"Huidige umask (%o) zou kunnen beletten dat SABnzbd toegang heeft tot de "
|
||||
"aangemaakte bestanden en mappen."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -337,7 +342,7 @@ msgstr "Ongewenste extensie ontdekt in \"%s\". Het ongewenste bestand is \"%s\"
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "De ongewenste extensie zit in RAR-bestand %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Afgebroken, ongewenste extensie ontdekt"
|
||||
|
||||
@@ -1019,7 +1024,7 @@ msgid "Update Available!"
|
||||
msgstr "Update beschikbaar!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Kon het volgende bestand niet uploaden: %s"
|
||||
|
||||
@@ -1457,103 +1462,18 @@ msgstr "Fout bij inladen van %s, corrupt bestand gevonden"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "Download aan wachtrij toegevoegd"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Dubbele download \"%s\" overgeslagen"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Download '%s' geweigerd omdat het een dubbele is"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Dubbele download"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "Corrupte NZB %s wordt overgeslagen (foutmelding: %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "NZB-bestand %s is leeg"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "Wachtrij filter script heeft de download afgekeurd"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Ongewenste extensie gevonden in %s (%s) "
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Afgebroken, kan niet voltooid worden"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Fout bij importeren van %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUBBEL"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATIEF"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "VERSLEUTELD"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "TE GROOT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "ONVOLLEDIG"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "ONGEWENST"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "WACHT %s sec"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "VERSPREIDINGSWACHTTIJD %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Gedownload in %s met een gemiddelde snelheid van %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Leeftijd"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artikelen zijn misvormd"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artikelen ontbreken"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artikelen hadden afwijkende duplicaten"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Dubbele download \"%s\" gepauzeerd"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Probleem met"
|
||||
@@ -3894,6 +3814,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Inschakelen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4673,6 +4604,11 @@ msgstr "Verwijder"
|
||||
msgid "Filename"
|
||||
msgstr "Bestandsnaam"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Leeftijd"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Vrije ruimte"
|
||||
@@ -5107,6 +5043,10 @@ msgstr "Bestand bestaat niet op de server"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "De server kon de opdracht niet uitvoeren"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "NZB-bestand %s is leeg"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/pl.po
120
po/main/pl.po
@@ -138,6 +138,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -312,7 +317,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Niepożądane rozszerzenie w pliku RAR %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Przerwano, wykryto niepożądane rozszerzenie"
|
||||
|
||||
@@ -961,7 +966,7 @@ msgid "Update Available!"
|
||||
msgstr "Dostępna aktualizacja!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1390,103 +1395,18 @@ msgstr "Błąd ładowania %s, wykryto uszkodzony plik"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB dodany do kolejki"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignoruję zduplikowany NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Pusty plik NZB %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Przerwano, nie można ukończyć"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Błąd importu %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLIKAT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ZASZYFROWANY"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "ZA DUŻY"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "NIEKOMPLETNY"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "NIEPOŻĄDANY"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "CZEKAM %s s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Pobrano w %s ze średnią %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Wiek"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artykułów było uszkodzonych"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "Brakowało %s artykułów"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artykułów posiadało niepasujące duplikaty"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Wstrzymuję zduplikowany NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problem z"
|
||||
@@ -3754,6 +3674,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Włączony"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4505,6 +4436,11 @@ msgstr "Usuń"
|
||||
msgid "Filename"
|
||||
msgstr "Nazwa pliku"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Wiek"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Wolne miejsce"
|
||||
@@ -4931,6 +4867,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Pusty plik NZB %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/pt_BR.po
120
po/main/pt_BR.po
@@ -147,6 +147,11 @@ msgstr ""
|
||||
"Mascara atual (%o) pode negar ao SABnzbd acesso aos arquivos e diretórios "
|
||||
"criados."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -324,7 +329,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "A extensão indesejada está no arquivo rar %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Cancelado, extensão indesejada detectada"
|
||||
|
||||
@@ -973,7 +978,7 @@ msgid "Update Available!"
|
||||
msgstr "Atualização Disponível!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1399,103 +1404,18 @@ msgstr "Erro ao carregar %s. Arquivo corrompido detectado"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB adicionado à fila"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorando NZB duplicado \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Arquivo NZB %s vazio"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Cancelado, não é possível concluir"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Erro ao importar %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLICADO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "CRIPTOGRAFADO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "MUITO GRANDE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "INCOMPLETO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "INDESEJADO"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "Espere %s segundo(s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Baixado em %s a uma média de %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Idade"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artigos estavam malformados"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artigos estavam faltando"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artigos tinham duplicatas não-correspondentes"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Pausando NZB duplicado \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problema com"
|
||||
@@ -3765,6 +3685,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Habilitar"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4516,6 +4447,11 @@ msgstr "Eliminar"
|
||||
msgid "Filename"
|
||||
msgstr "Nome do arquivo"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Idade"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Espaço Disponível"
|
||||
@@ -4942,6 +4878,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Arquivo NZB %s vazio"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/ro.po
120
po/main/ro.po
@@ -147,6 +147,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -326,7 +331,7 @@ msgstr "Extensie nedorită în fișierul RAR al „%s”. Fișierul nedorit este
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Extensii fișier nedorite în fișierul rar %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Oprit, extensii nedorite detectate"
|
||||
|
||||
@@ -983,7 +988,7 @@ msgid "Update Available!"
|
||||
msgstr "Actualizare Disponibilă!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Eșuare la încărcarea fișierului: %s"
|
||||
|
||||
@@ -1417,103 +1422,18 @@ msgstr "Eroare încărcare %s, fişier corupt detectat"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB adăugat în coadă"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorăm duplicat NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "Eșuare duplicat NZB „%s”"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "NZB duplicat"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fişier NZB gol %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "Scriptul pre-coadă a marcat sarcina ca nereușită"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "Extensie nedorită în fișierul %s (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Anulat nu poate fi finalizat"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Eroare importare %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUPLICAT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ENCRIPTAT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "PREA MARE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "INCOMPLET"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "NEDORIT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "AŞTEAPTĂ %s sec"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "SE PROPAGHEAZĂ %s min"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Descărcat în %s cu o medie de %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Vârsta"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s articolele au fost incorecte"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s articolele au fost lipsă"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s articolele au avut duplicate diferite"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Întrerupem duplicat NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problemă cu"
|
||||
@@ -3786,6 +3706,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Activează"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4536,6 +4467,11 @@ msgstr "Şterge"
|
||||
msgid "Filename"
|
||||
msgstr "Nume de fișier"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Vârsta"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Spațiu liber"
|
||||
@@ -4964,6 +4900,10 @@ msgstr "Fișierul nu este pe server"
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Fişier NZB gol %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/ru.po
120
po/main/ru.po
@@ -142,6 +142,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -312,7 +317,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr ""
|
||||
|
||||
@@ -957,7 +962,7 @@ msgid "Update Available!"
|
||||
msgstr "Доступно обновление!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1385,103 +1390,18 @@ msgstr "Ошибка загрузки %s: обнаружен повреждён
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB-файл добавлен в очередь"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Пропущен повторяющийся NZB-файл «%s»"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Пустой NZB-файл %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr ""
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Ошибка импорта %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "ПОВТОР"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ЗАШИФРОВАН"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "СЛИШКОМ БОЛЬШОЙ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "НЕПОЛНЫЙ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "ОЖИДАНИЕ %s с"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Загружено за %s со средней скоростью %sБ/с"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Возраст"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s статей с ошибками"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s статей отсутствует"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s статей содержат несовпадающие повторы"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Приостановлен повторяющийся NZB-файл «%s»"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Проблема с"
|
||||
@@ -3743,6 +3663,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Включить"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4500,6 +4431,11 @@ msgstr "Удалить"
|
||||
msgid "Filename"
|
||||
msgstr "Название файла"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Возраст"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "свободно на диске"
|
||||
@@ -4927,6 +4863,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Пустой NZB-файл %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/sr.po
120
po/main/sr.po
@@ -140,6 +140,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -309,7 +314,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Neželjena ekstenzija je u rar datoteci %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Prekinuto, detektovana neželjena ekstenzija"
|
||||
|
||||
@@ -953,7 +958,7 @@ msgid "Update Available!"
|
||||
msgstr "Нова верзија доступна!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1380,103 +1385,18 @@ msgstr "Грешка учитавање %s, покварена датотека
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB додат у ред"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Игнорисање дуплог NZB-а \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Празан NZB %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Поништено, не може да се заврши"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Грешка увоза %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "ДУПЛИКАТ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ШИФРИРАНО"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "ПРЕВЕЛИКО"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "НЕПОТПУНО"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "NEŽELJENI"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "Чекање %s сек"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Преузето за %s на просек од %sБ/с"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Старост"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s артикла нису добро формирани"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s артикла недостају"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s артикла нису дупликате"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Паузирам због дуплог NZB-а \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Проблем са"
|
||||
@@ -3729,6 +3649,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Омогући"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4478,6 +4409,11 @@ msgstr "Обриши"
|
||||
msgid "Filename"
|
||||
msgstr "Име датотеке"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Старост"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Слободан простор"
|
||||
@@ -4904,6 +4840,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Празан NZB %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/sv.po
120
po/main/sv.po
@@ -140,6 +140,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -309,7 +314,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "Oönskad filändelse i RAR-fil %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "Avbruten, oönskad filändelse detekterad"
|
||||
|
||||
@@ -955,7 +960,7 @@ msgid "Update Available!"
|
||||
msgstr "Uppdatering tillgänglig"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr ""
|
||||
|
||||
@@ -1384,103 +1389,18 @@ msgstr "Laddningsfel %s, felaktig fil detekterad"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB tillagd i kön"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Ignorerar dubblett för NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "NZB filen %s är tom"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "Avbrutet, kan inte slutföras"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "Det gick inte att importera %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "DUBLETT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "KRYPTERAT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "FÖR STOR"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "INKOMPLETT"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "OÖNSKAD"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "VÄNTA %s SEKUNDER"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "Hämtade i %s vid ett genomsnitt på %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Ålder"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s artiklar var felaktiga"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s artiklar saknades"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s artiklar hade icke-matchande dubletter"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Pausar dubblett för NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Problem med"
|
||||
@@ -3741,6 +3661,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Aktivera"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4491,6 +4422,11 @@ msgstr "Ta bort"
|
||||
msgid "Filename"
|
||||
msgstr "Filnamn"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Ålder"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Ledigt diskutrymme"
|
||||
@@ -4918,6 +4854,10 @@ msgstr ""
|
||||
msgid "Server could not complete request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "NZB filen %s är tom"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
126
po/main/tr.po
126
po/main/tr.po
@@ -3,14 +3,14 @@
|
||||
#
|
||||
# Translators:
|
||||
# Taylan Tatlı, 2025
|
||||
# mauron, 2025
|
||||
# Safihre <safihre@sabnzbd.org>, 2025
|
||||
# mauron, 2025
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-4.6.0\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
|
||||
"Last-Translator: mauron, 2025\n"
|
||||
"Language-Team: Turkish (https://app.transifex.com/sabnzbd/teams/111101/tr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -151,6 +151,11 @@ msgstr ""
|
||||
"Güncel umask (%o), SABnzbd'nin oluşturduğu dosya ve dizinlere erişimini "
|
||||
"reddedebilir."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -342,7 +347,7 @@ msgstr ""
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "İstenmeyen uzantı %s rar dosyasındadır"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "İptal edildi, istenmeyen uzantı tespit edildi"
|
||||
|
||||
@@ -1008,7 +1013,7 @@ msgid "Update Available!"
|
||||
msgstr "Güncelleme Mevcut!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "Dosyanın gönderilmesi başarısız oldu: %s"
|
||||
|
||||
@@ -1445,103 +1450,18 @@ msgstr "%s yüklenirken hata, bozuk dosya tespit edildi"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB kuyruğa ilave edildi"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "Yinelenmiş NZB \"%s\" dikkate alınmıyor"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "\"%s\" NSB dosyasının yinelenmesi başarısız"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "Yinelenmiş NZB"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "Geçersiz NZB dosyası %s, atlanıyor (hata: %s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Boş NZB dosyası %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "Kuyruk öncesi betiği işi başarısız oldu olarak işaretlemiş"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "%s (%s) dosyasında İstenmeyen Uzantı"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "İptal edildi, tamamlanamıyor"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "%s unsurunun içe aktarılmasında hata"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "YİNELENMİŞ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "ALTERNATİF"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "ŞİFRELENMİŞ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "ÇOK BÜYÜK"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "TAMAMLANMAMIŞ"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "İSTENMEYEN"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "%s saniye BEKLEYİN"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "YAYINLANIYOR %s dakika"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "%s içinde ortalama %sB/s hızında indirildi"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Yaş"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s makale yanlış şekillendirilmişti"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s makale eksikti"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s makale eşleşmeyen yinelenmişler bulunduruyordu"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "Yinelenmiş NZB \"%s\" duraklatılıyor"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "Şununla sorun"
|
||||
@@ -3461,6 +3381,8 @@ msgstr "SFV temelli kontrolleri etkinleştir"
|
||||
msgid ""
|
||||
"If no par2 files are available, use sfv files (if present) to verify files"
|
||||
msgstr ""
|
||||
"Eğer hiçbir par2 dosyası mevcut değilse, dosyaları kontrol etmek için "
|
||||
"(mevcutsa) sfv dosyalarını kullan"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "User script can flag job as failed"
|
||||
@@ -3881,6 +3803,17 @@ msgstr ""
|
||||
msgid "Enable"
|
||||
msgstr "Etkinleştir"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4661,6 +4594,11 @@ msgstr "Sil"
|
||||
msgid "Filename"
|
||||
msgstr "Dosya ismi"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "Yaş"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "Boş alan"
|
||||
@@ -5100,6 +5038,10 @@ msgstr "Dosya sunucuda yok"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "Sunucu talebi tamamlayamadı"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "Boş NZB dosyası %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
120
po/main/zh_CN.po
120
po/main/zh_CN.po
@@ -140,6 +140,11 @@ msgid ""
|
||||
"creates."
|
||||
msgstr "当前 umask (%o) 可能会拒绝 SABnzbd 访问其创建的文件和文件夹。"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid ""
|
||||
@@ -310,7 +315,7 @@ msgstr "RAR 文件“%s”中出现不需要的扩展名。不需要的文件名
|
||||
msgid "Unwanted extension is in rar file %s"
|
||||
msgstr "rar 文件中出现不需要的扩展名 %s"
|
||||
|
||||
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/assembler.py
|
||||
msgid "Aborted, unwanted extension detected"
|
||||
msgstr "已中止,侦测到不需要的扩展名"
|
||||
|
||||
@@ -951,7 +956,7 @@ msgid "Update Available!"
|
||||
msgstr "有更新可用!"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/misc.py
|
||||
#: sabnzbd/misc.py, sabnzbd/skintext.py
|
||||
msgid "Failed to upload file: %s"
|
||||
msgstr "上传文件失败:%s"
|
||||
|
||||
@@ -1376,103 +1381,18 @@ msgstr "无法加载 %s,侦测到损坏文件"
|
||||
msgid "NZB added to queue"
|
||||
msgstr "NZB 已添加到队列"
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Ignoring duplicate NZB \"%s\""
|
||||
msgstr "正在忽略重复 NZB \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Failing duplicate NZB \"%s\""
|
||||
msgstr "失败于重复的 NZB 文件 \"%s\""
|
||||
|
||||
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
|
||||
#: sabnzbd/nzbqueue.py
|
||||
msgid "Duplicate NZB"
|
||||
msgstr "重复的 NZB 文件"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Invalid NZB file %s, skipping (error: %s)"
|
||||
msgstr "无效的 NZB 文件 %s,跳过(错误:%s)"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "空 NZB 文件 %s"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pre-queue script marked job as failed"
|
||||
msgstr "预队列脚本将任务标记为失败的"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Unwanted Extension in file %s (%s)"
|
||||
msgstr "文件 %s 中有不需要的扩展名 (%s)"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Aborted, cannot be completed"
|
||||
msgstr "已中止,无法完成"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Error importing %s"
|
||||
msgstr "导入 %s 出错"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "DUPLICATE"
|
||||
msgstr "*重复*"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr "备选"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
msgstr "*加密*"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "TOO LARGE"
|
||||
msgstr "*太大*"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "INCOMPLETE"
|
||||
msgstr "*不完整*"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "UNWANTED"
|
||||
msgstr "不需要"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "WAIT %s sec"
|
||||
msgstr "*等待* %s 秒"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "PROPAGATING %s min"
|
||||
msgstr "传播延迟生效,等待 %s 分钟"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Downloaded in %s at an average of %sB/s"
|
||||
msgstr "已下载,耗时 %s,平均速度 %sB/s"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "发布时间"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were malformed"
|
||||
msgstr "%s 篇文章损坏"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles were missing"
|
||||
msgstr "%s 篇文章缺失"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "%s articles had non-matching duplicates"
|
||||
msgstr "%s 篇文章存在未匹配的重复"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "Pausing duplicate NZB \"%s\""
|
||||
msgstr "正在暂停重复 NZB \"%s\""
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Problem with"
|
||||
msgstr "问题"
|
||||
@@ -3690,6 +3610,17 @@ msgstr "对于不稳定的服务器,在失败后将会被忽略更长的时间
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Articles per request"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Request multiple articles per connection without waiting for each response "
|
||||
"first.<br />This can improve download speeds, especially on connections with"
|
||||
" higher latency."
|
||||
msgstr ""
|
||||
|
||||
#. Button: Remove server
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Remove Server"
|
||||
@@ -4442,6 +4373,11 @@ msgstr "删除"
|
||||
msgid "Filename"
|
||||
msgstr "文件名"
|
||||
|
||||
#. Job details page, file age column header
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Age"
|
||||
msgstr "发布时间"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Free Space"
|
||||
msgstr "剩余空间"
|
||||
@@ -4865,6 +4801,10 @@ msgstr "服务器上无此文件"
|
||||
msgid "Server could not complete request"
|
||||
msgstr "服务器无法完成请求"
|
||||
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "Empty NZB file %s"
|
||||
msgstr "空 NZB 文件 %s"
|
||||
|
||||
#. Error message
|
||||
#: sabnzbd/urlgrabber.py
|
||||
msgid "URLGRABBER CRASHED"
|
||||
|
||||
@@ -10,7 +10,7 @@ configobj==5.0.9
|
||||
cheroot==11.1.2
|
||||
six==1.17.0
|
||||
cherrypy==18.10.0
|
||||
jaraco.functools==4.3.0
|
||||
jaraco.functools==4.4.0
|
||||
jaraco.collections==5.0.0
|
||||
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
|
||||
jaraco.classes==3.4.0
|
||||
@@ -67,7 +67,7 @@ paho-mqtt==1.6.1 # Pinned, newer versions don't work with AppRise yet
|
||||
# Requests Requirements
|
||||
charset_normalizer==3.4.4
|
||||
idna==3.11
|
||||
urllib3==2.6.0
|
||||
urllib3==2.6.2
|
||||
certifi==2025.11.12
|
||||
oauthlib==3.3.1
|
||||
PyJWT==2.10.1
|
||||
|
||||
@@ -32,11 +32,12 @@ from threading import Lock, Condition
|
||||
# Determine platform flags
|
||||
##############################################################################
|
||||
|
||||
WINDOWS = MACOS = MACOSARM64 = FOUNDATION = False
|
||||
WINDOWS = WINDOWSARM64 = MACOS = MACOSARM64 = FOUNDATION = False
|
||||
KERNEL32 = LIBC = MACOSLIBC = PLATFORM = None
|
||||
|
||||
if os.name == "nt":
|
||||
WINDOWS = True
|
||||
WINDOWSARM64 = platform.uname().machine == "ARM64"
|
||||
|
||||
if platform.uname().machine not in ["AMD64", "ARM64"]:
|
||||
print("SABnzbd only supports 64-bit Windows")
|
||||
@@ -82,15 +83,15 @@ from sabnzbd.version import __version__, __baseline__
|
||||
import sabnzbd.misc as misc
|
||||
import sabnzbd.filesystem as filesystem
|
||||
import sabnzbd.powersup as powersup
|
||||
import sabnzbd.rss as rss
|
||||
import sabnzbd.emailer as emailer
|
||||
import sabnzbd.encoding as encoding
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
import sabnzbd.database
|
||||
import sabnzbd.lang as lang
|
||||
import sabnzbd.nzb
|
||||
import sabnzbd.nzbparser as nzbparser
|
||||
import sabnzbd.nzbstuff
|
||||
import sabnzbd.rss as rss
|
||||
import sabnzbd.emailer as emailer
|
||||
import sabnzbd.getipaddress
|
||||
import sabnzbd.newsunpack
|
||||
import sabnzbd.par2file
|
||||
@@ -269,7 +270,6 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
|
||||
cfg.language.callback(cfg.guard_language)
|
||||
cfg.enable_https_verification.callback(cfg.guard_https_ver)
|
||||
cfg.guard_https_ver()
|
||||
cfg.pipelining_requests.callback(cfg.guard_restart)
|
||||
|
||||
# Set language files
|
||||
lang.set_locale_info("SABnzbd", DIR_LANGUAGE)
|
||||
@@ -482,6 +482,10 @@ def delayed_startup_actions():
|
||||
sabnzbd.ORG_UMASK,
|
||||
)
|
||||
|
||||
# Check if maybe we are running x64 version on ARM hardware
|
||||
if sabnzbd.WINDOWSARM64 and "AMD64" in sys.version:
|
||||
misc.helpful_warning(T("Windows ARM version of SABnzbd is available from our Downloads page!"))
|
||||
|
||||
# List the number of certificates available (can take up to 1.5 seconds)
|
||||
if cfg.log_level() > 1:
|
||||
logging.debug("Available certificates = %s", repr(ssl.create_default_context().cert_store_stats()))
|
||||
|
||||
@@ -57,6 +57,7 @@ from sabnzbd.constants import (
|
||||
PP_LOOKUP,
|
||||
STAGES,
|
||||
DEF_NETWORKING_TEST_TIMEOUT,
|
||||
DEF_PIPELINING_REQUESTS,
|
||||
)
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
@@ -85,7 +86,7 @@ from sabnzbd.encoding import xml_name, utob
|
||||
from sabnzbd.getipaddress import local_ipv4, public_ipv4, public_ipv6, dnslookup, active_socks5_proxy
|
||||
from sabnzbd.database import HistoryDB
|
||||
from sabnzbd.lang import is_rtl
|
||||
from sabnzbd.nzbstuff import NzbObject
|
||||
from sabnzbd.nzb import TryList, NzbObject
|
||||
from sabnzbd.newswrapper import NewsWrapper, NNTPPermanentError
|
||||
import sabnzbd.emailer
|
||||
import sabnzbd.sorting
|
||||
@@ -1006,7 +1007,7 @@ def _api_gc_stats(name: str, kwargs: dict[str, Union[str, list[str]]]) -> bytes:
|
||||
# Collect before we check
|
||||
gc.collect()
|
||||
# We cannot create any lists/dicts, as they would create a reference
|
||||
return report(data=[str(obj) for obj in gc.get_objects() if isinstance(obj, sabnzbd.nzbstuff.TryList)])
|
||||
return report(data=[str(obj) for obj in gc.get_objects() if isinstance(obj, TryList)])
|
||||
|
||||
|
||||
##############################################################################
|
||||
@@ -1309,6 +1310,7 @@ def test_nntp_server_dict(kwargs: dict[str, Union[str, list[str]]]) -> tuple[boo
|
||||
ssl = int_conv(kwargs.get("ssl", 0))
|
||||
ssl_verify = int_conv(kwargs.get("ssl_verify", 3))
|
||||
ssl_ciphers = kwargs.get("ssl_ciphers", "").strip()
|
||||
pipelining_requests = int_conv(kwargs.get("pipelining_requests", DEF_PIPELINING_REQUESTS))
|
||||
|
||||
if not host:
|
||||
return False, T("The hostname is not set.")
|
||||
@@ -1345,6 +1347,7 @@ def test_nntp_server_dict(kwargs: dict[str, Union[str, list[str]]]) -> tuple[boo
|
||||
use_ssl=ssl,
|
||||
ssl_verify=ssl_verify,
|
||||
ssl_ciphers=ssl_ciphers,
|
||||
pipelining_requests=lambda: pipelining_requests,
|
||||
username=username,
|
||||
password=password,
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ from typing import Collection
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.constants import GIGI, ANFO, ASSEMBLER_WRITE_THRESHOLD
|
||||
from sabnzbd.nzbstuff import Article
|
||||
from sabnzbd.nzb import Article
|
||||
|
||||
# Operations on the article table are handled via try/except.
|
||||
# The counters need to be made atomic to ensure consistency.
|
||||
|
||||
@@ -41,7 +41,7 @@ from sabnzbd.filesystem import (
|
||||
)
|
||||
from sabnzbd.constants import Status, GIGI
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.nzbstuff import NzbObject, NzbFile
|
||||
from sabnzbd.nzb import NzbFile, NzbObject
|
||||
import sabnzbd.par2file as par2file
|
||||
|
||||
|
||||
|
||||
@@ -535,7 +535,6 @@ ssdp_broadcast_interval = OptionNumber("misc", "ssdp_broadcast_interval", 15, mi
|
||||
ext_rename_ignore = OptionList("misc", "ext_rename_ignore", validation=lower_case_ext)
|
||||
unrar_parameters = OptionStr("misc", "unrar_parameters", validation=supported_unrar_parameters)
|
||||
outgoing_nntp_ip = OptionStr("misc", "outgoing_nntp_ip")
|
||||
pipelining_requests = OptionNumber("misc", "pipelining_requests", DEF_PIPELINING_REQUESTS, minval=1, maxval=10)
|
||||
|
||||
|
||||
##############################################################################
|
||||
|
||||
@@ -42,6 +42,7 @@ from sabnzbd.constants import (
|
||||
CONFIG_BACKUP_HTTPS,
|
||||
DEF_INI_FILE,
|
||||
DEF_SORTER_RENAME_SIZE,
|
||||
DEF_PIPELINING_REQUESTS,
|
||||
)
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.filesystem import clip_path, real_path, create_real_path, renamer, remove_file, is_writable
|
||||
@@ -444,6 +445,7 @@ class ConfigServer:
|
||||
self.enable = OptionBool(name, "enable", True, add=False)
|
||||
self.required = OptionBool(name, "required", False, add=False)
|
||||
self.optional = OptionBool(name, "optional", False, add=False)
|
||||
self.pipelining_requests = OptionNumber(name, "pipelining_requests", DEF_PIPELINING_REQUESTS, 1, 20, add=False)
|
||||
self.retention = OptionNumber(name, "retention", 0, add=False)
|
||||
self.expire_date = OptionStr(name, "expire_date", add=False)
|
||||
self.quota = OptionStr(name, "quota", add=False)
|
||||
@@ -476,6 +478,7 @@ class ConfigServer:
|
||||
"enable",
|
||||
"required",
|
||||
"optional",
|
||||
"pipelining_requests",
|
||||
"retention",
|
||||
"expire_date",
|
||||
"quota",
|
||||
@@ -511,6 +514,7 @@ class ConfigServer:
|
||||
output_dict["enable"] = self.enable()
|
||||
output_dict["required"] = self.required()
|
||||
output_dict["optional"] = self.optional()
|
||||
output_dict["pipelining_requests"] = self.pipelining_requests()
|
||||
output_dict["retention"] = self.retention()
|
||||
output_dict["expire_date"] = self.expire_date()
|
||||
output_dict["quota"] = self.quota()
|
||||
|
||||
@@ -103,7 +103,7 @@ SOFT_ASSEMBLER_QUEUE_LIMIT = 0.5
|
||||
ASSEMBLER_WRITE_THRESHOLD = 5
|
||||
NNTP_BUFFER_SIZE = int(256 * KIBI)
|
||||
NTTP_MAX_BUFFER_SIZE = int(10 * MEBI)
|
||||
DEF_PIPELINING_REQUESTS = 2
|
||||
DEF_PIPELINING_REQUESTS = 1
|
||||
|
||||
REPAIR_PRIORITY = 3
|
||||
FORCE_PRIORITY = 2
|
||||
|
||||
@@ -498,9 +498,14 @@ def convert_search(search: str) -> str:
|
||||
return search
|
||||
|
||||
|
||||
def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_output: str, script_line: str):
|
||||
def build_history_info(
|
||||
nzo: "sabnzbd.nzb.NzbObject",
|
||||
workdir_complete: str,
|
||||
postproc_time: int,
|
||||
script_output: str,
|
||||
script_line: str,
|
||||
):
|
||||
"""Collects all the information needed for the database"""
|
||||
nzo: sabnzbd.nzbstuff.NzbObject
|
||||
completed = int(time.time())
|
||||
pp = PP_LOOKUP.get(opts_to_pp(nzo.repair, nzo.unpack, nzo.delete), "X")
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from typing import Optional
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import SABCTOOLS_VERSION_REQUIRED
|
||||
from sabnzbd.nzbstuff import Article
|
||||
from sabnzbd.nzb import Article
|
||||
from sabnzbd.misc import match_str
|
||||
|
||||
# Check for correct SABCTools version
|
||||
|
||||
@@ -189,7 +189,7 @@ def get_biggest_file(filelist: list[str]) -> str:
|
||||
return None
|
||||
|
||||
|
||||
def deobfuscate(nzo, filelist: list[str], usefulname: str) -> list[str]:
|
||||
def deobfuscate(nzo: "sabnzbd.nzb.NzbObject", filelist: list[str], usefulname: str) -> list[str]:
|
||||
"""
|
||||
For files in filelist:
|
||||
1. if a file has no meaningful extension, add it (for example ".txt" or ".png")
|
||||
@@ -227,9 +227,6 @@ def deobfuscate(nzo, filelist: list[str], usefulname: str) -> list[str]:
|
||||
|
||||
"""
|
||||
|
||||
# Can't be imported directly due to circular import
|
||||
nzo: sabnzbd.nzbstuff.NzbObject
|
||||
|
||||
# to be sure, only keep really existing files and remove any duplicates:
|
||||
filtered_filelist = list(set(f for f in filelist if os.path.isfile(f)))
|
||||
|
||||
@@ -320,7 +317,7 @@ def without_extension(fullpathfilename: str) -> str:
|
||||
return os.path.splitext(fullpathfilename)[0]
|
||||
|
||||
|
||||
def deobfuscate_subtitles(nzo, filelist: list[str]):
|
||||
def deobfuscate_subtitles(nzo: "sabnzbd.nzb.NzbObject", filelist: list[str]):
|
||||
"""
|
||||
input:
|
||||
nzo, so we can update result via set_unpack_info()
|
||||
@@ -345,10 +342,6 @@ def deobfuscate_subtitles(nzo, filelist: list[str]):
|
||||
Something.else.txt
|
||||
|
||||
"""
|
||||
|
||||
# Can't be imported directly due to circular import
|
||||
nzo: sabnzbd.nzbstuff.NzbObject
|
||||
|
||||
# find .srt files
|
||||
if not (srt_files := [f for f in filelist if f.endswith(".srt")]):
|
||||
logging.debug("No .srt files found, so nothing to do")
|
||||
|
||||
@@ -31,7 +31,7 @@ import sabnzbd
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.misc import int_conv, format_time_string, build_and_run_command
|
||||
from sabnzbd.filesystem import remove_all, real_path, remove_file, get_basename, clip_path
|
||||
from sabnzbd.nzbstuff import NzbObject, NzbFile
|
||||
from sabnzbd.nzb import NzbFile, NzbObject
|
||||
from sabnzbd.encoding import platform_btou
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.newsunpack import RAR_EXTRACTFROM_RE, RAR_EXTRACTED_RE, rar_volumelist, add_time_left
|
||||
|
||||
@@ -85,6 +85,7 @@ class Server:
|
||||
"retention",
|
||||
"username",
|
||||
"password",
|
||||
"pipelining_requests",
|
||||
"busy_threads",
|
||||
"next_busy_threads_check",
|
||||
"idle_threads",
|
||||
@@ -113,6 +114,7 @@ class Server:
|
||||
use_ssl,
|
||||
ssl_verify,
|
||||
ssl_ciphers,
|
||||
pipelining_requests,
|
||||
username=None,
|
||||
password=None,
|
||||
required=False,
|
||||
@@ -137,6 +139,7 @@ class Server:
|
||||
self.retention: int = retention
|
||||
self.username: Optional[str] = username
|
||||
self.password: Optional[str] = password
|
||||
self.pipelining_requests: Callable[[], int] = pipelining_requests
|
||||
|
||||
self.busy_threads: set[NewsWrapper] = set()
|
||||
self.next_busy_threads_check: float = 0
|
||||
@@ -151,7 +154,7 @@ class Server:
|
||||
self.request: bool = False # True if a getaddrinfo() request is pending
|
||||
self.have_body: bool = True # Assume server has "BODY", until proven otherwise
|
||||
self.have_stat: bool = True # Assume server has "STAT", until proven otherwise
|
||||
self.article_queue: Deque[sabnzbd.nzbstuff.Article] = deque()
|
||||
self.article_queue: Deque[sabnzbd.nzb.Article] = deque()
|
||||
|
||||
# Skip during server testing
|
||||
if threads:
|
||||
@@ -325,6 +328,7 @@ class Downloader(Thread):
|
||||
ssl = srv.ssl()
|
||||
ssl_verify = srv.ssl_verify()
|
||||
ssl_ciphers = srv.ssl_ciphers()
|
||||
pipelining_requests = srv.pipelining_requests
|
||||
username = srv.username()
|
||||
password = srv.password()
|
||||
required = srv.required()
|
||||
@@ -355,6 +359,7 @@ class Downloader(Thread):
|
||||
ssl,
|
||||
ssl_verify,
|
||||
ssl_ciphers,
|
||||
pipelining_requests,
|
||||
username,
|
||||
password,
|
||||
required,
|
||||
@@ -540,7 +545,7 @@ class Downloader(Thread):
|
||||
server.addrinfo = None
|
||||
|
||||
@staticmethod
|
||||
def decode(article: "sabnzbd.nzbstuff.Article", response: Optional[sabctools.NNTPResponse] = None):
|
||||
def decode(article: "sabnzbd.nzb.Article", response: Optional[sabctools.NNTPResponse] = None):
|
||||
"""Decode article"""
|
||||
# Need a better way of draining requests
|
||||
if article.nzf.nzo.removed_from_queue:
|
||||
@@ -694,7 +699,8 @@ class Downloader(Thread):
|
||||
if self.selector.get_map():
|
||||
if events := self.selector.select(timeout=1.0):
|
||||
for key, ev in events:
|
||||
process_nw_queue.put((key.data, ev))
|
||||
nw = key.data
|
||||
process_nw_queue.put((nw, ev, nw.generation))
|
||||
else:
|
||||
events = []
|
||||
BPSMeter.reset()
|
||||
@@ -738,20 +744,26 @@ class Downloader(Thread):
|
||||
logging.error(T("Fatal error in Downloader"), exc_info=True)
|
||||
self.pause()
|
||||
|
||||
def process_nw(self, nw: NewsWrapper, event: int):
|
||||
def process_nw(self, nw: NewsWrapper, event: int, generation: int):
|
||||
"""Receive data from a NewsWrapper and handle the response"""
|
||||
# Drop stale items
|
||||
if nw.generation != generation:
|
||||
return
|
||||
if event & selectors.EVENT_READ:
|
||||
self.process_nw_read(nw)
|
||||
self.process_nw_read(nw, generation)
|
||||
# If read caused a reset, don't proceed to write
|
||||
if nw.generation != generation:
|
||||
return
|
||||
if event & selectors.EVENT_WRITE:
|
||||
nw.write()
|
||||
|
||||
def process_nw_read(self, nw: NewsWrapper) -> None:
|
||||
def process_nw_read(self, nw: NewsWrapper, generation: int) -> None:
|
||||
bytes_received: int = 0
|
||||
bytes_pending: int = 0
|
||||
|
||||
while True:
|
||||
while nw.decoder and nw.generation == generation:
|
||||
try:
|
||||
n, bytes_pending = nw.read(nbytes=bytes_pending)
|
||||
n, bytes_pending = nw.read(nbytes=bytes_pending, generation=generation)
|
||||
bytes_received += n
|
||||
except ssl.SSLWantReadError:
|
||||
return
|
||||
@@ -768,6 +780,10 @@ class Downloader(Thread):
|
||||
if not bytes_pending:
|
||||
break
|
||||
|
||||
# Ignore metrics for reset connections
|
||||
if nw.generation != generation:
|
||||
return
|
||||
|
||||
server = nw.server
|
||||
|
||||
with DOWNLOADER_LOCK:
|
||||
@@ -900,7 +916,7 @@ class Downloader(Thread):
|
||||
wait: bool = True,
|
||||
count_article_try: bool = True,
|
||||
retry_article: bool = True,
|
||||
article: Optional["sabnzbd.nzbstuff.Article"] = None,
|
||||
article: Optional["sabnzbd.nzb.Article"] = None,
|
||||
):
|
||||
# Some warnings are errors, and not added as server.warning
|
||||
if warn and reset_msg:
|
||||
|
||||
@@ -1367,3 +1367,54 @@ def pathbrowser(path: str, show_hidden: bool = False, show_files: bool = False)
|
||||
)
|
||||
|
||||
return file_list
|
||||
|
||||
|
||||
def create_work_name(name: str) -> str:
|
||||
"""Remove ".nzb" and ".par(2)" and sanitize, skip URL's"""
|
||||
if name.find("://") < 0:
|
||||
# Invalid charters need to be removed before and after (see unit-tests)
|
||||
return sanitize_foldername(strip_extensions(sanitize_foldername(name)))
|
||||
else:
|
||||
return name.strip()
|
||||
|
||||
|
||||
def nzf_cmp_name(nzf1, nzf2):
|
||||
"""Comparison function for sorting NZB files.
|
||||
The comparison will sort .par2 files to the top of the queue followed by .rar files,
|
||||
they will then be sorted by name.
|
||||
|
||||
Note: nzf1 and nzf2 should be NzbFile objects, but we can't import that here
|
||||
to avoid circular dependencies.
|
||||
"""
|
||||
nzf1_name = nzf1.filename.lower()
|
||||
nzf2_name = nzf2.filename.lower()
|
||||
|
||||
# Determine vol-pars
|
||||
is_par1 = ".vol" in nzf1_name and ".par2" in nzf1_name
|
||||
is_par2 = ".vol" in nzf2_name and ".par2" in nzf2_name
|
||||
|
||||
# mini-par2 in front
|
||||
if not is_par1 and nzf1_name.endswith(".par2"):
|
||||
return -1
|
||||
if not is_par2 and nzf2_name.endswith(".par2"):
|
||||
return 1
|
||||
|
||||
# vol-pars go to the back
|
||||
if is_par1 and not is_par2:
|
||||
return 1
|
||||
if is_par2 and not is_par1:
|
||||
return -1
|
||||
|
||||
# Prioritize .rar files above any other type of file (other than vol-par)
|
||||
m1 = RAR_RE.search(nzf1_name)
|
||||
m2 = RAR_RE.search(nzf2_name)
|
||||
if m1 and not (is_par2 or m2):
|
||||
return -1
|
||||
elif m2 and not (is_par1 or m1):
|
||||
return 1
|
||||
# Force .rar to come before 'r00'
|
||||
if m1 and m1.group(1) == ".rar":
|
||||
nzf1_name = nzf1_name.replace(".rar", ".r//")
|
||||
if m2 and m2.group(1) == ".rar":
|
||||
nzf2_name = nzf2_name.replace(".rar", ".r//")
|
||||
return sabnzbd.misc.cmp(nzf1_name, nzf2_name)
|
||||
|
||||
@@ -913,7 +913,6 @@ SPECIAL_VALUE_LIST = (
|
||||
"ssdp_broadcast_interval",
|
||||
"unrar_parameters",
|
||||
"outgoing_nntp_ip",
|
||||
"pipelining_requests",
|
||||
)
|
||||
SPECIAL_LIST_LIST = (
|
||||
"rss_odd_titles",
|
||||
|
||||
@@ -57,7 +57,7 @@ import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.decorators import conditional_cache
|
||||
from sabnzbd.encoding import ubtou, platform_btou
|
||||
from sabnzbd.filesystem import userxbit, make_script_path, remove_file
|
||||
from sabnzbd.filesystem import userxbit, make_script_path, remove_file, strip_extensions
|
||||
|
||||
if sabnzbd.WINDOWS:
|
||||
try:
|
||||
@@ -85,6 +85,10 @@ RE_SAMPLE = re.compile(r"((^|[\W_])(sample|proof))", re.I) # something-sample o
|
||||
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)
|
||||
|
||||
# Name patterns for NZB parsing
|
||||
RE_SUBJECT_FILENAME_QUOTES = re.compile(r'"([^"]*)"')
|
||||
RE_SUBJECT_BASIC_FILENAME = re.compile(r"\b([\w\-+()' .,]+(?:\[[\w\-/+()' .,]*][\w\-+()' .,]*)*\.[A-Za-z0-9]{2,4})\b")
|
||||
|
||||
# Check if strings are defined for AM and PM
|
||||
HAVE_AMPM = bool(time.strftime("%p"))
|
||||
|
||||
@@ -1591,6 +1595,66 @@ def convert_history_retention():
|
||||
cfg.history_retention_option.set("all-delete")
|
||||
|
||||
|
||||
def scan_password(name: str) -> tuple[str, Optional[str]]:
|
||||
"""Get password (if any) from the title"""
|
||||
if "http://" in name or "https://" in name:
|
||||
return name, None
|
||||
|
||||
# Strip any unwanted usenet-related extensions
|
||||
name = strip_extensions(name)
|
||||
|
||||
# Identify any braces
|
||||
braces = name[1:].find("{{")
|
||||
if braces < 0:
|
||||
braces = len(name)
|
||||
else:
|
||||
braces += 1
|
||||
slash = name.find("/")
|
||||
|
||||
# Look for name/password, but make sure that '/' comes before any {{
|
||||
if 0 < slash < braces and "password=" not in name:
|
||||
# Is it maybe in 'name / password' notation?
|
||||
if slash == name.find(" / ") + 1 and name[: slash - 1].strip(". "):
|
||||
# Remove the extra space after name and before password
|
||||
return name[: slash - 1].strip(". "), name[slash + 2 :]
|
||||
if name[:slash].strip(". "):
|
||||
return name[:slash].strip(". "), name[slash + 1 :]
|
||||
|
||||
# Look for "name password=password"
|
||||
pw = name.find("password=")
|
||||
if pw > 0 and name[:pw].strip(". "):
|
||||
return name[:pw].strip(". "), name[pw + 9 :]
|
||||
|
||||
# Look for name{{password}}
|
||||
if braces < len(name):
|
||||
closing_braces = name.rfind("}}")
|
||||
if closing_braces > braces and name[:braces].strip(". "):
|
||||
return name[:braces].strip(". "), name[braces + 2 : closing_braces]
|
||||
|
||||
# Look again for name/password
|
||||
if slash > 0 and name[:slash].strip(". "):
|
||||
return name[:slash].strip(". "), name[slash + 1 :]
|
||||
|
||||
# No password found
|
||||
return name, None
|
||||
|
||||
|
||||
def subject_name_extractor(subject: str) -> str:
|
||||
"""Try to extract a file name from a subject line, return `subject` if in doubt"""
|
||||
# Filename nicely wrapped in quotes
|
||||
for name in re.findall(RE_SUBJECT_FILENAME_QUOTES, subject):
|
||||
if name := name.strip(' "'):
|
||||
return name
|
||||
|
||||
# Found nothing? Try a basic filename-like search
|
||||
for name in re.findall(RE_SUBJECT_BASIC_FILENAME, subject):
|
||||
if name := name.strip():
|
||||
return name
|
||||
|
||||
# Return the subject
|
||||
return subject
|
||||
|
||||
|
||||
##
|
||||
## SABnzbd patched rarfile classes
|
||||
## Patch for https://github.com/markokr/rarfile/issues/56#issuecomment-711146569
|
||||
|
||||
@@ -66,7 +66,7 @@ from sabnzbd.filesystem import (
|
||||
get_basename,
|
||||
create_all_dirs,
|
||||
)
|
||||
from sabnzbd.nzbstuff import NzbObject
|
||||
from sabnzbd.nzb import NzbObject
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.constants import Status
|
||||
|
||||
@@ -118,7 +118,14 @@ def find_programs(curdir: str):
|
||||
sabnzbd.newsunpack.SEVENZIP_COMMAND = check(curdir, "macos/7zip/7zz")
|
||||
|
||||
if sabnzbd.WINDOWS:
|
||||
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "win/par2/par2.exe")
|
||||
if sabnzbd.WINDOWSARM64:
|
||||
# ARM64 version of par2
|
||||
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "win/par2/arm64/par2.exe")
|
||||
else:
|
||||
# Regular x64 version
|
||||
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "win/par2/par2.exe")
|
||||
|
||||
# UnRAR has no arm64 version, so we skip it also for 7zip
|
||||
sabnzbd.newsunpack.RAR_COMMAND = check(curdir, "win/unrar/UnRAR.exe")
|
||||
sabnzbd.newsunpack.SEVENZIP_COMMAND = check(curdir, "win/7zip/7za.exe")
|
||||
else:
|
||||
|
||||
@@ -74,12 +74,14 @@ class NewsWrapper:
|
||||
"_response_queue",
|
||||
"selector_events",
|
||||
"lock",
|
||||
"generation",
|
||||
)
|
||||
|
||||
def __init__(self, server, thrdnum, block=False):
|
||||
def __init__(self, server: "sabnzbd.downloader.Server", thrdnum: int, block: bool = False, generation: int = 0):
|
||||
self.server: sabnzbd.downloader.Server = server
|
||||
self.thrdnum: int = thrdnum
|
||||
self.blocking: bool = block
|
||||
self.generation: int = generation
|
||||
|
||||
self.timeout: Optional[float] = None
|
||||
|
||||
@@ -97,16 +99,16 @@ class NewsWrapper:
|
||||
self.group: Optional[str] = None
|
||||
|
||||
# Command queue and concurrency
|
||||
self.next_request: Optional[tuple[bytes, Optional["sabnzbd.nzbstuff.Article"]]] = None
|
||||
self.next_request: Optional[tuple[bytes, Optional["sabnzbd.nzb.Article"]]] = None
|
||||
self.concurrent_requests: threading.BoundedSemaphore = threading.BoundedSemaphore(
|
||||
sabnzbd.cfg.pipelining_requests()
|
||||
self.server.pipelining_requests()
|
||||
)
|
||||
self._response_queue: deque[Optional[sabnzbd.nzbstuff.Article]] = deque()
|
||||
self._response_queue: deque[Optional[sabnzbd.nzb.Article]] = deque()
|
||||
self.selector_events = 0
|
||||
self.lock: threading.Lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def article(self) -> Optional["sabnzbd.nzbstuff.Article"]:
|
||||
def article(self) -> Optional["sabnzbd.nzb.Article"]:
|
||||
"""The article currently being downloaded"""
|
||||
with self.lock:
|
||||
if self._response_queue:
|
||||
@@ -177,12 +179,12 @@ class NewsWrapper:
|
||||
def queue_command(
|
||||
self,
|
||||
command: bytes,
|
||||
article: Optional["sabnzbd.nzbstuff.Article"] = None,
|
||||
article: Optional["sabnzbd.nzb.Article"] = None,
|
||||
) -> None:
|
||||
"""Add a command to the command queue"""
|
||||
self.next_request = command, article
|
||||
|
||||
def body(self, article: "sabnzbd.nzbstuff.Article") -> tuple[bytes, "sabnzbd.nzbstuff.Article"]:
|
||||
def body(self, article: "sabnzbd.nzb.Article") -> tuple[bytes, "sabnzbd.nzb.Article"]:
|
||||
"""Request the body of the article"""
|
||||
self.timeout = time.time() + self.server.timeout
|
||||
if article.nzf.nzo.precheck:
|
||||
@@ -196,7 +198,7 @@ class NewsWrapper:
|
||||
command = utob("ARTICLE <%s>\r\n" % article.article)
|
||||
return command, article
|
||||
|
||||
def on_response(self, response: sabctools.NNTPResponse, article: Optional["sabnzbd.nzbstuff.Article"]) -> None:
|
||||
def on_response(self, response: sabctools.NNTPResponse, article: Optional["sabnzbd.nzb.Article"]) -> None:
|
||||
"""A response to a NNTP request is received"""
|
||||
self.concurrent_requests.release()
|
||||
sabnzbd.Downloader.modify_socket(self, EVENT_READ | EVENT_WRITE)
|
||||
@@ -282,12 +284,21 @@ class NewsWrapper:
|
||||
self,
|
||||
nbytes: int = 0,
|
||||
on_response: Optional[Callable[[int, str], None]] = None,
|
||||
generation: Optional[int] = None,
|
||||
) -> Tuple[int, Optional[int]]:
|
||||
"""Receive data, return #bytes, #pendingbytes
|
||||
:param nbytes: maximum number of bytes to read
|
||||
:param on_response: callback for each complete response received
|
||||
:param generation: expected reset generation
|
||||
:return: #bytes, #pendingbytes
|
||||
"""
|
||||
if generation is None:
|
||||
generation = self.generation
|
||||
|
||||
# NewsWrapper is being reset
|
||||
if not self.decoder:
|
||||
return 0, None
|
||||
|
||||
# Receive data into the decoder pre-allocated buffer
|
||||
if not nbytes and self.nntp.nw.server.ssl and not self.nntp.nw.blocking and sabctools.openssl_linked:
|
||||
# Use patched version when downloading
|
||||
@@ -304,7 +315,12 @@ class NewsWrapper:
|
||||
|
||||
self.decoder.process(bytes_recv)
|
||||
for response in self.decoder:
|
||||
if self.generation != generation:
|
||||
break
|
||||
with self.lock:
|
||||
# Re-check under lock to avoid racing with hard_reset
|
||||
if self.generation != generation or not self._response_queue:
|
||||
break
|
||||
article = self._response_queue.popleft()
|
||||
if on_response:
|
||||
on_response(response.status_code, response.message)
|
||||
@@ -379,7 +395,7 @@ class NewsWrapper:
|
||||
not self.send_buffer
|
||||
and not self.next_request
|
||||
and not self._response_queue
|
||||
and (not server.active or server.restart or time.time() > self.timeout)
|
||||
and (not server.active or server.restart or not self.timeout or time.time() > self.timeout)
|
||||
):
|
||||
# Make socket available again
|
||||
server.busy_threads.discard(self)
|
||||
@@ -415,8 +431,9 @@ class NewsWrapper:
|
||||
self.nntp.close(send_quit=self.connected)
|
||||
self.nntp = None
|
||||
|
||||
# Reset all variables (including the NNTP connection)
|
||||
self.__init__(self.server, self.thrdnum)
|
||||
with self.lock:
|
||||
# Reset all variables (including the NNTP connection) and increment the generation counter
|
||||
self.__init__(self.server, self.thrdnum, generation=self.generation + 1)
|
||||
|
||||
# Wait before re-using this newswrapper
|
||||
if wait:
|
||||
@@ -428,7 +445,7 @@ class NewsWrapper:
|
||||
|
||||
def discard(
|
||||
self,
|
||||
article: Optional["sabnzbd.nzbstuff.Article"],
|
||||
article: Optional["sabnzbd.nzb.Article"],
|
||||
count_article_try: bool = True,
|
||||
retry_article: bool = True,
|
||||
) -> None:
|
||||
|
||||
59
sabnzbd/nzb/__init__.py
Normal file
59
sabnzbd/nzb/__init__.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
sabnzbd.nzb - NZB-related classes and functionality
|
||||
"""
|
||||
|
||||
# Article-related classes
|
||||
from sabnzbd.nzb.article import Article, ArticleSaver, TryList, TRYLIST_LOCK
|
||||
|
||||
# File-related classes
|
||||
from sabnzbd.nzb.file import NzbFile, NzbFileSaver, SkippedNzbFile
|
||||
|
||||
# Object-related classes
|
||||
from sabnzbd.nzb.object import (
|
||||
NzbObject,
|
||||
NzbObjectSaver,
|
||||
NzoAttributeSaver,
|
||||
NZO_LOCK,
|
||||
NzbEmpty,
|
||||
NzbRejected,
|
||||
NzbPreQueueRejected,
|
||||
NzbRejectToHistory,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Article
|
||||
"Article",
|
||||
"ArticleSaver",
|
||||
"TryList",
|
||||
"TRYLIST_LOCK",
|
||||
# File
|
||||
"NzbFile",
|
||||
"NzbFileSaver",
|
||||
"SkippedNzbFile",
|
||||
# Object
|
||||
"NzbObject",
|
||||
"NzbObjectSaver",
|
||||
"NzoAttributeSaver",
|
||||
"NZO_LOCK",
|
||||
"NzbEmpty",
|
||||
"NzbRejected",
|
||||
"NzbPreQueueRejected",
|
||||
"NzbRejectToHistory",
|
||||
]
|
||||
214
sabnzbd/nzb/article.py
Normal file
214
sabnzbd/nzb/article.py
Normal file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
sabnzbd.article - Article and TryList classes for NZB downloading
|
||||
"""
|
||||
import logging
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.downloader import Server
|
||||
from sabnzbd.filesystem import get_new_id
|
||||
from sabnzbd.decorators import synchronized
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Trylist
|
||||
##############################################################################
|
||||
|
||||
TRYLIST_LOCK = threading.RLock()
|
||||
|
||||
|
||||
class TryList:
|
||||
"""TryList keeps track of which servers have been tried for a specific article"""
|
||||
|
||||
# Pre-define attributes to save memory
|
||||
__slots__ = ("try_list",)
|
||||
|
||||
def __init__(self):
|
||||
# Sets are faster than lists
|
||||
self.try_list: set[Server] = set()
|
||||
|
||||
def server_in_try_list(self, server: Server) -> bool:
|
||||
"""Return whether specified server has been tried"""
|
||||
with TRYLIST_LOCK:
|
||||
return server in self.try_list
|
||||
|
||||
def all_servers_in_try_list(self, all_servers: set[Server]) -> bool:
|
||||
"""Check if all servers have been tried"""
|
||||
with TRYLIST_LOCK:
|
||||
return all_servers.issubset(self.try_list)
|
||||
|
||||
def add_to_try_list(self, server: Server):
|
||||
"""Register server as having been tried already"""
|
||||
with TRYLIST_LOCK:
|
||||
# Sets cannot contain duplicate items
|
||||
self.try_list.add(server)
|
||||
|
||||
def remove_from_try_list(self, server: Server):
|
||||
"""Remove server from list of tried servers"""
|
||||
with TRYLIST_LOCK:
|
||||
# Discard does not require the item to be present
|
||||
self.try_list.discard(server)
|
||||
|
||||
def reset_try_list(self):
|
||||
"""Clean the list"""
|
||||
with TRYLIST_LOCK:
|
||||
self.try_list = set()
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save the servers"""
|
||||
return set(server.id for server in self.try_list)
|
||||
|
||||
def __setstate__(self, servers_ids: list[str]):
|
||||
self.try_list = set()
|
||||
for server in sabnzbd.Downloader.servers:
|
||||
if server.id in servers_ids:
|
||||
self.add_to_try_list(server)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Article
|
||||
##############################################################################
|
||||
ArticleSaver = (
|
||||
"article",
|
||||
"art_id",
|
||||
"bytes",
|
||||
"lowest_partnum",
|
||||
"decoded",
|
||||
"file_size",
|
||||
"data_begin",
|
||||
"data_size",
|
||||
"on_disk",
|
||||
"nzf",
|
||||
"crc32",
|
||||
)
|
||||
|
||||
|
||||
class Article(TryList):
|
||||
"""Representation of one article"""
|
||||
|
||||
# Pre-define attributes to save memory
|
||||
__slots__ = ArticleSaver + ("fetcher", "fetcher_priority", "tries")
|
||||
|
||||
def __init__(self, article, article_bytes, nzf):
|
||||
super().__init__()
|
||||
self.article: str = article
|
||||
self.art_id: Optional[str] = None
|
||||
self.bytes: int = article_bytes
|
||||
self.lowest_partnum: bool = False
|
||||
self.fetcher: Optional[Server] = None
|
||||
self.fetcher_priority: int = 0
|
||||
self.tries: int = 0 # Try count
|
||||
self.decoded: bool = False
|
||||
self.file_size: Optional[int] = None
|
||||
self.data_begin: Optional[int] = None
|
||||
self.data_size: Optional[int] = None
|
||||
self.on_disk: bool = False
|
||||
self.crc32: Optional[int] = None
|
||||
self.nzf = nzf # NzbFile reference
|
||||
|
||||
@synchronized(TRYLIST_LOCK)
|
||||
def reset_try_list(self):
|
||||
"""In addition to resetting the try list, also reset fetcher so all servers
|
||||
are tried again. Locked so fetcher setting changes are also protected."""
|
||||
self.fetcher = None
|
||||
self.fetcher_priority = 0
|
||||
super().reset_try_list()
|
||||
|
||||
@synchronized(TRYLIST_LOCK)
|
||||
def allow_new_fetcher(self, remove_fetcher_from_try_list: bool = True):
|
||||
"""Let article get new fetcher and reset try lists of file and job.
|
||||
Locked so all resets are performed at once"""
|
||||
if remove_fetcher_from_try_list:
|
||||
self.remove_from_try_list(self.fetcher)
|
||||
self.fetcher = None
|
||||
self.tries = 0
|
||||
self.nzf.reset_try_list()
|
||||
self.nzf.nzo.reset_try_list()
|
||||
|
||||
def get_article(self, server: Server, servers: list[Server]):
|
||||
"""Return article when appropriate for specified server"""
|
||||
if self.fetcher or self.server_in_try_list(server):
|
||||
return None
|
||||
|
||||
if server.priority > self.fetcher_priority:
|
||||
# Check for higher priority server, taking advantage of servers list being sorted by priority
|
||||
for server_check in servers:
|
||||
if server_check.priority < server.priority:
|
||||
if server_check.active and not self.server_in_try_list(server_check):
|
||||
# There is a higher priority server, so set article priority and return
|
||||
self.fetcher_priority = server_check.priority
|
||||
return None
|
||||
else:
|
||||
# All servers with a higher priority have been checked
|
||||
break
|
||||
|
||||
# If no higher priority servers, use this server
|
||||
self.fetcher_priority = server.priority
|
||||
self.fetcher = server
|
||||
self.tries += 1
|
||||
return self
|
||||
|
||||
def get_art_id(self):
|
||||
"""Return unique article storage name, create if needed"""
|
||||
if not self.art_id:
|
||||
self.art_id = get_new_id("article", self.nzf.nzo.admin_path)
|
||||
return self.art_id
|
||||
|
||||
def search_new_server(self):
|
||||
"""Search for a new server for this article"""
|
||||
# Since we need a new server, this one can be listed as failed
|
||||
sabnzbd.BPSMeter.register_server_article_failed(self.fetcher.id)
|
||||
self.add_to_try_list(self.fetcher)
|
||||
# Servers-list could be modified during iteration, so we need a copy
|
||||
for server in sabnzbd.Downloader.servers[:]:
|
||||
if server.active and not self.server_in_try_list(server):
|
||||
if server.priority >= self.fetcher.priority:
|
||||
self.tries = 0
|
||||
# Allow all servers for this nzo and nzf again (but not this fetcher for this article)
|
||||
self.allow_new_fetcher(remove_fetcher_from_try_list=False)
|
||||
return True
|
||||
|
||||
logging.info("Article %s unavailable on all servers, discarding", self.article)
|
||||
return False
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save to pickle file, selecting attributes"""
|
||||
dict_ = {}
|
||||
for item in ArticleSaver:
|
||||
dict_[item] = getattr(self, item)
|
||||
dict_["try_list"] = super().__getstate__()
|
||||
return dict_
|
||||
|
||||
def __setstate__(self, dict_):
|
||||
"""Load from pickle file, selecting attributes"""
|
||||
for item in ArticleSaver:
|
||||
try:
|
||||
setattr(self, item, dict_[item])
|
||||
except KeyError:
|
||||
# Handle new attributes
|
||||
setattr(self, item, None)
|
||||
super().__setstate__(dict_.get("try_list", []))
|
||||
self.fetcher = None
|
||||
self.fetcher_priority = 0
|
||||
self.tries = 0
|
||||
|
||||
def __repr__(self):
|
||||
return "<Article: article=%s, bytes=%s, art_id=%s>" % (self.article, self.bytes, self.art_id)
|
||||
290
sabnzbd/nzb/file.py
Normal file
290
sabnzbd/nzb/file.py
Normal file
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
sabnzbd.nzb.file - NzbFile class for representing files in NZB downloads
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
import sabctools
|
||||
from sabnzbd.nzb.article import TryList, Article, TRYLIST_LOCK
|
||||
from sabnzbd.downloader import Server
|
||||
from sabnzbd.filesystem import (
|
||||
sanitize_filename,
|
||||
get_unique_filename,
|
||||
get_filename,
|
||||
remove_file,
|
||||
get_new_id,
|
||||
save_data,
|
||||
load_data,
|
||||
)
|
||||
from sabnzbd.misc import int_conv, subject_name_extractor
|
||||
from sabnzbd.decorators import synchronized
|
||||
|
||||
|
||||
##############################################################################
|
||||
# NzbFile
|
||||
##############################################################################
|
||||
class SkippedNzbFile(Exception):
|
||||
pass
|
||||
|
||||
|
||||
NzbFileSaver = (
|
||||
"date",
|
||||
"filename",
|
||||
"filename_checked",
|
||||
"filepath",
|
||||
"type",
|
||||
"is_par2",
|
||||
"vol",
|
||||
"blocks",
|
||||
"setname",
|
||||
"articles",
|
||||
"decodetable",
|
||||
"bytes",
|
||||
"bytes_left",
|
||||
"nzo",
|
||||
"nzf_id",
|
||||
"deleted",
|
||||
"import_finished",
|
||||
"crc32",
|
||||
"assembled",
|
||||
"md5of16k",
|
||||
)
|
||||
|
||||
|
||||
class NzbFile(TryList):
|
||||
"""Representation of one file consisting of multiple articles"""
|
||||
|
||||
# Pre-define attributes to save memory
|
||||
__slots__ = NzbFileSaver + ("lock",)
|
||||
|
||||
def __init__(self, date, subject, raw_article_db, file_bytes, nzo):
|
||||
"""Setup object"""
|
||||
super().__init__()
|
||||
self.lock = threading.RLock()
|
||||
|
||||
self.date: datetime.datetime = date
|
||||
self.type: Optional[str] = None
|
||||
self.filename: str = sanitize_filename(subject_name_extractor(subject))
|
||||
self.filename_checked = False
|
||||
self.filepath: Optional[str] = None
|
||||
|
||||
# Identifiers for par2 files
|
||||
self.is_par2: bool = False
|
||||
self.vol: Optional[int] = None
|
||||
self.blocks: Optional[int] = None
|
||||
self.setname: Optional[str] = None
|
||||
|
||||
# Articles are removed from "articles" after being fetched
|
||||
self.articles: dict[Article, Article] = {}
|
||||
self.decodetable: list[Article] = []
|
||||
|
||||
self.bytes: int = file_bytes
|
||||
self.bytes_left: int = file_bytes
|
||||
|
||||
self.nzo = nzo # NzbObject reference
|
||||
self.deleted = False
|
||||
self.import_finished = False
|
||||
|
||||
self.crc32: Optional[int] = 0
|
||||
self.assembled: bool = False
|
||||
self.md5of16k: Optional[bytes] = None
|
||||
|
||||
# Add first article to decodetable, this way we can check
|
||||
# if this is maybe a duplicate nzf
|
||||
if raw_article_db:
|
||||
first_article = self.add_article(raw_article_db.pop(0))
|
||||
first_article.lowest_partnum = True
|
||||
|
||||
if self in nzo.files:
|
||||
logging.info("File %s occurred twice in NZB, skipping", self.filename)
|
||||
raise SkippedNzbFile
|
||||
|
||||
# Create file on disk, which can fail in case of disk errors
|
||||
self.nzf_id: str = get_new_id("nzf", nzo.admin_path)
|
||||
if not self.nzf_id:
|
||||
# Error already shown to user from get_new_id
|
||||
raise SkippedNzbFile
|
||||
|
||||
# Any articles left?
|
||||
if raw_article_db:
|
||||
# Save the rest
|
||||
save_data(raw_article_db, self.nzf_id, nzo.admin_path)
|
||||
else:
|
||||
# All imported
|
||||
self.import_finished = True
|
||||
|
||||
def finish_import(self):
|
||||
"""Load the article objects from disk"""
|
||||
logging.debug("Finishing import on %s", self.filename)
|
||||
if raw_article_db := load_data(self.nzf_id, self.nzo.admin_path, remove=False):
|
||||
for raw_article in raw_article_db:
|
||||
self.add_article(raw_article)
|
||||
|
||||
# Make sure we have labeled the lowest part number
|
||||
# Also when DirectUnpack is disabled we need to know
|
||||
self.decodetable[0].lowest_partnum = True
|
||||
|
||||
# Mark safe to continue
|
||||
self.import_finished = True
|
||||
|
||||
def add_article(self, article_info):
|
||||
"""Add article to object database and return article object"""
|
||||
article = Article(article_info[0], article_info[1], self)
|
||||
with self.lock:
|
||||
self.articles[article] = article
|
||||
self.decodetable.append(article)
|
||||
return article
|
||||
|
||||
def remove_article(self, article: Article, success: bool) -> int:
|
||||
"""Handle completed article, possibly end of file"""
|
||||
with self.lock:
|
||||
if self.articles.pop(article, None) is not None:
|
||||
if success:
|
||||
self.bytes_left -= article.bytes
|
||||
return len(self.articles)
|
||||
|
||||
def set_par2(self, setname, vol, blocks):
|
||||
"""Designate this file as a par2 file"""
|
||||
self.is_par2 = True
|
||||
self.setname = setname
|
||||
self.vol = vol
|
||||
self.blocks = int_conv(blocks)
|
||||
|
||||
def update_crc32(self, crc32: Optional[int], length: int) -> None:
|
||||
if self.crc32 is None or crc32 is None:
|
||||
self.crc32 = None
|
||||
else:
|
||||
self.crc32 = sabctools.crc32_combine(self.crc32, crc32, length)
|
||||
|
||||
def get_articles(self, server: Server, servers: list[Server], fetch_limit: int):
|
||||
"""Get next articles to be downloaded"""
|
||||
articles = server.article_queue
|
||||
with self.lock:
|
||||
for article in self.articles:
|
||||
if article := article.get_article(server, servers):
|
||||
articles.append(article)
|
||||
if len(articles) >= fetch_limit:
|
||||
return
|
||||
self.add_to_try_list(server)
|
||||
|
||||
@synchronized(TRYLIST_LOCK)
|
||||
def reset_all_try_lists(self):
|
||||
"""Reset all try lists. Locked so reset is performed
|
||||
for all items at the same time without chance of another
|
||||
thread changing any of the items while we are resetting"""
|
||||
with self.lock:
|
||||
for art in self.articles:
|
||||
art.reset_try_list()
|
||||
self.reset_try_list()
|
||||
|
||||
def first_article_processed(self) -> bool:
|
||||
"""Check if the first article has been processed.
|
||||
This ensures we have attempted to extract md5of16k and filename information
|
||||
before creating the filepath.
|
||||
"""
|
||||
# The first article of decodetable is always the lowest
|
||||
first_article = self.decodetable[0]
|
||||
# If it's still in nzo.first_articles, it hasn't been processed yet
|
||||
return first_article not in self.nzo.first_articles
|
||||
|
||||
def prepare_filepath(self):
|
||||
"""Do all checks before making the final path"""
|
||||
if not self.filepath:
|
||||
# Wait for the first article to be processed so we can get md5of16k
|
||||
# and proper filename before creating the filepath
|
||||
if not self.first_article_processed():
|
||||
return None
|
||||
|
||||
self.nzo.verify_nzf_filename(self)
|
||||
filename = sanitize_filename(self.filename)
|
||||
self.filepath = get_unique_filename(os.path.join(self.nzo.download_path, filename))
|
||||
self.filename = get_filename(self.filepath)
|
||||
return self.filepath
|
||||
|
||||
@property
|
||||
def completed(self):
|
||||
"""Is this file completed?"""
|
||||
if not self.import_finished:
|
||||
return False
|
||||
with self.lock:
|
||||
return not self.articles
|
||||
|
||||
def remove_admin(self):
|
||||
"""Remove article database from disk (sabnzbd_nzf_<id>)"""
|
||||
try:
|
||||
logging.debug("Removing article database for %s", self.nzf_id)
|
||||
remove_file(os.path.join(self.nzo.admin_path, self.nzf_id))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.lock.acquire()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.lock.release()
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save to pickle file, selecting attributes"""
|
||||
dict_ = {}
|
||||
for item in NzbFileSaver:
|
||||
dict_[item] = getattr(self, item)
|
||||
dict_["try_list"] = super().__getstate__()
|
||||
return dict_
|
||||
|
||||
def __setstate__(self, dict_):
|
||||
"""Load from pickle file, selecting attributes"""
|
||||
for item in NzbFileSaver:
|
||||
try:
|
||||
setattr(self, item, dict_[item])
|
||||
except KeyError:
|
||||
# Handle new attributes
|
||||
setattr(self, item, None)
|
||||
super().__setstate__(dict_.get("try_list", []))
|
||||
self.lock = threading.RLock()
|
||||
if isinstance(self.articles, list):
|
||||
# Converted from list to dict
|
||||
self.articles = {x: x for x in self.articles}
|
||||
|
||||
def __eq__(self, other: "NzbFile"):
|
||||
"""Assume it's the same file if the number bytes and first article
|
||||
are the same or if there are no articles left, use the filenames.
|
||||
Some NZB's are just a mess and report different sizes for the same article.
|
||||
We used to compare (__eq__) articles based on article-ID, however, this failed
|
||||
because some NZB's had the same article-ID twice within one NZF.
|
||||
"""
|
||||
if other and (self.bytes == other.bytes or len(self.decodetable) == len(other.decodetable)):
|
||||
if self.decodetable and other.decodetable:
|
||||
return self.decodetable[0].article == other.decodetable[0].article
|
||||
# Fallback to filename comparison
|
||||
return self.filename == other.filename
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
"""Required because we implement eq. The same file can be spread
|
||||
over multiple NZO's so we make every NZF unique. Even though
|
||||
it's considered bad practice.
|
||||
"""
|
||||
return id(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<NzbFile: filename=%s, bytes=%s, nzf_id=%s>" % (self.filename, self.bytes, self.nzf_id)
|
||||
@@ -16,7 +16,7 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
sabnzbd.nzbstuff - misc
|
||||
sabnzbd.nzb.object - NzbObject class for representing NZB download jobs
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
@@ -30,7 +30,8 @@ from typing import Any, Optional, Union, BinaryIO, Deque
|
||||
|
||||
# SABnzbd modules
|
||||
import sabnzbd
|
||||
import sabctools
|
||||
from sabnzbd.nzb.article import TryList, Article, TRYLIST_LOCK
|
||||
from sabnzbd.nzb.file import NzbFile
|
||||
from sabnzbd.constants import (
|
||||
GIGI,
|
||||
ATTRIB_FILE,
|
||||
@@ -60,6 +61,8 @@ from sabnzbd.misc import (
|
||||
opts_to_pp,
|
||||
pp_to_opts,
|
||||
duplicate_warning,
|
||||
scan_password,
|
||||
subject_name_extractor,
|
||||
)
|
||||
from sabnzbd.filesystem import (
|
||||
sanitize_foldername,
|
||||
@@ -89,463 +92,19 @@ from sabnzbd.filesystem import (
|
||||
remove_data,
|
||||
strip_extensions,
|
||||
get_ext,
|
||||
create_work_name,
|
||||
nzf_cmp_name,
|
||||
RAR_RE,
|
||||
)
|
||||
from sabnzbd.par2file import FilePar2Info, has_par2_in_filename, analyse_par2, parse_par2_file, is_par2_file
|
||||
from sabnzbd.decorators import synchronized
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
import sabnzbd.nzbparser
|
||||
from sabnzbd.downloader import Server
|
||||
from sabnzbd.database import HistoryDB
|
||||
from sabnzbd.deobfuscate_filenames import is_probably_obfuscated
|
||||
|
||||
# Name patterns
|
||||
# In the subject, we expect the filename within double quotes
|
||||
RE_SUBJECT_FILENAME_QUOTES = re.compile(r'"([^"]*)"')
|
||||
# Otherwise something that looks like a filename
|
||||
RE_SUBJECT_BASIC_FILENAME = re.compile(r"\b([\w\-+()' .,]+(?:\[[\w\-/+()' .,]*][\w\-+()' .,]*)*\.[A-Za-z0-9]{2,4})\b")
|
||||
RE_RAR = re.compile(r"(\.rar|\.r\d\d|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d)$", re.I)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Trylist
|
||||
##############################################################################
|
||||
|
||||
TRYLIST_LOCK = threading.RLock()
|
||||
|
||||
|
||||
class TryList:
|
||||
"""TryList keeps track of which servers have been tried for a specific article"""
|
||||
|
||||
# Pre-define attributes to save memory
|
||||
__slots__ = ("try_list",)
|
||||
|
||||
def __init__(self):
|
||||
# Sets are faster than lists
|
||||
self.try_list: set[Server] = set()
|
||||
|
||||
def server_in_try_list(self, server: Server) -> bool:
|
||||
"""Return whether specified server has been tried"""
|
||||
with TRYLIST_LOCK:
|
||||
return server in self.try_list
|
||||
|
||||
def all_servers_in_try_list(self, all_servers: set[Server]) -> bool:
|
||||
"""Check if all servers have been tried"""
|
||||
with TRYLIST_LOCK:
|
||||
return all_servers.issubset(self.try_list)
|
||||
|
||||
def add_to_try_list(self, server: Server):
|
||||
"""Register server as having been tried already"""
|
||||
with TRYLIST_LOCK:
|
||||
# Sets cannot contain duplicate items
|
||||
self.try_list.add(server)
|
||||
|
||||
def remove_from_try_list(self, server: Server):
|
||||
"""Remove server from list of tried servers"""
|
||||
with TRYLIST_LOCK:
|
||||
# Discard does not require the item to be present
|
||||
self.try_list.discard(server)
|
||||
|
||||
def reset_try_list(self):
|
||||
"""Clean the list"""
|
||||
with TRYLIST_LOCK:
|
||||
self.try_list = set()
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save the servers"""
|
||||
return set(server.id for server in self.try_list)
|
||||
|
||||
def __setstate__(self, servers_ids: list[str]):
|
||||
self.try_list = set()
|
||||
for server in sabnzbd.Downloader.servers:
|
||||
if server.id in servers_ids:
|
||||
self.add_to_try_list(server)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Article
|
||||
##############################################################################
|
||||
ArticleSaver = (
|
||||
"article",
|
||||
"art_id",
|
||||
"bytes",
|
||||
"lowest_partnum",
|
||||
"decoded",
|
||||
"file_size",
|
||||
"data_begin",
|
||||
"data_size",
|
||||
"on_disk",
|
||||
"nzf",
|
||||
"crc32",
|
||||
)
|
||||
|
||||
|
||||
class Article(TryList):
|
||||
"""Representation of one article"""
|
||||
|
||||
# Pre-define attributes to save memory
|
||||
__slots__ = ArticleSaver + ("fetcher", "fetcher_priority", "tries")
|
||||
|
||||
def __init__(self, article, article_bytes, nzf):
|
||||
super().__init__()
|
||||
self.article: str = article
|
||||
self.art_id: Optional[str] = None
|
||||
self.bytes: int = article_bytes
|
||||
self.lowest_partnum: bool = False
|
||||
self.fetcher: Optional[Server] = None
|
||||
self.fetcher_priority: int = 0
|
||||
self.tries: int = 0 # Try count
|
||||
self.decoded: bool = False
|
||||
self.file_size: Optional[int] = None
|
||||
self.data_begin: Optional[int] = None
|
||||
self.data_size: Optional[int] = None
|
||||
self.on_disk: bool = False
|
||||
self.crc32: Optional[int] = None
|
||||
self.nzf: NzbFile = nzf
|
||||
|
||||
@synchronized(TRYLIST_LOCK)
|
||||
def reset_try_list(self):
|
||||
"""In addition to resetting the try list, also reset fetcher so all servers
|
||||
are tried again. Locked so fetcher setting changes are also protected."""
|
||||
self.fetcher = None
|
||||
self.fetcher_priority = 0
|
||||
super().reset_try_list()
|
||||
|
||||
@synchronized(TRYLIST_LOCK)
|
||||
def allow_new_fetcher(self, remove_fetcher_from_try_list: bool = True):
|
||||
"""Let article get new fetcher and reset try lists of file and job.
|
||||
Locked so all resets are performed at once"""
|
||||
if remove_fetcher_from_try_list:
|
||||
self.remove_from_try_list(self.fetcher)
|
||||
self.fetcher = None
|
||||
self.tries = 0
|
||||
self.nzf.reset_try_list()
|
||||
self.nzf.nzo.reset_try_list()
|
||||
|
||||
def get_article(self, server: Server, servers: list[Server]):
|
||||
"""Return article when appropriate for specified server"""
|
||||
if self.fetcher or self.server_in_try_list(server):
|
||||
return None
|
||||
|
||||
if server.priority > self.fetcher_priority:
|
||||
# Check for higher priority server, taking advantage of servers list being sorted by priority
|
||||
for server_check in servers:
|
||||
if server_check.priority < server.priority:
|
||||
if server_check.active and not self.server_in_try_list(server_check):
|
||||
# There is a higher priority server, so set article priority and return
|
||||
self.fetcher_priority = server_check.priority
|
||||
return None
|
||||
else:
|
||||
# All servers with a higher priority have been checked
|
||||
break
|
||||
|
||||
# If no higher priority servers, use this server
|
||||
self.fetcher_priority = server.priority
|
||||
self.fetcher = server
|
||||
self.tries += 1
|
||||
return self
|
||||
|
||||
def get_art_id(self):
|
||||
"""Return unique article storage name, create if needed"""
|
||||
if not self.art_id:
|
||||
self.art_id = get_new_id("article", self.nzf.nzo.admin_path)
|
||||
return self.art_id
|
||||
|
||||
def search_new_server(self):
|
||||
"""Search for a new server for this article"""
|
||||
# Since we need a new server, this one can be listed as failed
|
||||
sabnzbd.BPSMeter.register_server_article_failed(self.fetcher.id)
|
||||
self.add_to_try_list(self.fetcher)
|
||||
# Servers-list could be modified during iteration, so we need a copy
|
||||
for server in sabnzbd.Downloader.servers[:]:
|
||||
if server.active and not self.server_in_try_list(server):
|
||||
if server.priority >= self.fetcher.priority:
|
||||
self.tries = 0
|
||||
# Allow all servers for this nzo and nzf again (but not this fetcher for this article)
|
||||
self.allow_new_fetcher(remove_fetcher_from_try_list=False)
|
||||
return True
|
||||
|
||||
logging.info("Article %s unavailable on all servers, discarding", self.article)
|
||||
return False
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save to pickle file, selecting attributes"""
|
||||
dict_ = {}
|
||||
for item in ArticleSaver:
|
||||
dict_[item] = getattr(self, item)
|
||||
dict_["try_list"] = super().__getstate__()
|
||||
return dict_
|
||||
|
||||
def __setstate__(self, dict_):
|
||||
"""Load from pickle file, selecting attributes"""
|
||||
for item in ArticleSaver:
|
||||
try:
|
||||
setattr(self, item, dict_[item])
|
||||
except KeyError:
|
||||
# Handle new attributes
|
||||
setattr(self, item, None)
|
||||
super().__setstate__(dict_.get("try_list", []))
|
||||
self.fetcher = None
|
||||
self.fetcher_priority = 0
|
||||
self.tries = 0
|
||||
|
||||
def __repr__(self):
|
||||
return "<Article: article=%s, bytes=%s, art_id=%s>" % (self.article, self.bytes, self.art_id)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# NzbFile
|
||||
##############################################################################
|
||||
class SkippedNzbFile(Exception):
|
||||
pass
|
||||
|
||||
|
||||
NzbFileSaver = (
|
||||
"date",
|
||||
"filename",
|
||||
"filename_checked",
|
||||
"filepath",
|
||||
"type",
|
||||
"is_par2",
|
||||
"vol",
|
||||
"blocks",
|
||||
"setname",
|
||||
"articles",
|
||||
"decodetable",
|
||||
"bytes",
|
||||
"bytes_left",
|
||||
"nzo",
|
||||
"nzf_id",
|
||||
"deleted",
|
||||
"import_finished",
|
||||
"crc32",
|
||||
"assembled",
|
||||
"md5of16k",
|
||||
)
|
||||
|
||||
|
||||
class NzbFile(TryList):
|
||||
"""Representation of one file consisting of multiple articles"""
|
||||
|
||||
# Pre-define attributes to save memory
|
||||
__slots__ = NzbFileSaver + ("lock",)
|
||||
|
||||
def __init__(self, date, subject, raw_article_db, file_bytes, nzo):
|
||||
"""Setup object"""
|
||||
super().__init__()
|
||||
self.lock = threading.RLock()
|
||||
|
||||
self.date: datetime.datetime = date
|
||||
self.type: Optional[str] = None
|
||||
self.filename: str = sanitize_filename(name_extractor(subject))
|
||||
self.filename_checked = False
|
||||
self.filepath: Optional[str] = None
|
||||
|
||||
# Identifiers for par2 files
|
||||
self.is_par2: bool = False
|
||||
self.vol: Optional[int] = None
|
||||
self.blocks: Optional[int] = None
|
||||
self.setname: Optional[str] = None
|
||||
|
||||
# Articles are removed from "articles" after being fetched
|
||||
self.articles: dict[Article, Article] = {}
|
||||
self.decodetable: list[Article] = []
|
||||
|
||||
self.bytes: int = file_bytes
|
||||
self.bytes_left: int = file_bytes
|
||||
|
||||
self.nzo: NzbObject = nzo
|
||||
self.deleted = False
|
||||
self.import_finished = False
|
||||
|
||||
self.crc32: Optional[int] = 0
|
||||
self.assembled: bool = False
|
||||
self.md5of16k: Optional[bytes] = None
|
||||
|
||||
# Add first article to decodetable, this way we can check
|
||||
# if this is maybe a duplicate nzf
|
||||
if raw_article_db:
|
||||
first_article = self.add_article(raw_article_db.pop(0))
|
||||
first_article.lowest_partnum = True
|
||||
|
||||
if self in nzo.files:
|
||||
logging.info("File %s occurred twice in NZB, skipping", self.filename)
|
||||
raise SkippedNzbFile
|
||||
|
||||
# Create file on disk, which can fail in case of disk errors
|
||||
self.nzf_id: str = get_new_id("nzf", nzo.admin_path)
|
||||
if not self.nzf_id:
|
||||
# Error already shown to user from get_new_id
|
||||
raise SkippedNzbFile
|
||||
|
||||
# Any articles left?
|
||||
if raw_article_db:
|
||||
# Save the rest
|
||||
save_data(raw_article_db, self.nzf_id, nzo.admin_path)
|
||||
else:
|
||||
# All imported
|
||||
self.import_finished = True
|
||||
|
||||
def finish_import(self):
|
||||
"""Load the article objects from disk"""
|
||||
logging.debug("Finishing import on %s", self.filename)
|
||||
if raw_article_db := load_data(self.nzf_id, self.nzo.admin_path, remove=False):
|
||||
for raw_article in raw_article_db:
|
||||
self.add_article(raw_article)
|
||||
|
||||
# Make sure we have labeled the lowest part number
|
||||
# Also when DirectUnpack is disabled we need to know
|
||||
self.decodetable[0].lowest_partnum = True
|
||||
|
||||
# Mark safe to continue
|
||||
self.import_finished = True
|
||||
|
||||
def add_article(self, article_info):
|
||||
"""Add article to object database and return article object"""
|
||||
article = Article(article_info[0], article_info[1], self)
|
||||
with self.lock:
|
||||
self.articles[article] = article
|
||||
self.decodetable.append(article)
|
||||
return article
|
||||
|
||||
def remove_article(self, article: Article, success: bool) -> int:
|
||||
"""Handle completed article, possibly end of file"""
|
||||
with self.lock:
|
||||
if self.articles.pop(article, None) is not None:
|
||||
if success:
|
||||
self.bytes_left -= article.bytes
|
||||
return len(self.articles)
|
||||
|
||||
def set_par2(self, setname, vol, blocks):
|
||||
"""Designate this file as a par2 file"""
|
||||
self.is_par2 = True
|
||||
self.setname = setname
|
||||
self.vol = vol
|
||||
self.blocks = int_conv(blocks)
|
||||
|
||||
def update_crc32(self, crc32: Optional[int], length: int) -> None:
|
||||
if self.crc32 is None or crc32 is None:
|
||||
self.crc32 = None
|
||||
else:
|
||||
self.crc32 = sabctools.crc32_combine(self.crc32, crc32, length)
|
||||
|
||||
def get_articles(self, server: Server, servers: list[Server], fetch_limit: int):
|
||||
"""Get next articles to be downloaded"""
|
||||
articles = server.article_queue
|
||||
with self.lock:
|
||||
for article in self.articles:
|
||||
if article := article.get_article(server, servers):
|
||||
articles.append(article)
|
||||
if len(articles) >= fetch_limit:
|
||||
return
|
||||
self.add_to_try_list(server)
|
||||
|
||||
@synchronized(TRYLIST_LOCK)
|
||||
def reset_all_try_lists(self):
|
||||
"""Reset all try lists. Locked so reset is performed
|
||||
for all items at the same time without chance of another
|
||||
thread changing any of the items while we are resetting"""
|
||||
with self.lock:
|
||||
for art in self.articles:
|
||||
art.reset_try_list()
|
||||
self.reset_try_list()
|
||||
|
||||
def first_article_processed(self) -> bool:
|
||||
"""Check if the first article has been processed.
|
||||
This ensures we have attempted to extract md5of16k and filename information
|
||||
before creating the filepath.
|
||||
"""
|
||||
# The first article of decodetable is always the lowest
|
||||
first_article = self.decodetable[0]
|
||||
# If it's still in nzo.first_articles, it hasn't been processed yet
|
||||
return first_article not in self.nzo.first_articles
|
||||
|
||||
def prepare_filepath(self):
|
||||
"""Do all checks before making the final path"""
|
||||
if not self.filepath:
|
||||
# Wait for the first article to be processed so we can get md5of16k
|
||||
# and proper filename before creating the filepath
|
||||
if not self.first_article_processed():
|
||||
return None
|
||||
|
||||
self.nzo.verify_nzf_filename(self)
|
||||
filename = sanitize_filename(self.filename)
|
||||
self.filepath = get_unique_filename(os.path.join(self.nzo.download_path, filename))
|
||||
self.filename = get_filename(self.filepath)
|
||||
return self.filepath
|
||||
|
||||
@property
|
||||
def completed(self):
|
||||
"""Is this file completed?"""
|
||||
if not self.import_finished:
|
||||
return False
|
||||
with self.lock:
|
||||
return not self.articles
|
||||
|
||||
def remove_admin(self):
|
||||
"""Remove article database from disk (sabnzbd_nzf_<id>)"""
|
||||
try:
|
||||
logging.debug("Removing article database for %s", self.nzf_id)
|
||||
remove_file(os.path.join(self.nzo.admin_path, self.nzf_id))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.lock.acquire()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.lock.release()
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save to pickle file, selecting attributes"""
|
||||
dict_ = {}
|
||||
for item in NzbFileSaver:
|
||||
dict_[item] = getattr(self, item)
|
||||
dict_["try_list"] = super().__getstate__()
|
||||
return dict_
|
||||
|
||||
def __setstate__(self, dict_):
|
||||
"""Load from pickle file, selecting attributes"""
|
||||
for item in NzbFileSaver:
|
||||
try:
|
||||
setattr(self, item, dict_[item])
|
||||
except KeyError:
|
||||
# Handle new attributes
|
||||
setattr(self, item, None)
|
||||
super().__setstate__(dict_.get("try_list", []))
|
||||
self.lock = threading.RLock()
|
||||
if isinstance(self.articles, list):
|
||||
# Converted from list to dict
|
||||
self.articles = {x: x for x in self.articles}
|
||||
|
||||
def __eq__(self, other: "NzbFile"):
|
||||
"""Assume it's the same file if the number bytes and first article
|
||||
are the same or if there are no articles left, use the filenames.
|
||||
Some NZB's are just a mess and report different sizes for the same article.
|
||||
We used to compare (__eq__) articles based on article-ID, however, this failed
|
||||
because some NZB's had the same article-ID twice within one NZF.
|
||||
"""
|
||||
if other and (self.bytes == other.bytes or len(self.decodetable) == len(other.decodetable)):
|
||||
if self.decodetable and other.decodetable:
|
||||
return self.decodetable[0].article == other.decodetable[0].article
|
||||
# Fallback to filename comparison
|
||||
return self.filename == other.filename
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
"""Required because we implement eq. The same file can be spread
|
||||
over multiple NZO's so we make every NZF unique. Even though
|
||||
it's considered bad practice.
|
||||
"""
|
||||
return id(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<NzbFile: filename=%s, bytes=%s, nzf_id=%s>" % (self.filename, self.bytes, self.nzf_id)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# NzbObject
|
||||
##############################################################################
|
||||
class NzbEmpty(Exception):
|
||||
pass
|
||||
|
||||
@@ -1032,7 +591,7 @@ class NzbObject(TryList):
|
||||
logging.debug("Unwanted Extension: putting last rar after first rar")
|
||||
firstrarpos = lastrarpos = 0
|
||||
for nzfposcounter, nzf in enumerate(self.files):
|
||||
if RE_RAR.search(nzf.filename.lower()):
|
||||
if RAR_RE.search(nzf.filename.lower()):
|
||||
# a NZF found with '.rar' in the name
|
||||
if firstrarpos == 0:
|
||||
# this is the first .rar found, so remember this position
|
||||
@@ -2134,109 +1693,3 @@ class NzbObject(TryList):
|
||||
|
||||
def __repr__(self):
|
||||
return "<NzbObject: filename=%s, bytes=%s, nzo_id=%s>" % (self.filename, self.bytes, self.nzo_id)
|
||||
|
||||
|
||||
def nzf_cmp_name(nzf1: NzbFile, nzf2: NzbFile):
|
||||
# The comparison will sort .par2 files to the top of the queue followed by .rar files,
|
||||
# they will then be sorted by name.
|
||||
nzf1_name = nzf1.filename.lower()
|
||||
nzf2_name = nzf2.filename.lower()
|
||||
|
||||
# Determine vol-pars
|
||||
is_par1 = ".vol" in nzf1_name and ".par2" in nzf1_name
|
||||
is_par2 = ".vol" in nzf2_name and ".par2" in nzf2_name
|
||||
|
||||
# mini-par2 in front
|
||||
if not is_par1 and nzf1_name.endswith(".par2"):
|
||||
return -1
|
||||
if not is_par2 and nzf2_name.endswith(".par2"):
|
||||
return 1
|
||||
|
||||
# vol-pars go to the back
|
||||
if is_par1 and not is_par2:
|
||||
return 1
|
||||
if is_par2 and not is_par1:
|
||||
return -1
|
||||
|
||||
# Prioritize .rar files above any other type of file (other than vol-par)
|
||||
m1 = RE_RAR.search(nzf1_name)
|
||||
m2 = RE_RAR.search(nzf2_name)
|
||||
if m1 and not (is_par2 or m2):
|
||||
return -1
|
||||
elif m2 and not (is_par1 or m1):
|
||||
return 1
|
||||
# Force .rar to come before 'r00'
|
||||
if m1 and m1.group(1) == ".rar":
|
||||
nzf1_name = nzf1_name.replace(".rar", ".r//")
|
||||
if m2 and m2.group(1) == ".rar":
|
||||
nzf2_name = nzf2_name.replace(".rar", ".r//")
|
||||
return cmp(nzf1_name, nzf2_name)
|
||||
|
||||
|
||||
def create_work_name(name: str) -> str:
|
||||
"""Remove ".nzb" and ".par(2)" and sanitize, skip URL's"""
|
||||
if name.find("://") < 0:
|
||||
# Invalid charters need to be removed before and after (see unit-tests)
|
||||
return sanitize_foldername(strip_extensions(sanitize_foldername(name)))
|
||||
else:
|
||||
return name.strip()
|
||||
|
||||
|
||||
def scan_password(name: str) -> tuple[str, Optional[str]]:
|
||||
"""Get password (if any) from the title"""
|
||||
if "http://" in name or "https://" in name:
|
||||
return name, None
|
||||
|
||||
# Strip any unwanted usenet-related extensions
|
||||
name = strip_extensions(name)
|
||||
|
||||
# Identify any braces
|
||||
braces = name[1:].find("{{")
|
||||
if braces < 0:
|
||||
braces = len(name)
|
||||
else:
|
||||
braces += 1
|
||||
slash = name.find("/")
|
||||
|
||||
# Look for name/password, but make sure that '/' comes before any {{
|
||||
if 0 < slash < braces and "password=" not in name:
|
||||
# Is it maybe in 'name / password' notation?
|
||||
if slash == name.find(" / ") + 1 and name[: slash - 1].strip(". "):
|
||||
# Remove the extra space after name and before password
|
||||
return name[: slash - 1].strip(". "), name[slash + 2 :]
|
||||
if name[:slash].strip(". "):
|
||||
return name[:slash].strip(". "), name[slash + 1 :]
|
||||
|
||||
# Look for "name password=password"
|
||||
pw = name.find("password=")
|
||||
if pw > 0 and name[:pw].strip(". "):
|
||||
return name[:pw].strip(". "), name[pw + 9 :]
|
||||
|
||||
# Look for name{{password}}
|
||||
if braces < len(name):
|
||||
closing_braces = name.rfind("}}")
|
||||
if closing_braces > braces and name[:braces].strip(". "):
|
||||
return name[:braces].strip(". "), name[braces + 2 : closing_braces]
|
||||
|
||||
# Look again for name/password
|
||||
if slash > 0 and name[:slash].strip(". "):
|
||||
return name[:slash].strip(". "), name[slash + 1 :]
|
||||
|
||||
# No password found
|
||||
return name, None
|
||||
|
||||
|
||||
def name_extractor(subject: str) -> str:
|
||||
"""Try to extract a file name from a subject line, return `subject` if in doubt"""
|
||||
# Filename nicely wrapped in quotes
|
||||
for name in re.findall(RE_SUBJECT_FILENAME_QUOTES, subject):
|
||||
if name := name.strip(' "'):
|
||||
return name
|
||||
|
||||
# Found nothing? Try a basic filename-like search
|
||||
for name in re.findall(RE_SUBJECT_BASIC_FILENAME, subject):
|
||||
if name := name.strip():
|
||||
return name
|
||||
|
||||
# Return the subject
|
||||
return subject
|
||||
@@ -33,7 +33,15 @@ import cherrypy._cpreqbody
|
||||
from typing import Optional, Any, Union
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd import nzbstuff
|
||||
from sabnzbd.nzb import (
|
||||
NzbObject,
|
||||
NzbEmpty,
|
||||
NzbRejected,
|
||||
NzbPreQueueRejected,
|
||||
NzbRejectToHistory,
|
||||
NzbFile,
|
||||
SkippedNzbFile,
|
||||
)
|
||||
from sabnzbd.encoding import utob, correct_cherrypy_encoding
|
||||
from sabnzbd.filesystem import (
|
||||
get_filename,
|
||||
@@ -204,7 +212,7 @@ def process_nzb_archive_file(
|
||||
if datap:
|
||||
nzo = None
|
||||
try:
|
||||
nzo = nzbstuff.NzbObject(
|
||||
nzo = NzbObject(
|
||||
name,
|
||||
pp=pp,
|
||||
script=script,
|
||||
@@ -220,13 +228,13 @@ def process_nzb_archive_file(
|
||||
dup_check=dup_check,
|
||||
)
|
||||
except (
|
||||
sabnzbd.nzbstuff.NzbEmpty,
|
||||
sabnzbd.nzbstuff.NzbRejected,
|
||||
sabnzbd.nzbstuff.NzbPreQueueRejected,
|
||||
NzbEmpty,
|
||||
NzbRejected,
|
||||
NzbPreQueueRejected,
|
||||
):
|
||||
# Empty or fully rejected (including pre-queue rejections)
|
||||
pass
|
||||
except sabnzbd.nzbstuff.NzbRejectToHistory as err:
|
||||
except NzbRejectToHistory as err:
|
||||
# Duplicate or unwanted extension directed to history
|
||||
sabnzbd.NzbQueue.fail_to_history(err.nzo)
|
||||
nzo_ids.append(err.nzo.nzo_id)
|
||||
@@ -315,7 +323,7 @@ def process_single_nzb(
|
||||
nzo = None
|
||||
nzo_ids = []
|
||||
try:
|
||||
nzo = nzbstuff.NzbObject(
|
||||
nzo = NzbObject(
|
||||
filename,
|
||||
pp=pp,
|
||||
script=script,
|
||||
@@ -330,16 +338,16 @@ def process_single_nzb(
|
||||
nzo_id=nzo_id,
|
||||
dup_check=dup_check,
|
||||
)
|
||||
except sabnzbd.nzbstuff.NzbEmpty:
|
||||
except NzbEmpty:
|
||||
# Malformed or might not be an NZB file
|
||||
result = AddNzbFileResult.NO_FILES_FOUND
|
||||
except sabnzbd.nzbstuff.NzbRejected:
|
||||
except NzbRejected:
|
||||
# Rejected as duplicate
|
||||
result = AddNzbFileResult.ERROR
|
||||
except sabnzbd.nzbstuff.NzbPreQueueRejected:
|
||||
except NzbPreQueueRejected:
|
||||
# Rejected by pre-queue script - should be silently ignored for URL fetches
|
||||
result = AddNzbFileResult.PREQUEUE_REJECTED
|
||||
except sabnzbd.nzbstuff.NzbRejectToHistory as err:
|
||||
except NzbRejectToHistory as err:
|
||||
# Duplicate or unwanted extension directed to history
|
||||
sabnzbd.NzbQueue.fail_to_history(err.nzo)
|
||||
nzo_ids.append(err.nzo.nzo_id)
|
||||
@@ -366,7 +374,7 @@ def process_single_nzb(
|
||||
|
||||
def nzbfile_parser(full_nzb_path: str, nzo):
|
||||
# For type-hinting
|
||||
nzo: sabnzbd.nzbstuff.NzbObject
|
||||
nzo: NzbObject
|
||||
|
||||
# Hash for dupe-checking
|
||||
md5sum = hashlib.md5()
|
||||
@@ -470,8 +478,8 @@ def nzbfile_parser(full_nzb_path: str, nzo):
|
||||
|
||||
# Create NZF
|
||||
try:
|
||||
nzf = sabnzbd.nzbstuff.NzbFile(file_date, file_name, raw_article_db_sorted, file_bytes, nzo)
|
||||
except sabnzbd.nzbstuff.SkippedNzbFile:
|
||||
nzf = NzbFile(file_date, file_name, raw_article_db_sorted, file_bytes, nzo)
|
||||
except SkippedNzbFile:
|
||||
# Did not meet requirements, so continue
|
||||
skipped_files += 1
|
||||
continue
|
||||
|
||||
@@ -26,7 +26,7 @@ import cherrypy._cpreqbody
|
||||
from typing import Union, Optional
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.nzbstuff import NzbObject, Article
|
||||
from sabnzbd.nzb import Article, NzbObject
|
||||
from sabnzbd.misc import exit_sab, cat_to_opts, int_conv, caller_name, safe_lower, duplicate_warning
|
||||
from sabnzbd.filesystem import get_admin_path, remove_all, globber_full, remove_file, is_valid_script
|
||||
from sabnzbd.nzbparser import process_single_nzb
|
||||
|
||||
@@ -73,7 +73,7 @@ from sabnzbd.filesystem import (
|
||||
get_ext,
|
||||
get_filename,
|
||||
)
|
||||
from sabnzbd.nzbstuff import NzbObject
|
||||
from sabnzbd.nzb import NzbObject
|
||||
from sabnzbd.sorting import Sorter
|
||||
from sabnzbd.constants import (
|
||||
REPAIR_PRIORITY,
|
||||
|
||||
183
sabnzbd/rss.py
183
sabnzbd/rss.py
@@ -25,6 +25,8 @@ import time
|
||||
import datetime
|
||||
import threading
|
||||
import urllib.parse
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union, Optional, Iterator
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import RSS_FILE_NAME, DEFAULT_PRIORITY
|
||||
@@ -142,43 +144,7 @@ class RSSReader:
|
||||
return T('Incorrect RSS feed description "%s"') % feed
|
||||
|
||||
uris = feeds.uri()
|
||||
defCat = feeds.cat()
|
||||
|
||||
if not notdefault(defCat) or defCat not in sabnzbd.api.list_cats(default=False):
|
||||
defCat = None
|
||||
defPP = feeds.pp()
|
||||
if not notdefault(defPP):
|
||||
defPP = None
|
||||
defScript = feeds.script()
|
||||
if not notdefault(defScript):
|
||||
defScript = None
|
||||
defPrio = feeds.priority()
|
||||
if not notdefault(defPrio):
|
||||
defPrio = None
|
||||
|
||||
# Preparations, convert filters to regex's
|
||||
regexes = []
|
||||
reTypes = []
|
||||
reCats = []
|
||||
rePPs = []
|
||||
rePrios = []
|
||||
reScripts = []
|
||||
reEnabled = []
|
||||
for feed_filter in feeds.filters():
|
||||
reCat = feed_filter[0]
|
||||
if defCat in ("", "*"):
|
||||
reCat = None
|
||||
reCats.append(reCat)
|
||||
rePPs.append(feed_filter[1])
|
||||
reScripts.append(feed_filter[2])
|
||||
reTypes.append(feed_filter[3])
|
||||
if feed_filter[3] in ("<", ">", "F", "S"):
|
||||
regexes.append(feed_filter[4])
|
||||
else:
|
||||
regexes.append(convert_filter(feed_filter[4]))
|
||||
rePrios.append(feed_filter[5])
|
||||
reEnabled.append(feed_filter[6] != "0")
|
||||
regcount = len(regexes)
|
||||
filters = prepare_feed(feeds)
|
||||
|
||||
# Set first if this is the very first scan of this URI
|
||||
first = (feed not in self.jobs) and ignoreFirst
|
||||
@@ -302,71 +268,71 @@ class RSSReader:
|
||||
# Match this title against all filters
|
||||
logging.debug("Trying title %s", title)
|
||||
result = False
|
||||
myCat = defCat
|
||||
myPP = defPP
|
||||
myScript = defScript
|
||||
myPrio = defPrio
|
||||
myCat = filters.default_category
|
||||
myPP = filters.default_pp
|
||||
myScript = filters.default_script
|
||||
myPrio = filters.default_priority
|
||||
n = 0
|
||||
if ("F" in reTypes or "S" in reTypes) and (not season or not episode):
|
||||
if filters.has_type("F", "S") and (not season or not episode):
|
||||
show_analysis = sabnzbd.sorting.BasicAnalyzer(title)
|
||||
season = show_analysis.info.get("season_num")
|
||||
episode = show_analysis.info.get("episode_num")
|
||||
|
||||
# Match against all filters until an positive or negative match
|
||||
logging.debug("Size %s", size)
|
||||
for n in range(regcount):
|
||||
if reEnabled[n]:
|
||||
if category and reTypes[n] == "C":
|
||||
found = re.search(regexes[n], category)
|
||||
for rule in filters:
|
||||
if rule.enabled:
|
||||
if category and rule.type == "C":
|
||||
found = re.search(rule.regex, category)
|
||||
if not found:
|
||||
logging.debug("Filter rejected on rule %d", n)
|
||||
result = False
|
||||
break
|
||||
elif reTypes[n] == "<" and size and from_units(regexes[n]) < size:
|
||||
elif rule.type == "<" and size and from_units(rule.regex) < size:
|
||||
# "Size at most" : too large
|
||||
logging.debug("Filter rejected on rule %d", n)
|
||||
result = False
|
||||
break
|
||||
elif reTypes[n] == ">" and size and from_units(regexes[n]) > size:
|
||||
elif rule.type == ">" and size and from_units(rule.regex) > size:
|
||||
# "Size at least" : too small
|
||||
logging.debug("Filter rejected on rule %d", n)
|
||||
result = False
|
||||
break
|
||||
elif reTypes[n] == "F" and not ep_match(season, episode, regexes[n]):
|
||||
elif rule.type == "F" and not ep_match(season, episode, rule.regex):
|
||||
# "Starting from SxxEyy", too early episode
|
||||
logging.debug("Filter requirement match on rule %d", n)
|
||||
result = False
|
||||
break
|
||||
elif reTypes[n] == "S" and ep_match(season, episode, regexes[n], title):
|
||||
elif rule.type == "S" and ep_match(season, episode, rule.regex, title):
|
||||
logging.debug("Filter matched on rule %d", n)
|
||||
result = True
|
||||
break
|
||||
else:
|
||||
if regexes[n]:
|
||||
found = re.search(regexes[n], title)
|
||||
if rule.regex:
|
||||
found = re.search(rule.regex, title)
|
||||
else:
|
||||
found = False
|
||||
if reTypes[n] == "M" and not found:
|
||||
if rule.type == "M" and not found:
|
||||
logging.debug("Filter rejected on rule %d", n)
|
||||
result = False
|
||||
break
|
||||
if found and reTypes[n] == "A":
|
||||
if found and rule.type == "A":
|
||||
logging.debug("Filter matched on rule %d", n)
|
||||
result = True
|
||||
break
|
||||
if found and reTypes[n] == "R":
|
||||
if found and rule.type == "R":
|
||||
logging.debug("Filter rejected on rule %d", n)
|
||||
result = False
|
||||
break
|
||||
|
||||
if len(reCats):
|
||||
if not result and defCat:
|
||||
if filters and (rule := filters.rules[-1]):
|
||||
if not result and filters.default_category:
|
||||
# Apply Feed-category on non-matched items
|
||||
myCat = defCat
|
||||
elif result and notdefault(reCats[n]):
|
||||
myCat = filters.default_category
|
||||
elif result and notdefault(rule.category):
|
||||
# Use the matched info
|
||||
myCat = reCats[n]
|
||||
elif category and not defCat:
|
||||
myCat = rule.category
|
||||
elif category and not filters.default_category:
|
||||
# No result and no Feed-category
|
||||
myCat = cat_convert(category)
|
||||
|
||||
@@ -374,17 +340,17 @@ class RSSReader:
|
||||
myCat, catPP, catScript, catPrio = cat_to_opts(myCat)
|
||||
else:
|
||||
myCat = catPP = catScript = catPrio = None
|
||||
if notdefault(rePPs[n]):
|
||||
myPP = rePPs[n]
|
||||
elif not (reCats[n] or category):
|
||||
if notdefault(rule.pp):
|
||||
myPP = rule.pp
|
||||
elif not (rule.category or category):
|
||||
myPP = catPP
|
||||
if notdefault(reScripts[n]):
|
||||
myScript = reScripts[n]
|
||||
elif not (notdefault(reCats[n]) or category):
|
||||
if notdefault(rule.script):
|
||||
myScript = rule.script
|
||||
elif not (notdefault(rule.category) or category):
|
||||
myScript = catScript
|
||||
if rePrios[n] not in (str(DEFAULT_PRIORITY), ""):
|
||||
myPrio = rePrios[n]
|
||||
elif not ((rePrios[n] != str(DEFAULT_PRIORITY)) or category):
|
||||
if rule.priority not in (str(DEFAULT_PRIORITY), ""):
|
||||
myPrio = rule.priority
|
||||
elif not ((rule.priority != str(DEFAULT_PRIORITY)) or category):
|
||||
myPrio = catPrio
|
||||
|
||||
act = download and not first
|
||||
@@ -532,6 +498,83 @@ class RSSReader:
|
||||
self.jobs[feed][item]["status"] = "D-"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeedRule:
|
||||
regex: Union[str, re.Pattern]
|
||||
type: str
|
||||
category: Optional[str] = None
|
||||
pp: Optional[str] = None
|
||||
priority: Optional[int] = None
|
||||
script: Optional[str] = None
|
||||
enabled: bool = True
|
||||
|
||||
def __post_init__(self):
|
||||
# Convert regex if needed
|
||||
if self.type not in {"<", ">", "F", "S"}:
|
||||
self.regex = convert_filter(self.regex)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeedConfig:
|
||||
default_category: Optional[str] = None
|
||||
default_pp: Optional[str] = None
|
||||
default_script: Optional[str] = None
|
||||
default_priority: Optional[int] = None
|
||||
rules: list[FeedRule] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
# Normalise categories for all rules automatically
|
||||
if self.default_category in ("", "*"):
|
||||
for rule in self.rules:
|
||||
rule.category = None
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.rules) # True if there are any rules
|
||||
|
||||
def __iter__(self) -> Iterator[FeedRule]:
|
||||
"""Allow iteration directly over FeedConfig to access rules."""
|
||||
return iter(self.rules)
|
||||
|
||||
def has_type(self, *types: str) -> bool:
|
||||
"""Check if any rule matches the given types"""
|
||||
return any(rule.type in types for rule in self.rules)
|
||||
|
||||
|
||||
def prepare_feed(c: config.ConfigRSS) -> FeedConfig:
|
||||
def normalise_default(value):
|
||||
return value if notdefault(value) else None
|
||||
|
||||
default_category = normalise_default(c.cat())
|
||||
if default_category not in sabnzbd.api.list_cats(default=False):
|
||||
default_category = None
|
||||
default_pp = normalise_default(c.pp())
|
||||
default_script = normalise_default(c.script())
|
||||
default_priority = normalise_default(c.priority())
|
||||
|
||||
# Preparations, convert filters to regex's
|
||||
rules: list[FeedRule] = []
|
||||
for cat, pp, script, ftype, regex, priority, enabled in c.filters():
|
||||
rules.append(
|
||||
FeedRule(
|
||||
regex=regex,
|
||||
type=ftype,
|
||||
category=cat,
|
||||
pp=pp,
|
||||
priority=priority,
|
||||
script=script,
|
||||
enabled=(enabled != "0"),
|
||||
)
|
||||
)
|
||||
|
||||
return FeedConfig(
|
||||
default_category=default_category,
|
||||
default_pp=default_pp,
|
||||
default_script=default_script,
|
||||
default_priority=default_priority,
|
||||
rules=rules,
|
||||
)
|
||||
|
||||
|
||||
def patch_feedparser():
|
||||
"""Apply options that work for SABnzbd
|
||||
Add additional parsing of attributes
|
||||
|
||||
@@ -574,6 +574,11 @@ SKIN_TEXT = {
|
||||
"For unreliable servers, will be ignored longer in case of failures"
|
||||
), #: Explain server optional tickbox
|
||||
"srv-enable": TT("Enable"), #: Enable server tickbox
|
||||
"srv-pipelining_requests": TT("Articles per request"),
|
||||
"explain-pipelining_requests": TT(
|
||||
"Request multiple articles per connection without waiting for each response first.<br />"
|
||||
"This can improve download speeds, especially on connections with higher latency."
|
||||
),
|
||||
"button-addServer": TT("Add Server"), #: Button: Add server
|
||||
"button-delServer": TT("Remove Server"), #: Button: Remove server
|
||||
"button-testServer": TT("Test Server"), #: Button: Test server
|
||||
@@ -899,6 +904,7 @@ SKIN_TEXT = {
|
||||
"Glitter-notification-removing1": TT("Removing job"), # Notification window
|
||||
"Glitter-notification-removing": TT("Removing jobs"), # Notification window
|
||||
"Glitter-notification-shutdown": TT("Shutting down"), # Notification window
|
||||
"Glitter-notification-upload-failed": TT("Failed to upload file: %s"), # Notification window
|
||||
# Wizard
|
||||
"wizard-quickstart": TT("SABnzbd Quick-Start Wizard"),
|
||||
"wizard-version": TT("SABnzbd Version"),
|
||||
|
||||
@@ -47,7 +47,8 @@ from sabnzbd.constants import (
|
||||
GUESSIT_SORT_TYPES,
|
||||
)
|
||||
from sabnzbd.misc import is_sample, from_units, sort_to_opts
|
||||
from sabnzbd.nzbstuff import NzbObject, scan_password
|
||||
from sabnzbd.misc import scan_password
|
||||
from sabnzbd.nzb import NzbObject
|
||||
|
||||
# Do not rename .vob files as they are usually DVD's
|
||||
EXCLUDED_FILE_EXTS = (".vob", ".bin")
|
||||
|
||||
@@ -51,7 +51,7 @@ import sabnzbd.notifier as notifier
|
||||
from sabnzbd.decorators import NZBQUEUE_LOCK
|
||||
from sabnzbd.encoding import ubtou, utob
|
||||
from sabnzbd.nzbparser import AddNzbFileResult
|
||||
from sabnzbd.nzbstuff import NzbObject, NzbRejected, NzbRejectToHistory
|
||||
from sabnzbd.nzb import NzbObject
|
||||
|
||||
|
||||
class URLGrabber(Thread):
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
# You MUST use double quotes (so " and not ')
|
||||
# Do not forget to update the appdata file for every major release!
|
||||
|
||||
__version__ = "4.6.0Alpha2"
|
||||
__version__ = "4.6.0Beta2"
|
||||
__baseline__ = "unknown"
|
||||
|
||||
@@ -28,7 +28,7 @@ from unittest import mock
|
||||
|
||||
import sabctools
|
||||
import sabnzbd.decoder as decoder
|
||||
from sabnzbd.nzbstuff import Article
|
||||
from sabnzbd.nzb import Article
|
||||
|
||||
|
||||
def uu(data: bytes):
|
||||
|
||||
@@ -663,7 +663,7 @@ class TestListdirFull(ffs.TestCase):
|
||||
):
|
||||
self.fs.create_file(file)
|
||||
assert os.path.exists(file) is True
|
||||
assert filesystem.listdir_full("/rsc") == ["/rsc/base_file", "/rsc/not._base_file"]
|
||||
assert sorted(filesystem.listdir_full("/rsc")) == ["/rsc/base_file", "/rsc/not._base_file"]
|
||||
|
||||
def test_invalid_file_argument(self):
|
||||
# This is obviously not intended use; the function expects a directory
|
||||
@@ -750,7 +750,7 @@ class TestListdirFullWin(ffs.TestCase):
|
||||
):
|
||||
self.fs.create_file(file)
|
||||
assert os.path.exists(file) is True
|
||||
assert filesystem.listdir_full(r"f:\rsc") == [r"f:\rsc\base_file", r"f:\rsc\not._base_file"]
|
||||
assert sorted(filesystem.listdir_full(r"f:\rsc")) == [r"f:\rsc\base_file", r"f:\rsc\not._base_file"]
|
||||
|
||||
def test_invalid_file_argument(self):
|
||||
# This is obviously not intended use; the function expects a directory
|
||||
@@ -1256,3 +1256,21 @@ class TestOtherFileSystemFunctions:
|
||||
)
|
||||
def test_strip_extensions(self, name, ext_to_remove, output):
|
||||
assert filesystem.strip_extensions(name, ext_to_remove) == output
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_name, clean_file_name",
|
||||
[
|
||||
("my_awesome_nzb_file.pAr2.nZb", "my_awesome_nzb_file"),
|
||||
("my_awesome_nzb_file.....pAr2.nZb", "my_awesome_nzb_file"),
|
||||
("my_awesome_nzb_file....par2..", "my_awesome_nzb_file"),
|
||||
(" my_awesome_nzb_file .pAr.nZb", "my_awesome_nzb_file"),
|
||||
("with.extension.and.period.par2.", "with.extension.and.period"),
|
||||
("nothing.in.here", "nothing.in.here"),
|
||||
(" just.space ", "just.space"),
|
||||
("http://test.par2 ", "http://test.par2"),
|
||||
],
|
||||
)
|
||||
def test_create_work_name(self, file_name, clean_file_name):
|
||||
# Only test stuff specific for create_work_name
|
||||
# The sanitizing is already tested in tests for sanitize_foldername
|
||||
assert filesystem.create_work_name(file_name) == clean_file_name
|
||||
|
||||
@@ -837,6 +837,94 @@ class TestMisc:
|
||||
|
||||
_func()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"argument, name, password",
|
||||
[
|
||||
("my_awesome_nzb_file{{password}}", "my_awesome_nzb_file", "password"),
|
||||
("file_with_text_after_pw{{passw0rd}}_[180519]", "file_with_text_after_pw", "passw0rd"),
|
||||
("file_without_pw", "file_without_pw", None),
|
||||
("multiple_pw{{first-pw}}_{{second-pw}}", "multiple_pw", "first-pw}}_{{second-pw"), # Greed is Good
|
||||
("デビアン", "デビアン", None), # Unicode
|
||||
("Gentoo_Hobby_Edition {{secret}}", "Gentoo_Hobby_Edition", "secret"), # Space between name and password
|
||||
("Test {{secret}}.nzb", "Test", "secret"),
|
||||
("Mandrake{{top{{secret}}", "Mandrake", "top{{secret"), # Double opening {{
|
||||
("Красная}}{{Шляпа}}", "Красная}}", "Шляпа"), # Double closing }}
|
||||
("{{Jobname{{PassWord}}", "{{Jobname", "PassWord"), # {{ at start
|
||||
("Hello/kITTY", "Hello", "kITTY"), # Notation with slash
|
||||
("Hello/kITTY.nzb", "Hello", "kITTY"), # Notation with slash and extension
|
||||
("/Jobname", "/Jobname", None), # Slash at start
|
||||
("Jobname/Top{{Secret}}", "Jobname", "Top{{Secret}}"), # Slash with braces
|
||||
("Jobname / Top{{Secret}}", "Jobname", "Top{{Secret}}"), # Slash with braces and extra spaces
|
||||
("Jobname / Top{{Secret}}.nzb", "Jobname", "Top{{Secret}}"),
|
||||
("לינוקס/معلومات سرية", "לינוקס", "معلومات سرية"), # LTR with slash
|
||||
("לינוקס{{معلومات سرية}}", "לינוקס", "معلومات سرية"), # LTR with brackets
|
||||
("thư điện tử password=mật_khẩu", "thư điện tử", "mật_khẩu"), # Password= notation
|
||||
("password=PartOfTheJobname", "password=PartOfTheJobname", None), # Password= at the start
|
||||
("Job password=Test.par2", "Job", "Test"), # Password= including extension
|
||||
("Job}}Name{{FTW", "Job}}Name{{FTW", None), # Both {{ and }} present but incorrect order (no password)
|
||||
("./Text", "./Text", None), # Name would end up empty after the function strips the dot
|
||||
],
|
||||
)
|
||||
def test_scan_password(self, argument, name, password):
|
||||
assert misc.scan_password(argument) == (name, password)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subject, filename",
|
||||
[
|
||||
('Great stuff (001/143) - "Filename.txt" yEnc (1/1)', "Filename.txt"),
|
||||
(
|
||||
'"910a284f98ebf57f6a531cd96da48838.vol01-03.par2" yEnc (1/3)',
|
||||
"910a284f98ebf57f6a531cd96da48838.vol01-03.par2",
|
||||
),
|
||||
('Subject-KrzpfTest [02/30] - ""KrzpfTest.part.nzb"" yEnc', "KrzpfTest.part.nzb"),
|
||||
(
|
||||
'[PRiVATE]-[WtFnZb]-[Supertje-_S03E11-12_-blabla_+_blabla_WEBDL-480p.mkv]-[4/12] - "" yEnc 9786 (1/1366)',
|
||||
"Supertje-_S03E11-12_-blabla_+_blabla_WEBDL-480p.mkv",
|
||||
),
|
||||
(
|
||||
'[N3wZ] MAlXD245333\\::[PRiVATE]-[WtFnZb]-[Show.S04E04.720p.AMZN.WEBRip.x264-GalaxyTV.mkv]-[1/2] - "" yEnc 293197257 (1/573)',
|
||||
"Show.S04E04.720p.AMZN.WEBRip.x264-GalaxyTV.mkv",
|
||||
),
|
||||
(
|
||||
'reftestnzb bf1664007a71 [1/6] - "20b9152c-57eb-4d02-9586-66e30b8e3ac2" yEnc (1/22) 15728640',
|
||||
"20b9152c-57eb-4d02-9586-66e30b8e3ac2",
|
||||
),
|
||||
(
|
||||
"Re: REQ Author Child's The Book-Thanks much - Child, Lee - Author - The Book.epub (1/1)",
|
||||
"REQ Author Child's The Book-Thanks much - Child, Lee - Author - The Book.epub",
|
||||
),
|
||||
('63258-0[001/101] - "63258-2.0" yEnc (1/250) (1/250)', "63258-2.0"),
|
||||
# If specified between ", the extension is allowed to be too long
|
||||
('63258-0[001/101] - "63258-2.0toolong" yEnc (1/250) (1/250)', "63258-2.0toolong"),
|
||||
(
|
||||
"Singer - A Album (2005) - [04/25] - 02 Sweetest Somebody (I Know).flac",
|
||||
"Singer - A Album (2005) - [04/25] - 02 Sweetest Somebody (I Know).flac",
|
||||
),
|
||||
("<>random!>", "<>random!>"),
|
||||
("nZb]-[Supertje-_S03E11-12_", "nZb]-[Supertje-_S03E11-12_"),
|
||||
("Bla [Now it's done.exe]", "Now it's done.exe"),
|
||||
# If specified between [], the extension should be a valid one
|
||||
("Bla [Now it's done.123nonsense]", "Bla [Now it's done.123nonsense]"),
|
||||
('[PRiVATE]-[WtFnZb]-[00000.clpi]-[1/46] - "" yEnc 788 (1/1)', "00000.clpi"),
|
||||
(
|
||||
'[PRiVATE]-[WtFnZb]-[Video_(2001)_AC5.1_-RELEASE_[TAoE].mkv]-[1/23] - "" yEnc 1234567890 (1/23456)',
|
||||
"Video_(2001)_AC5.1_-RELEASE_[TAoE].mkv",
|
||||
),
|
||||
(
|
||||
"[PRiVATE]-[WtFnZb]-[219]-[1/series.name.s01e01.1080p.web.h264-group.mkv] - "
|
||||
" yEnc (1/[PRiVATE] \\c2b510b594\\::686ea969999193.155368eba4965e56a8cd263382e012.f2712fdc::/97bd201cf931/) 1 (1/0)",
|
||||
"series.name.s01e01.1080p.web.h264-group.mkv",
|
||||
),
|
||||
(
|
||||
"[PRiVATE]-[WtFnZb]-[/More.Bla.S02E01.1080p.WEB.h264-EDITH[eztv.re].mkv-WtF[nZb]/"
|
||||
'More.Bla.S02E01.1080p.WEB.h264-EDITH.mkv]-[1/2] - "" yEnc 2990558544 (1/4173)',
|
||||
"More.Bla.S02E01.1080p.WEB.h264-EDITH[eztv.re].mkv",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_name_extractor(self, subject, filename):
|
||||
assert misc.subject_name_extractor(subject) == filename
|
||||
|
||||
|
||||
class TestBuildAndRunCommand:
|
||||
# Path should exist
|
||||
|
||||
69
tests/test_nzbarticle.py
Normal file
69
tests/test_nzbarticle.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
tests.test_nzbarticle - Testing functions in nzbarticle.py
|
||||
"""
|
||||
from sabnzbd.nzb import Article
|
||||
|
||||
from tests.testhelper import *
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, host, priority, active):
|
||||
self.host = host
|
||||
self.priority = priority
|
||||
self.active = active
|
||||
|
||||
|
||||
class TestArticle:
|
||||
def test_get_article(self):
|
||||
article_id = "test@host" + os.urandom(8).hex() + ".sab"
|
||||
article = Article(article_id, randint(4321, 54321), None)
|
||||
servers = []
|
||||
servers.append(Server("testserver1", 10, True))
|
||||
servers.append(Server("testserver2", 20, True))
|
||||
servers.append(Server("testserver3", 30, True))
|
||||
|
||||
# Test fetching top priority server
|
||||
server = servers[0]
|
||||
assert article.get_article(server, servers) == article
|
||||
assert article.fetcher_priority == 10
|
||||
assert article.fetcher == server
|
||||
assert article.get_article(server, servers) == None
|
||||
article.fetcher = None
|
||||
article.add_to_try_list(server)
|
||||
assert article.get_article(server, servers) == None
|
||||
|
||||
# Test fetching when there is a higher priority server available
|
||||
server = servers[2]
|
||||
assert article.fetcher_priority == 10
|
||||
assert article.get_article(server, servers) == None
|
||||
assert article.fetcher_priority == 20
|
||||
|
||||
# Server should be used even if article.fetcher_priority is a higher number than server.priority
|
||||
article.fetcher_priority = 30
|
||||
server = servers[1]
|
||||
assert article.get_article(server, servers) == article
|
||||
|
||||
# Inactive servers in servers list should be ignored
|
||||
article.fetcher = None
|
||||
article.fetcher_priority = 0
|
||||
servers[1].active = False
|
||||
server = servers[2]
|
||||
assert article.get_article(server, servers) == article
|
||||
assert article.tries == 3
|
||||
67
tests/test_nzbobject.py
Normal file
67
tests/test_nzbobject.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
tests.test_nzbobject - Testing functions in nzbobject.py
|
||||
"""
|
||||
from sabnzbd.nzb import NzbObject
|
||||
from sabnzbd.config import ConfigCat
|
||||
from sabnzbd.constants import NORMAL_PRIORITY
|
||||
from sabnzbd.filesystem import globber
|
||||
|
||||
from tests.testhelper import *
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("clean_cache_dir")
|
||||
class TestNZO:
|
||||
@set_config({"download_dir": SAB_CACHE_DIR})
|
||||
def test_nzo_basic(self):
|
||||
# Need to create the Default category, as we would in normal instance
|
||||
# Otherwise it will try to save the config
|
||||
def_cat = ConfigCat("*", {"pp": 3, "script": "None", "priority": NORMAL_PRIORITY})
|
||||
|
||||
# Create empty object, normally used to grab URL's
|
||||
nzo = NzbObject("test_basic")
|
||||
assert nzo.work_name == "test_basic"
|
||||
assert not nzo.files
|
||||
|
||||
# Create NZB-file to import
|
||||
nzb_fp = create_and_read_nzb_fp("basic_rar5")
|
||||
|
||||
# Very basic test of NZO creation with data
|
||||
nzo = NzbObject("test_basic_data", nzb_fp=nzb_fp)
|
||||
assert nzo.final_name == "test_basic_data"
|
||||
assert nzo.files
|
||||
assert nzo.files[0].filename == "testfile.rar"
|
||||
assert nzo.bytes == 283
|
||||
assert nzo.files[0].bytes == 283
|
||||
|
||||
# work_name can be trimmed in Windows due to max-path-length
|
||||
assert "test_basic_data".startswith(nzo.work_name)
|
||||
assert os.path.exists(nzo.admin_path)
|
||||
|
||||
# Check if there's an nzf file and the backed-up nzb
|
||||
assert globber(nzo.admin_path, "*.nzb.gz")
|
||||
assert globber(nzo.admin_path, "SABnzbd_nzf*")
|
||||
|
||||
# Should have picked up the default category settings
|
||||
assert nzo.cat == "*"
|
||||
assert nzo.script == def_cat.script() == "None"
|
||||
assert nzo.priority == def_cat.priority() == NORMAL_PRIORITY
|
||||
assert nzo.repair and nzo.unpack and nzo.delete
|
||||
|
||||
# TODO: More checks!
|
||||
@@ -21,7 +21,7 @@ tests.test_nzbparser - Tests of basic NZB parsing
|
||||
|
||||
from tests.testhelper import *
|
||||
import sabnzbd.nzbparser as nzbparser
|
||||
from sabnzbd import nzbstuff
|
||||
from sabnzbd.nzb import NzbObject
|
||||
from sabnzbd.filesystem import save_compressed
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from sabnzbd.filesystem import save_compressed
|
||||
class TestNzbParser:
|
||||
@set_config({"download_dir": SAB_CACHE_DIR})
|
||||
def test_nzbparser(self):
|
||||
nzo = nzbstuff.NzbObject("test_basic")
|
||||
nzo = NzbObject("test_basic")
|
||||
# Create test file
|
||||
metadata = {"category": "test", "password": "testpass"}
|
||||
nzb_fp = create_and_read_nzb_fp("..", metadata=metadata)
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/usr/bin/python3 -OO
|
||||
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
"""
|
||||
tests.test_nzbstuff - Testing functions in nzbstuff.py
|
||||
"""
|
||||
import sabnzbd.nzbstuff as nzbstuff
|
||||
from sabnzbd.config import ConfigCat
|
||||
from sabnzbd.constants import NORMAL_PRIORITY
|
||||
from sabnzbd.filesystem import globber
|
||||
|
||||
from tests.testhelper import *
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("clean_cache_dir")
|
||||
class TestNZO:
|
||||
@set_config({"download_dir": SAB_CACHE_DIR})
|
||||
def test_nzo_basic(self):
|
||||
# Need to create the Default category, as we would in normal instance
|
||||
# Otherwise it will try to save the config
|
||||
def_cat = ConfigCat("*", {"pp": 3, "script": "None", "priority": NORMAL_PRIORITY})
|
||||
|
||||
# Create empty object, normally used to grab URL's
|
||||
nzo = nzbstuff.NzbObject("test_basic")
|
||||
assert nzo.work_name == "test_basic"
|
||||
assert not nzo.files
|
||||
|
||||
# Create NZB-file to import
|
||||
nzb_fp = create_and_read_nzb_fp("basic_rar5")
|
||||
|
||||
# Very basic test of NZO creation with data
|
||||
nzo = nzbstuff.NzbObject("test_basic_data", nzb_fp=nzb_fp)
|
||||
assert nzo.final_name == "test_basic_data"
|
||||
assert nzo.files
|
||||
assert nzo.files[0].filename == "testfile.rar"
|
||||
assert nzo.bytes == 283
|
||||
assert nzo.files[0].bytes == 283
|
||||
|
||||
# work_name can be trimmed in Windows due to max-path-length
|
||||
assert "test_basic_data".startswith(nzo.work_name)
|
||||
assert os.path.exists(nzo.admin_path)
|
||||
|
||||
# Check if there's an nzf file and the backed-up nzb
|
||||
assert globber(nzo.admin_path, "*.nzb.gz")
|
||||
assert globber(nzo.admin_path, "SABnzbd_nzf*")
|
||||
|
||||
# Should have picked up the default category settings
|
||||
assert nzo.cat == "*"
|
||||
assert nzo.script == def_cat.script() == "None"
|
||||
assert nzo.priority == def_cat.priority() == NORMAL_PRIORITY
|
||||
assert nzo.repair and nzo.unpack and nzo.delete
|
||||
|
||||
# TODO: More checks!
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, host, priority, active):
|
||||
self.host = host
|
||||
self.priority = priority
|
||||
self.active = active
|
||||
|
||||
|
||||
class TestArticle:
|
||||
def test_get_article(self):
|
||||
article_id = "test@host" + os.urandom(8).hex() + ".sab"
|
||||
article = nzbstuff.Article(article_id, randint(4321, 54321), None)
|
||||
servers = []
|
||||
servers.append(Server("testserver1", 10, True))
|
||||
servers.append(Server("testserver2", 20, True))
|
||||
servers.append(Server("testserver3", 30, True))
|
||||
|
||||
# Test fetching top priority server
|
||||
server = servers[0]
|
||||
assert article.get_article(server, servers) == article
|
||||
assert article.fetcher_priority == 10
|
||||
assert article.fetcher == server
|
||||
assert article.get_article(server, servers) == None
|
||||
article.fetcher = None
|
||||
article.add_to_try_list(server)
|
||||
assert article.get_article(server, servers) == None
|
||||
|
||||
# Test fetching when there is a higher priority server available
|
||||
server = servers[2]
|
||||
assert article.fetcher_priority == 10
|
||||
assert article.get_article(server, servers) == None
|
||||
assert article.fetcher_priority == 20
|
||||
|
||||
# Server should be used even if article.fetcher_priority is a higher number than server.priority
|
||||
article.fetcher_priority = 30
|
||||
server = servers[1]
|
||||
assert article.get_article(server, servers) == article
|
||||
|
||||
# Inactive servers in servers list should be ignored
|
||||
article.fetcher = None
|
||||
article.fetcher_priority = 0
|
||||
servers[1].active = False
|
||||
server = servers[2]
|
||||
assert article.get_article(server, servers) == article
|
||||
assert article.tries == 3
|
||||
|
||||
|
||||
class TestNZBStuffHelpers:
|
||||
@pytest.mark.parametrize(
|
||||
"argument, name, password",
|
||||
[
|
||||
("my_awesome_nzb_file{{password}}", "my_awesome_nzb_file", "password"),
|
||||
("file_with_text_after_pw{{passw0rd}}_[180519]", "file_with_text_after_pw", "passw0rd"),
|
||||
("file_without_pw", "file_without_pw", None),
|
||||
("multiple_pw{{first-pw}}_{{second-pw}}", "multiple_pw", "first-pw}}_{{second-pw"), # Greed is Good
|
||||
("デビアン", "デビアン", None), # Unicode
|
||||
("Gentoo_Hobby_Edition {{secret}}", "Gentoo_Hobby_Edition", "secret"), # Space between name and password
|
||||
("Test {{secret}}.nzb", "Test", "secret"),
|
||||
("Mandrake{{top{{secret}}", "Mandrake", "top{{secret"), # Double opening {{
|
||||
("Красная}}{{Шляпа}}", "Красная}}", "Шляпа"), # Double closing }}
|
||||
("{{Jobname{{PassWord}}", "{{Jobname", "PassWord"), # {{ at start
|
||||
("Hello/kITTY", "Hello", "kITTY"), # Notation with slash
|
||||
("Hello/kITTY.nzb", "Hello", "kITTY"), # Notation with slash and extension
|
||||
("/Jobname", "/Jobname", None), # Slash at start
|
||||
("Jobname/Top{{Secret}}", "Jobname", "Top{{Secret}}"), # Slash with braces
|
||||
("Jobname / Top{{Secret}}", "Jobname", "Top{{Secret}}"), # Slash with braces and extra spaces
|
||||
("Jobname / Top{{Secret}}.nzb", "Jobname", "Top{{Secret}}"),
|
||||
("לינוקס/معلومات سرية", "לינוקס", "معلومات سرية"), # LTR with slash
|
||||
("לינוקס{{معلومات سرية}}", "לינוקס", "معلومات سرية"), # LTR with brackets
|
||||
("thư điện tử password=mật_khẩu", "thư điện tử", "mật_khẩu"), # Password= notation
|
||||
("password=PartOfTheJobname", "password=PartOfTheJobname", None), # Password= at the start
|
||||
("Job password=Test.par2", "Job", "Test"), # Password= including extension
|
||||
("Job}}Name{{FTW", "Job}}Name{{FTW", None), # Both {{ and }} present but incorrect order (no password)
|
||||
("./Text", "./Text", None), # Name would end up empty after the function strips the dot
|
||||
],
|
||||
)
|
||||
def test_scan_password(self, argument, name, password):
|
||||
assert nzbstuff.scan_password(argument) == (name, password)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"file_name, clean_file_name",
|
||||
[
|
||||
("my_awesome_nzb_file.pAr2.nZb", "my_awesome_nzb_file"),
|
||||
("my_awesome_nzb_file.....pAr2.nZb", "my_awesome_nzb_file"),
|
||||
("my_awesome_nzb_file....par2..", "my_awesome_nzb_file"),
|
||||
(" my_awesome_nzb_file .pAr.nZb", "my_awesome_nzb_file"),
|
||||
("with.extension.and.period.par2.", "with.extension.and.period"),
|
||||
("nothing.in.here", "nothing.in.here"),
|
||||
(" just.space ", "just.space"),
|
||||
("http://test.par2 ", "http://test.par2"),
|
||||
],
|
||||
)
|
||||
def test_create_work_name(self, file_name, clean_file_name):
|
||||
# Only test stuff specific for create_work_name
|
||||
# The sanitizing is already tested in tests for sanitize_foldername
|
||||
assert nzbstuff.create_work_name(file_name) == clean_file_name
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subject, filename",
|
||||
[
|
||||
('Great stuff (001/143) - "Filename.txt" yEnc (1/1)', "Filename.txt"),
|
||||
(
|
||||
'"910a284f98ebf57f6a531cd96da48838.vol01-03.par2" yEnc (1/3)',
|
||||
"910a284f98ebf57f6a531cd96da48838.vol01-03.par2",
|
||||
),
|
||||
('Subject-KrzpfTest [02/30] - ""KrzpfTest.part.nzb"" yEnc', "KrzpfTest.part.nzb"),
|
||||
(
|
||||
'[PRiVATE]-[WtFnZb]-[Supertje-_S03E11-12_-blabla_+_blabla_WEBDL-480p.mkv]-[4/12] - "" yEnc 9786 (1/1366)',
|
||||
"Supertje-_S03E11-12_-blabla_+_blabla_WEBDL-480p.mkv",
|
||||
),
|
||||
(
|
||||
'[N3wZ] MAlXD245333\\::[PRiVATE]-[WtFnZb]-[Show.S04E04.720p.AMZN.WEBRip.x264-GalaxyTV.mkv]-[1/2] - "" yEnc 293197257 (1/573)',
|
||||
"Show.S04E04.720p.AMZN.WEBRip.x264-GalaxyTV.mkv",
|
||||
),
|
||||
(
|
||||
'reftestnzb bf1664007a71 [1/6] - "20b9152c-57eb-4d02-9586-66e30b8e3ac2" yEnc (1/22) 15728640',
|
||||
"20b9152c-57eb-4d02-9586-66e30b8e3ac2",
|
||||
),
|
||||
(
|
||||
"Re: REQ Author Child's The Book-Thanks much - Child, Lee - Author - The Book.epub (1/1)",
|
||||
"REQ Author Child's The Book-Thanks much - Child, Lee - Author - The Book.epub",
|
||||
),
|
||||
('63258-0[001/101] - "63258-2.0" yEnc (1/250) (1/250)', "63258-2.0"),
|
||||
# If specified between ", the extension is allowed to be too long
|
||||
('63258-0[001/101] - "63258-2.0toolong" yEnc (1/250) (1/250)', "63258-2.0toolong"),
|
||||
(
|
||||
"Singer - A Album (2005) - [04/25] - 02 Sweetest Somebody (I Know).flac",
|
||||
"Singer - A Album (2005) - [04/25] - 02 Sweetest Somebody (I Know).flac",
|
||||
),
|
||||
("<>random!>", "<>random!>"),
|
||||
("nZb]-[Supertje-_S03E11-12_", "nZb]-[Supertje-_S03E11-12_"),
|
||||
("Bla [Now it's done.exe]", "Now it's done.exe"),
|
||||
# If specified between [], the extension should be a valid one
|
||||
("Bla [Now it's done.123nonsense]", "Bla [Now it's done.123nonsense]"),
|
||||
('[PRiVATE]-[WtFnZb]-[00000.clpi]-[1/46] - "" yEnc 788 (1/1)', "00000.clpi"),
|
||||
(
|
||||
'[PRiVATE]-[WtFnZb]-[Video_(2001)_AC5.1_-RELEASE_[TAoE].mkv]-[1/23] - "" yEnc 1234567890 (1/23456)',
|
||||
"Video_(2001)_AC5.1_-RELEASE_[TAoE].mkv",
|
||||
),
|
||||
(
|
||||
"[PRiVATE]-[WtFnZb]-[219]-[1/series.name.s01e01.1080p.web.h264-group.mkv] - "
|
||||
" yEnc (1/[PRiVATE] \\c2b510b594\\::686ea969999193.155368eba4965e56a8cd263382e012.f2712fdc::/97bd201cf931/) 1 (1/0)",
|
||||
"series.name.s01e01.1080p.web.h264-group.mkv",
|
||||
),
|
||||
(
|
||||
"[PRiVATE]-[WtFnZb]-[/More.Bla.S02E01.1080p.WEB.h264-EDITH[eztv.re].mkv-WtF[nZb]/"
|
||||
'More.Bla.S02E01.1080p.WEB.h264-EDITH.mkv]-[1/2] - "" yEnc 2990558544 (1/4173)',
|
||||
"More.Bla.S02E01.1080p.WEB.h264-EDITH[eztv.re].mkv",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_name_extractor(self, subject, filename):
|
||||
assert nzbstuff.name_extractor(subject) == filename
|
||||
BIN
win/par2/arm64/par2.exe
Normal file
BIN
win/par2/arm64/par2.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user