Compare commits

...

123 Commits

Author SHA1 Message Date
Safihre
11ba9ae12a Set version to 4.5.5 2025-10-24 12:58:51 +02:00
Safihre
a61a5539a7 Merge branch '4.5.x' 2025-10-24 12:58:35 +02:00
Safihre
77f7490aea Update text files for 4.5.5 2025-10-24 12:56:26 +02:00
Safihre
a7198b6a81 Merge branch 'develop' into 4.5.x 2025-10-24 12:47:36 +02:00
Safihre
4c77954526 Add 4.5.5 to appdata 2025-10-24 12:47:26 +02:00
SABnzbd Automation
a229a2a5ea Update translatable texts
[skip ci]
2025-10-24 10:35:59 +00:00
Safihre
0a2f3865ee Check if all macOS binary files support the minimal required version 2025-10-24 12:27:48 +02:00
Safihre
900e68bb9a Resolve PyGithub deprecation warnings 2025-10-22 23:25:42 +02:00
Safihre
977dbc805f Set version to 4.5.4 2025-10-22 23:09:30 +02:00
Safihre
abcca19820 Merge branch '4.5.x' 2025-10-22 23:09:14 +02:00
Safihre
52a7b5dcff Update text files for 4.5.4 2025-10-22 23:05:40 +02:00
Safihre
9518714885 Merge branch 'develop' into 4.5.x 2025-10-22 22:35:07 +02:00
Safihre
1de674a532 Correct appdata between branches 2025-10-22 22:33:55 +02:00
Safihre
e1dad3e4c4 Start SABnzbd service after installation, if installed 2025-10-20 13:18:44 +02:00
SABnzbd Automation
44f2eb8620 Update translatable texts
[skip ci]
2025-10-20 07:57:56 +00:00
renovate[bot]
70945a9c5b Update all dependencies (#3167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 09:57:12 +02:00
SABnzbd Automation
fdfca97dfa Update translatable texts
[skip ci]
2025-10-16 08:58:27 +00:00
Safihre
b84900dcb5 Add extra warning to Remove All Orphans
See https://forums.sabnzbd.org/viewtopic.php?p=133922
2025-10-16 10:57:31 +02:00
Safihre
d989ec928a Small styling issue for tooltip in Night mode 2025-10-14 11:12:06 +02:00
Safihre
4a89fcf8ea Update text files for 4.5.4RC1 2025-10-13 16:25:48 +02:00
SABnzbd Automation
d7fa3e1f7b Update translatable texts
[skip ci]
2025-10-13 14:24:54 +00:00
Safihre
d11e757c6e Merge branch 'develop' into 4.5.x 2025-10-13 16:24:19 +02:00
Safihre
c1417c319d Add tooltip that users need to Test Server before saving/next 2025-10-13 16:24:04 +02:00
Safihre
6689939cc9 Large par2 files could require more parsing to get all crc32 slices
Closes #3164
2025-10-13 15:26:55 +02:00
Safihre
09347d0766 Switch everything to Python 3.14 2025-10-13 07:44:35 +02:00
SABnzbd Automation
41db09057c Update translatable texts
[skip ci]
2025-10-13 01:10:52 +00:00
renovate[bot]
6983058f49 Update all dependencies (#3165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 01:09:52 +00:00
SABnzbd Automation
fb2d412c97 Update translatable texts
[skip ci]
2025-10-08 21:01:13 +00:00
Safihre
1c0b1205b2 Add quota notifications
Closes #2926
2025-10-08 22:58:08 +02:00
Safihre
f556cea488 Use release version of Python 3.14 in CI 2025-10-08 20:07:39 +02:00
Sander
a2447253a0 Local ipv4 with socks5 proxy (#3161)
* Update all dependencies

* use socks5 server as test server

* make black happy

* improved active_socks5_proxy(): default port = 1080

* improved local_ipv4()

* use int_conv

* black

* use socks.socksocket.default_proxy directly

* active_socks5_proxy cleaner with int_conv

* correct to windows-2022

* socks.socksocket.default_proxy as check

* uniform naming socks5host/port

Closes #3154
---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: sanderjo <sander.jonkers+github@github.com>
2025-10-08 19:10:47 +02:00
Safihre
3393d7c976 Changing server name shows button "failure" instead of "saving..."
Closes #1551
2025-10-06 15:54:11 +02:00
renovate[bot]
06572bdf7d Update all dependencies (#3159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 02:33:57 +00:00
Safihre
4f9ed7803f Update text files for 4.5.4Beta2 2025-10-05 23:08:14 +02:00
Safihre
95bc069af9 Merge branch 'develop' into 4.5.x 2025-10-05 22:58:51 +02:00
Safihre
d4411f1b8f Replace vendored rarfile with official one
Closes #2560
2025-10-05 22:40:30 +02:00
SABnzbd Automation
1bfd1b8f41 Update translatable texts
[skip ci]
2025-10-05 20:38:14 +00:00
Safihre
c47dbfdc26 Let unrar handle rename of chars invalid on Windows filesystem
Closes #1574

Add tests for long paths

Make sure long path is >260

Add rar test file with invalid Windows filenames

Add rar_unpack tests for unicode and passworded sets

Simplify Unrar command building

Add test for rar_invalid_windows

Remove check for 260 chars in rar_unpack

Should never happen anymore

Let Unrar rename invalid filenames

Check full path output if rar_unpack

Add helper for check

Correct test_rar_unpack_invalid_windows_filenames

Apply changes also to Direct Unpacker

Extend testing to make sure full paths are tested

Add tests for long paths inside rar

Unrar auto-rename message is different on Linux
2025-10-05 22:37:22 +02:00
Safihre
b5e55cd9b2 Unselect Multi-Operations Play/Resume on second click
Closes #2725
2025-10-03 15:06:37 +02:00
Hugo Lloreda
85c98d7203 Add option to bind outgoing connections (#3155)
* refactor outgoing interface

* refactor

* rollback old change

* We actually don't need another port


Closes #3153

* refactor

* refactor

* refactor to be compatible with old python versions

* forgot to remove match

* fix no route to host on mac

* fix no route to host on mac + rename interface to ip

* fix black + try to fix windows error

* fix black + try to fix windows error

* fix windows error

* fix windows failure

* rollback optional changes

* Remove optional type

* rollback changes + fix issue

* black change

* refactor

* missing refactor
2025-10-03 11:28:44 +02:00
Safihre
9e95717619 Enable Make Windows compatible if we cannot write special characters 2025-09-30 12:18:18 +02:00
SABnzbd Automation
90b4ff2720 Update translatable texts
[skip ci]
2025-09-30 09:28:01 +00:00
Safihre
0f97a9fdfc Workaround for macOS statvfs no longer needed
Part of Python 3.13 and above.
https://github.com/python/cpython/pull/99570
2025-09-30 11:21:14 +02:00
Safihre
90caf0c164 Lock changes to job properties during URL-grabbed NZO creation
Closes #1908
2025-09-29 15:51:26 +02:00
Safihre
9b3fe470a0 Run tests on Python 3.14
Force newer pytest

Force beta release of tavern

Unfix werkzeug

Allow latest tavern only on Python 3.11 and above

Fix test failure in Python 3.14
2025-09-29 13:29:50 +02:00
SABnzbd Automation
ab318729ab Update translatable texts
[skip ci]
2025-09-29 10:43:50 +00:00
Safihre
9576554426 Move to top/bottom for Multi edit
Closes #1088
2025-09-29 12:40:44 +02:00
Safihre
3cd819b78d Refactor history API call handling 2025-09-29 11:56:03 +02:00
renovate[bot]
bb24f3f04e Update all dependencies (#3156)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 07:28:52 +00:00
Safihre
6f4416236d Prevent renovate from updating macOS version
Due to #3131
2025-09-29 09:10:23 +02:00
SABnzbd Automation
47dcccd17f Update translatable texts
[skip ci]
2025-09-26 14:29:44 +00:00
Safihre
6b026d8274 Add way to mark job as Completed and remove Incomplete
Closes #1174
2025-09-26 16:28:57 +02:00
Safihre
ec18606557 Require correct server test in Wizard
Closes #3148
General refactor.
2025-09-25 13:40:25 +02:00
Safihre
d1d9bab65a Update text files for 4.5.4 Beta 1 2025-09-22 14:11:13 +02:00
Safihre
e2560bf214 Merge branch 'develop' into 4.5.x 2025-09-22 14:10:50 +02:00
Safihre
895c8549ba Add 4.5.4 to appdata 2025-09-22 14:09:54 +02:00
Safihre
0d80efb898 Update Python to 3.13.7 2025-09-22 13:54:16 +02:00
Safihre
deace9f8ae Add SignPath to the release notes 2025-09-22 13:30:43 +02:00
Safihre
1c96dff133 Implement SignPath binary signing for Windows releases
Keep zip structure

Download all signed artifacts for release step

Correctly download all releases

Only sign when tagging release

Restore CI tests

Test production certificate

Closes #2870
2025-09-22 12:16:10 +02:00
renovate[bot]
1734b11338 Update all dependencies (#3144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 08:34:30 +00:00
Safihre
5f3c4d17da Prevent Renovate from updating GitHub Actions Windows runner 2025-09-22 10:18:58 +02:00
jcfp
4ffe0e27fb Handle weird anime episode notation (#3146)
* handle weird anime episode notation

* make black even happier /s
2025-09-15 22:55:07 +02:00
renovate[bot]
951bc0c957 Update all dependencies (#3142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 20:00:14 +00:00
SABnzbd Automation
60f985ba00 Update translatable texts
[skip ci]
2025-09-08 19:37:06 +00:00
Safihre
a42a2db196 Github Actions Windows 2025 runners do not included NSIS 2025-09-08 21:36:13 +02:00
SABnzbd Automation
64034c5636 Update translatable texts
[skip ci]
2025-08-26 13:30:14 +00:00
Safihre
e03a031342 Add Run SABnzbd to Windows Installer 2025-08-26 15:29:34 +02:00
Safihre
825322baa4 Set version to 4.5.3 2025-08-25 15:25:56 +02:00
Safihre
7a5ca5b226 Merge branch '4.5.x' 2025-08-25 15:25:41 +02:00
Safihre
cb4f022d17 Update text files for 4.5.3 2025-08-25 15:25:21 +02:00
Safihre
da3d72b484 No longer reduce threads counter on connection loss
Closes #3137
2025-08-25 13:48:00 +02:00
SABnzbd Automation
e3042a6106 Update translatable texts
[skip ci]
2025-08-25 00:59:25 +00:00
renovate[bot]
55f1253a56 Update all dependencies (#3138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 00:58:50 +00:00
Safihre
913e4ea02e Update text files for 4.5.3RC1 2025-08-19 16:34:20 +02:00
Safihre
aa0d44a60b Merge branch 'develop' into 4.5.x 2025-08-19 16:33:20 +02:00
Safihre
5e432bea37 Cache par2 download 2025-08-19 16:22:50 +02:00
renovate[bot]
2d0cc08987 Update all dependencies (#3136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 01:59:29 +00:00
Safihre
627797f8c7 No longer apply Bug and Feature Request directly
This way they can be auto-closed if stale.
2025-08-14 10:50:42 +02:00
Safihre
e37a777f29 Run CI Tests on macOS 13
See #3131
2025-08-12 11:44:31 +02:00
SABnzbd Automation
13a76e5824 Update translatable texts
[skip ci]
2025-08-11 12:07:26 +00:00
Safihre
e4c64cac12 Automated update of translations
Used AI to add missing translations.
Not 100% coverage, it keeps missing some texts.
Stopping here.
2025-08-11 14:06:40 +02:00
SABnzbd Automation
c6694483e4 Update translatable texts
[skip ci]
2025-08-11 10:32:26 +00:00
Safihre
8b5f29df8f Update text files for 4.5.3Beta2 2025-08-11 12:10:29 +02:00
Safihre
82954f5930 Merge branch 'develop' into 4.5.x 2025-08-11 12:02:33 +02:00
thezoggy
bc793e11c4 bump windows unrar to 7.13 for CVE-2025-8088 (#3129) 2025-08-11 11:59:12 +02:00
Safihre
4980fc70a0 Small tweaks to the workflows 2025-08-11 11:57:54 +02:00
Safihre
8afac4f6fb Push the snap to stable for any tag
This is the same we did before using the Python script.
2025-08-11 11:50:11 +02:00
Safihre
c78b633da8 Update snap publish credentials 2025-08-11 11:32:25 +02:00
SABnzbd Automation
a1ee1677dc Update translatable texts
[skip ci]
2025-08-11 09:27:39 +00:00
Safihre
511bb153d7 Add Publish snap step 2025-08-11 11:26:28 +02:00
Safihre
c1af36f6b0 Move user logged in notification 2025-08-11 11:22:13 +02:00
Safihre
6028824966 RSS Next scan time is not recalculated after manual Read All Feeds Now
Closes #2979
2025-08-08 22:06:02 +02:00
Safihre
49a7300ad6 Pre-queue script rejected NZBs are sometimes reported in history as "URL Fetching failed;"
Closes #3023
2025-08-08 21:57:50 +02:00
Safihre
8e8e560eac Add building of snap as a GitHub Action 2025-08-08 09:49:19 +02:00
Safihre
e51da569ca Add curl as snapcraft par2cmdline build-package 2025-08-08 08:52:21 +02:00
mnightingale
6ce43eed5f Fix snap with core24 (#3126)
* Fix snap with core24

* Remove probably unused

* Switch to 7zip-standalone package which only has the 7zz binary
2025-08-08 08:31:32 +02:00
Safihre
73ec6d8323 Add tests for new conditional_cache decorator 2025-08-07 22:09:25 +02:00
Safihre
27a7531f79 Make smarter decorator that only caches if there's a result 2025-08-07 21:55:18 +02:00
Safihre
423bdb4f81 Replace HappyEyeBalls with concurrent fastest connection check 2025-08-07 21:25:06 +02:00
Safihre
7f8081e2cc Correct add_fake_history_jobs after adding time_added 2025-08-07 20:21:43 +02:00
Safihre
50c2d5e2ab Add time_added to History output and interface
Closes #2914
2025-08-07 16:41:01 +02:00
Safihre
552bfd4b72 Add time_added to queue output 2025-08-07 14:41:40 +02:00
Safihre
3d522c8205 Update snapcraft.yaml format 2025-08-07 10:02:29 +02:00
Safihre
b6b0d10367 Try to get the snap to compile again 2025-08-05 14:04:56 +02:00
Safihre
67f1858315 Update text files for 4.5.3 Beta 1 2025-08-05 11:33:53 +02:00
Safihre
55bb81ceef Merge branch 'develop' into 4.5.x 2025-08-05 11:33:00 +02:00
Safihre
8d4d69d56b Add 4.5.3 to Appdata file 2025-08-05 11:32:33 +02:00
Safihre
0e475e593a Update to par2cmdline-turbo 1.3.0 that supports older macOS
And it's universal2
2025-08-05 11:09:24 +02:00
renovate[bot]
78424318ce chore(deps): update all dependencies (#3123)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 01:01:09 +00:00
mnightingale
57c90b2554 Skip adding postproc items to the history if they are already completed (#3120)
* Skip adding postproc items to the history if they are already completed

* Add test that duplicate nzo_ids are not added to the history
2025-08-02 12:11:47 +02:00
Safihre
9b94d22621 Broken "Show Logging" function when console logging
Closes #3107
2025-08-01 14:57:33 +02:00
SABnzbd Automation
5c30b0ee29 Update translatable texts
[skip ci]
2025-08-01 12:50:43 +00:00
Safihre
3a1c60a3ed Persist the Permanently delete checkbox
On Archive page, never even ask the question.
Yeah, if you combine History+Archive it will be always Permanently deleted. So be it.
2025-08-01 14:49:39 +02:00
SABnzbd Automation
bd07a79c97 Update translatable texts
[skip ci]
2025-08-01 07:09:26 +00:00
Safihre
c562c9a468 Update sabctools to 8.2.6
Remove reference to Github issue for sabctools feedback. That's ages ago already.
2025-08-01 09:08:31 +02:00
renovate[bot]
fdd3f590cd chore(deps): update all dependencies (#3119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 02:35:15 +00:00
thezoggy
77e9627e64 Skip cbr durning rarcheck to prevent badly renamed (#3115) 2025-07-24 10:53:09 +02:00
renovate[bot]
c40d1274d2 chore(deps): update all dependencies (#3116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 02:43:33 +00:00
Safihre
d1948071fc No longer crash on Windows 8 for binary releases 2025-07-14 12:48:55 +02:00
renovate[bot]
4f79d924e6 chore(deps): update dependency certifi to v2025.7.14 (#3111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 04:58:53 +00:00
SABnzbd Automation
872cf835df Update translatable texts
[skip ci]
2025-07-09 18:05:33 +00:00
thezoggy
69bb1a87a4 Bump 7zip to 25 (#3109) 2025-07-09 20:05:00 +02:00
renovate[bot]
e3339a1ab4 chore(deps): update all dependencies (#3108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 20:04:28 +02:00
122 changed files with 4033 additions and 4451 deletions

View File

@@ -3,7 +3,7 @@ description: >
Did you discover a bug in SABnzbd? Report it here!
If you are not 100% certain this is a bug please go to our forums, Reddit or Discord server first.
labels:
- Bug
- Support
body:
- type: input
attributes:

View File

@@ -1,7 +1,7 @@
name: Feature request
description: What new feature would you like to have added to SABnzbd?
labels:
- Feature request
- Support
body:
- type: textarea
attributes:

View File

@@ -26,6 +26,11 @@
"werkzeug"
],
"packageRules": [
{
"matchManagers": ["github-actions"],
"matchPackageNames": ["windows", "macos"],
"enabled": false
},
{
"matchPackagePatterns": [
"*"

View File

@@ -9,14 +9,14 @@ env:
jobs:
build_windows:
name: Build Windows binary
runs-on: windows-latest
runs-on: windows-2022
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
python-version: "3.14"
architecture: "x64"
cache: pip
cache-dependency-path: "**/requirements.txt"
@@ -27,18 +27,59 @@ jobs:
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary and installer
run: python builder/package.py installer
- name: Upload Windows standalone binary
- name: Build Windows standalone binary
id: windows_binary
run: python builder/package.py binary
- name: Upload Windows standalone binary (unsigned)
uses: actions/upload-artifact@v4
id: upload-unsigned-binary
with:
path: "*-win64-bin.zip"
name: Windows standalone binary
- name: Sign Windows standalone binary
uses: signpath/github-action-submit-signing-request@v1
if: contains(github.ref, 'refs/tags/')
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
project-slug: "sabnzbd"
artifact-configuration-slug: "sabnzbd-binary"
signing-policy-slug: "release-signing"
github-artifact-id: ${{ steps.upload-unsigned-binary.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: "signed"
- name: Upload Windows standalone binary (signed)
uses: actions/upload-artifact@v4
if: contains(github.ref, 'refs/tags/')
with:
name: Windows standalone binary (signed)
path: "signed"
- name: Build Windows installer
run: python builder/package.py installer
- name: Upload Windows installer
uses: actions/upload-artifact@v4
id: upload-unsigned-installer
with:
path: "*-win-setup.exe"
name: Windows installer
- name: Sign Windows installer
uses: signpath/github-action-submit-signing-request@v1
if: contains(github.ref, 'refs/tags/')
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
project-slug: "sabnzbd"
artifact-configuration-slug: "sabnzbd-installer"
signing-policy-slug: "release-signing"
github-artifact-id: ${{ steps.upload-unsigned-installer.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: "signed"
- name: Upload Windows installer (signed)
if: contains(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: Windows installer (signed)
path: "signed/*-win-setup.exe"
build_macos:
name: Build macOS binary
@@ -48,18 +89,18 @@ jobs:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: "3.13.5"
MACOSX_DEPLOYMENT_TARGET: "10.13"
PYTHON_VERSION: "3.14.0"
MACOSX_DEPLOYMENT_TARGET: "10.15"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64
ARCHFLAGS: -arch x86_64 -arch arm64
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
- uses: actions/checkout@v5
- name: Set up Python
# Only use this for the caching of pip packages!
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.13"
python-version: "3.14"
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Cache Python download
@@ -89,8 +130,8 @@ jobs:
if: env.CERTIFICATES_P12
run: |
echo $CERTIFICATES_P12 | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security default-keychain -s build.keychain
security create-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security set-keychain-settings -lut 21600 build.keychain
security import certificate.p12 -k build.keychain -P "$CERTIFICATES_P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign -T /usr/bin/xcrun
@@ -117,22 +158,83 @@ jobs:
path: "*-macos.dmg"
name: macOS binary
build-snap:
name: Build Snap Packages (${{ matrix.linux_arch }})
timeout-minutes: 30
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
linux_arch: amd64
- os: ubuntu-24.04-arm
linux_arch: arm64
steps:
- uses: actions/checkout@v5
- name: Cache par2cmdline-turbo tarball
uses: actions/cache@v4
id: cache-par2cmdline
# Clearing the cache in case of new version requires manual clearing in GitHub!
with:
path: snap/par2cmdline.tar.gz
key: cache-par2cmdline
- name: Download par2cmdline-turbo tarball
if: steps.cache-par2cmdline.outputs.cache-hit != 'true'
run: |
PAR2_TARBALL=$(curl -sL https://api.github.com/repos/animetosho/par2cmdline-turbo/releases/latest | jq -r '.tarball_url')
curl -o snap/par2cmdline.tar.gz -L "$PAR2_TARBALL"
- uses: snapcore/action-build@v1
name: Build snap
id: snapcraft
- name: Test snap installation
run: |
sudo snap install --dangerous *.snap
sudo snap connect sabnzbd:removable-media
# Basic smoke test - check that the binary exists and can show help
timeout 10s snap run sabnzbd --help || true
sudo snap remove sabnzbd
- name: Upload snap
uses: actions/upload-artifact@v4
with:
name: Snap package (${{ matrix.linux_arch }})
path: ${{ steps.snapcraft.outputs.snap }}
- name: Publish snap
uses: snapcore/action-publish@v1
if: contains(github.ref, 'refs/tags/')
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
with:
store_login: ${{ secrets.SNAP_TOKEN }}
snap: ${{ steps.snapcraft.outputs.snap }}
release: stable
release:
name: Prepare Release
runs-on: ubuntu-latest
needs: [build_windows, build_macos]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Download all artifacts
uses: actions/download-artifact@v4
python-version: "3.14"
cache: pip
cache-dependency-path: "builder/release-requirements.txt"
- name: Download Source distribution artifact
uses: actions/download-artifact@v5
with:
path: dist
- name: Move all artifacts to main folder
run: find dist -type f -exec mv {} . \;
name: Source distribution
- name: Download macOS artifact
uses: actions/download-artifact@v5
with:
name: macOS binary
- name: Download Windows artifacts
uses: actions/download-artifact@v5
with:
pattern: ${{ (contains(github.ref, 'refs/tags/')) && '*signed*' || '*Windows*' }}
merge-multiple: true
- name: Prepare official release
env:
AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
@@ -140,10 +242,3 @@ jobs:
run: |
pip3 install -r builder/release-requirements.txt
python3 builder/release.py
- name: Release latest available Snap
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
run: |
sudo snap install snapcraft --classic
python3 snap/local/release_snap.py

View File

@@ -7,7 +7,7 @@ jobs:
name: Black Code Formatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Black Code Formatter
uses: lgeiger/black-action@master
with:
@@ -31,24 +31,23 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
name: ["Linux"]
os: [ubuntu-latest]
include:
- name: macOS
os: macos-latest
python-version: "3.13"
os: macos-13
python-version: "3.14"
- name: Windows
os: windows-latest
python-version: "3.13"
os: windows-2022
python-version: "3.14"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
architecture: "x64"
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Install system dependencies

View File

@@ -10,7 +10,7 @@ jobs:
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
days-before-stale: 21
days-before-close: 7

View File

@@ -12,7 +12,7 @@ jobs:
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
- name: Generate translatable texts
@@ -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@v6.0.1
uses: stefanzweifel/git-auto-commit-action@v7.0.0
if: env.TX_TOKEN
with:
commit_message: |

View File

@@ -66,3 +66,12 @@ Conditions:
- Bugfixes created specifically for a release branch are done there (because they are specific, they're not cherry-picked to `develop`).
- Bugfixes done on `develop` may be cherry-picked to a release branch.
- We will not release a 1.0.2 if a 1.1.0 has already been released.
## Privacy Policy
This program will not transfer any information to other networked systems unless
specifically requested by the user or the person installing or operating it.
## Code Signing Policy
For our Windows release, free code signing is provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org).

View File

@@ -1,6 +1,57 @@
Release Notes - SABnzbd 4.5.2
Release Notes - SABnzbd 4.5.5
=========================================================
## Bug fixes and changes in 4.5.5
* macOS: Failed to start on versions of macOS older than 11.
Python 3.14 dropped support for macOS 10.13 and 10.14.
Because of that macOS 10.15 is required to run 4.5.5.
## Bug fixes and changes in 4.5.4
### New Features
* History details now includes option to mark job as `Completed`.
* `Quota` notifications available for all notification services.
- Sends alerts at 75%, 90%, and 100% quota usage.
* Multi-Operations now supports Move to Top/Bottom.
* New `outgoing_nntp_ip` option to bind outgoing NNTP connections to specific IP address.
### Improvements
* Setup wizard now requires successful Server Test before proceeding.
* Anime episode notation `S04 - 10` now supported for Sorting and Duplicate Detection.
* Multi-Operations: Play/Resume button unselects on second click for better usability.
* Unrar now handles renaming of invalid characters on Windows filesystem.
* Switched from vendored `sabnzbd.rarfile` module to `rarfile>=4.2`.
* Warning displayed when removing all Orphaned jobs (clears Temporary Download folder).
### Bug Fixes
* Active connections counter in Status window now updates correctly.
* Job setting changes during URL-grabbing no longer ignored.
* Incomplete `.par2` file parsing no longer leaves files behind.
* `Local IPv4 address` now detectable when using Socks5 proxy.
* Server configuration changes no longer show `Failure` message during page reload.
### Platform-Specific
* Linux: `Make Windows compatible` automatically enabled when needed.
* Windows: Executables are now signed using SignPath Foundation certificate.
* Windows: Can now start SABnzbd directly from installer.
* Windows and macOS: Binaries now use Python 3.14.
## Bug fixes and changes in 4.5.3
* Remember if `Permanently delete` was previously checked.
* All available IP-addresses will be included when selecting the fastest.
* Pre-queue script rejected NZBs were sometimes reported as `URL Fetching failed`.
* RSS `Next scan` time was not adjusted after manual `Read All Feeds Now`.
* Prevent renaming of `.cbr` files during verification.
* If `--disable-file-log` was enabled, `Show Logging` would crash.
* API: Added `time_added`, timestamp of when the job was added to the queue.
* API: History output could contain duplicate items.
* Snap: Updated packages and changed build process for reliability.
* macOS: Repair would fail on macOS 10.13 High Sierra.
* Windows: Unable to start on Windows 8.
* Windows: Updated Unrar to 7.13, which resolves CVE-2025-8088.
## Bug fixes and changes in 4.5.2
* Added Tab and Shift+Tab navigation to move between rename fields in queue.
@@ -37,16 +88,19 @@ Release Notes - SABnzbd 4.5.2
## Upgrade notices
* You can directly upgrade from version 3.0.0 and newer.
* Upgrading from older versions will require performing a `Queue repair`.
* Downgrading from version 4.2.0 or newer to 3.7.2 or older will require
performing a `Queue repair` due to changes in the internal data format.
* Direct upgrade supported from version 3.0.0 and newer.
* Older versions require performing a `Queue repair` after upgrading.
## Known problems and solutions
* Read `ISSUES.txt` or https://sabnzbd.org/wiki/introduction/known-issues
## Code Signing Policy
Windows code signing is provided by SignPath.io using a SignPath Foundation certificate.
## About
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically, thanks to its web-based
user interface and advanced built-in post-processing options that automatically verify, repair,

View File

@@ -426,10 +426,7 @@ def print_modules():
# Check if we managed to link, warning for now
# It won't work on OpenSSL < 1.1.1 anyway, so we skip the check there
if not sabnzbd.decoder.SABCTOOLS_OPENSSL_LINKED and ssl.OPENSSL_VERSION_INFO >= (1, 1, 1):
logging.warning(
"Could not link to OpenSSL library, please report here: "
"https://github.com/sabnzbd/sabnzbd/issues/2421"
)
helpful_warning(T("Unable to link to OpenSSL, optimized SSL connection functions will not be used."))
else:
# Wrong SABCTools version, if it was fully missing it would fail to start due to check at the very top
logging.error(
@@ -1103,12 +1100,13 @@ def main():
logging_level = sabnzbd.cfg.log_level()
else:
sabnzbd.cfg.log_level.set(logging_level)
sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
logformat = "%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s"
logger.setLevel(LOGLEVELS[logging_level + 1])
try:
if not no_file_log:
sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
rollover_log = logging.handlers.RotatingFileHandler(
sabnzbd.LOGFILE, "a+", sabnzbd.cfg.log_size(), sabnzbd.cfg.log_backups()
)

View File

@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import platform
import re
import sys
import os
@@ -28,6 +27,7 @@ import tarfile
import urllib.request
import urllib.error
import configobj
import packaging.version
from typing import List
from constants import (
@@ -70,9 +70,9 @@ def delete_files_glob(glob_pattern: str, allow_no_matches: bool = False):
raise FileNotFoundError(f"No files found that match '{glob_pattern}'")
def run_external_command(command: List[str], print_output: bool = True):
def run_external_command(command: List[str], print_output: bool = True, **kwargs):
"""Wrapper to ease the use of calling external programs"""
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs)
output, _ = process.communicate()
ret = process.wait()
if (output and print_output) or ret != 0:
@@ -109,6 +109,52 @@ def patch_version_file(release_name):
ver.write(version_file)
def test_macos_min_version(binary_path: str):
# Skip check if nothing was set
if macos_min_version := os.environ.get("MACOSX_DEPLOYMENT_TARGET"):
# Skip any arm64 specific files
if "arm64" in binary_path:
print(f"Skipping arm64 binary {binary_path}")
return
# Check minimum macOS version is at least mac OS10.13
# We only check the x86_64 since for arm64 it's always macOS 11+
print(f"Checking if binary supports macOS {macos_min_version} and above: {binary_path}")
otool_output = run_external_command(
[
"otool",
"-arch",
"x86_64",
"-l",
binary_path,
],
print_output=False,
)
# Parse the output for LC_BUILD_VERSION minos
# The output is very large, so that's why we enumerate over it
req_version = packaging.version.parse(macos_min_version)
bin_version = None
lines = otool_output.split("\n")
for line_nr, line in enumerate(lines):
if "LC_VERSION_MIN_MACOSX" in line:
# Display the version in the next lines
bin_version = packaging.version.parse(lines[line_nr + 2].split()[1])
elif "minos" in line:
bin_version = packaging.version.parse(line.split()[1])
if bin_version and bin_version > req_version:
raise ValueError(f"{binary_path} requires {bin_version}, we want {req_version}")
else:
# We got the information we need
break
else:
print(lines)
raise RuntimeError(f"Could not determine minimum macOS version for {binary_path}")
else:
print(f"Skipping macOS version check, MACOSX_DEPLOYMENT_TARGET not set")
def test_sab_binary(binary_path: str):
"""Wrapper to have a simple start-up test for the binary"""
with tempfile.TemporaryDirectory() as config_dir:
@@ -201,23 +247,21 @@ if __name__ == "__main__":
if not os.path.exists("locale"):
raise FileNotFoundError("Failed to compile language files")
# Make sure we remove any existing build-folders
safe_remove("build")
safe_remove("dist")
safe_remove(RELEASE_NAME)
# Copy the specification
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
if "binary" in sys.argv or "installer" in sys.argv:
if "binary" in sys.argv:
# Must be run on Windows
if sys.platform != "win32":
raise RuntimeError("Binary should be created on Windows")
# Make sure we remove any existing build-folders
safe_remove("build")
safe_remove("dist")
# Remove any leftovers
safe_remove(RELEASE_NAME)
safe_remove(RELEASE_BINARY)
# Run PyInstaller and check output
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
shutil.copytree("dist/SABnzbd-console", "dist/SABnzbd", dirs_exist_ok=True)
@@ -228,33 +272,49 @@ if __name__ == "__main__":
delete_files_glob("dist/SABnzbd/api-ms-win*.dll", allow_no_matches=True)
delete_files_glob("dist/SABnzbd/ucrtbase.dll", allow_no_matches=True)
if "installer" in sys.argv:
# Compile NSIS translations
safe_remove("NSIS_Installer.nsi")
safe_remove("NSIS_Installer.nsi.tmp")
shutil.copyfile("builder/win/NSIS_Installer.nsi", "NSIS_Installer.nsi")
run_external_command([sys.executable, "tools/make_mo.py", "nsis"])
# Run NSIS to build installer
run_external_command(
[
"makensis.exe",
"/V3",
"/DSAB_VERSION=%s" % RELEASE_VERSION,
"/DSAB_VERSIONKEY=%s" % ".".join(map(str, RELEASE_VERSION_TUPLE)),
"/DSAB_FILE=%s" % RELEASE_INSTALLER,
"NSIS_Installer.nsi.tmp",
]
)
# Rename the folder
shutil.copytree("dist/SABnzbd", RELEASE_NAME)
# Test the release
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
# Create the archive
run_external_command(["win/7zip/7za.exe", "a", RELEASE_BINARY, RELEASE_NAME])
run_external_command(["win/7zip/7za.exe", "a", RELEASE_BINARY, "SABnzbd"], cwd="dist")
shutil.move(f"dist/{RELEASE_BINARY}", RELEASE_BINARY)
# Test the release, as the very last step to not mess with any release code
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
if "installer" in sys.argv:
# Check if we have the dist folder
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
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}"):
print("Using signed version of SABnzbd binaries")
safe_remove("dist/SABnzbd")
run_external_command(["win/7zip/7za.exe", "x", "-odist", f"signed/{RELEASE_BINARY}"])
# Make sure it exists
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
raise FileNotFoundError("SABnzbd executable not found, signed zip extraction failed")
elif RELEASE_THIS:
raise FileNotFoundError("Signed SABnzbd executable not found, required for release!")
else:
print("Using unsigned version of SABnzbd binaries")
# Compile NSIS translations
safe_remove("NSIS_Installer.nsi")
safe_remove("NSIS_Installer.nsi.tmp")
shutil.copyfile("builder/win/NSIS_Installer.nsi", "NSIS_Installer.nsi")
run_external_command([sys.executable, "tools/make_mo.py", "nsis"])
# Run NSIS to build installer
run_external_command(
[
"makensis.exe",
"/V3",
"/DSAB_VERSION=%s" % RELEASE_VERSION,
"/DSAB_VERSIONKEY=%s" % ".".join(map(str, RELEASE_VERSION_TUPLE)),
"/DSAB_FILE=%s" % RELEASE_INSTALLER,
"NSIS_Installer.nsi.tmp",
]
)
if "app" in sys.argv:
# Must be run on macOS
@@ -271,13 +331,16 @@ if __name__ == "__main__":
if authority:
files_to_sign = [
"macos/par2/par2",
"macos/par2/arm64/par2",
"macos/unrar/unrar",
"macos/unrar/arm64/unrar",
"macos/7zip/7zz",
]
for file_to_sign in files_to_sign:
print("Signing %s with hardended runtime" % file_to_sign)
# Make sure it supports the macOS versions we want first
test_macos_min_version(file_to_sign)
# Then sign in
print("Signing %s with hardened runtime" % file_to_sign)
run_external_command(
[
"codesign",
@@ -297,17 +360,21 @@ if __name__ == "__main__":
print("Signed %s!" % file_to_sign)
# Run PyInstaller and check output
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
# Make sure we created a fully universal2 release when releasing or during CI
if RELEASE_THIS or ON_GITHUB_ACTIONS:
for bin_to_check in glob.glob("dist/SABnzbd.app/Contents/MacOS/**/*.so", recursive=True):
for bin_to_check in glob.glob("dist/SABnzbd.app/**/*.so", recursive=True):
print("Checking if binary is universal2: %s" % bin_to_check)
file_output = run_external_command(["file", bin_to_check], print_output=False)
# Make sure we have both arm64 and x86
if not ("x86_64" in file_output and "arm64" in file_output):
raise RuntimeError("Non-universal2 binary found!")
# Make sure it supports the macOS versions we want
test_macos_min_version(bin_to_check)
# Only continue if we can sign
if authority:
# We use PyInstaller to sign the main SABnzbd executable and the SABnzbd.app

View File

@@ -1,2 +1,2 @@
PyGithub==2.6.1
PyGithub==2.8.1
praw==7.8.1

View File

@@ -75,7 +75,7 @@ print("----")
# Check if tagged as release and check for token
gh_token = os.environ.get("AUTOMATION_GITHUB_TOKEN", "")
if RELEASE_THIS and gh_token:
gh_obj = github.Github(gh_token)
gh_obj = github.Github(auth=github.Auth.Token(gh_token))
gh_repo = gh_obj.get_repo("sabnzbd/sabnzbd")
# Read the release notes
@@ -86,7 +86,7 @@ if RELEASE_THIS and gh_token:
for release in gh_repo.get_releases():
if release.tag_name == RELEASE_VERSION:
gh_release = release
print("Found existing release %s" % gh_release.title)
print("Found existing release %s" % gh_release.name)
break
else:
# Did not find it, so create the release, use the GitHub tag we got as input

View File

@@ -1,10 +1,10 @@
# Basic build requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.14.1
pyinstaller==6.16.0
packaging==25.0
pyinstaller-hooks-contrib==2025.5
pyinstaller-hooks-contrib==2025.9
altgraph==0.17.4
wrapt==1.17.2
wrapt==2.0.0
setuptools==80.9.0
# For the Windows build
@@ -16,4 +16,4 @@ dmgbuild==1.6.5; sys_platform == 'darwin'
mac-alias==2.2.2; sys_platform == 'darwin'
macholib==1.16.3; sys_platform == 'darwin'
ds-store==1.3.1; sys_platform == 'darwin'
PyNaCl==1.5.0; sys_platform == 'darwin'
PyNaCl==1.6.0; sys_platform == 'darwin'

View File

@@ -29,6 +29,7 @@ Unicode true
!include "nsProcess.nsh"
!include "x64.nsh"
!include "servicelib.nsh"
!include "StdUtils.nsh"
;------------------------------------------------------------------
;
@@ -139,9 +140,9 @@ Unicode true
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
!insertmacro MUI_PAGE_INSTFILES
; !define MUI_FINISHPAGE_RUN
; !define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
; !define MUI_FINISHPAGE_RUN_TEXT $(MsgRunSAB)
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
!define MUI_FINISHPAGE_RUN_TEXT $(MsgRunSAB)
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
!define MUI_FINISHPAGE_SHOWREADME_TEXT $(MsgShowRelNote)
!define MUI_FINISHPAGE_LINK $(MsgSupportUs)
@@ -154,12 +155,21 @@ Unicode true
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
;------------------------------------------------------------------
; Run as user-level at end of install
; DOES NOT WORK
; Function PageFinishRun
; !insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\SABnzbd.exe" "" "" ""
; FunctionEnd
Function PageFinishRun
; Check if SABnzbd service is installed
!insertmacro SERVICE "installed" "SABnzbd" ""
Pop $0 ;response
${If} $0 == true
; Service is installed, start the service
!insertmacro SERVICE "start" "SABnzbd" ""
${Else}
; Service not installed, run executable as user
${StdUtils.ExecShellAsUser} $0 "$INSTDIR\SABnzbd.exe" "" ""
${EndIf}
FunctionEnd
;------------------------------------------------------------------
@@ -188,7 +198,6 @@ Unicode true
!insertmacro MUI_LANGUAGE "SimpChinese"
;------------------------------------------------------------------
;Reserve Files
;If you are using solid compression, files that are required before
@@ -361,14 +370,6 @@ Function .onInit
FunctionEnd
;------------------------------------------------------------------
; Show the shortcuts at end of install so user can start SABnzbd
; This is instead of us trying to run SAB from the installer
;
Function .onInstSuccess
ExecShell "open" "$SMPROGRAMS\$STARTMENU_FOLDER"
FunctionEnd
;--------------------------------
; begin uninstall settings/section
UninstallText $(MsgUninstall)
@@ -410,6 +411,8 @@ SectionEnd
;Language strings
LangString MsgShowRelNote ${LANG_ENGLISH} "Show Release Notes"
LangString MsgRunSAB ${LANG_ENGLISH} "Run SABnzbd"
LangString MsgSupportUs ${LANG_ENGLISH} "Support the project, Donate!"
LangString MsgServChange ${LANG_ENGLISH} "The SABnzbd Windows Service changed in SABnzbd 3.0.0. $\nYou will need to reinstall the SABnzbd service. $\n$\nClick `OK` to remove the existing services or `Cancel` to cancel this upgrade."

View File

@@ -0,0 +1,501 @@
#################################################################################
# StdUtils plug-in for NSIS
# Copyright (C) 2004-2018 LoRd_MuldeR <MuldeR2@GMX.de>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# http://www.gnu.org/licenses/lgpl-2.1.txt
#################################################################################
# DEVELOPER NOTES:
# - Please see "https://github.com/lordmulder/stdutils/" for news and updates!
# - Please see "Docs\StdUtils\StdUtils.html" for detailed function descriptions!
# - Please see "Examples\StdUtils\StdUtilsTest.nsi" for usage examples!
#################################################################################
# FUNCTION DECLARTIONS
#################################################################################
!ifndef ___STDUTILS__NSH___
!define ___STDUTILS__NSH___
!define StdUtils.Time '!insertmacro _StdU_Time' #time(), as in C standard library
!define StdUtils.GetMinutes '!insertmacro _StdU_GetMinutes' #GetSystemTimeAsFileTime(), returns the number of minutes
!define StdUtils.GetHours '!insertmacro _StdU_GetHours' #GetSystemTimeAsFileTime(), returns the number of hours
!define StdUtils.GetDays '!insertmacro _StdU_GetDays' #GetSystemTimeAsFileTime(), returns the number of days
!define StdUtils.Rand '!insertmacro _StdU_Rand' #rand(), as in C standard library
!define StdUtils.RandMax '!insertmacro _StdU_RandMax' #rand(), as in C standard library, with maximum value
!define StdUtils.RandMinMax '!insertmacro _StdU_RandMinMax' #rand(), as in C standard library, with minimum/maximum value
!define StdUtils.RandList '!insertmacro _StdU_RandList' #rand(), as in C standard library, with list support
!define StdUtils.RandBytes '!insertmacro _StdU_RandBytes' #Generates random bytes, returned as Base64-encoded string
!define StdUtils.FormatStr '!insertmacro _StdU_FormatStr' #sprintf(), as in C standard library, one '%d' placeholder
!define StdUtils.FormatStr2 '!insertmacro _StdU_FormatStr2' #sprintf(), as in C standard library, two '%d' placeholders
!define StdUtils.FormatStr3 '!insertmacro _StdU_FormatStr3' #sprintf(), as in C standard library, three '%d' placeholders
!define StdUtils.ScanStr '!insertmacro _StdU_ScanStr' #sscanf(), as in C standard library, one '%d' placeholder
!define StdUtils.ScanStr2 '!insertmacro _StdU_ScanStr2' #sscanf(), as in C standard library, two '%d' placeholders
!define StdUtils.ScanStr3 '!insertmacro _StdU_ScanStr3' #sscanf(), as in C standard library, three '%d' placeholders
!define StdUtils.TrimStr '!insertmacro _StdU_TrimStr' #Remove whitspaces from string, left and right
!define StdUtils.TrimStrLeft '!insertmacro _StdU_TrimStrLeft' #Remove whitspaces from string, left side only
!define StdUtils.TrimStrRight '!insertmacro _StdU_TrimStrRight' #Remove whitspaces from string, right side only
!define StdUtils.RevStr '!insertmacro _StdU_RevStr' #Reverse a string, e.g. "reverse me" <-> "em esrever"
!define StdUtils.ValidFileName '!insertmacro _StdU_ValidFileName' #Test whether string is a valid file name - no paths allowed
!define StdUtils.ValidPathSpec '!insertmacro _StdU_ValidPathSpec' #Test whether string is a valid full(!) path specification
!define StdUtils.ValidDomainName '!insertmacro _StdU_ValidDomain' #Test whether string is a valid host name or domain name
!define StdUtils.StrToUtf8 '!insertmacro _StdU_StrToUtf8' #Convert string from Unicode (UTF-16) or ANSI to UTF-8 bytes
!define StdUtils.StrFromUtf8 '!insertmacro _StdU_StrFromUtf8' #Convert string from UTF-8 bytes to Unicode (UTF-16) or ANSI
!define StdUtils.SHFileMove '!insertmacro _StdU_SHFileMove' #SHFileOperation(), using the FO_MOVE operation
!define StdUtils.SHFileCopy '!insertmacro _StdU_SHFileCopy' #SHFileOperation(), using the FO_COPY operation
!define StdUtils.AppendToFile '!insertmacro _StdU_AppendToFile' #Append contents of an existing file to another file
!define StdUtils.ExecShellAsUser '!insertmacro _StdU_ExecShlUser' #ShellExecute() as NON-elevated user from elevated installer
!define StdUtils.InvokeShellVerb '!insertmacro _StdU_InvkeShlVrb' #Invokes a "shell verb", e.g. for pinning items to the taskbar
!define StdUtils.ExecShellWaitEx '!insertmacro _StdU_ExecShlWaitEx' #ShellExecuteEx(), returns the handle of the new process
!define StdUtils.WaitForProcEx '!insertmacro _StdU_WaitForProcEx' #WaitForSingleObject(), e.g. to wait for a running process
!define StdUtils.GetParameter '!insertmacro _StdU_GetParameter' #Get the value of a specific command-line option
!define StdUtils.TestParameter '!insertmacro _StdU_TestParameter' #Test whether a specific command-line option has been set
!define StdUtils.ParameterCnt '!insertmacro _StdU_ParameterCnt' #Get number of command-line tokens, similar to argc in main()
!define StdUtils.ParameterStr '!insertmacro _StdU_ParameterStr' #Get the n-th command-line token, similar to argv[i] in main()
!define StdUtils.GetAllParameters '!insertmacro _StdU_GetAllParams' #Get complete command-line, but without executable name
!define StdUtils.GetRealOSVersion '!insertmacro _StdU_GetRealOSVer' #Get the *real* Windows version number, even on Windows 8.1+
!define StdUtils.GetRealOSBuildNo '!insertmacro _StdU_GetRealOSBld' #Get the *real* Windows build number, even on Windows 8.1+
!define StdUtils.GetRealOSName '!insertmacro _StdU_GetRealOSStr' #Get the *real* Windows version, as a "friendly" name
!define StdUtils.GetOSEdition '!insertmacro _StdU_GetOSEdition' #Get the Windows edition, i.e. "workstation" or "server"
!define StdUtils.GetOSReleaseId '!insertmacro _StdU_GetOSRelIdNo' #Get the Windows release identifier (on Windows 10)
!define StdUtils.GetOSReleaseName '!insertmacro _StdU_GetOSRelIdStr' #Get the Windows release (on Windows 10), as a "friendly" name
!define StdUtils.VerifyOSVersion '!insertmacro _StdU_VrfyRealOSVer' #Compare *real* operating system to an expected version number
!define StdUtils.VerifyOSBuildNo '!insertmacro _StdU_VrfyRealOSBld' #Compare *real* operating system to an expected build number
!define StdUtils.HashText '!insertmacro _StdU_HashText' #Compute hash from text string (CRC32, MD5, SHA1/2/3, BLAKE2)
!define StdUtils.HashFile '!insertmacro _StdU_HashFile' #Compute hash from file (CRC32, MD5, SHA1/2/3, BLAKE2)
!define StdUtils.NormalizePath '!insertmacro _StdU_NormalizePath' #Simplifies the path to produce a direct, well-formed path
!define StdUtils.GetParentPath '!insertmacro _StdU_GetParentPath' #Get parent path by removing the last component from the path
!define StdUtils.SplitPath '!insertmacro _StdU_SplitPath' #Split the components of the given path
!define StdUtils.GetDrivePart '!insertmacro _StdU_GetDrivePart' #Get drive component of path
!define StdUtils.GetDirectoryPart '!insertmacro _StdU_GetDirPart' #Get directory component of path
!define StdUtils.GetFileNamePart '!insertmacro _StdU_GetFNamePart' #Get file name component of path
!define StdUtils.GetExtensionPart '!insertmacro _StdU_GetExtnPart' #Get file extension component of path
!define StdUtils.TimerCreate '!insertmacro _StdU_TimerCreate' #Create a new event-timer that will be triggered periodically
!define StdUtils.TimerDestroy '!insertmacro _StdU_TimerDestroy' #Destroy a running timer created with TimerCreate()
!define StdUtils.ProtectStr '!insertmacro _StdU_PrtctStr' #Protect a given String using Windows' DPAPI
!define StdUtils.UnprotectStr '!insertmacro _StdU_UnprtctStr' #Unprotect a string that was protected via ProtectStr()
!define StdUtils.GetLibVersion '!insertmacro _StdU_GetLibVersion' #Get the current StdUtils library version (for debugging)
!define StdUtils.SetVerbose '!insertmacro _StdU_SetVerbose' #Enable or disable "verbose" mode (for debugging)
#################################################################################
# MACRO DEFINITIONS
#################################################################################
!macro _StdU_Time out
StdUtils::Time /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetMinutes out
StdUtils::GetMinutes /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetHours out
StdUtils::GetHours /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetDays out
StdUtils::GetDays /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_Rand out
StdUtils::Rand /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandMax out max
push ${max}
StdUtils::RandMax /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandMinMax out min max
push ${min}
push ${max}
StdUtils::RandMinMax /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandList count max
push ${max}
push ${count}
StdUtils::RandList /NOUNLOAD
!macroend
!macro _StdU_RandBytes out count
push ${count}
StdUtils::RandBytes /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr out format val
push `${format}`
push ${val}
StdUtils::FormatStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr2 out format val1 val2
push `${format}`
push ${val1}
push ${val2}
StdUtils::FormatStr2 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr3 out format val1 val2 val3
push `${format}`
push ${val1}
push ${val2}
push ${val3}
StdUtils::FormatStr3 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ScanStr out format input default
push `${format}`
push `${input}`
push ${default}
StdUtils::ScanStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ScanStr2 out1 out2 format input default1 default2
push `${format}`
push `${input}`
push ${default1}
push ${default2}
StdUtils::ScanStr2 /NOUNLOAD
pop ${out1}
pop ${out2}
!macroend
!macro _StdU_ScanStr3 out1 out2 out3 format input default1 default2 default3
push `${format}`
push `${input}`
push ${default1}
push ${default2}
push ${default3}
StdUtils::ScanStr3 /NOUNLOAD
pop ${out1}
pop ${out2}
pop ${out3}
!macroend
!macro _StdU_TrimStr var
push ${var}
StdUtils::TrimStr /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_TrimStrLeft var
push ${var}
StdUtils::TrimStrLeft /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_TrimStrRight var
push ${var}
StdUtils::TrimStrRight /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_RevStr var
push ${var}
StdUtils::RevStr /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_ValidFileName out test
push `${test}`
StdUtils::ValidFileName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ValidPathSpec out test
push `${test}`
StdUtils::ValidPathSpec /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ValidDomain out test
push `${test}`
StdUtils::ValidDomainName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_StrToUtf8 out str
push `${str}`
StdUtils::StrToUtf8 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_StrFromUtf8 out trnc str
push ${trnc}
push `${str}`
StdUtils::StrFromUtf8 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SHFileMove out from to hwnd
push `${from}`
push `${to}`
push ${hwnd}
StdUtils::SHFileMove /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SHFileCopy out from to hwnd
push `${from}`
push `${to}`
push ${hwnd}
StdUtils::SHFileCopy /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_AppendToFile out from dest offset maxlen
push `${from}`
push `${dest}`
push ${offset}
push ${maxlen}
StdUtils::AppendToFile /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ExecShlUser out file verb args
push `${file}`
push `${verb}`
push `${args}`
StdUtils::ExecShellAsUser /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_InvkeShlVrb out path file verb_id
push "${path}"
push "${file}"
push ${verb_id}
StdUtils::InvokeShellVerb /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ExecShlWaitEx out_res out_val file verb args
push `${file}`
push `${verb}`
push `${args}`
StdUtils::ExecShellWaitEx /NOUNLOAD
pop ${out_res}
pop ${out_val}
!macroend
!macro _StdU_WaitForProcEx out handle
push `${handle}`
StdUtils::WaitForProcEx /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetParameter out name default
push `${name}`
push `${default}`
StdUtils::GetParameter /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TestParameter out name
push `${name}`
StdUtils::TestParameter /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ParameterCnt out
StdUtils::ParameterCnt /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ParameterStr out index
push ${index}
StdUtils::ParameterStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetAllParams out truncate
push `${truncate}`
StdUtils::GetAllParameters /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetRealOSVer out_major out_minor out_spack
StdUtils::GetRealOsVersion /NOUNLOAD
pop ${out_major}
pop ${out_minor}
pop ${out_spack}
!macroend
!macro _StdU_GetRealOSBld out
StdUtils::GetRealOsBuildNo /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetRealOSStr out
StdUtils::GetRealOsName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_VrfyRealOSVer out major minor spack
push `${major}`
push `${minor}`
push `${spack}`
StdUtils::VerifyRealOsVersion /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_VrfyRealOSBld out build
push `${build}`
StdUtils::VerifyRealOsBuildNo /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSEdition out
StdUtils::GetOsEdition /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSRelIdNo out
StdUtils::GetOsReleaseId /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSRelIdStr out
StdUtils::GetOsReleaseName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_HashText out type text
push `${type}`
push `${text}`
StdUtils::HashText /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_HashFile out type file
push `${type}`
push `${file}`
StdUtils::HashFile /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_NormalizePath out path
push `${path}`
StdUtils::NormalizePath /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetParentPath out path
push `${path}`
StdUtils::GetParentPath /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SplitPath out_drive out_dir out_fname out_ext path
push `${path}`
StdUtils::SplitPath /NOUNLOAD
pop ${out_drive}
pop ${out_dir}
pop ${out_fname}
pop ${out_ext}
!macroend
!macro _StdU_GetDrivePart out path
push `${path}`
StdUtils::GetDrivePart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetDirPart out path
push `${path}`
StdUtils::GetDirectoryPart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetFNamePart out path
push `${path}`
StdUtils::GetFileNamePart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetExtnPart out path
push `${path}`
StdUtils::GetExtensionPart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TimerCreate out callback interval
GetFunctionAddress ${out} ${callback}
push ${out}
push ${interval}
StdUtils::TimerCreate /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TimerDestroy out timer_id
push ${timer_id}
StdUtils::TimerDestroy /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_PrtctStr out dpsc salt text
push `${dpsc}`
push `${salt}`
push `${text}`
StdUtils::ProtectStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_UnprtctStr out trnc salt data
push `${trnc}`
push `${salt}`
push `${data}`
StdUtils::UnprotectStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetLibVersion out_ver out_tst
StdUtils::GetLibVersion /NOUNLOAD
pop ${out_ver}
pop ${out_tst}
!macroend
!macro _StdU_SetVerbose enable
Push ${enable}
StdUtils::SetVerboseMode /NOUNLOAD
!macroend
#################################################################################
# MAGIC NUMBERS
#################################################################################
!define StdUtils.Const.ShellVerb.PinToTaskbar 0
!define StdUtils.Const.ShellVerb.UnpinFromTaskbar 1
!define StdUtils.Const.ShellVerb.PinToStart 2
!define StdUtils.Const.ShellVerb.UnpinFromStart 3
!endif # !___STDUTILS__NSH___

View File

Binary file not shown.

View File

@@ -132,7 +132,7 @@
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
</div>
<div class="field-pair no-field-pair-bg">
<button class="btn btn-default addNewServer" disabled><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default addNewServer" disabled data-toggle="tooltip" data-placement="top" title="$T('wizard-test-server-required')"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
</div>
<div class="field-pair result-box">
@@ -464,14 +464,14 @@
When finished loading
**/
jQuery(document).ready(function(){
// Initialize tooltips
jQuery('[data-toggle="tooltip"]').tooltip()
// Reload form in case we change items that make the servers appear different
jQuery('input[name="priority"], input[name="displayname"], textarea[name="notes"]').on('change', function() {
jQuery('.fullform').submit(function() {
// No ajax this time
jQuery('input[name="ajax"]').val('')
// Skip the fancy stuff, just submit
this.submit()
})
jQuery('input[name="priority"], input[name="displayname"], textarea[name="notes"]').on('change', function(event) {
var parentForm = jQuery(event.target).parents("form")
parentForm.unbind("submit")
parentForm.find('input[name="ajax"]').val('')
})
/**
@@ -563,6 +563,9 @@
// Allow adding the new server if we are in the new-server section
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").removeAttr("disabled")
jQuery(".addNewServer").removeAttr("data-toggle")
jQuery(".addNewServer").removeAttr("title")
jQuery(".addNewServer").tooltip("destroy")
}
} else {
resultBox.addClass('alert-danger')
@@ -571,6 +574,10 @@
// Disable the adding of new server, just to be sure
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").attr("disabled", "disabled")
jQuery(".addNewServer").attr("data-toggle", "tooltip")
jQuery(".addNewServer").attr("data-placement", "top")
jQuery(".addNewServer").attr("title", "$T('wizard-test-server-required')")
jQuery(".addNewServer").tooltip()
}
}
});

View File

@@ -50,7 +50,6 @@ textarea,
.navbar-default .navbar-nav>li>a:focus,
.navbar-logo:hover,
.quoteBlock,
.selected,
.server-disabled,
#serverResponse,
.table>tbody>tr:nth-child(odd),
@@ -62,30 +61,10 @@ select:hover {
color: #EBEBEB !important;
}
.correct {
border: 2px solid #00cc22 !important;
}
.failed,
.required-star,
.error-text {
.failed {
color: #ff3333 !important;
}
.unselected,
.selected {
border: 1px solid #EBEBEB !important;
color: #EBEBEB !important;
}
.incorrect {
border: 2px solid #ff3333 !important;
}
.disabled-text {
color: #777 !important;
}
#rightGreyText,
small {
color: #c7c7c7 !important;
@@ -306,6 +285,14 @@ col2 h3 a,
border-top-color: #E4E4E4 !important;
}
.tooltip.left .tooltip-arrow {
border-left-color: #E4E4E4 !important;
}
.tooltip.right .tooltip-arrow {
border-right-color: #E4E4E4 !important;
}
.Special .glyphicon-asterisk {
color: #E4E4E4 !important;
}

View File

@@ -83,13 +83,20 @@
<div class="col-sm-2">$T('name')</div>
<div class="col-sm-10" data-bind="text: historyStatus.name"></div>
</div>
<div class="row" data-bind="visible: historyStatus.time_added">
<div class="col-sm-2">$T('rss-added')</div>
<div class="col-sm-10" data-bind="text: timeAdded(), attr: { 'data-timestamp': historyStatus.time_added }"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('post-Completed')</div>
<div class="col-sm-10" data-bind="text: completedOn, attr: { 'data-timestamp': completed }"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('status')</div>
<div class="col-sm-10" data-bind="text: glitterTranslate.status[historyStatus.status()] ? glitterTranslate.status[historyStatus.status()] : statusText()"></div>
<div class="col-sm-10">
<span data-bind="text: glitterTranslate.status[historyStatus.status()] ? glitterTranslate.status[historyStatus.status()] : statusText()"></span>
<a href="#" class="mark-completed-link" data-bind="visible: failed(), click: markAsCompleted" title="$T('button-mark-completed')"><span class="glyphicon glyphicon-ok"></span> $T('post-Completed')</a>
</div>
</div>
<div class="row">
<div class="col-sm-2">$T('size')</div>
@@ -142,6 +149,7 @@
<a href="#" class="hover-button history-archive" title="$T('showArchive') / $T('showAllHis')" data-tooltip="true" data-placement="top" data-bind="click: history.toggleShowArchive, css: { 'history-options-show-failed': history.showArchive }"><svg viewBox="6 6 36 36" height="14" width="14" class="archive-icon"><path d="M41.09 10.45l-2.77-3.36c-.56-.66-1.39-1.09-2.32-1.09h-24c-.93 0-1.76.43-2.31 1.09l-2.77 3.36c-.58.7-.92 1.58-.92 2.55v25c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4v-25c0-.97-.34-1.85-.91-2.55zm-17.09 24.55l-11-11h7v-4h8v4h7l-11 11zm-13.75-25l1.63-2h24l1.87 2h-27.5z"/></svg></a>
<a href="#" class="hover-button" title="$T('showFailedHis') / $T('showAllHis')" data-tooltip="true" data-placement="top" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="top" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('button-mark-completed')" data-bind="visible: (history.isMultiEditing() && hasHistory()), click: history.doMultiMarkCompleted" data-tooltip="true" data-placement="top"><span class="glyphicon glyphicon-ok"></span></a>
<div data-bind="visible: (history.isMultiEditing() && hasHistory())">
<span class="label label-default" data-bind="text: history.multiEditItems().length">0</span>

View File

@@ -681,9 +681,9 @@
</ul>
</div>
<div class="modal-footer">
<div class="checkbox">
<div class="checkbox" data-bind="visible: !history.showArchive()">
<label>
<input type="checkbox" data-bind="checked: history.showArchive()"> <span>$T('permanently-delete')</span>
<input type="checkbox" data-bind="checked: history.permanentlyDelete"> <span>$T('permanently-delete')</span>
</label>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">$T('cancel')</button>
@@ -802,7 +802,7 @@
<div class="modal-footer">
<div class="checkbox">
<label>
<input type="checkbox" data-bind="checked: history.showArchive()"> <span>$T('permanently-delete')</span>
<input type="checkbox" data-bind="checked: history.permanentlyDelete"> <span>$T('permanently-delete')</span>
</label>
</div>
</div>

View File

@@ -179,14 +179,20 @@
</a>
</div>
<div class="add-nzb-inputbox add-nzb-inputbox-small">
<label for="multiedit-play">
<label for="multiedit-play" data-bind="event: { mousedown: queue.handleMultiEditStatusMouseDown }">
<input type="radio" name="multiedit-status" value="resume" id="multiedit-play" data-bind="event: { change: queue.doMultiEditUpdate }" />
<span class="glyphicon glyphicon-play" title="$T('link-resume')" data-tooltip="true" data-placement="top"></span>
</label>
<label for="multiedit-pause">
<label for="multiedit-pause" data-bind="event: { mousedown: queue.handleMultiEditStatusMouseDown }">
<input type="radio" name="multiedit-status" value="pause" id="multiedit-pause" data-bind="event: { change: queue.doMultiEditUpdate }" />
<span class="glyphicon glyphicon-pause" title="$T('link-pause')" data-tooltip="true" data-placement="top"></span>
</label>
<a href="#" class="hover-button" title="$T('Glitter-top')" data-bind="click: queue.doMultiMoveToTop" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-chevron-up"></span>
</a>
<a href="#" class="hover-button" title="$T('Glitter-bottom')" data-bind="click: queue.doMultiMoveToBottom" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-chevron-down"></span>
</a>
<span class="label label-default" data-bind="text: queue.multiEditItems().length">0</span>
</div>
<div class="add-nzb-inputbox-clear"></div>

View File

@@ -52,16 +52,15 @@
var glitterTranslate = new Object();
glitterTranslate.paused = "$T('post-Paused')";
glitterTranslate.left = "$T('Glitter-left')";
glitterTranslate.clearWarn = "$T('confirm')";
glitterTranslate.clearOrphanWarning = "$T('Glitter-clearOrphanWarning')";
glitterTranslate.pausePromptFail = "$T('Glitter-pausePromptFail')"
glitterTranslate.pauseFor = "$T('pauseFor')"
glitterTranslate.minutes = "$T('mins')"
glitterTranslate.shutdown = "$T('shutdownOK?')";
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.deleteMsg = "$T('nzo-delete')";
glitterTranslate.removeDown = "$T('confirm')";
glitterTranslate.removeDow1 = "$T('confirm')";
glitterTranslate.confirm = "$T('confirm')";
glitterTranslate.markComplete = "$T('button-mark-completed')";
glitterTranslate.renameAbort = "$T('Glitter-confirmAbortDirectUnpack')\n$T('confirm')";
glitterTranslate.retryAll = "$T('link-retryAll')?";
glitterTranslate.fetch = "$T('Glitter-fetch')";

View File

@@ -10,6 +10,7 @@ function HistoryListModel(parent) {
self.historyItems = ko.observableArray([])
self.showFailed = ko.observable(false).extend({ persist: 'historyShowFailed' });
self.showArchive = ko.observable(false).extend({ persist: 'historyShowArchive' });
self.permanentlyDelete = ko.observable(false).extend({ persist: 'permanentlyDelete' });
self.isLoading = ko.observable(false).extend({ rateLimit: 100 });
self.searchTerm = ko.observable('').extend({ rateLimit: { timeout: 400, method: "notifyWhenChangesStop" } });
self.paginationLimit = ko.observable(10).extend({ persist: 'historyPaginationLimit' });
@@ -391,6 +392,10 @@ function HistoryListModel(parent) {
}
if(strIDsHistory !== "") {
var skipArchive = $('#modal-delete-history-job input[type="checkbox"]').prop("checked")
// Permanently delete if we are on the Archive page
if(self.showArchive()) skipArchive = true
callAPI({
mode: 'history',
name: 'delete',
@@ -415,6 +420,42 @@ function HistoryListModel(parent) {
self.triggerRemoveDownload(self.multiEditItems())
}
// Mark jobs as completed
self.markAsCompleted = function(items) {
// Confirm
if(!confirm(glitterTranslate.markComplete)) {
return
}
// Single or multiple items?
var strIDs = '';
if(items.length) {
$.each(items, function(index) {
strIDs = strIDs + this.id + ',';
})
} else {
strIDs = items.id
}
// Send the API call
callAPI({
mode: 'history',
name: 'mark_as_completed',
value: strIDs
}).then(function(response) {
// Force refresh to update the UI
self.parent.refresh(true);
});
}
// Mark all selected as completed
self.doMultiMarkCompleted = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Mark them
self.markAsCompleted(self.multiEditItems());
}
// Focus on the confirm button
$('#modal-delete-history-job').on("shown.bs.modal", function() {
$('#modal-delete-history-job .btn[type="submit"]').focus()
@@ -546,6 +587,11 @@ function HistoryModel(parent, data) {
return displayDateTime(self.completed(), parent.parent.dateFormat(), 'X')
});
// Format time added
self.timeAdded = ko.pureComputed(function() {
return displayDateTime(self.historyStatus.time_added(), parent.parent.dateFormat(), 'X')
});
// Subscribe to retryEvent so we can load the password
self.canRetry.subscribe(function() {
self.updateAllHistory = true;
@@ -561,6 +607,11 @@ function HistoryModel(parent, data) {
$('#modal-retry-job').modal("show")
};
// Mark as completed button
self.markAsCompleted = function() {
parent.markAsCompleted(self);
};
// Update information only on click
self.updateAllHistoryInfo = function(data, event) {
// Show

View File

@@ -704,7 +704,6 @@ function ViewModel() {
data.append("apikey", apiKey);
// Add this one
debugger
$.ajax({
url: "./api",
type: "POST",
@@ -896,7 +895,7 @@ function ViewModel() {
// Orphaned folder deletion of all
self.removeAllOrphaned = function() {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
if (confirm(glitterTranslate.clearOrphanWarning)) {
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.statusInfo.folders().length)
// Delete them all
@@ -913,7 +912,7 @@ function ViewModel() {
// Orphaned folder adding of all
self.addAllOrphaned = function() {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
if (confirm(glitterTranslate.confirm)) {
// Show notification
showNotification('.main-notification-box-sendback')
// Delete them all

View File

@@ -423,6 +423,21 @@ function QueueListModel(parent) {
}
// Handle mousedown to capture state before change
self.handleMultiEditStatusMouseDown = function(item, event) {
var clickedValue = $(event.currentTarget).find("input").val();
// If this radio was already selected (same value as previous), clear it
if ($('.multioperations-selector input[name="multiedit-status"]:checked').val() === clickedValue) {
// Clear all radio buttons in this group after the click finished
// Hacky, but it works
setTimeout(function () {
$('.multioperations-selector input[name="multiedit-status"]').prop('checked', false);
}, 200)
}
return true;
}
// Remove downloads from queue
self.removeDownloads = function(form) {
// Hide modal and show notification
@@ -456,6 +471,50 @@ function QueueListModel(parent) {
self.triggerRemoveDownload(self.multiEditItems())
}
// Move all selected to top
self.doMultiMoveToTop = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Move each item to the top, starting from the last one in the sorted list
var arrayList = self.multiEditItems()
var movePromises = [];
for(var i = arrayList.length - 1; i >= 0; i--) {
movePromises.push(callAPI({
mode: "switch",
value: arrayList[i].id,
value2: 0
}));
}
// Wait for all moves to complete then refresh
Promise.all(movePromises).then(function() {
self.parent.refresh();
});
}
// Move all selected to bottom
self.doMultiMoveToBottom = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Move each item to the bottom, starting from the first one in the sorted list
var arrayList = self.multiEditItems()
var movePromises = [];
for(var i = 0; i < arrayList.length; i++) {
movePromises.push(callAPI({
mode: "switch",
value: arrayList[i].id,
value2: self.totalItems() - 1
}));
}
// Wait for all moves to complete then refresh
Promise.all(movePromises).then(function() {
self.parent.refresh();
});
}
// Focus on the confirm button
$('#modal-delete-queue-job').on("shown.bs.modal", function() {
$('#modal-delete-queue-job .btn[type="submit"]').focus()

View File

@@ -860,7 +860,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox {
width: 20%;
width: 19%;
float: left;
}
@@ -871,7 +871,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox-small {
width: 80px;
width: 115px;
float: right;
padding-left: 0;
padding-top: 12px;
@@ -1097,6 +1097,13 @@ tr.queue-item>td:first-child>a {
font-weight: bold;
}
.mark-completed-link {
font-weight: bold !important;
color: #28a745 !important;
text-decoration: underline;
margin-left: 10px;
}
.history-status-hidden {
display: none;
}

View File

@@ -163,7 +163,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox-small {
width: 72px;
width: 115px;
}
.multioperations-selector .add-nzb-inputbox-clear {

View File

@@ -7,7 +7,9 @@
<h1>$T('wizard-quickstart')</h1>
<hr />
<script type="text/javascript">
var txtTestServer = "$T('wizard-server-text')";
var txtChecking = "$T('srv-testing')";
var txtTestRequired = "$T('wizard-test-server-required')";
<!--#include raw $webdir + "/static/javascript/checkserver.js"#-->
</script>
<h3>$T('wizard-server')</h3>
@@ -22,7 +24,7 @@
<div class="form-group">
<label for="host" class="col-sm-4 control-label">$T('srv-host')</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="host" id="host" value="$host" placeholder="$T('wizard-example') news.newshosting.com" />
<input type="text" class="form-control" name="host" id="host" value="$host" placeholder="$T('wizard-example') news.newshosting.com" required />
</div>
</div>
<div class="form-group">
@@ -57,13 +59,13 @@
<div class="form-group">
<label for="port" class="col-sm-4 control-label">$T('srv-port')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" min="0" max="65535" />
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" min="0" max="65535" required />
</div>
</div>
<div class="form-group">
<label for="connections" class="col-sm-4 control-label">$T('srv-connections')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" min="1" max="500" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" />
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" min="1" max="500" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" required />
</div>
</div>
<div class="form-group">
@@ -81,7 +83,7 @@
</div>
<div class="row">
<div class="col-sm-4">
<button id="serverTest" class="btn btn-default"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
<button id="serverTest" class="btn btn-default" data-toggle="tooltip" data-placement="left"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
</div>
<div class="col-sm-8">
<div id="serverResponse" class="well well-sm">$T('wizard-server-text')</div>

View File

@@ -1,9 +1,31 @@
// Variable to track server test results
var serverTestSuccessful = false;
function resetTestResult() {
serverTestSuccessful = false;
$('#serverResponse').html(txtTestServer);
checkRequired();
}
function setTestResult(success) {
serverTestSuccessful = success;
checkRequired();
}
function checkRequired() {
if ($("#host").val() && $("#connections").val()) {
// Check if form is valid using HTML5 validation and if server test passed
if ($("form").get(0).checkValidity() && serverTestSuccessful) {
$("#next-button").removeClass('disabled')
$("#next-button").removeAttr('data-toggle')
$("#next-button").removeAttr('title')
$("#next-button").tooltip('destroy')
return true;
} else {
$("#next-button").addClass('disabled')
$("#next-button").attr('data-toggle', 'tooltip')
$("#next-button").attr('data-placement', 'left')
$("#next-button").attr('title', txtTestRequired)
$("#next-button").tooltip()
return false;
}
}
@@ -12,8 +34,13 @@ $(document).ready(function() {
// Add tooltips
$('[data-toggle="tooltip"]').tooltip()
// On form-submit
// On server test button click
$("#serverTest").click(function() {
// Check HTML5 form validation before testing server
if (!$("form").get(0).reportValidity()) {
return false;
}
$('#serverResponse').html(txtChecking);
$.getJSON(
"../api?mode=config&name=test_server&output=json",
@@ -21,8 +48,10 @@ $(document).ready(function() {
function(result) {
if (result.value.result) {
r = '<span class="success"><span class="glyphicon glyphicon-ok"></span> ' + result.value.message + '</span>';
setTestResult(true);
} else {
r = '<span class="failed"><span class="glyphicon glyphicon-minus-sign"></span> ' + result.value.message + '</span>';
setTestResult(false);
}
r = r.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="failed" target="_blank">https://sabnzbd.org/certificate-errors</a>')
$('#serverResponse').html(r);
@@ -31,26 +60,9 @@ $(document).ready(function() {
return false;
});
$("#port, #connections").bind('keyup blur', function() {
if (this.value > 0) {
$(this).removeClass("incorrect");
$(this).addClass("correct");
} else {
$(this).removeClass("correct");
$(this).addClass("incorrect");
}
checkRequired()
});
$("#host, #username, #password").bind('keyup blur', function() {
if (this.value) {
$(this).removeClass("incorrect");
$(this).addClass("correct");
} else {
$(this).removeClass("correct");
$(this).addClass("incorrect");
}
checkRequired();
// Reset test result when any form field changes
$("#host, #username, #password, #port, #connections, #ssl_verify").bind('input change', function() {
resetTestResult();
});
$('#ssl').click(function() {
@@ -65,13 +77,14 @@ $(document).ready(function() {
$('#port').val('119')
}
}
resetTestResult();
})
checkRequired()
$('form').submit(function(event) {
// Double check
if(!checkRequired()) {
// Check if server test passed (HTML5 validation is automatic)
if(!serverTestSuccessful) {
event.preventDefault();
}
})

View File

@@ -98,43 +98,15 @@ label {
.align-center {
text-align: center;
}
.unselected,
.selected {
display: inline-block;
}
.unselected {
padding: 6px 10px 6px 10px;
border: 1px solid #636363;
margin-left: 8px;
margin-right: 8px;
color: #636363;
}
.selected {
padding: 6px 10px 6px 10px;
color: white;
background-color: #636363;
border: 1px solid #636363;
margin-left: 8px;
margin-right: 8px;
}
.bigger {
font-size: 14px;
}
.bigger input {
font-size: 16px;
}
.required-star {
color: red;
}
.full-width {
width: 100%;
}
.correct {
border: 2px solid #00cc22;
}
.incorrect {
border: 2px solid red;
}
.hidden {
display: none !important;
}
@@ -150,20 +122,12 @@ label {
.input-group-bw {
width: 150px;
}
.disabled-text {
text-decoration: line-through;
color: #ccc;
}
#serverResponse {
padding: 6px 10px;
}
#host-tip {
margin-bottom: 5px;
}
.error-text {
display: inline;
color: red;
}
#bandwidth {
display: inline-block;
}

View File

@@ -30,6 +30,9 @@
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<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"/>

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -32,6 +32,11 @@ msgstr ""
msgid "Cannot find web template: %s, trying standard template"
msgstr ""
#. Warning message
#: SABnzbd.py
msgid "Unable to link to OpenSSL, optimized SSL connection functions will not be used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid "SABCTools disabled: no correct version found! (Found v%s, expecting v%s)"
@@ -293,6 +298,19 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr ""
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Quota"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr ""
@@ -670,6 +688,14 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "API Key missing, please enter the api key from Config->General into your 3rd party program:"
msgstr ""
#: sabnzbd/interface.py
msgid "API Key incorrect, Use the api key from Config->General in your 3rd party program:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
@@ -679,14 +705,6 @@ msgstr ""
msgid "User logged in"
msgstr ""
#: sabnzbd/interface.py
msgid "API Key missing, please enter the api key from Config->General into your 3rd party program:"
msgstr ""
#: sabnzbd/interface.py
msgid "API Key incorrect, Use the api key from Config->General in your 3rd party program:"
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -996,10 +1014,6 @@ msgstr ""
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr ""
@@ -2239,6 +2253,11 @@ msgstr ""
msgid "Retry"
msgstr ""
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3296,10 +3315,6 @@ msgstr ""
msgid "Naming"
msgstr ""
#: sabnzbd/skintext.py
msgid "Quota"
msgstr ""
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr ""
@@ -3397,7 +3412,7 @@ msgid "Warn 5 days in advance of account expiration date."
msgstr ""
#: sabnzbd/skintext.py
msgid "Quota for this account, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few minutes."
msgid "Quota for this server, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />Checked every few minutes. Notification is sent when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4303,6 +4318,10 @@ msgstr ""
msgid "Retry all"
msgstr ""
#: sabnzbd/skintext.py
msgid "Are you sure you want to delete all folders in your Temporary Download Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr ""
@@ -4541,6 +4560,11 @@ msgstr ""
msgid "Start Wizard"
msgstr ""
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -37,6 +37,13 @@ msgstr "Nezdařilo se spustit webové rozhraní"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Šablona pro web nebyla nalezena: %s, zkouším standardní šablonu"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -328,6 +335,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Kvóta přesažena, pozastavuji stahování"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Nesprávný parametr"
@@ -723,15 +744,6 @@ msgstr "Odmítnuto spojení z:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Odmítnuté spojení s hostem \"%s\" z:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Uživatel přihlášen do webového rozhraní"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Uživatel přihlášen"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -748,6 +760,15 @@ msgstr ""
"Nesprávný API klíč, použijte api klíč z Nastavení->Obecné ve vašem programu "
"třetí strany:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Uživatel přihlášen do webového rozhraní"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Uživatel přihlášen"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1069,10 +1090,6 @@ msgstr "Rozbalování selhalo, chyba zápisu nebo plný disk?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Rozbalování selhalo, cesta k souboru je příliš dlouhá."
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Rozbalení selhalo, archiv vyžaduje heslo"
@@ -2327,6 +2344,11 @@ msgstr "Jméno"
msgid "Retry"
msgstr "Opakovat"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3466,10 +3488,6 @@ msgstr ""
msgid "Naming"
msgstr ""
#: sabnzbd/skintext.py
msgid "Quota"
msgstr ""
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr ""
@@ -3572,9 +3590,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4517,6 +4535,12 @@ msgstr ""
msgid "Retry all"
msgstr "Opakovat vše"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Získat NZB z URL"
@@ -4763,6 +4787,11 @@ msgstr ""
msgid "Start Wizard"
msgstr ""
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# Safihre <safihre@sabnzbd.org>, 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>, 2023\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -36,6 +36,15 @@ msgstr "Kunne ikke starte web-interface"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Kan ikke finde webskabeloner: %s, forsøger med standardskabelon"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
"Kan ikke linke til OpenSSL, optimerede SSL-forbindelsesfunktioner vil ikke "
"blive brugt."
#. Error message
#: SABnzbd.py
msgid ""
@@ -67,7 +76,7 @@ msgstr "7za binær... IKKE fundet!"
#. Error message
#: SABnzbd.py
msgid "Essential modules are missing, downloading cannot start."
msgstr ""
msgstr "Nødvendige moduler mangler, download kan ikke starte."
#. Warning message
#: SABnzbd.py
@@ -86,7 +95,7 @@ msgstr "HTTP og HTTPS porte kan ikke være de samme"
#. Warning message
#: SABnzbd.py
msgid "Could not load additional certificates from certifi package"
msgstr ""
msgstr "Kunne ikke indlæse yderligere certifikater fra certifi-pakken"
#. Warning message
#: SABnzbd.py
@@ -96,7 +105,7 @@ msgstr "HTTPS fejlede på grund af manglende CERT og KEY filer"
#. Warning message
#: SABnzbd.py
msgid "Disabled HTTPS because of invalid CERT and KEY files"
msgstr ""
msgstr "HTTPS deaktiveret på grund af ugyldige CERT og KEY filer"
#. Error message
#: SABnzbd.py
@@ -136,6 +145,7 @@ msgid ""
"Current umask (%o) might deny SABnzbd access to the files and folders it "
"creates."
msgstr ""
"Aktuel umask (%o) kan nægte SABnzbd adgang til filer og mapper den opretter."
#. Warning message
#: sabnzbd/__init__.py
@@ -143,26 +153,28 @@ msgid ""
"Completed Download Folder %s is on FAT file system, limiting maximum file "
"size to 4GB"
msgstr ""
"Færdig Download Mappe %s er på FAT filsystem, begrænser maksimal "
"filstørrelse til 4GB"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed postprocessor"
msgstr ""
msgstr "Genstarter på grund af styrt efterbehandler"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed downloader"
msgstr ""
msgstr "Genstarter på grund af styrt downloader"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed assembler"
msgstr ""
msgstr "Genstarter på grund af styrt assembler"
#. Warning message
#: sabnzbd/__init__.py
msgid "Cannot access PID file %s"
msgstr ""
msgstr "Kan ikke få adgang til PID-fil %s"
#: sabnzbd/api.py, sabnzbd/emailer.py
msgid "Email succeeded"
@@ -194,18 +206,25 @@ msgid ""
"server (port 80), possibly an indexer, not a usenet server. You have to fill"
" a usenet server."
msgstr ""
"Kunne ikke forbinde til %s på port %s. Det ser ud til at %s fungerer som en "
"webserver (port 80), muligvis en indexer, ikke en usenet server. Du skal "
"angive en usenet server."
#: sabnzbd/api.py
msgid ""
"Could not connect to %s on port %s. Use the default usenet settings: port "
"563 and SSL turned on"
msgstr ""
"Kunne ikke forbinde til %s på port %s. Brug standard usenet-indstillinger: "
"port 563 og SSL slået til"
#: sabnzbd/api.py
msgid ""
"Could not connect to %s on port %s. Use the default usenet settings: port "
"119 and SSL turned off"
msgstr ""
"Kunne ikke forbinde til %s på port %s. Brug standard usenet-indstillinger: "
"port 119 og SSL slået fra"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Server address \"%s:%s\" is not valid."
@@ -320,17 +339,33 @@ msgstr "Afbrudt, uønsket extension fundet"
#: sabnzbd/assembler.py
msgid "Job \"%s\" is probably encrypted due to RAR with same name inside this RAR"
msgstr ""
"Job \"%s\" er sandsynligvis krypteret pga. en RAR med samme navn inde i "
"denne RAR"
#. Warning message
#: sabnzbd/assembler.py
msgid "Job \"%s\" is probably encrypted: \"password\" in filename \"%s\""
msgstr ""
msgstr "Job \"%s\" er sandsynligvis krypteret: \"password\" i filnavnet \"%s\""
#. Warning message
#: sabnzbd/bpsmeter.py
msgid "Quota spent, pausing downloading"
msgstr "Kvote brugt, pause downloading"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kvota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Fejl parameter"
@@ -349,7 +384,7 @@ msgstr "Ugyldig server adresse."
#: sabnzbd/cfg.py
msgid "%s is not a valid script"
msgstr ""
msgstr "%s er ikke et gyldigt script"
#: sabnzbd/cfg.py
msgid "%s is not a correct octal value"
@@ -361,11 +396,13 @@ msgid ""
"Permissions setting of %s might deny SABnzbd access to the files and folders"
" it creates."
msgstr ""
"Tilladelsesindstillingen for %s kan nægte SABnzbd adgang til de filer og "
"mapper, den opretter."
#. Warning message
#: sabnzbd/cfg.py
msgid "Network path \"%s\" should not be used here"
msgstr ""
msgstr "Netværksstien \"%s\" bør ikke bruges her"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -376,6 +413,8 @@ msgid ""
"The Completed Download Folder cannot be the same or a subfolder of the "
"Temporary Download Folder"
msgstr ""
"Mappen Færdige downloads kan ikke være den samme som eller en undermappe af "
"den Midlertidige download-mappe"
#. Warning message
#: sabnzbd/cfg.py
@@ -383,17 +422,21 @@ msgid ""
"Do not use a folder in the application folder as your Scripts Folder, it "
"might be emptied during updates."
msgstr ""
"Brug ikke en mappe i programmappen som din Scripts-mappe, den kan blive tømt"
" under opdateringer."
#. Warning message
#: sabnzbd/cfg.py
msgid ""
"The par2 application was switched, any custom par2 parameters were removed"
msgstr ""
"par2-programmet blev skiftet, eventuelle brugerdefinerede par2-parametre er "
"fjernet"
#. Warning message
#: sabnzbd/config.py
msgid "Configuration locked, cannot save settings"
msgstr ""
msgstr "Konfiguration låst, kan ikke gemme indstillinger"
#. Error message
#: sabnzbd/config.py
@@ -408,7 +451,7 @@ msgstr "Kan ikke oprette backup fil for %s"
#. Warning message
#: sabnzbd/config.py
msgid "Could not restore backup"
msgstr ""
msgstr "Kunne ikke gendanne backup"
#. Error message
#: sabnzbd/config.py
@@ -452,23 +495,23 @@ msgstr "Ukendt fejl under afkodning af %s"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate skipped due to DVD/Bluray directories"
msgstr ""
msgstr "Dekryptering sprang over på grund af DVD/Blu-ray-mapper"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate corrected the extension of %d file(s)"
msgstr ""
msgstr "Dekryptering rettede filendelsen på %d fil(er)"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d file(s)"
msgstr ""
msgstr "Dekryptering omdøbte %d fil(er)"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
msgstr "Dekryptering omdøbte %d undertekst-fil(er)"
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
msgstr ""
msgstr "Direkte udpakning"
#. PP status
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
@@ -482,13 +525,15 @@ msgstr "Udpakket %s filer/mapper i %s"
#. Warning message
#: sabnzbd/directunpacker.py
msgid "Direct Unpack was automatically enabled."
msgstr ""
msgstr "Direkte udpakning blev automatisk aktiveret."
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid ""
"Jobs will start unpacking during the downloading to reduce post-processing "
"time. Only works for jobs that do not need repair."
msgstr ""
"Jobs vil begynde at blive udpakket under download for at reducere "
"efterbehandlingstiden. Virker kun for jobs, der ikke kræver reparation."
#. Error message
#: sabnzbd/dirscanner.py
@@ -528,7 +573,7 @@ msgstr "Server %s vil blive ignoreret for i %s minutter"
#. Warning message
#: sabnzbd/downloader.py
msgid "There are no active servers!"
msgstr ""
msgstr "Der er ingen aktive servere!"
#. Error message
#: sabnzbd/downloader.py
@@ -538,12 +583,12 @@ msgstr "Det lykkedes ikke at initialisere %s@%s med begrundelse %s"
#. Error message
#: sabnzbd/downloader.py
msgid "Fatal error in Downloader"
msgstr ""
msgstr "Alvorlig fejl i Downloader"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
msgstr "%s@%s: Modtog ukendt statuskode %s for artikel %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
@@ -554,6 +599,8 @@ msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgstr ""
"Login fra for mange forskellige IP-adresser til server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
@@ -576,12 +623,12 @@ msgstr "Påbegynder lukning af SABnzbd"
#. Warning message
#: sabnzbd/downloader.py
msgid "Server %s is expiring in %s day(s)"
msgstr ""
msgstr "Server %s udløber om %s dag(e)"
#. Warning message
#: sabnzbd/downloader.py
msgid "Server %s has used the specified quota"
msgstr ""
msgstr "Server %s har brugt den angivne kvota"
#: sabnzbd/emailer.py
msgid "Failed to connect to mail server"
@@ -687,7 +734,7 @@ msgstr "Det lykkedes ikke at flytte %s til %s"
#. Error message
#: sabnzbd/filesystem.py
msgid "Blocked attempt to create directory %s"
msgstr ""
msgstr "Blokerede forsøg på at oprette mappe %s"
#. Error message
#: sabnzbd/filesystem.py
@@ -707,17 +754,17 @@ msgstr "Downloadning af %s mislykkedes"
#. Warning message
#: sabnzbd/filesystem.py
msgid "%s is not writable at all. This blocks downloads."
msgstr ""
msgstr "%s er slet ikke skrivbar. Dette blokerer downloads."
#. Warning message
#: sabnzbd/filesystem.py
msgid "Cannot write a long filename to %s. This can cause problems."
msgstr ""
msgstr "Kan ikke skrive et langt filnavn til %s. Dette kan give problemer."
#. Warning message
#: sabnzbd/filesystem.py
msgid "Cannot write a unicode filename to %s. This can cause problems."
msgstr ""
msgstr "Kan ikke skrive et Unicode-filnavn til %s. Dette kan give problemer."
#. Warning message
#: sabnzbd/filesystem.py
@@ -725,23 +772,15 @@ msgid ""
"%s is not writable with special character filenames. This can cause "
"problems."
msgstr ""
"%s er ikke skrivbar med filnavne med specialtegn. Dette kan give problemer."
#: sabnzbd/interface.py
msgid "Refused connection from:"
msgstr ""
msgstr "Afviste forbindelse fra:"
#: sabnzbd/interface.py
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Bruger logget på webgrænsefladen"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Bruger logget ind"
msgstr "Afviste forbindelse med værtsnavn \"%s\" fra:"
#: sabnzbd/interface.py
msgid ""
@@ -759,6 +798,15 @@ msgstr ""
"Forkert API-nøgle, anvend api-nøglen fra Konfiguration->Generelt i dit "
"tredjepartsprogram:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Bruger logget på webgrænsefladen"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Bruger logget ind"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -766,7 +814,7 @@ msgstr "Mislykkede login forsøg fra %s"
#: sabnzbd/interface.py
msgid "Invalid backup archive"
msgstr ""
msgstr "Ugyldigt backup-arkiv"
#. Config->RSS, tab header
#: sabnzbd/interface.py, sabnzbd/skintext.py
@@ -817,6 +865,7 @@ msgstr "Udefineret server!"
msgid ""
"Category folder cannot be a subfolder of the Temporary Download Folder."
msgstr ""
"Kategorimappe kan ikke være en undermappe i den midlertidige download-mappe."
#: sabnzbd/interface.py, sabnzbd/skintext.py
msgid "ERROR:"
@@ -937,6 +986,8 @@ msgstr "Standser..."
msgid ""
"To prevent all helpful warnings, disable Special setting 'helpful_warnings'."
msgstr ""
"For at undertrykke alle hjælpsomme advarsler, deaktiver Special-"
"indstillingen 'helpful_warnings'."
#: sabnzbd/misc.py
msgid "d"
@@ -957,7 +1008,7 @@ msgstr "Opdatering tilgængelig!"
#. Error message
#: sabnzbd/misc.py
msgid "Failed to upload file: %s"
msgstr ""
msgstr "Kunne ikke uploade fil: %s"
#. Error message
#: sabnzbd/misc.py
@@ -970,16 +1021,18 @@ msgid ""
"Your password file contains more than 30 passwords, testing all these "
"passwords takes a lot of time. Try to only list useful passwords."
msgstr ""
"Din adgangskodefil indeholder over 30 adgangskoder, at afprøve dem alle "
"tager lang tid. Prøv kun at liste nyttige adgangskoder."
#. Warning message
#: sabnzbd/misc.py
msgid "Failed to read the password file %s"
msgstr ""
msgstr "Kunne ikke læse adgangskodefilen %s"
#. Error message
#: sabnzbd/misc.py
msgid "[%s] The command in build_command is undefined."
msgstr ""
msgstr "[%s] Kommandoen i build_command er ikke defineret."
#. Error message
#: sabnzbd/misc.py
@@ -1075,11 +1128,7 @@ msgstr "Udpakning mislykkedes, skrivefejl eller disken fuld?"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Udpakningen mislykkedes, stien er for lang"
msgstr "Udpakning mislykkedes, disken er fuld"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
@@ -1176,7 +1225,7 @@ msgstr "[%s] Repareret i %s"
#: sabnzbd/newsunpack.py
msgid "Verifying repair"
msgstr ""
msgstr "Verificerer reparation"
#. Notification
#: sabnzbd/newsunpack.py, sabnzbd/notifier.py
@@ -1208,6 +1257,8 @@ msgid ""
"Certificate hostname mismatch: the server hostname is not listed in the "
"certificate. This is a server issue."
msgstr ""
"Certifikatets værtsnavn stemmer ikke: serverens værtsnavn er ikke angivet i "
"certifikatet. Dette er et serverproblem."
#: sabnzbd/newswrapper.py
msgid ""
@@ -1215,6 +1266,9 @@ msgid ""
" locally injected certificate (for example by firewall or virus scanner). "
"Try setting Certificate verification to Medium."
msgstr ""
"Certifikatet kunne ikke valideres. Dette kan være et serverproblem eller "
"skyldes et lokalt indsat certifikat (f.eks. af firewall eller virus-"
"scanner). Prøv at sætte Certifikatkontrol til Medium."
#: sabnzbd/newswrapper.py
msgid "Server %s uses an untrusted certificate [%s]"
@@ -1227,7 +1281,7 @@ msgstr "Wiki"
#: sabnzbd/newswrapper.py
msgid "Failed to connect: %s %s@%s:%s (%s)"
msgstr ""
msgstr "Kunne ikke oprette forbindelse: %s %s@%s:%s (%s)"
#. Notification
#: sabnzbd/notifier.py
@@ -1266,7 +1320,7 @@ msgstr "Andre beskeder"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
msgstr "Åbn mappe"
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
@@ -1285,7 +1339,7 @@ msgstr "Ikke tilgængelig"
#: sabnzbd/notifier.py
msgid "Failed to send macOS notification"
msgstr ""
msgstr "Kunne ikke sende macOS-notifikation"
#. Warning message
#: sabnzbd/notifier.py
@@ -1295,21 +1349,21 @@ msgstr "Kunne ikke sende Prowl besked"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send Apprise message - no URLs defined"
msgstr ""
msgstr "Kunne ikke sende Apprise-besked - ingen URL'er angivet"
#. Warning message
#: sabnzbd/notifier.py
msgid "One or more Apprise URLs could not be loaded."
msgstr ""
msgstr "En eller flere Apprise-URL'er kunne ikke indlæses."
#: sabnzbd/notifier.py
msgid "Failed to send one or more Apprise Notifications"
msgstr ""
msgstr "Kunne ikke sende en eller flere Apprise-notifikationer"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send Apprise message"
msgstr ""
msgstr "Kunne ikke sende Apprise-besked"
#. Error message
#: sabnzbd/notifier.py
@@ -1585,6 +1639,8 @@ msgid ""
"Unable to bind to port %s on %s. Some other software uses the port or "
"SABnzbd is already running."
msgstr ""
"Kan ikke binde til port %s på %s. En anden software bruger porten eller "
"SABnzbd kører allerede."
#. Warning message
#: sabnzbd/panic.py
@@ -1860,7 +1916,7 @@ msgstr "Udpak"
#. PP phase "deobfuscate"
#: sabnzbd/skintext.py
msgid "Deobfuscate"
msgstr ""
msgstr "Af-obfuskér"
#. PP phase "script" - Notification Script settings
#: sabnzbd/skintext.py
@@ -2362,6 +2418,11 @@ msgstr "Navn"
msgid "Retry"
msgstr "Forsøg igen"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -2576,15 +2637,15 @@ msgstr "Nameserver/DNS Lookup"
#: sabnzbd/skintext.py
msgid "Download speed limited by"
msgstr ""
msgstr "Downloadhastighed begrænset af"
#: sabnzbd/skintext.py
msgid "Disk speed"
msgstr ""
msgstr "Diskhastighed"
#: sabnzbd/skintext.py
msgid "System load"
msgstr ""
msgstr "Systembelastning"
#. Do not translate Pystone
#: sabnzbd/skintext.py
@@ -2601,7 +2662,7 @@ msgstr "Komplet mappe hastighed"
#: sabnzbd/skintext.py
msgid "Internet Bandwidth"
msgstr ""
msgstr "Internetbåndbredde"
#: sabnzbd/skintext.py
msgid "Repeat test"
@@ -2616,6 +2677,8 @@ msgid ""
"Adds a verified test NZB of the specified size, filled with random data. Can"
" be used to verify your setup."
msgstr ""
"Tilføjer en verificeret test-NZB af den angivne størrelse, fyldt med "
"tilfældige data. Kan bruges til at verificere din opsætning."
#: sabnzbd/skintext.py
msgid "Config File"
@@ -2629,7 +2692,7 @@ msgstr "Brugt chace"
#. What platform we are on (e.g. Windows/macOS/Ubuntu/UnRaid/etc)
#: sabnzbd/skintext.py
msgid "Platform"
msgstr ""
msgstr "Platform"
#: sabnzbd/skintext.py
msgid ""
@@ -2735,11 +2798,11 @@ msgstr "Port som SABnzbd ska lytte på."
#: sabnzbd/skintext.py
msgid "Web Interface Theme"
msgstr ""
msgstr "Tema for webgrænseflade"
#: sabnzbd/skintext.py
msgid "Choose a theme."
msgstr ""
msgstr "Vælg et tema."
#: sabnzbd/skintext.py
msgid "SABnzbd Username"
@@ -2780,6 +2843,8 @@ msgid ""
"Modern web browsers and other clients will not accept self-signed "
"certificates and will give a warning and/or won't connect at all."
msgstr ""
"Moderne webbrowsere og andre klienter accepterer ikke selvsignerede "
"certifikater og vil give en advarsel og/eller slet ikke forbinde."
#: sabnzbd/skintext.py
msgid "HTTPS Port"
@@ -2861,7 +2926,7 @@ msgstr ""
#. Config->Scheduling
#: sabnzbd/skintext.py
msgid "Create backup"
msgstr ""
msgstr "Opret backup"
#: sabnzbd/skintext.py
msgid ""
@@ -2873,7 +2938,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Cleanup List"
msgstr "Ryd listen"
msgstr "Rydningsliste"
#: sabnzbd/skintext.py
msgid ""
@@ -2885,7 +2950,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "History Retention"
msgstr ""
msgstr "Bevarelse af historik"
#: sabnzbd/skintext.py
msgid "Keep all jobs"
@@ -2895,28 +2960,29 @@ msgstr "Behold alle jobs"
msgid ""
"Move jobs to the archive if the history exceeds specified number of jobs"
msgstr ""
"Flyt jobs til arkivet hvis historikken overstiger det angivne antal jobs"
#: sabnzbd/skintext.py
msgid ""
"Delete jobs if the history and archive exceeds specified number of jobs"
msgstr ""
msgstr "Slet jobs hvis historik og arkiv overstiger det angivne antal jobs"
#: sabnzbd/skintext.py
msgid "Move jobs to the archive after specified number of days"
msgstr ""
msgstr "Flyt jobs til arkivet efter angivet antal dage"
#: sabnzbd/skintext.py
msgid ""
"Delete jobs from the history and archive after specified number of days"
msgstr ""
msgstr "Slet jobs fra historik og arkiv efter angivet antal dage"
#: sabnzbd/skintext.py
msgid "Move all completed jobs to archive"
msgstr ""
msgstr "Flyt alle fuldførte jobs til arkivet"
#: sabnzbd/skintext.py
msgid "Delete all completed jobs"
msgstr ""
msgstr "Slet alle fuldførte jobs"
#: sabnzbd/skintext.py
msgid "Jobs"
@@ -3064,14 +3130,16 @@ msgstr ""
msgid ""
"Use Sorting to automatically organize and rename your completed downloads."
msgstr ""
"Brug Sortering til automatisk at organisere og omdøbe dine fuldførte "
"downloads."
#: sabnzbd/skintext.py
msgid "Minimum Free Space for Completed Download Folder"
msgstr ""
msgstr "Minimum fri plads for mappen Færdige downloads"
#: sabnzbd/skintext.py
msgid "Will not work if a category folder is on a different disk."
msgstr ""
msgstr "Virker ikke hvis en kategorimappe er på en anden disk."
#. Auto-resume download on the reset day
#: sabnzbd/skintext.py
@@ -3145,7 +3213,7 @@ msgstr "Systemmapper"
#: sabnzbd/skintext.py
msgid "Hidden Folders"
msgstr ""
msgstr "Skjulte mapper"
#: sabnzbd/skintext.py
msgid "Administrative Folder"
@@ -3161,7 +3229,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Backup Folder"
msgstr ""
msgstr "Backup-mappe"
#: sabnzbd/skintext.py
msgid ""
@@ -3373,11 +3441,11 @@ msgstr "Brugt før, en NZB kommer ind i køen."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
msgstr "Script når køen afsluttes"
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
msgstr "Køres når køens downloads er afsluttet."
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
@@ -3547,10 +3615,6 @@ msgstr "Efterbehandling"
msgid "Naming"
msgstr "Navngivning"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kvota"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Hvor meget der kan downloades i denne måned (K/M/G)"
@@ -3658,9 +3722,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4150,26 +4214,29 @@ msgstr "Enhed som meddelse skal sendes til"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Enable Apprise notifications"
msgstr ""
msgstr "Aktiver Apprise-notifikationer"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgstr ""
"Send notifikationer via Apprise til næsten enhver notifikationstjeneste"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr ""
msgstr "Standard Apprise-URL'er"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgstr ""
msgstr "Brug komma og/eller mellemrum for at angive flere URL'er."
#: sabnzbd/skintext.py
msgid ""
"Override the default URLs for specific notification types below, if desired."
msgstr ""
"Tilsidesæt standard-URL'er for specifikke notifikationstyper nedenfor, hvis "
"ønsket."
#. Header for Notification Script notification section
#: sabnzbd/skintext.py
@@ -4618,6 +4685,12 @@ msgstr "Fjern alle"
msgid "Retry all"
msgstr "Prøv igen alle"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Hent NZB fra URL"
@@ -4868,6 +4941,11 @@ msgstr "Afslut SABnzbd"
msgid "Start Wizard"
msgstr "Start guide"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -52,6 +52,13 @@ msgstr ""
"Konnte Web-Vorlage nicht finden: %s Versuche die Standard-Vorlage zu "
"verwenden."
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -369,6 +376,20 @@ msgstr "Aufgabe \"%s\" ist wahrscheinlich verschlüsselt: \"Passwort\" im Datein
msgid "Quota spent, pausing downloading"
msgstr "Kontingent aufgebraucht, Downloads werden angehalten"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kontingent"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Fehlerhafter Parameter"
@@ -798,15 +819,6 @@ msgstr "Abgelehnte Verbindung von:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Verbindung vom Host \"%s\" abgelehnt von:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Benutzer im Web-Interface angemeldet"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Benutzer angemeldet"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -823,6 +835,15 @@ msgstr ""
"API-Schlüssel ungültig. Bitte API-Schlüssel aus Einstellungen->Allgemein in "
"die externe Anwendung eingeben:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Benutzer im Web-Interface angemeldet"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Benutzer angemeldet"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1150,10 +1171,6 @@ msgstr ""
msgid "Unpacking failed, disk full"
msgstr "Fehler beim Entpacken: Festplatte voll"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Entpacken fehlgeschlagen, Pfad ist zu lang"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Entpacken fehlgeschlagen. Archiv benötigt ein Passwort."
@@ -2458,6 +2475,11 @@ msgstr "Name"
msgid "Retry"
msgstr "Erneut versuchen"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3722,10 +3744,6 @@ msgstr "Nachbearbeitung"
msgid "Naming"
msgstr "Benennung"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kontingent"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Wie viel kann in diesem Monat heruntergeladen werden (K/M/G)?"
@@ -3839,13 +3857,10 @@ msgstr "5 Tage vor dem Ablauf des Accounts warnen."
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Kontingent für dieses Konto, gezählt ab dem Zeitpunkt, an dem es festgelegt "
"wird. In Bytes, optional gefolgt von K, M, G.<br />Warne, wenn es 0 "
"erreicht, wird alle paar Minuten überprüft."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4836,6 +4851,12 @@ msgstr "Alle löschen"
msgid "Retry all"
msgstr "Alle wiederholen"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "NZB aus URL laden"
@@ -5091,6 +5112,11 @@ msgstr "SABnzbd beenden"
msgid "Start Wizard"
msgstr "Assistenten starten"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Backup wiederherstellen"

View File

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,13 @@ msgstr "Web-käyttöliittymän käynnistys epäonnistui"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Web-mallia %s ei löydy, yritetään käyttää oletusmallia"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -327,6 +334,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Latausrajoitus saavutettu, keskeytetään lataukset"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Latausrajoitus"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Virheellinen parametri"
@@ -729,15 +750,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Käyttäjä kirjautui sisään web-käyttöliittymään"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Käyttäjä kirjautui sisään"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -754,6 +766,15 @@ msgstr ""
"API avain virheellinen, käytä Asetukset->Yleiset löytyvää api avainta "
"käyttämääsi kolmannen osapuolen ohjelmaan:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Käyttäjä kirjautui sisään web-käyttöliittymään"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Käyttäjä kirjautui sisään"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1072,10 +1093,6 @@ msgstr "Purkaminen epäonnistui, kirjoitusvirhe tai levy täynnä?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Purkaminen epäonnistui, polku on liian pitkä"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Purkaminen epäonnistui, arkisto vaatii salasanan"
@@ -2354,6 +2371,11 @@ msgstr "Nimi"
msgid "Retry"
msgstr "Yritä uudelleen"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3554,10 +3576,6 @@ msgstr "Jälkikäsittely"
msgid "Naming"
msgstr "Nimeäminen"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Latausrajoitus"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Kuinka paljon voidaan ladata tässä kuussa (K/M/G)"
@@ -3663,9 +3681,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4624,6 +4642,12 @@ msgstr "Poista kaikki"
msgid "Retry all"
msgstr "Yritä uudelleen kaikki"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Nouda NZB osoitteesta"
@@ -4876,6 +4900,11 @@ msgstr "Poistu SABnzbd:stä"
msgid "Start Wizard"
msgstr "Käynnistä velho"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -2,7 +2,7 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# Safihre <safihre@sabnzbd.org>, 2025
# Fred L <88com88@gmail.com>, 2025
#
msgid ""
@@ -39,6 +39,15 @@ msgstr ""
"Impossible de trouver le template de l'interface web : %s, nouvelle "
"tentative avec le template standard"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
"Impossible d'établir une connexion avec OpenSSL, les fonctions de connexion "
"SSL optimisées ne seront pas utilisées."
#. Error message
#: SABnzbd.py
msgid ""
@@ -361,6 +370,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Quota atteint, téléchargement mis en pause"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr "Avertissement de limite de quota (%d%%)"
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "Le téléchargement a repris après la réinitialisation du quota."
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Paramètre incorrect"
@@ -787,15 +810,6 @@ msgstr "Connexion refusée de:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Connexion refusée avec le nom d'hôte \"%s\" à partir de :"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utilisateur connecté à l'interface web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utilisateur connecté"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -812,6 +826,15 @@ msgstr ""
"Clé API incorrecte, utilisez la clé API de la configuration générale dans "
"votre application tierce :"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utilisateur connecté à l'interface web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utilisateur connecté"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1142,10 +1165,6 @@ msgstr ""
msgid "Unpacking failed, disk full"
msgstr "Échec de l'extraction, disque plein"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Extraction échoué, le chemin est trop long"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Échec de l'extraction, l'archive nécessite un mot de passe"
@@ -2442,6 +2461,11 @@ msgstr "Nom"
msgid "Retry"
msgstr "Réessayer"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr "Marquer comme terminé & supprimer les fichiers temporaires"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3704,10 +3728,6 @@ msgstr "Post-traitement"
msgid "Naming"
msgstr "Appellation"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quota"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Combien peut-être télécharger ce mois (K/M/G)"
@@ -3818,13 +3838,13 @@ msgstr "Avertir 5 jours avant la date d'expiration du compte."
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Quota pour ce compte calculé à partir du moment où il est défini. En octets,"
" éventuellement suivi de K,M,G.<br />Avertir quand il atteint 0, vérifié "
"toutes les quelques minutes."
"Quota pour ce serveur, calculé à partir du moment où il est défini. En "
"octets, suivi éventuellement de K,M,G.<br />Vérifié toutes les quelques "
"minutes. Une notification est envoyée lorsque le quota est atteint."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4818,6 +4838,14 @@ msgstr "Tout supprimer"
msgid "Retry all"
msgstr "Réessayer tous"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
"Êtes-vous sûr de vouloir supprimer tous les dossiers de votre Dossier de "
"Téléchargement Temporaire ? Cette opération ne peut pas être annulée !"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Importer le NZB depuis l'URL"
@@ -5076,6 +5104,11 @@ msgstr "Quitter SABnzbd"
msgid "Start Wizard"
msgstr "Lancer l'assistant"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr "Cliquez sur Tester le Serveur avant de continuer"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Restaurer la sauvegarde"

View File

@@ -3,13 +3,13 @@
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# ION, 2024
# ION, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: ION, 2024\n"
"Last-Translator: ION, 2025\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -37,6 +37,13 @@ msgstr "כישלון בהתחלת ממשק רשת"
msgid "Cannot find web template: %s, trying standard template"
msgstr "לא ניתן למצוא תבניות רשת: %s, מנסה תבנית תקנית"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -328,6 +335,20 @@ msgstr "העבודה \"%s\" כנראה מוצפנת: \"סיסמה\" בשם הק
msgid "Quota spent, pausing downloading"
msgstr "מכסה נוצלה, משהה הורדה"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "מכסה"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "פרמטר שגוי"
@@ -738,15 +759,6 @@ msgstr "חיבור מסורב מאת:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr "חיבור מסורב עם שם המארח \"%s\" מאת:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "משתמש התחבר לממשק הרשת"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "משתמש התחבר"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -761,6 +773,15 @@ msgid ""
"program:"
msgstr "מפתח API שגוי, השתמש במפתח ה־API מתצורה->כללי בתוכנית הצד השלישי שלך:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "משתמש התחבר לממשק הרשת"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "משתמש התחבר"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1080,11 +1101,7 @@ msgstr "פריקה נכשלה, שגיאת כתיבה או דיסק מלא?"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "פריקה נכשלה, נתיב ארוך מדי"
msgstr "פריקה נכשלה, דיסק מלא"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
@@ -2367,6 +2384,11 @@ msgstr "שם"
msgid "Retry"
msgstr "נסה שוב"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -2638,7 +2660,7 @@ msgstr "מטמון בשימוש"
#. What platform we are on (e.g. Windows/macOS/Ubuntu/UnRaid/etc)
#: sabnzbd/skintext.py
msgid "Platform"
msgstr ""
msgstr "פלטפורמה"
#: sabnzbd/skintext.py
msgid ""
@@ -3558,10 +3580,6 @@ msgstr "בתר־עיבוד"
msgid "Naming"
msgstr "מתן שמות"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "מכסה"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "כמה ניתן להוריד החודש (ק״ב/מ״ב/ג״ב)"
@@ -3666,12 +3684,10 @@ msgstr "הזהר 5 ימים טרם תאריך תפוגת החשבון."
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"מכסה עבור חשבון זה, נספרת מהזמן שהיא הוגדרה. בבתים, יכולה לבוא עם K,M,G.<br "
"/>הזהר כאשר המכסה מגיעה אל 0, היא נבדקת כל כמה דקות."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4634,6 +4650,12 @@ msgstr "מחק הכל"
msgid "Retry all"
msgstr "נסה שוב הכל"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "משוך NZB מכתובת"
@@ -4888,6 +4910,11 @@ msgstr "צא מן SABnzbd"
msgid "Start Wizard"
msgstr "התחל אשף"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "שחזר גיבוי"

View File

@@ -36,6 +36,13 @@ msgstr "Impossibile avviare l'interfaccia web"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Impossibile trovare il modello web: %s, si prova il modello standard"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -354,6 +361,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Quota esaurita, download in pausa"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Parametro non corretto"
@@ -778,15 +799,6 @@ msgstr "Connessione rifiutata da:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Connessione rifiutata con hostname \"%s\" da:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utente ha effettuato l'accesso all'interfaccia web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utente ha effettuato l'accesso"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -803,6 +815,15 @@ msgstr ""
"Chiave API non corretta, Usa la chiave API da Config->Generale nel tuo "
"programma di terze parti:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utente ha effettuato l'accesso all'interfaccia web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utente ha effettuato l'accesso"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1127,10 +1148,6 @@ msgstr "Estrazione fallita, errore di scrittura o disco pieno?"
msgid "Unpacking failed, disk full"
msgstr "Estrazione fallita, disco pieno"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Estrazione fallita, percorso troppo lungo"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Estrazione fallita, l'archivio richiede una password"
@@ -2422,6 +2439,11 @@ msgstr "Nome"
msgid "Retry"
msgstr "Riprova"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3665,10 +3687,6 @@ msgstr "Post-elaborazione"
msgid "Naming"
msgstr "Denominazione"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quota"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Quanto può essere scaricato questo mese (K/M/G)"
@@ -3778,13 +3796,10 @@ msgstr "Avvisa 5 giorni prima della data di scadenza dell'account."
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Quota per questo account, contata dal momento in cui è impostata. In byte, "
"opzionalmente seguito da K,M,G.<br />Avvisa quando raggiunge 0, controllato "
"ogni pochi minuti."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4772,6 +4787,12 @@ msgstr "Elimina tutto"
msgid "Retry all"
msgstr "Riprova tutto"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Recupera NZB da URL"
@@ -5028,6 +5049,11 @@ msgstr "Esci da SABnzbd"
msgid "Start Wizard"
msgstr "Avvia procedura guidata"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Ripristina backup"

View File

@@ -36,6 +36,13 @@ msgstr "Kunne ikke starte webgrensesnittet"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Kan ikke finne webmal: %s, prøver standardmal"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -325,6 +332,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Kvote oppbrukt, setter nedlasting på pause"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kvote"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Feil parameter"
@@ -726,15 +747,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Bruker logget inn i webgrensesnitt"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Bruker pålogget"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -751,6 +763,15 @@ msgstr ""
"API-nøkkel er feil, bruk API-nøkkel fra Konfigurasjon->Generelt i ditt "
"tredjepartsprogram:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Bruker logget inn i webgrensesnitt"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Bruker pålogget"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1069,10 +1090,6 @@ msgstr "Utpakking mislyktes, skrivefeil eller er disken full?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Utpakking feilet, stien er for lang"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Utpakking mislyktes, arkivet krever passord"
@@ -2352,6 +2369,11 @@ msgstr "Navn"
msgid "Retry"
msgstr "Prøv igjen"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3531,10 +3553,6 @@ msgstr "Postprosessering"
msgid "Naming"
msgstr "Filnavn"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kvote"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Hvor mye can lastes ned denne måneden (K/M/G)"
@@ -3642,9 +3660,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4598,6 +4616,12 @@ msgstr "Ta bort alle"
msgid "Retry all"
msgstr "Prøv alle på nytt"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Hent NZB fra URL"
@@ -4848,6 +4872,11 @@ msgstr "Avslutt SABnzbd"
msgid "Start Wizard"
msgstr "Start Veiviser"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -38,6 +38,13 @@ msgstr "Webinterface kan niet gestart worden"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Websjabloon %s niet te vinden; het standaardsjabloon wordt gebruikt."
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -351,6 +358,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Quotum verbruikt, download is gestopt"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quotum"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Incorrecte parameter"
@@ -781,15 +802,6 @@ msgstr "Verbinding geweigerd van: "
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Verbinding met hostnaam \"%s\" geweigerd van:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Gebruiker heeft ingelogd"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Gebruiker ingelogd"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -806,6 +818,15 @@ msgstr ""
"API-sleutel incorrect; vul de API-sleutel van 'Configuratie' => 'Algemeen' "
"in bij het externe programma:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Gebruiker heeft ingelogd"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Gebruiker ingelogd"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1131,10 +1152,6 @@ msgstr "Uitpakken mislukt, schrijffout of schijf vol?"
msgid "Unpacking failed, disk full"
msgstr "Uitpakken mislukt, de schijf is vol"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Uitpakken mislukt, bestandspad is te lang"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Uitpakken mislukt, archief vereist wachtwoord"
@@ -2425,6 +2442,11 @@ msgstr "Naam"
msgid "Retry"
msgstr "Opnieuw"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3665,10 +3687,6 @@ msgstr "Nabewerking"
msgid "Naming"
msgstr "Naamgeving"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quotum"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Hoeval mag deze maand worden gedownload (K/M/G)"
@@ -3780,14 +3798,10 @@ msgstr "Ontvang 5 dagen voor de verloopdatum een waarschuwing."
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Quotum voor dit account, wordt geteld vanaf het moment dat het voor het "
"eerst ingesteld wordt. In bytes, in K,M,G notatie.<br />Er wordt een "
"waarschuwing gegeven als het quotum bereikt is, dit wordt elke paar minuten "
"gecontroleerd."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4770,6 +4784,12 @@ msgstr "Alles wissen"
msgid "Retry all"
msgstr "Alles opnieuw proberen"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Haal NZB op via URL"
@@ -5025,6 +5045,11 @@ msgstr "Stop SABnzbd"
msgid "Start Wizard"
msgstr "Wizard starten"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Backup herstellen"

View File

@@ -36,6 +36,13 @@ msgstr "Nie udało się uruchomić interfejsu WWW"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Nie znaleziono szablonu: %s, próbuję użyć standardowego szablonu"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -324,6 +331,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Przekroczono limit, wstrzymywanie pobierania"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Limit pobierania"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Błędny parametr"
@@ -729,15 +750,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -754,6 +766,15 @@ msgstr ""
"Klucz API jest nieprawidłowy, użyj klucza API z sekcji Konfiguracja->Ogólne "
"w zewnętrznym programie:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1072,10 +1093,6 @@ msgstr "Rozpakowywanie nie powiodło się, błąd zapisu lub zapełniony dysk?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Rozpakowywanie nie powiodło się, zbyt długa ścieżka"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Rozpakowywanie nie powiodło się, archiwum wymaga podania hasła"
@@ -2361,6 +2378,11 @@ msgstr "Nazwa"
msgid "Retry"
msgstr "Ponów"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3542,10 +3564,6 @@ msgstr "Przetwarzanie końcowe"
msgid "Naming"
msgstr "Nazwy"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Limit pobierania"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Ile danych można pobrać w miesiącu (K/M/G)"
@@ -3654,9 +3672,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4610,6 +4628,12 @@ msgstr "Usuń wszystko"
msgid "Retry all"
msgstr "Ponów wszystkie"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Pobierz NZB z URL"
@@ -4858,6 +4882,11 @@ msgstr "Wyjście z SABnzbd"
msgid "Start Wizard"
msgstr "Uruchom kreatora konfiguracji"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -38,6 +38,13 @@ msgid "Cannot find web template: %s, trying standard template"
msgstr ""
"Não foi possível encontrar o template web: %s. Tentando o template padrão"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -336,6 +343,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Quota esgotada, pausando o download"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Parâmetro incorreto"
@@ -741,15 +762,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -766,6 +778,15 @@ msgstr ""
"Chave de API incorreta. Use a chave de API de Configuração->Geral em seu "
"programa de terceiros:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1084,10 +1105,6 @@ msgstr "A descompactação falhou. Erro de escrita ou disco cheio?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Descompactação falhou, o caminho é muito extenso"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "A descompactação falhou. O arquivo exige uma senha"
@@ -2372,6 +2389,11 @@ msgstr "Nome"
msgid "Retry"
msgstr "Repetir"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3554,10 +3576,6 @@ msgstr "Pós-processamento"
msgid "Naming"
msgstr "Nomeando"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Quota"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Quanto pode ser baixado neste mês (K/M/G)"
@@ -3665,9 +3683,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4621,6 +4639,12 @@ msgstr "Excluir Todos"
msgid "Retry all"
msgstr "Repetir todos"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Buscar NZB de uma URL"
@@ -4869,6 +4893,11 @@ msgstr "Sair do SABnzbd"
msgid "Start Wizard"
msgstr "Iniciar o Assistente"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -37,6 +37,13 @@ msgstr "Pornirea interfeţei-web nereuşită"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Nu se poate găsi şablon web:%s, se încearcă şablon standard"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -340,6 +347,20 @@ msgstr "Sarcina „%s” este probabil criptată: „parolă” în fișierul
msgid "Quota spent, pausing downloading"
msgstr "Cotă epuizată, întrerupem descărcarea"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Cotă"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Parametru Incorect"
@@ -749,15 +770,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Conectare refuzată cu gazda „%s” de la:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utilizatorul s-a autentificat în interfața web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utilizator logat"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -774,6 +786,15 @@ msgstr ""
"Cheie API incorectă, Folosiţi cheia api din Configurare->General în "
"programul dumneavoastră terţ:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Utilizatorul s-a autentificat în interfața web"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Utilizator logat"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1097,10 +1118,6 @@ msgstr "Dezarhivare nereuşită, eroare scriere sau disc plin?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Dezarhivare eșuată, calea este prea lungă"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Dezarhivare nereuşită, arhiva necesită o parolă"
@@ -2390,6 +2407,11 @@ msgstr "Nume"
msgid "Retry"
msgstr "Reîncearcă"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3573,10 +3595,6 @@ msgstr "Post procesare"
msgid "Naming"
msgstr "Redenumire"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Cotă"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Cât de mult poate fi descărcat în acestă lună (K/M/G)"
@@ -3685,9 +3703,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4640,6 +4658,12 @@ msgstr "Șterge tot"
msgid "Retry all"
msgstr "Reîncearcă toate"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Descarcă NZB din URL"
@@ -4891,6 +4915,11 @@ msgstr "Închide SABnzbd"
msgid "Start Wizard"
msgstr "Porneşte Vrăjitor"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -38,6 +38,13 @@ msgstr ""
"Не удаётся найти шаблон веб-интерфейса: %s. Выполняется попытка использовать"
" стандартный шаблон"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -324,6 +331,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Квота исчерпана. Загрузка приостановлена"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Квота"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Неправильный параметр"
@@ -725,15 +746,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -750,6 +762,15 @@ msgstr ""
"Неправильный ключ API. Используйте в сторонней программе ключ API из раздела"
" «Настройка -> Общие»:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1068,10 +1089,6 @@ msgstr "Не удалось распаковать: ошибка записи и
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Ошибка распаковки: архив защищён паролем"
@@ -2354,6 +2371,11 @@ msgstr "Название"
msgid "Retry"
msgstr "Повторить"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3533,10 +3555,6 @@ msgstr "Пост-обработка"
msgid "Naming"
msgstr "Именование"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Квота"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Объем, который можно загрузить в месяц (K/M/G)"
@@ -3643,9 +3661,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4604,6 +4622,12 @@ msgstr "Удалить всё"
msgid "Retry all"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr ""
@@ -4854,6 +4878,11 @@ msgstr ""
msgid "Start Wizard"
msgstr ""
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -36,6 +36,13 @@ msgstr "Neuspešno pokretanje web interfejsa"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Немогуће наћи веб модел: %s, програм покушава са стандардним моделом"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -321,6 +328,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Kvota utrošena, pauziram preuzimanja"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Квота"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Погрешан параметар"
@@ -723,15 +744,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -746,6 +758,15 @@ msgid ""
msgstr ""
"API кључ је погрешан, унети у спољни програм API кључ из Подешавања->Опште:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1064,10 +1085,6 @@ msgstr "Neuspašno raspakivanje, greška u pisanju ili je disk pun?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Neuspešno raspakivanje, putanja je predugačka"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Neuspešno raspakivanje, arhiva zahteva lozinku"
@@ -2347,6 +2364,11 @@ msgstr "Име"
msgid "Retry"
msgstr "Покушај опет"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3518,10 +3540,6 @@ msgstr "Накнадна обрада"
msgid "Naming"
msgstr "Именовање"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Квота"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Колико може да се преузме овог месеца (К/М/Г)"
@@ -3629,9 +3647,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4583,6 +4601,12 @@ msgstr "Избриши све"
msgid "Retry all"
msgstr "Ponovo pokušaj sve"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Povuci NZB sa URL"
@@ -4831,6 +4855,11 @@ msgstr "Затвори SABnzbd"
msgid "Start Wizard"
msgstr "Покрени чаробњака"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -36,6 +36,13 @@ msgstr "Det gick inte att starta webbgränssnittet"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Hittar inte webbmall: %s, försöker med standardmall"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -321,6 +328,20 @@ msgstr ""
msgid "Quota spent, pausing downloading"
msgstr "Din kvot är uppnådd, pausar nerladdning"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kvot"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Fel parameter"
@@ -723,15 +744,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -748,6 +760,15 @@ msgstr ""
"API-nyckel felaktig, använd api-nyckeln från Konfiguration-> Allmänt i ditt "
"tredjepartsprogram:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr ""
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr ""
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1066,10 +1087,6 @@ msgstr "Uppackning misslyckades, skrivfel eller disken full?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Uppackning misslyckades, sökvägen är för lång"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Uppackning misslyckades, arkivet kräver lösenord"
@@ -2353,6 +2370,11 @@ msgstr "Namn"
msgid "Retry"
msgstr "Försök igen"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3530,10 +3552,6 @@ msgstr "Efterbehandling"
msgid "Naming"
msgstr "Döpning"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kvot"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Hur mycket kan laddas ner denna månad (K/M/G)"
@@ -3641,9 +3659,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4595,6 +4613,12 @@ msgstr "Ta bort alla"
msgid "Retry all"
msgstr "Starta om alla"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Hämta NZB från URL"
@@ -4845,6 +4869,11 @@ msgstr "Avsluta SABnzbd"
msgid "Start Wizard"
msgstr "Starta guide"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -37,6 +37,15 @@ msgstr "Web arayüzünün başlatılması başarısız oldu"
msgid "Cannot find web template: %s, trying standard template"
msgstr "Web şablonu bulunamadı: %s, standart şablon denenecek"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
"OpenSSL unsuruna bağlanılamıyor, en uygun hale getirilmiş SSL bağlantı "
"işlevleri kullanılmayacaktır."
#. Error message
#: SABnzbd.py
msgid ""
@@ -353,6 +362,20 @@ msgstr "\"%s\" işi muhtemelen şifrelenmiştir: \"parola\", \"%s\" dosya ismind
msgid "Quota spent, pausing downloading"
msgstr "Kota kullanıldı, indirme duraklatılıyor"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr "Kota sınır ikazı (%d%%)"
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "İndirme kota sıfırlamasının ardından devam etti"
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Yanlış parametre"
@@ -771,15 +794,6 @@ msgstr "Şuradan bağlantı reddedildi:"
msgid "Refused connection with hostname \"%s\" from:"
msgstr "Şuradan \"%s\" makine ismi ile bağlantı reddedildi:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Kullanıcı web arayüzünde oturum açtı"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Kullanıcı oturum açtı"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -796,6 +810,15 @@ msgstr ""
"API anahtarı yanlış, 3. taraf programınızda Yapılandırma->Genel'den api "
"anahtarını kullanın:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "Kullanıcı web arayüzünde oturum açtı"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "Kullanıcı oturum açtı"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1118,10 +1141,6 @@ msgstr "Açma başarısız oldu, yazma hatası mı yoksa disk doldu mu?"
msgid "Unpacking failed, disk full"
msgstr "Açma başarısız oldu, disk dolu"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "Açma başarısız oldu, yok çok uzun"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "Açma başarısız oldu, arşiv bir parola gerektiriyor"
@@ -2413,6 +2432,11 @@ msgstr "İsim"
msgid "Retry"
msgstr "Tekrar dene"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr "Tamamlanmış Olarak İşaretle ve Geçici Dosyaları Kaldır"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3651,10 +3675,6 @@ msgstr "Post processing"
msgid "Naming"
msgstr "İsimlendirme"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "Kota"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "Bu ay ne kadar indirme yapılabileceği (K/M/G)"
@@ -3762,13 +3782,14 @@ msgstr "Sonlanma tarihinden 5 gün evvel ikaz et."
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Bu hesap için kota, bu seçeneğin ayarlanmasından itibaren hesaplanır. Bayt "
"olarak, seçime dayalı bir şekilde K,M,G takip edebilir.<br />0 değerine "
"ulaştığında ikazda bulun, her birkaç dakikada bir kontrol edilir."
"Bu sunucu için, ayarlandığı zamandan itibaren hesaplanan kota. Bayt olarak, "
"seçime dayalı bir şekilde K, M, G takip edebilir. <br /> Her birkaç "
"dakikada bir kontrol edilir. Kota sonuna ulaşıldığında bir bildirim "
"gönderilir."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4759,6 +4780,14 @@ msgstr "Tümünü Sil"
msgid "Retry all"
msgstr "Tümünü tekrar dene"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
"Geçici İndirme Dizini'ndeki tüm dizinleri silmek istediğinizden emin "
"misiniz? Bu geri alınamaz!"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "URL konumundan NZB al"
@@ -5015,6 +5044,11 @@ msgstr "SABnzb'den çık"
msgid "Start Wizard"
msgstr "Sihirbazı Başlat"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr "Devam etmeden evvel Sunucuyu Dene unsuruna tıklayın"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Yedeklemeyi geri getir"

View File

@@ -38,6 +38,13 @@ msgstr "web 界面启动失败"
msgid "Cannot find web template: %s, trying standard template"
msgstr "无法找到 web 模板: %s正在尝试标准模板"
#. Warning message
#: SABnzbd.py
msgid ""
"Unable to link to OpenSSL, optimized SSL connection functions will not be "
"used."
msgstr ""
#. Error message
#: SABnzbd.py
msgid ""
@@ -320,6 +327,20 @@ msgstr "任务 \"%s\" 可能受加密保护:文件名 \"%s\" 中有 \"password
msgid "Quota spent, pausing downloading"
msgstr "配额已耗尽,暂停下载"
#. Warning message - Notification
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "配额"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "参数不正确"
@@ -721,15 +742,6 @@ msgstr ""
msgid "Refused connection with hostname \"%s\" from:"
msgstr ""
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "用户已在 web 界面登录"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "用户已登录"
#: sabnzbd/interface.py
msgid ""
"API Key missing, please enter the api key from Config->General into your 3rd"
@@ -742,6 +754,15 @@ msgid ""
"program:"
msgstr "API Key 不正确,请在第三方程序中使用“配置”->“常规”中的 api key:"
#: sabnzbd/interface.py
msgid "User logged in to the web interface"
msgstr "用户已在 web 界面登录"
#. Notification
#: sabnzbd/interface.py, sabnzbd/notifier.py
msgid "User logged in"
msgstr "用户已登录"
#. Warning message
#: sabnzbd/interface.py
msgid "Unsuccessful login attempt from %s"
@@ -1060,10 +1081,6 @@ msgstr "解压失败,写入出错或磁盘已满?"
msgid "Unpacking failed, disk full"
msgstr ""
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, path is too long"
msgstr "解压失败,路径过长"
#: sabnzbd/newsunpack.py
msgid "Unpacking failed, archive requires a password"
msgstr "解压失败,压缩文件需要密码"
@@ -2341,6 +2358,11 @@ msgstr "名称"
msgid "Retry"
msgstr "重试"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr ""
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3478,10 +3500,6 @@ msgstr "后期处理"
msgid "Naming"
msgstr "命名"
#: sabnzbd/skintext.py
msgid "Quota"
msgstr "配额"
#: sabnzbd/skintext.py
msgid "How much can be downloaded this month (K/M/G)"
msgstr "本月能下载多少数据量 (K/M/G)"
@@ -3584,9 +3602,9 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Quota for this account, counted from the time it is set. In bytes, "
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
" minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally"
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
#. Server's retention time in days
@@ -4535,6 +4553,12 @@ msgstr "全部删除"
msgid "Retry all"
msgstr "全部重试"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "从 URL 装取 NZB"
@@ -4781,6 +4805,11 @@ msgstr "退出 SABnzbd"
msgid "Start Wizard"
msgstr "启动向导"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -16,6 +16,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr ""
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr ""
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr ""

View File

@@ -3,12 +3,13 @@
#
# Translators:
# Pavel C <quoing_transifex@mess.cz>, 2022
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2022\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -18,7 +19,11 @@ msgstr ""
#: builder/win/NSIS_Installer.nsi
msgid "Show Release Notes"
msgstr ""
msgstr "Zobrazit poznámky k vydání"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Spustit SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
@@ -30,39 +35,42 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"Služba SABnzbd pro Windows byla ve verzi SABnzbd 3.0.0 změněna.\\nBudete "
"muset znovu nainstalovat službu SABnzbd.\\n\\nKlikněte na `OK` pro "
"odstranění stávajících služeb nebo na `Zrušit` pro zrušení této aktualizace."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd podporuje pouze 64bitové Windows."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd podporuje pouze Windows 8.1 a novější."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Vypínání SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"
msgstr ""
msgstr "Tímto odinstalujete SABnzbd z vašeho systému"
#: builder/win/NSIS_Installer.nsi
msgid "Run at startup"
msgstr ""
msgstr "Spouštět při startu systému"
#: builder/win/NSIS_Installer.nsi
msgid "Desktop Icon"
msgstr ""
msgstr "Ikona na ploše"
#: builder/win/NSIS_Installer.nsi
msgid "NZB File association"
msgstr ""
msgstr "Přiřazení souborů NZB"
#: builder/win/NSIS_Installer.nsi
msgid "Delete Program"
msgstr ""
msgstr "Odstranit program"
#: builder/win/NSIS_Installer.nsi
msgid "Delete Settings"
msgstr ""
msgstr "Smazat nastavení"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Vis udgivelsesbemærkninger"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Kør SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Støt projektet, donér!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"SABnzbd Windows-tjenesten blev ændret i SABnzbd 3.0.0.\\nDu skal "
"geninstallere SABnzbd-tjenesten.\\n\\nKlik på `OK` for at fjerne de "
"eksisterende tjenester eller `Annuller` for at afbryde denne opgradering."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd understøtter kun 64-bit Windows."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd understøtter kun Windows 8.1 og nyere."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Lukker SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,15 +2,15 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# HandyDandy04, 2024
# Gjelbrim Haskaj, 2024
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Gjelbrim Haskaj, 2024\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -22,6 +22,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Versionshinweise anzeigen"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "SABnzbd starten"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Bitte unterstützen Sie das Projekt durch eine Spende!"

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Ester Molla Aragones <moarages@gmail.com>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Ester Molla Aragones <moarages@gmail.com>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,6 +21,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Mostrar notas de la versión"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Ejecutar SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "¡Apoye el proyecto, haga una donación!"
@@ -38,15 +42,15 @@ msgstr ""
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd solo es compatible con Windows de 64 bits."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd solo es compatible con Windows 8.1 y superiores."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Apagando SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Näytä julkaisutiedot"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Käynnistä SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Tue projektia, lahjoita!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"SABnzbdin Windows-palvelu muuttui versiossa SABnzbd 3.0.0.\\nSinun täytyy "
"asentaa SABnzbd-palvelu uudelleen.\\n\\nNapsauta `OK` poistaaksesi olemassa "
"olevat palvelut tai `Peruuta` peruuttaaksesi tämän päivityksen."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd tukee vain 64-bittistä Windowsia."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd tukee vain Windows 8.1:tä ja uudempia."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Sammutetaan SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Fred L <88com88@gmail.com>, 2024
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2024\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,6 +21,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Afficher les notes de version"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Lancer SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Soutenez le projet, faites un don !"

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# ION, 2024
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: ION, 2024\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,6 +21,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "הראה הערות שחרור"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "הפעל את SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "תמוך במיזם, תרום!"

View File

@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Mostra note di rilascio"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Avvia SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Sostieni il progetto, dona!"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Vis versjonsmerknader"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Kjør SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Støtt prosjektet, donèr!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"SABnzbd Windows-tjenesten ble endret i SABnzbd 3.0.0.\\nDu må installere "
"SABnzbd-tjenesten på nytt.\\n\\nKlikk `OK` for å fjerne eksisterende "
"tjenester eller `Avbryt` for å avbryte denne oppgraderingen."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd støtter kun 64-bit Windows."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd støtter kun Windows 8.1 og nyere."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Slår av SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2024
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2024\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Toon opmerkingen bij deze uitgave"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "SABnzbd starten"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Steun het project, doneer!"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Pokaż informacje o wydaniu"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Uruchom SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Wspomóż projekt!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"Usługa SABnzbd dla Windows została zmieniona w wersji SABnzbd "
"3.0.0.\\nMusisz ponownie zainstalować usługę SABnzbd.\\n\\nKliknij `OK`, aby"
" usunąć istniejące usługi, lub `Anuluj`, aby przerwać tę aktualizację."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd obsługuje tylko system Windows 64-bit."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd obsługuje tylko Windows 8.1 i nowsze."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Zamykanie SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Mostrar Notas de Lançamento"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Executar o SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Apoie o projeto. Faça uma doação!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"O Serviço do Windows do SABnzbd mudou no SABnzbd 3.0.0.\\nVocê precisará "
"reinstalar o serviço do SABnzbd.\\n\\nClique em `OK` para remover os "
"serviços existentes ou em `Cancelar` para cancelar esta atualização."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "O SABnzbd oferece suporte apenas ao Windows de 64 bits."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "O SABnzbd oferece suporte apenas ao Windows 8.1 e superior."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Encerrando o SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Arată Notele de Publicare"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Rulați SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Susţine proiectul, Donează!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"Serviciul SABnzbd pentru Windows s-a schimbat în SABnzbd 3.0.0.\\nVa trebui "
"să reinstalați serviciul SABnzbd.\\n\\nFaceți clic pe `OK` pentru a elimina "
"serviciile existente sau pe `Anulare` pentru a anula această actualizare."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd acceptă doar Windows pe 64 de biți."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd acceptă doar Windows 8.1 și versiunile ulterioare."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Se oprește SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Показать заметки о выпуске"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Запустить SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Поддержите проект. Сделайте пожертвование!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"Служба SABnzbd для Windows была изменена в SABnzbd 3.0.0.\\nВам необходимо "
"переустановить службу SABnzbd.\\n\\nНажмите «ОК», чтобы удалить существующие"
" службы, или «Отмена», чтобы прервать это обновление."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd поддерживает только 64-битные версии Windows."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd поддерживает только Windows 8.1 и более новые."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Завершение работы SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Прикажи белешке о издању"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Покрени SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Подржите пројекат, дајте добровољан прилог!"
@@ -30,18 +34,21 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"Windows услуга за SABnzbd је измењена у верзији SABnzbd 3.0.0.\\nМораћете "
"поново да инсталирате SABnzbd услугу.\\n\\nКликните „У реду“ да уклоните "
"постојеће услуге или „Откажи“ да откажете ово ажурирање."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd подржава само 64битни Windows."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd подржава само Windows 8.1 и новије."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "Искључивање SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Petter Ramme, 2024
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Petter Ramme, 2024\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,6 +21,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Visa releasenoteringar"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "Kör SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Donera och stöd detta projekt!"
@@ -32,16 +36,16 @@ msgid ""
"services or `Cancel` to cancel this upgrade."
msgstr ""
"SABnzbd Windows tjänsten ändrades i SABnzbd 3.0.0.\\nSABnzbd tjänsten "
"behöver installeras om.\\n\\Välj OK` för att ta bort den befintliga "
"behöver installeras om.\\n\\nVälj OK` för att ta bort den befintliga "
"tjänsten, eller välj `Cancel`för att avbryta uppdateringen."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd stöder endast 64-bitars Windows."
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd stöder endast Windows 8.1 och senare."
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"

View File

@@ -3,12 +3,13 @@
#
# Translators:
# mauron, 2025
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: mauron, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 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"
@@ -20,6 +21,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "Yayın Notlarını Göster"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "SABnzbd'yi çalıştır"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "Projeye destek olun, Bağış Yapın!"

View File

@@ -2,13 +2,13 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,6 +20,10 @@ msgstr ""
msgid "Show Release Notes"
msgstr "显示版本说明"
#: builder/win/NSIS_Installer.nsi
msgid "Run SABnzbd"
msgstr "运行 SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "Support the project, Donate!"
msgstr "支持该项目,捐助!"
@@ -30,18 +34,20 @@ msgid ""
"reinstall the SABnzbd service. \\n\\nClick `OK` to remove the existing "
"services or `Cancel` to cancel this upgrade."
msgstr ""
"SABnzbd 的 Windows 服务在 SABnzbd 3.0.0 中发生了变化。\\n您需要重新安装 SABnzbd "
"服务。\\n\\n点击“确定”以移除现有服务或点击“取消”以取消此次升级。"
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports 64-bit Windows."
msgstr ""
msgstr "SABnzbd 仅支持 64 位 Windows。"
#: builder/win/NSIS_Installer.nsi
msgid "SABnzbd only supports Windows 8.1 and above."
msgstr ""
msgstr "SABnzbd 仅支持 Windows 8.1 及更高版本。"
#: builder/win/NSIS_Installer.nsi
msgid "Shutting down SABnzbd"
msgstr ""
msgstr "正在关闭 SABnzbd"
#: builder/win/NSIS_Installer.nsi
msgid "This will uninstall SABnzbd from your system"

View File

@@ -1,22 +1,22 @@
# Main requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
apprise==1.9.3
sabctools==8.2.5
apprise==1.9.5
sabctools==8.2.6
CT3==3.4.0
cffi==1.17.1
pycparser==2.22
feedparser==6.0.11
cffi==2.0.0
pycparser==2.23
feedparser==6.0.12
configobj==5.0.9
cheroot==10.0.1
cheroot==11.0.0
six==1.17.0
cherrypy==18.10.0
jaraco.functools==4.2.1
jaraco.functools==4.3.0
jaraco.collections==5.0.0
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
jaraco.classes==3.4.0
jaraco.context==4.3.0
more-itertools==10.7.0
zc.lockfile==3.0.post1
more-itertools==10.8.0
zc.lockfile==4.0
python-dateutil==2.9.0.post0
tempora==5.8.1
pytz==2025.2
@@ -24,49 +24,50 @@ sgmllib3k==1.0.0
portend==3.2.1
chardet==5.2.0
PySocks==1.7.1
puremagic==1.29
puremagic==1.30
rarfile==4.2
guessit==3.8.0
babelfish==0.6.1
rebulk==3.2.0
# Recent cryptography versions require Rust. If you run into issues compiling this
# SABnzbd will also work with older pre-Rust versions such as cryptography==3.3.2
cryptography==45.0.4
cryptography==46.0.3
# We recommend using "orjson" as it is 2x as fast as "ujson". However, it requires
# Rust so SABnzbd works just as well with "ujson" or the Python built in "json" module
ujson==5.10.0
orjson==3.10.18
ujson==5.11.0
orjson==3.11.3
# Windows system integration
pywin32==310; sys_platform == 'win32'
pywin32==311; sys_platform == 'win32'
windows-toasts==1.3.1; sys_platform == 'win32'
winrt-runtime==3.2.1; sys_platform == 'win32'
winrt-Windows.Data.Xml.Dom==3.2.1; sys_platform == 'win32'
winrt-Windows.Foundation==3.2.1; sys_platform == 'win32'
winrt-Windows.Foundation.Collections==3.2.1; sys_platform == 'win32'
winrt-Windows.UI.Notifications==3.2.1; sys_platform == 'win32'
typing_extensions==4.14.0; sys_platform == 'win32'
typing_extensions==4.15.0; sys_platform == 'win32'
# macOS system calls
pyobjc-core==11.1; sys_platform == 'darwin'
pyobjc-framework-Cocoa==11.1; sys_platform == 'darwin'
pyobjc-core==12.0; sys_platform == 'darwin'
pyobjc-framework-Cocoa==12.0; sys_platform == 'darwin'
# Linux notifications
notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
# Apprise Requirements
requests==2.32.4
requests==2.32.5
requests-oauthlib==2.0.0
PyYAML==6.0.2
markdown==3.8.2
PyYAML==6.0.3
markdown==3.9
paho-mqtt==1.6.1 # Pinned, newer versions don't work with AppRise yet
# Requests Requirements
charset_normalizer==3.4.2
idna==3.10
charset_normalizer==3.4.4
idna==3.11
urllib3==2.5.0
certifi==2025.6.15
certifi==2025.10.5
oauthlib==3.3.1
PyJWT==2.10.1
blinker==1.9.0

View File

@@ -480,8 +480,78 @@ def _api_add_all_orphan(value: str, kwargs: Dict[str, Union[str, List[str]]]) ->
def _api_history(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
"""API: accepts value(=nzo_id), start, limit, search, nzo_ids"""
"""API: Dispatcher for mode=history"""
value = kwargs.get("value", "")
return _api_history_table.get(name, (_api_history_default, 2))[0](value, kwargs)
def _api_history_delete(value: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
"""API: accepts value(=nzo_id or special), search, archive, del_files"""
search = kwargs.get("search")
archive = True
# Only skip archive if specifically requested
if kwargs.get("archive") == "0" or cfg.disable_archive():
archive = False
special = value.lower()
del_files = bool_conv(kwargs.get("del_files"))
if special in ("all", "failed", "completed"):
history_db = sabnzbd.get_db_connection()
if special in ("all", "failed"):
if del_files:
del_job_files(history_db.get_failed_paths(search))
if archive:
history_db.archive_with_status(Status.FAILED, search)
else:
history_db.remove_with_status(Status.FAILED, search)
if special in ("all", "completed"):
if archive:
history_db.archive_with_status(Status.COMPLETED, search)
else:
history_db.remove_with_status(Status.COMPLETED, search)
history_updated()
return report()
elif value:
for job in clean_comma_separated_list(value):
if sabnzbd.PostProcessor.get_path(job):
# This is always a permanent delete, no archiving
sabnzbd.PostProcessor.delete(job, del_files=del_files)
else:
history_db = sabnzbd.get_db_connection()
if del_files:
remove_all(history_db.get_incomplete_path(job), recursive=True)
if archive:
history_db.archive(job)
else:
history_db.remove(job)
history_updated()
return report()
else:
return report(_MSG_NO_VALUE)
def _api_history_mark_as_completed(value: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
"""API: accepts value(=nzo_id)"""
if value:
history_db = sabnzbd.get_db_connection()
for job in clean_comma_separated_list(value):
# Get incomplete path before marking as completed
incomplete_path = history_db.get_incomplete_path(job)
history_db.mark_as_completed(job)
# Remove incomplete folder if it exists
if incomplete_path:
remove_all(incomplete_path, recursive=True)
history_updated()
return report()
else:
return report(_MSG_NO_VALUE)
def _api_history_default(value: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
"""API: accepts start, limit, search, failed_only, archive, cat, status, nzo_ids"""
start = int_conv(kwargs.get("start"))
limit = int_conv(kwargs.get("limit"))
last_history_update = int_conv(kwargs.get("last_history_update", 0))
@@ -491,84 +561,38 @@ def _api_history(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
failed_only = bool_conv(kwargs.get("failed_only"))
nzo_ids = clean_comma_separated_list(kwargs.get("nzo_ids"))
archive = True
# Do we need to send anything?
if last_history_update == sabnzbd.LAST_HISTORY_UPDATE:
return report(keyword="history", data=False)
if name == "delete":
# Only skip archive if specifically requested
if kwargs.get("archive") == "0" or cfg.disable_archive():
archive = False
if failed_only:
# We ignore any other statuses, having both doesn't make sense
statuses = [Status.FAILED]
special = value.lower()
del_files = bool_conv(kwargs.get("del_files"))
if special in ("all", "failed", "completed"):
history_db = sabnzbd.get_db_connection()
if special in ("all", "failed"):
if del_files:
del_job_files(history_db.get_failed_paths(search))
if archive:
history_db.archive_with_status(Status.FAILED, search)
else:
history_db.remove_with_status(Status.FAILED, search)
if special in ("all", "completed"):
if archive:
history_db.archive_with_status(Status.COMPLETED, search)
else:
history_db.remove_with_status(Status.COMPLETED, search)
history_updated()
return report()
elif value:
for job in clean_comma_separated_list(value):
if sabnzbd.PostProcessor.get_path(job):
# This is always a permanent delete, no archiving
sabnzbd.PostProcessor.delete(job, del_files=del_files)
else:
history_db = sabnzbd.get_db_connection()
if del_files:
remove_all(history_db.get_incomplete_path(job), recursive=True)
if archive:
history_db.archive(job)
else:
history_db.remove(job)
history_updated()
return report()
else:
return report(_MSG_NO_VALUE)
elif not name:
# Do we need to send anything?
if last_history_update == sabnzbd.LAST_HISTORY_UPDATE:
return report(keyword="history", data=False)
if not limit:
limit = cfg.history_limit()
if failed_only:
# We ignore any other statuses, having both doesn't make sense
statuses = [Status.FAILED]
# Only show archive if specifically requested
archive = bool(int_conv(kwargs.get("archive")))
if not limit:
limit = cfg.history_limit()
# Only show archive if specifically requested
if not int_conv(kwargs.get("archive")):
archive = False
history = {}
grand, month, week, day = sabnzbd.BPSMeter.get_sums()
history["total_size"] = to_units(grand)
history["month_size"] = to_units(month)
history["week_size"] = to_units(week)
history["day_size"] = to_units(day)
history["slots"], history["ppslots"], history["noofslots"] = build_history(
start=start,
limit=limit,
archive=archive,
search=search,
categories=categories,
statuses=statuses,
nzo_ids=nzo_ids,
)
history["last_history_update"] = sabnzbd.LAST_HISTORY_UPDATE
history["version"] = sabnzbd.__version__
return report(keyword="history", data=history)
else:
return report(_MSG_NOT_IMPLEMENTED)
history = {}
grand, month, week, day = sabnzbd.BPSMeter.get_sums()
history["total_size"] = to_units(grand)
history["month_size"] = to_units(month)
history["week_size"] = to_units(week)
history["day_size"] = to_units(day)
history["slots"], history["ppslots"], history["noofslots"] = build_history(
start=start,
limit=limit,
archive=archive,
search=search,
categories=categories,
statuses=statuses,
nzo_ids=nzo_ids,
)
history["last_history_update"] = sabnzbd.LAST_HISTORY_UPDATE
history["version"] = sabnzbd.__version__
return report(keyword="history", data=history)
def _api_get_files(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
@@ -667,8 +691,11 @@ def _api_showlog(name: str, kwargs: Dict[str, Union[str, List[str]]]) -> bytes:
log_data += b"The log includes a copy of your sabnzbd.ini with\nall usernames, passwords and API-keys removed."
log_data += b"\n\n--------------------------------\n"
with open(sabnzbd.LOGFILE, "rb") as f:
log_data += f.read()
if sabnzbd.LOGFILE and os.path.exists(sabnzbd.LOGFILE):
with open(sabnzbd.LOGFILE, "rb") as f:
log_data += f.read()
else:
log_data += b"\nFile log disabled or not found.\n\n"
with open(config.get_filename(), "rb") as f:
log_data += f.read()
@@ -1048,6 +1075,12 @@ _api_queue_table = {
"sort": (_api_queue_sort, 2),
}
_api_history_table = {
"delete": (_api_history_delete, 2),
"mark_as_completed": (_api_history_mark_as_completed, 2),
}
_api_status_table = {
"unblock_server": (_api_unblock_server, 2),
"delete_orphan": (_api_delete_orphan, 2),
@@ -1072,6 +1105,8 @@ def api_level(mode: str, name: str) -> int:
"""Return access level required for this API call"""
if mode == "queue" and name in _api_queue_table:
return _api_queue_table[name][1]
if mode == "history" and name in _api_history_table:
return _api_history_table[name][1]
if mode == "status" and name in _api_status_table:
return _api_status_table[name][1]
if mode == "config" and name in _api_config_table:
@@ -1413,7 +1448,7 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
# build up header full of basic information
info = build_header(trans_functions=False)
info["logfile"] = clip_path(sabnzbd.LOGFILE)
info["logfile"] = clip_path(sabnzbd.LOGFILE) if sabnzbd.LOGFILE else ""
info["weblogfile"] = clip_path(sabnzbd.WEBLOGFILE)
info["webdir"] = clip_path(info["webdir"])
info["loglevel"] = str(cfg.log_level())
@@ -1610,6 +1645,9 @@ def build_queue(
else:
slot["avg_age"] = calc_age(nzo.avg_date)
# Add timestamp when the item was added to the queue
slot["time_added"] = nzo.time_added
slotinfo.append(slot)
n += 1
@@ -1895,7 +1933,13 @@ def build_history(
def add_active_history(postproc_queue: List[NzbObject], items: List[Dict[str, Any]]):
"""Get the active history queue and add it to the existing items list"""
nzo_ids = set([nzo["nzo_id"] for nzo in items])
for nzo in postproc_queue:
# Skip already in history
if nzo.nzo_id in nzo_ids:
continue
# This output has to be the same as fetch_history!
item = {
"completed": int(time.time()),
@@ -1929,6 +1973,7 @@ def add_active_history(postproc_queue: List[NzbObject], items: List[Dict[str, An
"loaded": nzo.pp_active,
"retry": False,
"archive": False,
"time_added": nzo.time_added,
}
# Add stage information, in the correct order
for stage in STAGES:

View File

@@ -26,9 +26,10 @@ import re
from threading import Thread
import ctypes
from typing import Tuple, Optional, List
import rarfile
import sabnzbd
from sabnzbd.misc import get_all_passwords, match_str
from sabnzbd.misc import get_all_passwords, match_str, SABRarFile
from sabnzbd.filesystem import (
set_permissions,
clip_path,
@@ -42,7 +43,6 @@ from sabnzbd.constants import Status, GIGI, MAX_ASSEMBLER_QUEUE
import sabnzbd.cfg as cfg
from sabnzbd.nzbstuff import NzbObject, NzbFile
import sabnzbd.par2file as par2file
import sabnzbd.utils.rarfile as rarfile
class Assembler(Thread):
@@ -295,7 +295,7 @@ def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> Tuple[b
# Is it even a rarfile?
if rarfile.is_rarfile(filepath):
# Open the rar
zf = rarfile.RarFile(filepath, single_file_check=True)
zf = SABRarFile(filepath, part_only=True)
# Check for encryption
if (
@@ -322,12 +322,7 @@ def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> Tuple[b
logging.info('Trying password "%s" on job "%s"', password, nzo.final_name)
try:
zf.setpassword(password)
except rarfile.Error:
# On weird passwords the setpassword() will fail
# but the actual testrar() will work
pass
try:
zf.testrar()
zf.trigger_parse()
password_hit = password
break
except rarfile.RarWrongPassword:

View File

@@ -122,6 +122,7 @@ class BPSMeter:
"q_hour",
"q_minute",
"quota_enabled",
"quota_notifications_sent",
)
def __init__(self):
@@ -161,6 +162,7 @@ class BPSMeter:
self.q_hour = 0 # Quota reset hour
self.q_minute = 0 # Quota reset minute
self.quota_enabled: bool = True # Scheduled quota enable/disable
self.quota_notifications_sent: int = 0 # Track highest quota threshold that has been notified
def save(self):
"""Save admin to disk"""
@@ -323,10 +325,7 @@ class BPSMeter:
# Quota check
if self.have_quota and self.quota_enabled:
self.left -= self.sum_cached_amount
if self.left <= 0.0:
if not sabnzbd.Downloader.paused:
sabnzbd.Downloader.pause()
logging.warning(T("Quota spent, pausing downloading"))
self.check_quota()
# Speedometer
try:
@@ -431,15 +430,47 @@ class BPSMeter:
# We record every second, but display at the user's refresh-rate
return self.bps_list[::refresh_rate]
def check_quota(self):
"""Pause the queue when all quota is spent
Notify at specific quota usages (75%, 90%, 100%)
"""
if self.left <= 0.0:
if not sabnzbd.Downloader.paused:
sabnzbd.Downloader.pause()
logging.warning(T("Quota spent, pausing downloading"))
# Guard against zero division
if self.quota:
# Check for quota notifications (75%, 90%, 100%)
# Only send notification for the highest applicable threshold that hasn't been notified yet
used_percentage = ((self.quota - self.left) / self.quota) * 100
if used_percentage >= 100 and self.quota_notifications_sent < 100:
sabnzbd.notifier.send_notification(T("Quota"), T("Quota spent, pausing downloading"), "quota")
elif used_percentage >= 90 and self.quota_notifications_sent < 90:
sabnzbd.notifier.send_notification(
T("Quota"),
T("Quota limit warning (%d%%)") % used_percentage,
"quota",
)
elif used_percentage >= 75 and self.quota_notifications_sent < 75:
sabnzbd.notifier.send_notification(
T("Quota"),
T("Quota limit warning (%d%%)") % used_percentage,
"quota",
)
self.quota_notifications_sent = used_percentage
def reset_quota(self, force: bool = False):
"""Check if it's time to reset the quota, optionally resuming
Return True, when still paused or should be paused
"""
if force or (self.have_quota and time.time() > (self.q_time - 50)):
self.quota = self.left = cfg.quota_size.get_float()
self.quota_notifications_sent = 0
logging.info("Quota was reset to %s", self.quota)
if cfg.quota_resume():
logging.info("Auto-resume due to quota reset")
sabnzbd.notifier.send_notification(T("Quota"), T("Downloading resumed after quota reset"), "quota")
sabnzbd.Downloader.resume()
self.next_reset()
return False

View File

@@ -531,6 +531,7 @@ switchinterval = OptionNumber("misc", "switchinterval", 0.005, minval=0.001)
ssdp_broadcast_interval = OptionNumber("misc", "ssdp_broadcast_interval", 15, minval=1, maxval=600)
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")
##############################################################################
@@ -558,6 +559,7 @@ ncenter_prio_pp = OptionBool("ncenter", "ncenter_prio_pp", False)
ncenter_prio_complete = OptionBool("ncenter", "ncenter_prio_complete", True)
ncenter_prio_failed = OptionBool("ncenter", "ncenter_prio_failed", True)
ncenter_prio_disk_full = OptionBool("ncenter", "ncenter_prio_disk_full", True)
ncenter_prio_quota = OptionBool("ncenter", "ncenter_prio_quota", True)
ncenter_prio_new_login = OptionBool("ncenter", "ncenter_prio_new_login", False)
ncenter_prio_warning = OptionBool("ncenter", "ncenter_prio_warning", False)
ncenter_prio_error = OptionBool("ncenter", "ncenter_prio_error", False)
@@ -574,6 +576,7 @@ acenter_prio_pp = OptionBool("acenter", "acenter_prio_pp", False)
acenter_prio_complete = OptionBool("acenter", "acenter_prio_complete", True)
acenter_prio_failed = OptionBool("acenter", "acenter_prio_failed", True)
acenter_prio_disk_full = OptionBool("acenter", "acenter_prio_disk_full", True)
acenter_prio_quota = OptionBool("acenter", "acenter_prio_quota", True)
acenter_prio_new_login = OptionBool("acenter", "acenter_prio_new_login", False)
acenter_prio_warning = OptionBool("acenter", "acenter_prio_warning", False)
acenter_prio_error = OptionBool("acenter", "acenter_prio_error", False)
@@ -590,6 +593,7 @@ ntfosd_prio_pp = OptionBool("ntfosd", "ntfosd_prio_pp", False)
ntfosd_prio_complete = OptionBool("ntfosd", "ntfosd_prio_complete", True)
ntfosd_prio_failed = OptionBool("ntfosd", "ntfosd_prio_failed", True)
ntfosd_prio_disk_full = OptionBool("ntfosd", "ntfosd_prio_disk_full", True)
ntfosd_prio_quota = OptionBool("ntfosd", "ntfosd_prio_quota", True)
ntfosd_prio_new_login = OptionBool("ntfosd", "ntfosd_prio_new_login", False)
ntfosd_prio_warning = OptionBool("ntfosd", "ntfosd_prio_warning", False)
ntfosd_prio_error = OptionBool("ntfosd", "ntfosd_prio_error", False)
@@ -607,6 +611,7 @@ prowl_prio_pp = OptionNumber("prowl", "prowl_prio_pp", -3)
prowl_prio_complete = OptionNumber("prowl", "prowl_prio_complete", 0)
prowl_prio_failed = OptionNumber("prowl", "prowl_prio_failed", 1)
prowl_prio_disk_full = OptionNumber("prowl", "prowl_prio_disk_full", 1)
prowl_prio_quota = OptionNumber("prowl", "prowl_prio_quota", 0)
prowl_prio_new_login = OptionNumber("prowl", "prowl_prio_new_login", -3)
prowl_prio_warning = OptionNumber("prowl", "prowl_prio_warning", -3)
prowl_prio_error = OptionNumber("prowl", "prowl_prio_error", -3)
@@ -628,6 +633,7 @@ pushover_prio_pp = OptionNumber("pushover", "pushover_prio_pp", -3)
pushover_prio_complete = OptionNumber("pushover", "pushover_prio_complete", -1)
pushover_prio_failed = OptionNumber("pushover", "pushover_prio_failed", -1)
pushover_prio_disk_full = OptionNumber("pushover", "pushover_prio_disk_full", 1)
pushover_prio_quota = OptionNumber("pushover", "pushover_prio_quota", -1)
pushover_prio_new_login = OptionNumber("pushover", "pushover_prio_new_login", -3)
pushover_prio_warning = OptionNumber("pushover", "pushover_prio_warning", 1)
pushover_prio_error = OptionNumber("pushover", "pushover_prio_error", 1)
@@ -646,6 +652,7 @@ pushbullet_prio_pp = OptionBool("pushbullet", "pushbullet_prio_pp", False)
pushbullet_prio_complete = OptionBool("pushbullet", "pushbullet_prio_complete", True)
pushbullet_prio_failed = OptionBool("pushbullet", "pushbullet_prio_failed", True)
pushbullet_prio_disk_full = OptionBool("pushbullet", "pushbullet_prio_disk_full", True)
pushbullet_prio_quota = OptionBool("pushbullet", "pushbullet_prio_quota", True)
pushbullet_prio_new_login = OptionBool("pushbullet", "pushbullet_prio_new_login", False)
pushbullet_prio_warning = OptionBool("pushbullet", "pushbullet_prio_warning", False)
pushbullet_prio_error = OptionBool("pushbullet", "pushbullet_prio_error", False)
@@ -670,6 +677,8 @@ apprise_target_failed = OptionStr("apprise", "apprise_target_failed")
apprise_target_failed_enable = OptionBool("apprise", "apprise_target_failed_enable", True)
apprise_target_disk_full = OptionStr("apprise", "apprise_target_disk_full")
apprise_target_disk_full_enable = OptionBool("apprise", "apprise_target_disk_full_enable", False)
apprise_target_quota = OptionStr("apprise", "apprise_target_quota")
apprise_target_quota_enable = OptionBool("apprise", "apprise_target_quota_enable", True)
apprise_target_new_login = OptionStr("apprise", "apprise_target_new_login")
apprise_target_new_login_enable = OptionBool("apprise", "apprise_target_new_login_enable", True)
apprise_target_warning = OptionStr("apprise", "apprise_target_warning")
@@ -693,6 +702,7 @@ nscript_prio_pp = OptionBool("nscript", "nscript_prio_pp", False)
nscript_prio_complete = OptionBool("nscript", "nscript_prio_complete", True)
nscript_prio_failed = OptionBool("nscript", "nscript_prio_failed", True)
nscript_prio_disk_full = OptionBool("nscript", "nscript_prio_disk_full", True)
nscript_prio_quota = OptionBool("nscript", "nscript_prio_quota", True)
nscript_prio_new_login = OptionBool("nscript", "nscript_prio_new_login", False)
nscript_prio_warning = OptionBool("nscript", "nscript_prio_warning", False)
nscript_prio_error = OptionBool("nscript", "nscript_prio_error", False)

View File

@@ -50,7 +50,7 @@ RENAMES_FILE = "__renames__"
ATTRIB_FILE = "SABnzbd_attrib"
REPAIR_REQUEST = "repair-all.sab"
SABCTOOLS_VERSION_REQUIRED = "8.2.5"
SABCTOOLS_VERSION_REQUIRED = "8.2.6"
DB_HISTORY_VERSION = 1
DB_HISTORY_NAME = "history%s.db" % DB_HISTORY_VERSION
@@ -180,6 +180,7 @@ class DuplicateStatus:
class AddNzbFileResult:
RETRY = "Retry" # File could not be read
ERROR = "Error" # Rejected as duplicate, by pre-queue script or other failure to process file
ERROR = "Error" # Rejected as duplicate or other failure to process file
PREQUEUE_REJECTED = "Pre-queue rejected" # Rejected by pre-queue script
OK = "OK" # Added to queue
NO_FILES_FOUND = "No files found" # Malformed or might not be an NZB file

View File

@@ -110,6 +110,10 @@ class HistoryDB:
_ = self.execute("PRAGMA user_version = 4;") and self.execute(
"ALTER TABLE history ADD COLUMN archive INTEGER;"
)
if version < 5:
_ = self.execute("PRAGMA user_version = 5;") and self.execute(
"ALTER TABLE history ADD COLUMN time_added INTEGER;"
)
HistoryDB.startup_done = True
@@ -187,11 +191,12 @@ class HistoryDB:
"md5sum" TEXT,
"password" TEXT,
"duplicate_key" TEXT,
"archive" INTEGER
"archive" INTEGER,
"time_added" INTEGER
)
"""
)
self.execute("PRAGMA user_version = 4;")
self.execute("PRAGMA user_version = 5;")
def close(self):
"""Close database connection"""
@@ -227,6 +232,11 @@ class HistoryDB:
logging.info("Removing all jobs with status=%s", status)
self.execute("""DELETE FROM history WHERE name LIKE ? AND status = ?""", (search, status))
def mark_as_completed(self, job: str):
"""Mark a job as completed in the history"""
self.execute("""UPDATE history SET status = ? WHERE nzo_id = ?""", (Status.COMPLETED, job))
logging.info("[%s] Marked job %s as completed", caller_name(), job)
def get_failed_paths(self, search: Optional[str] = None) -> List[str]:
"""Return list of all storage paths of failed jobs (may contain non-existing or empty paths)"""
search = convert_search(search)
@@ -293,8 +303,8 @@ class HistoryDB:
self.execute(
"""INSERT INTO history (completed, name, nzb_name, category, pp, script, report,
url, status, nzo_id, storage, path, script_log, script_line, download_time, postproc_time, stage_log,
downloaded, fail_message, url_info, bytes, duplicate_key, md5sum, password)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
downloaded, fail_message, url_info, bytes, duplicate_key, md5sum, password, time_added)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
t,
)
logging.info("Added job %s to history", nzo.final_name)
@@ -540,6 +550,7 @@ def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_ou
nzo.duplicate_key,
nzo.md5sum,
nzo.correct_password,
nzo.time_added,
)

View File

@@ -64,22 +64,52 @@ def NzbQueueLocker(func: Callable):
return call_func
def cache_maintainer(clear_time: int):
def conditional_cache(cache_time: int):
"""
A function decorator that clears functools.cache or functools.lru_cache clear_time seconds
:param clear_time: In seconds, how often to clear cache (only checks when called)
A decorator that caches function results for a specified time, but only if the result is not empty.
Empty results (None, empty collections, empty strings, False, 0) are not cached.
If a keyword argument of `force=True` is used, the cache is skipped.
Unhashable types (such as List) can not be used as an input to the wrapped function in the current implementation!
:param cache_time: Time in seconds to cache non-empty results
"""
def inner(func):
def decorator(func):
cache = {}
def wrapper(*args, **kwargs):
if hasattr(func, "next_clear"):
if time.time() > func.next_clear or kwargs.get("force"):
func.cache_clear()
func.next_clear = time.time() + clear_time
else:
func.next_clear = time.time() + clear_time
return func(*args, **kwargs)
current_time = time.time()
# Create cache key using functools._make_key
try:
key = functools._make_key(args, kwargs, typed=False)
# Make sure it's a hashable to be used as key, this changed in Python 3.14
hash(key)
except TypeError:
# If args/kwargs aren't hashable, skip caching entirely
return func(*args, **kwargs)
# Allow force kward to skip cache
if not kwargs.get("force"):
# Check if we have a valid cached result
if key in cache:
cached_result, timestamp = cache[key]
if current_time - timestamp < cache_time:
return cached_result
# Cache entry expired, remove it
del cache[key]
# Call the original function
result = func(*args, **kwargs)
# Only cache non-empty results
# This excludes None, [], {}, "", 0, False, etc.
if result:
cache[key] = (result, current_time)
return result
return wrapper
return inner
return decorator

View File

@@ -30,13 +30,14 @@ from typing import Optional, Dict, List, Tuple
import sabnzbd
import sabnzbd.cfg as cfg
from sabnzbd.misc import int_conv, format_time_string, build_and_run_command
from sabnzbd.filesystem import long_path, remove_all, real_path, remove_file, get_basename
from sabnzbd.filesystem import remove_all, real_path, remove_file, get_basename, clip_path
from sabnzbd.nzbstuff import NzbObject, NzbFile
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
from sabnzbd.postproc import prepare_extraction_path
from sabnzbd.utils.rarfile import RarFile
from sabnzbd.misc import SABRarFile
import rarfile
from sabnzbd.utils.diskspeed import diskspeedmeasure
# Need a lock to make sure start and stop is handled correctly
@@ -415,40 +416,24 @@ class DirectUnpacker(threading.Thread):
# Generate command
rarfile_path = os.path.join(self.nzo.download_path, self.rarfile_nzf.filename)
if sabnzbd.WINDOWS:
# On Windows, UnRar uses a custom argument parser
# See: https://github.com/sabnzbd/sabnzbd/issues/1043
# The -scf forces the output to be UTF8
command = [
sabnzbd.newsunpack.RAR_COMMAND,
action,
"-vp",
"-idp",
"-scf",
"-o+",
"-ai",
password_command,
rarfile_path,
"%s\\" % long_path(extraction_path),
]
else:
# The -scf forces the output to be UTF8
command = [
sabnzbd.newsunpack.RAR_COMMAND,
action,
"-vp",
"-idp",
"-scf",
"-o+",
"-ai",
password_command,
rarfile_path,
"%s/" % extraction_path,
]
# The -scf forces the output to be UTF8
command = [
sabnzbd.newsunpack.RAR_COMMAND,
action,
"-vp",
"-idp",
"-scf",
"-o+",
"-ai",
password_command,
rarfile_path,
clip_path(extraction_path),
]
if cfg.ignore_unrar_dates():
command.insert(3, "-tsm-")
if unrar_parameters := cfg.unrar_parameters().strip().split():
if unrar_parameters := cfg.unrar_parameters().split():
for param in unrar_parameters:
command.insert(-2, param)
@@ -456,6 +441,8 @@ class DirectUnpacker(threading.Thread):
self.cur_volume = 1
# Need to disable buffer to have direct feedback
# On Windows, UnRar uses a custom argument parser
# See: https://github.com/sabnzbd/sabnzbd/issues/1043
self.active_instance = build_and_run_command(
command, windows_unrar_command=True, text_mode=False, stdin=subprocess.PIPE
)
@@ -508,8 +495,8 @@ class DirectUnpacker(threading.Thread):
if one_folder:
# RarFile can fail for mysterious reasons
try:
rar_contents = RarFile(
os.path.join(self.nzo.download_path, rarfile_nzf.filename), single_file_check=True
rar_contents = SABRarFile(
os.path.join(self.nzo.download_path, rarfile_nzf.filename), part_only=True
).filelist()
for rm_file in rar_contents:
# Flat-unpack, so remove foldername from RarFile output

View File

@@ -35,7 +35,7 @@ from sabnzbd.newswrapper import NewsWrapper, NNTPPermanentError
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.misc import from_units, helpful_warning, int_conv, MultiAddQueue
from sabnzbd.happyeyeballs import happyeyeballs, AddrInfo
from sabnzbd.get_addrinfo import get_fastest_addrinfo, AddrInfo
from sabnzbd.constants import SOFT_QUEUE_LIMIT
@@ -123,7 +123,7 @@ class Server:
self.host: str = host
self.port: int = port
self.timeout: int = timeout
self.threads: int = threads
self.threads: int = threads # Total number of configured connections, not dynamic
self.priority: int = priority
self.ssl: bool = use_ssl
self.ssl_verify: int = ssl_verify
@@ -206,7 +206,7 @@ class Server:
self.article_queue = []
def request_addrinfo(self):
"""Launch async request to resolve server address and perform Happy Eyeballs.
"""Launch async request to resolve server address and select the fastest.
In some situations this can be slow and result in delayed starts and timeouts on connections.
Because of this, the results will be cached in the server object."""
if not self.request:
@@ -214,7 +214,7 @@ class Server:
Thread(target=self.request_addrinfo_blocking).start()
def request_addrinfo_blocking(self):
"""Blocking attempt to run getaddrinfo() and Happy Eyeballs for specified server"""
"""Blocking attempt to run getaddrinfo() and address selection for specified server"""
logging.debug("Retrieving server address information for %s", self)
# Disable IPV6 if desired
@@ -222,7 +222,7 @@ class Server:
if not cfg.ipv6_servers():
family = socket.AF_INET
self.addrinfo = happyeyeballs(self.host, self.port, self.timeout, family)
self.addrinfo = get_fastest_addrinfo(self.host, self.port, self.timeout, family)
if not self.addrinfo:
self.bad_cons += self.threads
# Notify next call to maybe_block_server
@@ -490,7 +490,7 @@ class Downloader(Thread):
# Optional and active server had too many problems.
# Disable it now and send a re-enable plan to the scheduler
if server.optional and server.active and (server.threads < 1 or (server.bad_cons / server.threads) > 3):
if server.optional and server.active and (server.bad_cons / server.threads) > 0.3:
# Deactivate server
server.bad_cons = 0
server.deactivate()
@@ -870,7 +870,6 @@ class Downloader(Thread):
# Don't count this for the tries (max_art_tries) on this server
self.__reset_nw(nw)
self.plan_server(server, _PENALTY_TOOMANY)
server.threads -= 1
elif error.code in (502, 481, 482) and clues_too_many_ip(error.msg):
# Login from (too many) different IP addresses
errormsg = T(
@@ -1144,6 +1143,11 @@ def check_server_quota():
if server.quota():
if server.quota.get_int() + server.usage_at_start() < sabnzbd.BPSMeter.grand_total.get(srv, 0):
logging.warning(T("Server %s has used the specified quota"), server.displayname())
sabnzbd.notifier.send_notification(
T("Quota"),
T("Server %s has used the specified quota") % server.displayname(),
"quota",
)
server.quota.set("")
config.save_config()

View File

@@ -33,7 +33,6 @@ import fnmatch
import stat
import ctypes
import random
import functools
from typing import Union, List, Tuple, Any, Dict, Optional, BinaryIO
try:
@@ -44,7 +43,7 @@ except ImportError:
pass
import sabnzbd
from sabnzbd.decorators import synchronized, cache_maintainer
from sabnzbd.decorators import synchronized, conditional_cache
from sabnzbd.constants import (
FUTURE_Q_FOLDER,
JOB_ADMIN,
@@ -55,7 +54,7 @@ from sabnzbd.constants import (
DEX_FILE_EXTENSION_MAX,
)
from sabnzbd.encoding import correct_unknown_encoding, utob, limit_encoded_length
from sabnzbd.utils import rarfile
import rarfile
# For Windows: determine executable extensions
@@ -991,50 +990,6 @@ def remove_all(path: str, pattern: str = "*", keep_folder: bool = False, recursi
##############################################################################
# Diskfree
##############################################################################
def disk_free_macos_clib_statfs64(directory: str) -> Tuple[int, int]:
# MacOS only!
# direct system call to c-lib's statfs(), not python's os.statvfs()
# because statvfs() on MacOS has a rollover at 4TB (possibly a 32bit rollover with 10bit block size)
# See https://bugs.python.org/issue43638
# Based on code of pudquick and blackntan
# Input: directory.
# Output: disksize and available space, in bytes
# format & parameters: on MacOS, see "man statfs", lines starting at
# "struct statfs { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */"
class statfs64(ctypes.Structure):
_fields_ = [
("f_bsize", ctypes.c_uint32),
("f_iosize", ctypes.c_int32),
("f_blocks", ctypes.c_uint64),
("f_bfree", ctypes.c_uint64),
("f_bavail", ctypes.c_uint64),
("f_files", ctypes.c_uint64),
("f_ffree", ctypes.c_uint64),
("f_fsid", ctypes.c_uint64),
("f_owner", ctypes.c_uint32),
("f_type", ctypes.c_uint32),
("f_flags", ctypes.c_uint32),
("f_fssubtype", ctypes.c_uint32),
("f_fstypename", ctypes.c_char * 16),
("f_mntonname", ctypes.c_char * 1024),
("f_mntfromname", ctypes.c_char * 1024),
("f_reserved", ctypes.c_uint32 * 8),
]
fs_info64 = statfs64() # set up the parameters to be filled out
result = sabnzbd.MACOSLIBC.statfs64(
ctypes.create_string_buffer(utob(directory)), ctypes.byref(fs_info64)
) # fs_info64 gets filled out via the byref()
if result == 0:
# result = 0: "Upon successful completion, a value of 0 is returned."
return fs_info64.f_blocks * fs_info64.f_bsize, fs_info64.f_bavail * fs_info64.f_bsize
else:
# result = -1: "Otherwise, -1 is returned and the global variable errno is set to indicate the error."
logging.debug("Call to MACOSLIBC.statfs64 not successful. Value of errno is %s", ctypes.get_errno())
return 0, 0
def diskspace_base(dir_to_check: str) -> Tuple[float, float]:
"""Return amount of free and used diskspace in GBytes"""
# Find first folder level that exists in the path
@@ -1049,10 +1004,6 @@ def diskspace_base(dir_to_check: str) -> Tuple[float, float]:
return disk_size / GIGI, available / GIGI
except Exception:
return 0.0, 0.0
elif sabnzbd.MACOS:
# MacOS diskfree ... via c-lib call statfs()
disk_size, available = disk_free_macos_clib_statfs64(dir_to_check)
return disk_size / GIGI, available / GIGI
elif hasattr(os, "statvfs"):
# posix diskfree
try:
@@ -1072,10 +1023,9 @@ def diskspace_base(dir_to_check: str) -> Tuple[float, float]:
return 20.0, 10.0
@cache_maintainer(clear_time=10)
@functools.lru_cache(maxsize=None)
@conditional_cache(cache_time=10)
def diskspace(force: bool = False) -> Dict[str, Tuple[float, float]]:
"""Wrapper to keep results cached by cache_maintainer
"""Wrapper to keep results cached by conditional_cache
If called with force=True, the wrapper will clear the results"""
return {
"download_dir": diskspace_base(sabnzbd.cfg.download_dir.get_path()),
@@ -1291,6 +1241,10 @@ def check_filesystem_capabilities(test_dir: str) -> bool:
# if not on Windows, check special chars like \ and :
if not sabnzbd.WINDOWS and not directory_is_writable_with_file(test_dir, "sab_test \\ bla :: , bla.txt"):
# Always enable "Make Windows Compatible"
sabnzbd.cfg.sanitize_safe.set(True)
# However, external programs like unrar can still try to write them so we still warn the user
sabnzbd.misc.helpful_warning(
T("%s is not writable with special character filenames. This can cause problems."), test_dir
)

View File

@@ -16,31 +16,24 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
sabnzbd.happyeyeballs - Python implementation of RFC 6555 / Happy Eyeballs: find the quickest IPv4/IPv6 connection
sabnzbd.get_addrinfo - Concurrent IP address testing: find the fastest IPv4/IPv6 connection
"""
# Python implementation of RFC 6555/8305 (Happy Eyeballs): find the quickest IPv4/IPv6 connection
# See https://tools.ietf.org/html/rfc6555
# See https://tools.ietf.org/html/rfc8305
import socket
import threading
import time
import logging
import queue
import functools
from dataclasses import dataclass
from typing import Tuple, Union, Optional
from more_itertools import roundrobin
from typing import Tuple, Union, Optional
import sabnzbd.cfg as cfg
from sabnzbd.constants import DEF_NETWORKING_TIMEOUT
from sabnzbd.decorators import cache_maintainer
from sabnzbd.decorators import conditional_cache
# How long to delay between connection attempts? The RFC suggests 250ms, but this is
# quite long and might give us a slow host that just happened to be on top of the list.
# The absolute minimum specified in RFC 8305 is 10ms, so we use that.
CONNECTION_ATTEMPT_DELAY = 0.01
# How often to check for connection results
CONNECTION_RESULT_CHECK = 0.1 # 100ms
# While providers are afraid to add IPv6 to their standard hostnames
# we map a number of well known hostnames to their IPv6 alternatives.
@@ -71,6 +64,7 @@ class AddrInfo:
sockaddr: Union[Tuple[str, int], Tuple[str, int, int, int]]
ipaddress: str = ""
port: int = 0
connection_time: float = 0.0
def __post_init__(self):
# For easy access
@@ -90,25 +84,26 @@ def family_type(family) -> str:
# Called by each thread
def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo, timeout: int):
"""Connect to the ip, and put the result into the queue"""
def do_socket_connect(results_list: list, addrinfo: AddrInfo, timeout: int):
"""Connect to the ip, and add the result with timing info to the shared list"""
try:
start = time.time()
s = socket.socket(addrinfo.family, addrinfo.type)
s.settimeout(timeout)
try:
s.connect(addrinfo.sockaddr)
result_queue.put(addrinfo)
addrinfo.connection_time = time.time() - start
results_list.append(addrinfo)
logging.debug(
"Happy Eyeballs connected to %s (%s, port=%d) in %dms",
"Connected to %s (%s, port=%d) in %dms",
addrinfo.ipaddress,
addrinfo.canonname,
addrinfo.port,
1000 * (time.time() - start),
1000 * addrinfo.connection_time,
)
except socket.error:
logging.debug(
"Happy Eyeballs failed to connect to %s (%s, port=%d) in %dms",
"Failed to connect to %s (%s, port=%d) in %dms",
addrinfo.ipaddress,
addrinfo.canonname,
addrinfo.port,
@@ -120,28 +115,28 @@ def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo, timeout: in
pass
@cache_maintainer(clear_time=10)
@functools.lru_cache(maxsize=None)
def happyeyeballs(
@conditional_cache(cache_time=60)
def get_fastest_addrinfo(
host: str,
port: int,
timeout: int = DEF_NETWORKING_TIMEOUT,
family=socket.AF_UNSPEC,
) -> Optional[AddrInfo]:
"""Return the fastest result of getaddrinfo() based on RFC 6555/8305 (Happy Eyeballs),
including IPv6 addresses if desired. Returns None in case no addresses were returned
by getaddrinfo or if no connection could be made to any of the addresses.
If family is specified, only that family is tried"""
"""Return the fastest result of getaddrinfo() by testing all IP addresses concurrently.
Tests all available IP addresses simultaneously (alternating IPv4/6) in separate threads and returns the
connection with the shortest response time after CONNECTION_CHECK interval.
Returns None in case no addresses were returned by getaddrinfo or if no connection
could be made to any of the addresses. If family is specified, only that family is tried"""
try:
# See if we can add a IPv6 alternative
# See if we can add an IPv6 alternative
check_hosts = [host]
if cfg.ipv6_staging() and host in IPV6_MAPPING:
check_hosts.append(IPV6_MAPPING[host])
logging.info("Added IPv6 alternative %s for host %s", IPV6_MAPPING[host], host)
last_canonname = ""
ipv4_addrinfo = []
ipv6_addrinfo = []
last_canonname = ""
for check_host in check_hosts:
try:
for addrinfo in socket.getaddrinfo(
@@ -178,39 +173,44 @@ def happyeyeballs(
len(ipv6_addrinfo),
)
# To optimize success, the RFC states to alternate between trying the
# IPv6 and IPv4 results, starting with IPv6 since it is the preferred method.
result_queue: queue.Queue[AddrInfo] = queue.Queue()
addr_tried = 0
result: Optional[AddrInfo] = None
if not ipv4_addrinfo and not ipv6_addrinfo:
raise ConnectionError("No usable IP addresses found for %s" % ", ".join(check_hosts))
# Try IPv6 and IPv4 alternating since there is delay in starting threads
successful_connections = []
threads = []
for addrinfo in roundrobin(ipv6_addrinfo, ipv4_addrinfo):
threading.Thread(target=do_socket_connect, args=(result_queue, addrinfo, timeout), daemon=True).start()
addr_tried += 1
try:
result = result_queue.get(timeout=CONNECTION_ATTEMPT_DELAY)
break
except queue.Empty:
# Start a thread for the next address in the list if the previous
# connection attempt did not complete in time or if it wasn't a success
continue
thread = threading.Thread(
target=do_socket_connect,
args=(successful_connections, addrinfo, timeout),
daemon=True,
)
thread.start()
threads.append(thread)
# If we had no results, we might just need to give it more time
if not result:
try:
# Reduce waiting time by time already spent
result = result_queue.get(timeout=timeout - addr_tried * CONNECTION_ATTEMPT_DELAY)
except queue.Empty:
raise ConnectionError("No usable IP addresses found for %s" % ", ".join(check_hosts))
# Wait for the first successful connection
start_time = time.time()
while time.time() - start_time < timeout:
time.sleep(CONNECTION_RESULT_CHECK)
# Check if we have any successful connections
if successful_connections:
# Return the fastest connection
fastest_addrinfo = min(successful_connections, key=lambda result: result.connection_time)
logging.info(
"Fastest connection to %s (port=%d, %s): %s (%s) in %dms (out of %d results)",
host,
port,
family_type(family),
fastest_addrinfo.ipaddress,
fastest_addrinfo.canonname,
1000 * fastest_addrinfo.connection_time,
len(successful_connections),
)
return fastest_addrinfo
# If no connections succeeded within timeout
raise ConnectionError("No usable IP addresses found for %s" % ", ".join(check_hosts))
logging.info(
"Quickest IP address for %s (port=%d, %s): %s (%s)",
host,
port,
family_type(family),
result.ipaddress,
result.canonname,
)
return result
except Exception as e:
logging.debug("Failed Happy Eyeballs lookup: %s", e)
logging.debug("Failed IP address lookup: %s", e)
return None

View File

@@ -32,7 +32,7 @@ import socks
import sabnzbd
import sabnzbd.cfg
from sabnzbd.encoding import ubtou
from sabnzbd.happyeyeballs import happyeyeballs, family_type
from sabnzbd.get_addrinfo import get_fastest_addrinfo, family_type
from sabnzbd.constants import DEF_NETWORKING_SHORT_TIMEOUT
@@ -73,9 +73,11 @@ def addresslookup6(myhost):
def active_socks5_proxy() -> Optional[str]:
"""Return the active proxy"""
if socket.socket == socks.socksocket:
return "%s:%s" % socks.socksocket.default_proxy[1:3]
"""Return the active proxy. And None if no proxy is set"""
if socks.socksocket.default_proxy:
socks5host = socks.socksocket.default_proxy[1]
socks5port = sabnzbd.misc.int_conv(socks.socksocket.default_proxy[2], default=1080)
return f"{socks5host}:{socks5port}"
return None
@@ -92,11 +94,21 @@ def dnslookup() -> bool:
def local_ipv4() -> Optional[str]:
"""return IPv4 address of default local LAN interface"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
# Option: use 100.64.1.1 (IANA-Reserved IPv4 Prefix for Shared Address Space)
s_ipv4.connect(("10.255.255.255", 80))
ipv4 = s_ipv4.getsockname()[0]
if not socks.socksocket.default_proxy:
# No socks5 proxy, so we can use UDP (SOCK_DGRAM) and a non-reachable host
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
s_ipv4.connect(("10.255.255.255", 80))
ipv4 = s_ipv4.getsockname()[0]
else:
# socks5 proxy set, so we must use TCP (SOCK_STREAM) and a reachable host: the proxy server
socks5host = socks.socksocket.default_proxy[1]
socks5port = sabnzbd.misc.int_conv(socks.socksocket.default_proxy[2], default=1080)
logging.debug(f"Using proxy {socks5host} on port {socks5port} to determine local IPv4 address")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s_ipv4:
s_ipv4.connect((socks5host, socks5port))
ipv4 = s_ipv4.getsockname()[0]
except socket.error:
ipv4 = None
@@ -109,7 +121,7 @@ def public_ip(family: int = socket.AF_UNSPEC) -> Optional[str]:
Reports the client's public IP address (IPv4 or IPv6, if specified by family), as reported by selftest host
"""
start = time.time()
if resolvehostaddress := happyeyeballs(
if resolvehostaddress := get_fastest_addrinfo(
sabnzbd.cfg.selftest_host(),
port=443,
timeout=DEF_NETWORKING_SHORT_TIMEOUT,

View File

@@ -55,7 +55,7 @@ from sabnzbd.misc import (
get_cpu_name,
clean_comma_separated_list,
)
from sabnzbd.happyeyeballs import happyeyeballs
from sabnzbd.get_addrinfo import get_fastest_addrinfo
from sabnzbd.filesystem import (
real_path,
globber,
@@ -310,9 +310,6 @@ def set_login_cookie(remove=False, remember_me=False):
if remove:
cherrypy.response.cookie["login_cookie"]["expires"] = 0
cherrypy.response.cookie["login_salt"]["expires"] = 0
else:
# Notify about new login
notifier.send_notification(T("User logged in"), T("User logged in to the web interface"), "new_login")
def check_login_cookie():
@@ -679,6 +676,8 @@ class LoginPage:
set_login_cookie(remember_me=kwargs.get("remember_me", False))
# Log the success
logging.info("Successful login from %s", cherrypy.request.remote_label)
# Notify about new login
notifier.send_notification(T("User logged in"), T("User logged in to the web interface"), "new_login")
# Redirect
raise Raiser("/")
elif kwargs.get("username") or kwargs.get("password"):
@@ -912,6 +911,7 @@ SPECIAL_VALUE_LIST = (
"selftest_host",
"ssdp_broadcast_interval",
"unrar_parameters",
"outgoing_nntp_ip",
)
SPECIAL_LIST_LIST = (
"rss_odd_titles",
@@ -1174,7 +1174,7 @@ def handle_server(kwargs, root=None, new_svr=False):
kwargs["connections"] = "1"
if kwargs.get("enable") == "1":
if not happyeyeballs(
if not get_fastest_addrinfo(
host, int_conv(port), int_conv(kwargs.get("timeout"), default=DEF_NETWORKING_TEST_TIMEOUT)
):
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port), ajax)
@@ -2029,6 +2029,7 @@ NOTIFY_OPTIONS = {
"ncenter_prio_complete",
"ncenter_prio_failed",
"ncenter_prio_disk_full",
"ncenter_prio_quota",
"ncenter_prio_warning",
"ncenter_prio_error",
"ncenter_prio_queue_done",
@@ -2045,6 +2046,7 @@ NOTIFY_OPTIONS = {
"acenter_prio_complete",
"acenter_prio_failed",
"acenter_prio_disk_full",
"acenter_prio_quota",
"acenter_prio_warning",
"acenter_prio_error",
"acenter_prio_queue_done",
@@ -2061,6 +2063,7 @@ NOTIFY_OPTIONS = {
"ntfosd_prio_complete",
"ntfosd_prio_failed",
"ntfosd_prio_disk_full",
"ntfosd_prio_quota",
"ntfosd_prio_warning",
"ntfosd_prio_error",
"ntfosd_prio_queue_done",
@@ -2078,6 +2081,7 @@ NOTIFY_OPTIONS = {
"prowl_prio_complete",
"prowl_prio_failed",
"prowl_prio_disk_full",
"prowl_prio_quota",
"prowl_prio_warning",
"prowl_prio_error",
"prowl_prio_queue_done",
@@ -2097,6 +2101,7 @@ NOTIFY_OPTIONS = {
"pushover_prio_complete",
"pushover_prio_failed",
"pushover_prio_disk_full",
"pushover_prio_quota",
"pushover_prio_warning",
"pushover_prio_error",
"pushover_prio_queue_done",
@@ -2117,6 +2122,7 @@ NOTIFY_OPTIONS = {
"pushbullet_prio_complete",
"pushbullet_prio_failed",
"pushbullet_prio_disk_full",
"pushbullet_prio_quota",
"pushbullet_prio_warning",
"pushbullet_prio_error",
"pushbullet_prio_queue_done",
@@ -2141,6 +2147,8 @@ NOTIFY_OPTIONS = {
"apprise_target_failed_enable",
"apprise_target_disk_full",
"apprise_target_disk_full_enable",
"apprise_target_quota",
"apprise_target_quota_enable",
"apprise_target_warning",
"apprise_target_warning_enable",
"apprise_target_error",
@@ -2164,6 +2172,7 @@ NOTIFY_OPTIONS = {
"nscript_prio_complete",
"nscript_prio_failed",
"nscript_prio_disk_full",
"nscript_prio_quota",
"nscript_prio_warning",
"nscript_prio_error",
"nscript_prio_queue_done",

View File

@@ -31,7 +31,7 @@ from typing import Dict
import sabctools
import sabnzbd
from sabnzbd.constants import DEF_NETWORKING_SHORT_TIMEOUT
from sabnzbd.happyeyeballs import happyeyeballs, family_type
from sabnzbd.get_addrinfo import get_fastest_addrinfo, family_type
TEST_HOSTNAME = "sabnzbd.org"
TEST_PORT = 443
@@ -88,8 +88,8 @@ def internetspeed_interal(family: int = socket.AF_UNSPEC) -> float:
context.verify_flags &= ~ssl.VERIFY_X509_STRICT
try:
if not (addrinfo := happyeyeballs(TEST_HOSTNAME, TEST_PORT, DEF_NETWORKING_SHORT_TIMEOUT, family)):
# no addrinfo from happyeyeballs, so no connection was possible
if not (addrinfo := get_fastest_addrinfo(TEST_HOSTNAME, TEST_PORT, DEF_NETWORKING_SHORT_TIMEOUT, family)):
# no addrinfo from get_fastest_addrinfo, so no connection was possible
return 0.0 # no speed at all
for _ in range(NR_CONNECTIONS):

View File

@@ -24,7 +24,6 @@ import platform
import ssl
import sys
import logging
import functools
import urllib.request
import urllib.parse
import re
@@ -39,6 +38,7 @@ import html
import ipaddress
import socks
import math
import rarfile
from threading import Thread
from collections.abc import Iterable
from typing import Union, Tuple, Any, AnyStr, Optional, List, Dict, Collection
@@ -55,7 +55,7 @@ from sabnzbd.constants import (
)
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.decorators import cache_maintainer
from sabnzbd.decorators import conditional_cache
from sabnzbd.encoding import ubtou, platform_btou
from sabnzbd.filesystem import userxbit, make_script_path, remove_file
@@ -774,8 +774,7 @@ def get_macos_memory():
return float(system_output.split()[1])
@cache_maintainer(clear_time=3600)
@functools.lru_cache(maxsize=None)
@conditional_cache(cache_time=3600)
def get_cpu_name():
"""Find the CPU name (which needs a different method per OS), and return it
If none found, return platform.platform()"""
@@ -1586,3 +1585,40 @@ def convert_history_retention():
cfg.history_retention_number.set(to_keep)
elif to_keep < 0:
cfg.history_retention_option.set("all-delete")
##
## SABnzbd patched rarfile classes
## Patch for https://github.com/markokr/rarfile/issues/56#issuecomment-711146569
##
class SABRarFile(rarfile.RarFile):
"""SABnzbd patched RarFile class with info_callback fix for multi-volume archives"""
def __init__(self, *args, **kwargs):
"""Patch RarFile-call when using `part_only`
to store filenames inside the RAR-files"""
if kwargs.get("part_only"):
kwargs["info_callback"] = self.info_callback
# Let RarFile handle the rest!
super().__init__(*args, **kwargs)
def info_callback(self, rar_obj: rarfile.RarInfo):
"""Called for every RarInfo-object found"""
# We only care about files inside the Rar
# For Rar5 there is a separate object, for Rar3 we need to check if a filename was parsed
if isinstance(rar_obj, (rarfile.Rar5FileInfo, rarfile.Rar3Info)) and rar_obj.filename:
# Avoid duplicates
if rar_obj not in self._file_parser._info_list:
self._file_parser._info_list.append(rar_obj)
self._file_parser._info_map[rar_obj.filename.rstrip("/")] = rar_obj
def filelist(self):
"""Return list of filenames in archive."""
return [f.filename for f in self.infolist() if not f.isdir()]
def trigger_parse(self):
"""Force re-parse, wich is needed to trigger password checking logic"""
self._parse()

View File

@@ -28,11 +28,11 @@ import time
import io
import shutil
import functools
import rarfile
from typing import Tuple, List, BinaryIO, Optional, Dict, Any, Union, Set
import sabnzbd
from sabnzbd.encoding import correct_unknown_encoding, ubtou
import sabnzbd.utils.rarfile as rarfile
from sabnzbd.misc import (
format_time_string,
find_on_path,
@@ -44,6 +44,7 @@ from sabnzbd.misc import (
build_and_run_command,
format_time_left,
is_none,
SABRarFile,
)
from sabnzbd.filesystem import (
make_script_path,
@@ -66,7 +67,7 @@ from sabnzbd.filesystem import (
)
from sabnzbd.nzbstuff import NzbObject
import sabnzbd.cfg as cfg
from sabnzbd.constants import Status, JOB_ADMIN
from sabnzbd.constants import Status
# Regex globals
@@ -105,14 +106,14 @@ def find_programs(curdir: str):
if sabnzbd.MACOS:
if sabnzbd.MACOSARM64:
# M1 (ARM64) versions
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "macos/par2/arm64/par2")
# ARM64 version of unrar
sabnzbd.newsunpack.RAR_COMMAND = check(curdir, "macos/unrar/arm64/unrar")
else:
# Regular x64 versions
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "macos/par2/par2")
# Regular x64 version
sabnzbd.newsunpack.RAR_COMMAND = check(curdir, "macos/unrar/unrar")
# The 7zip binary is universal2
# The par2 and 7zip binary are universal2
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "macos/par2/par2")
sabnzbd.newsunpack.SEVENZIP_COMMAND = check(curdir, "macos/7zip/7zz")
if sabnzbd.WINDOWS:
@@ -647,15 +648,14 @@ def rar_extract_core(
"""
start = time.time()
logging.debug("rar_extract(): Extractionpath: %s", extraction_path)
logging.debug("Extraction path: %s", extraction_path)
logging.debug("Found rar version: %s", rarfile.is_rarfile(rarfile_path))
if password:
password_command = "-p%s" % password
else:
password_command = "-p-"
############################################################################
if one_folder or cfg.flat_unpack():
action = "e"
else:
@@ -667,24 +667,7 @@ def rar_extract_core(
overwrite = "-o-" # Disable overwrite
rename = "-or" # Auto renaming
if sabnzbd.WINDOWS:
# On Windows, UnRar uses a custom argument parser
# See: https://github.com/sabnzbd/sabnzbd/issues/1043
# The -scf forces the output to be UTF8
command = [
RAR_COMMAND,
action,
"-idp",
"-scf",
overwrite,
rename,
"-ai",
password_command,
rarfile_path,
"%s\\" % long_path(extraction_path),
]
elif RAR_PROBLEM:
if RAR_PROBLEM:
# Use only oldest options, specifically no "-or" or "-scf"
command = [
RAR_COMMAND,
@@ -693,10 +676,11 @@ def rar_extract_core(
overwrite,
password_command,
rarfile_path,
"%s/" % extraction_path,
extraction_path,
]
else:
# The -scf forces the output to be UTF8
# On Windows, specifically remove long path from destination so Unrar handles it
command = [
RAR_COMMAND,
action,
@@ -707,17 +691,17 @@ def rar_extract_core(
"-ai",
password_command,
rarfile_path,
"%s/" % extraction_path,
clip_path(extraction_path),
]
if cfg.ignore_unrar_dates():
command.insert(3, "-tsm-")
if not RAR_PROBLEM and (unrar_parameters := cfg.unrar_parameters().strip().split()):
for param in unrar_parameters:
command.insert(-2, param)
if cfg.ignore_unrar_dates():
command.insert(3, "-tsm-")
if unrar_parameters := cfg.unrar_parameters().split():
for param in unrar_parameters:
command.insert(-2, param)
# Get list of all the volumes part of this set
logging.debug("Analyzing rar file ... %s found", rarfile.is_rarfile(rarfile_path))
# On Windows, UnRar uses a custom argument parser
# See: https://github.com/sabnzbd/sabnzbd/issues/1043
p = build_and_run_command(command, windows_unrar_command=True)
sabnzbd.PostProcessor.external_process = p
@@ -793,11 +777,21 @@ def rar_extract_core(
requires_kill = True
elif line.startswith("Cannot create"):
line2 = p.stdout.readline()
if "must not exceed 260" in line2:
msg = "%s: %s" % (T("Unpacking failed, path is too long"), line[13:])
else:
msg = "%s %s" % (T("Unpacking failed, write error or disk is full?"), line[13:])
# Check if maybe it can be salvaged
line = p.stdout.readline()
lines.append(line.strip())
# Error is different on Linux and Windows
if line.startswith(("Invalid argument", "The filename, directory name, or volume label syntax")):
# Read another line
line = p.stdout.readline()
lines.append(line.strip())
# Will it try to correct?
if line.startswith("WARNING: Attempting to correct"):
# Great! Let it try
logging.info("Unrar detected invalid filename and is attempting to correct")
continue
msg = "%s %s" % (T("Unpacking failed, write error or disk is full?"), line)
nzo.fail_msg = msg
nzo.set_unpack_info("Unpack", msg, setname)
fail = 1
@@ -1474,7 +1468,7 @@ def rar_volumelist(rarfile_path: str, password: str, known_volumes: List[str]) -
# UnRar is required to read some RAR files
# RarFile can fail in special cases
try:
zf = rarfile.RarFile(rarfile_path)
zf = SABRarFile(rarfile_path)
# setpassword can fail due to bugs in RarFile
if password:

View File

@@ -32,7 +32,7 @@ import sabnzbd
import sabnzbd.cfg
from sabnzbd.constants import DEF_NETWORKING_TIMEOUT, NNTP_BUFFER_SIZE, NTTP_MAX_BUFFER_SIZE
from sabnzbd.encoding import utob, ubtou
from sabnzbd.happyeyeballs import AddrInfo
from sabnzbd.get_addrinfo import AddrInfo
from sabnzbd.decorators import synchronized, DOWNLOADER_LOCK
from sabnzbd.misc import int_conv
@@ -342,6 +342,20 @@ class NNTP:
self.sock.settimeout(self.nw.server.timeout)
# Connect
if outgoing_nntp_ip := sabnzbd.cfg.outgoing_nntp_ip():
try:
self.sock.bind((outgoing_nntp_ip, 0))
socket_info = self.sock.getsockname()
logging.debug(
"%s@%s: Successfully bound to following ip address: %s at following port: %d",
self.nw.thrdnum,
self.nw.server.host,
socket_info[0],
socket_info[1],
)
except socket.error:
raise ConnectionError(f"Could not bind to outgoing interface {outgoing_nntp_ip}")
self.sock.connect(self.addrinfo.sockaddr)
# Secured or unsecured?

View File

@@ -41,12 +41,14 @@ from sabnzbd.misc import build_and_run_command, int_conv
from sabnzbd.newsunpack import create_env
if sabnzbd.WINDOWS:
windows_major_version = int_conv(platform.version().split(".")[0])
try:
from win32comext.shell import shell
from windows_toasts import InteractableWindowsToaster, Toast, ToastActivatedEventArgs, ToastButton
# Only Windows 10 and above are supported
if int_conv(platform.version().split(".")[0]) < 10:
if windows_major_version < 10:
raise OSError
# Set a custom AUMID to display the right icon, it is written to the registry by the installer
@@ -54,7 +56,7 @@ if sabnzbd.WINDOWS:
_HAVE_WINDOWS_TOASTER = True
except Exception:
# This needs to work on Windows releases
if hasattr(sys, "frozen"):
if windows_major_version >= 10 and hasattr(sys, "frozen"):
raise
# Sending toasts on non-supported platforms results in segfaults
@@ -87,6 +89,7 @@ NOTIFICATION_TYPES = {
"warning": TT("Warning"), #: Notification
"error": TT("Error"), #: Notification
"disk_full": TT("Disk full"), #: Notification
"quota": TT("Quota"), #: Notification
"queue_done": TT("Queue finished"), #: Notification
"new_login": TT("User logged in"), #: Notification
"other": TT("Other Messages"), #: Notification
@@ -321,6 +324,8 @@ def send_apprise(title, msg, notification_type, force=False, test=None):
"error": apprise.common.NotifyType.FAILURE,
# Disk full
"disk_full": apprise.common.NotifyType.WARNING,
# Quota
"quota": apprise.common.NotifyType.WARNING,
# Queue finished
"queue_done": apprise.common.NotifyType.INFO,
# User logged in

View File

@@ -43,7 +43,8 @@ from sabnzbd.filesystem import (
)
from sabnzbd.misc import name_to_cat, cat_pp_script_sanitizer
from sabnzbd.constants import DEFAULT_PRIORITY, VALID_ARCHIVES, AddNzbFileResult
from sabnzbd.utils import rarfile
from sabnzbd.misc import SABRarFile
import rarfile
def add_nzbfile(
@@ -169,7 +170,7 @@ def process_nzb_archive_file(
if zipfile.is_zipfile(path):
zf = zipfile.ZipFile(path)
elif rarfile.is_rarfile(path):
zf = rarfile.RarFile(path)
zf = SABRarFile(path)
elif sabnzbd.newsunpack.is_sevenfile(path):
zf = sabnzbd.newsunpack.SevenZip(path)
else:
@@ -218,8 +219,12 @@ def process_nzb_archive_file(
nzo_id=nzo_id,
dup_check=dup_check,
)
except (sabnzbd.nzbstuff.NzbEmpty, sabnzbd.nzbstuff.NzbRejected):
# Empty or fully rejected
except (
sabnzbd.nzbstuff.NzbEmpty,
sabnzbd.nzbstuff.NzbRejected,
sabnzbd.nzbstuff.NzbPreQueueRejected,
):
# Empty or fully rejected (including pre-queue rejections)
pass
except sabnzbd.nzbstuff.NzbRejectToHistory as err:
# Duplicate or unwanted extension directed to history
@@ -329,8 +334,11 @@ def process_single_nzb(
# Malformed or might not be an NZB file
result = AddNzbFileResult.NO_FILES_FOUND
except sabnzbd.nzbstuff.NzbRejected:
# Rejected as duplicate or by pre-queue script
# Rejected as duplicate
result = AddNzbFileResult.ERROR
except sabnzbd.nzbstuff.NzbPreQueueRejected:
# Rejected by pre-queue script - should be silently ignored for URL fetches
result = AddNzbFileResult.PREQUEUE_REJECTED
except sabnzbd.nzbstuff.NzbRejectToHistory as err:
# Duplicate or unwanted extension directed to history
sabnzbd.NzbQueue.fail_to_history(err.nzo)

View File

@@ -246,7 +246,9 @@ class NzbQueue:
def set_top_only(self, value):
self.__top_only = value
@NzbQueueLocker
def change_opts(self, nzo_ids: List[str], pp: int) -> int:
"""Locked so changes during URLGrabbing are correctly passed to new job"""
result = 0
for nzo_id in nzo_ids:
if nzo_id in self.__nzo_table:
@@ -254,7 +256,9 @@ class NzbQueue:
result += 1
return result
@NzbQueueLocker
def change_script(self, nzo_ids: List[str], script: str) -> int:
"""Locked so changes during URLGrabbing are correctly passed to new job"""
result = 0
if (script is None) or is_valid_script(script):
for nzo_id in nzo_ids:
@@ -264,7 +268,9 @@ class NzbQueue:
result += 1
return result
@NzbQueueLocker
def change_cat(self, nzo_ids: List[str], cat: str) -> int:
"""Locked so changes during URLGrabbing are correctly passed to new job"""
result = 0
for nzo_id in nzo_ids:
if nzo_id in self.__nzo_table:
@@ -278,7 +284,9 @@ class NzbQueue:
result += 1
return result
@NzbQueueLocker
def change_name(self, nzo_id: str, name: str, password: str = None) -> bool:
"""Locked so changes during URLGrabbing are correctly passed to new job"""
if nzo_id in self.__nzo_table:
nzo = self.__nzo_table[nzo_id]
logging.info("Renaming %s to %s", nzo.final_name, name)
@@ -440,7 +448,9 @@ class NzbQueue:
handled.append(nzo_id)
return handled
@NzbQueueLocker
def pause_nzo(self, nzo_id: str) -> List[str]:
"""Locked so changes during URLGrabbing are correctly passed to new job"""
handled = []
if nzo_id in self.__nzo_table:
nzo = self.__nzo_table[nzo_id]

View File

@@ -523,6 +523,10 @@ class NzbRejected(Exception):
pass
class NzbPreQueueRejected(Exception):
pass
class NzbRejectToHistory(Exception):
def __init__(self, nzo, fail_msg):
self.nzo: NzbObject = nzo
@@ -587,6 +591,7 @@ NzbObjectSaver = (
"servercount",
"unwanted_ext",
"renames",
"time_added",
)
NzoAttributeSaver = ("cat", "pp", "script", "priority", "final_name", "password", "url")
@@ -668,6 +673,7 @@ class NzbObject(TryList):
self.avg_stamp = 0.0 # Avg age in seconds (calculated from avg_age)
self.propagation_delay: Optional[float] = None # Set during parsing
self.correct_password: Optional[str] = None
self.time_added: int = int(time.time()) # When the NZB was added to the queue
# Bookkeeping values
self.meta = {}
@@ -868,7 +874,7 @@ class NzbObject(TryList):
accept = int_conv(accept)
if accept < 1:
self.purge_data()
raise NzbRejected
raise NzbPreQueueRejected
if accept == 2:
raise NzbRejectToHistory(self, T("Pre-queue script marked job as failed"))
@@ -2090,6 +2096,9 @@ class NzbObject(TryList):
if not isinstance(self.saved_articles, set):
# Converted from list to set
self.saved_articles = set(self.saved_articles)
if self.time_added is None:
# For backward compatibility with older saved NZOs
self.time_added = 0
def __repr__(self):
return "<NzbObject: filename=%s, bytes=%s, nzo_id=%s>" % (self.filename, self.bytes, self.nzo_id)

View File

@@ -194,9 +194,10 @@ def parse_par2_file(fname: str, md5of16k: Dict[bytes, str]) -> Tuple[str, Dict[s
for i in range(48, pack_len - 32, 20):
filecrc32[fileid].append(struct.unpack("<I", data[i + 16 : i + 20])[0])
# On large files, we stop after seeing all the listings
# On large files, we stop after seeing all the listings and have crc32 data for all listings
# Our unit-tests do not include large par2 files, so we cannot verify cases like #3164!
# On smaller files, we scan them fully to get the par2-creator
if total_size > SCAN_LIMIT and len(filepar2info) == nr_files:
if total_size > SCAN_LIMIT and len(filepar2info) == nr_files == len(filecrc32):
break
# Process all the data

View File

@@ -26,6 +26,7 @@ import time
import re
import gc
import queue
import rarfile
from typing import List, Optional, Tuple
import sabnzbd
@@ -46,6 +47,7 @@ from sabnzbd.misc import (
change_queue_complete_action,
run_script,
is_none,
SABRarFile,
)
from sabnzbd.filesystem import (
real_path,
@@ -89,7 +91,6 @@ import sabnzbd.config as config
import sabnzbd.cfg as cfg
import sabnzbd.database as database
import sabnzbd.notifier as notifier
import sabnzbd.utils.rarfile as rarfile
import sabnzbd.utils.rarvolinfo as rarvolinfo
import sabnzbd.utils.checkdir
import sabnzbd.deobfuscate_filenames as deobfuscate
@@ -892,7 +893,7 @@ def try_rar_check(nzo: NzbObject, rars: List[str]) -> bool:
nzo.set_action_line(T("Trying RAR-based verification"), "...")
try:
# Requires de-unicode for RarFile to work!
zf = rarfile.RarFile(rars[0])
zf = SABRarFile(rars[0])
# Skip if it's encrypted
if zf.needs_password():
@@ -947,9 +948,13 @@ def rar_renamer(nzo: NzbObject) -> int:
if not os.path.isfile(file_to_check):
continue
# guard against cbr files due to pr#3114
if get_ext(file_to_check) == ".cbr":
continue
if rarfile.is_rarfile(file_to_check):
# if a rar file is fully encrypted, rarfile.RarFile() will return an empty list:
if not rarfile.RarFile(file_to_check, single_file_check=True).filelist():
# if a rar file is fully encrypted, RarFile() will return an empty list:
if not SABRarFile(file_to_check, part_only=True).filelist():
logging.info(
"Download %s contains a fully encrypted & obfuscated rar-file: %s.",
nzo.final_name,
@@ -965,9 +970,7 @@ def rar_renamer(nzo: NzbObject) -> int:
logging.debug("Detected volume-number %s from RAR-header: %s ", rar_vol, file_to_check)
volnrext[file_to_check] = (rar_vol, new_extension)
# The files inside rar file
rar_contents = rarfile.RarFile(
os.path.join(nzo.download_path, file_to_check), single_file_check=True
).filelist()
rar_contents = SABRarFile(os.path.join(nzo.download_path, file_to_check), part_only=True).filelist()
try:
rarvolnr[rar_vol]
except Exception:

View File

@@ -40,6 +40,7 @@ class Scheduler:
self.scheduler = kronos.ThreadedScheduler()
self.pause_end: Optional[float] = None # Moment when pause will end
self.resume_task: Optional[kronos.Task] = None
self.rss_task: Optional[kronos.Task] = None # RSS interval task
self.restart_scheduler = False
self.pp_pause_event = False
self.load_schedules()
@@ -189,8 +190,7 @@ class Scheduler:
delay = random.randint(0, interval - 1)
logging.info("Scheduling RSS interval task every %s min (delay=%s)", interval, delay)
sabnzbd.RSSReader.next_run = time.time() + delay * 60
self.scheduler.add_interval_task(sabnzbd.RSSReader.run, "RSS", delay * 60, interval * 60)
self.scheduler.add_single_task(sabnzbd.RSSReader.run, "RSS", 15)
self.rss_task = self.scheduler.add_interval_task(sabnzbd.RSSReader.run, "RSS", delay * 60, interval * 60)
if cfg.version_check():
# Check for new release daily at a random time
@@ -449,8 +449,15 @@ class Scheduler:
self.scheduler.add_single_task(action, "", interval * 60, args=parms)
def force_rss(self):
"""Add a one-time RSS scan, one second from now"""
self.scheduler.add_single_task(sabnzbd.RSSReader.run, "RSS", 1)
"""Run RSS scan immediately and reschedule interval task"""
# Cancel the current RSS interval task
if self.rss_task:
self.scheduler.cancel(self.rss_task)
# Schedule a new interval task and start one now
interval = cfg.rss_rate() * 60 # Convert minutes to seconds
sabnzbd.RSSReader.next_run = time.time() + interval
self.rss_task = self.scheduler.add_interval_task(sabnzbd.RSSReader.run, "RSS", 0, interval)
def sort_schedules(all_events, now=None):

View File

@@ -171,6 +171,7 @@ SKIN_TEXT = {
"mode": TT("Processing"), #: Queue page table column header
"name": TT("Name"), #: Queue page table column header
"button-retry": TT("Retry"), #: Queue page button
"button-mark-completed": TT("Mark as Completed & Remove Temporary Files"), #: History page button
"eoq-scripts": TT("Scripts"), #: Queue page table, script selection menu
"purgeQueue": TT("Purge Queue"), #: Queue page button
"purgeQueueConf": TT("Delete all items from the queue?"), #: Confirmation popup
@@ -544,7 +545,8 @@ SKIN_TEXT = {
"srv-expire_date": TT("Account expiration date"),
"srv-explain-expire_date": TT("Warn 5 days in advance of account expiration date."),
"srv-explain-quota": TT(
"Quota for this account, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few minutes."
"Quota for this server, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />"
"Checked every few minutes. Notification is sent when quota is spent."
),
"srv-retention": TT("Retention time"), #: Server's retention time in days
"srv-ssl": TT("SSL"), #: Server SSL tickbox
@@ -838,6 +840,9 @@ SKIN_TEXT = {
"Glitter-backToQueue": TT("Send back to queue"),
"Glitter-purgeOrphaned": TT("Delete All"),
"Glitter-retryAllOrphaned": TT("Retry all"),
"Glitter-clearOrphanWarning": TT(
"Are you sure you want to delete all folders in your Temporary Download Folder? This cannot be undone!"
),
"Glitter-deleteJobAndFolders": TT("Remove NZB & Delete Files"),
"Glitter-addFromURL": TT("Fetch NZB from URL"),
"Glitter-addFromFile": TT("Upload NZB"),
@@ -914,6 +919,7 @@ SKIN_TEXT = {
"wizard-goto": TT("Go to SABnzbd"), #: Wizard step
"wizard-exit": TT("Exit SABnzbd"), #: Wizard EXIT button on first page
"wizard-start": TT("Start Wizard"), #: Wizard START button on first page
"wizard-test-server-required": TT("Click on Test Server before continuing"), #: Tooltip for disabled Next button
"restore-backup": TT("Restore backup"),
# Special
"yourRights": TT(

View File

@@ -660,6 +660,16 @@ def guess_what(name: str) -> MatchesDict:
# Unfix the title
guess["title"] = guess.get("title", "")[len(digit_fix) :]
# Handle weird anime episode notation, that results in the episode number ending up as the episode title
if (
guess.get("type") == "episode"
and not "episode" in guess
and "season" in guess
and guess.get("episode_title", "").isdigit()
):
guess.setdefault("episode", default=int(guess.get("episode_title")))
guess.pop("episode_title")
# Force season to 1 for seasonless episodes with no date
if guess.get("type") == "episode" and "date" not in guess:
guess.setdefault("season", 1)

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