Compare commits

...

166 Commits

Author SHA1 Message Date
Safihre
63c03b42a9 Update text files for 4.6.0Beta2 2025-12-22 22:00:02 +01:00
SABnzbd Automation
4539837fad Update translatable texts
[skip ci]
2025-12-22 20:53:43 +00:00
Safihre
a0cd48e3f5 Notify user if they run AMD64 version on ARM64 Windows machine
Closes #3235
2025-12-22 21:52:58 +01:00
Safihre
ceeb7cb162 Add Windows ARM64 binary 2025-12-22 21:25:17 +01:00
SABnzbd Automation
f9f4e1b028 Update translatable texts
[skip ci]
2025-12-22 15:40:48 +00:00
Safihre
6487944c6c Move Pipelining setting to Server-level 2025-12-22 16:38:46 +01:00
renovate[bot]
239fddf39c Update all dependencies (develop) (#3238)
* Update all dependencies

* Compare fakefs result after sorting

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Safihre <safihre@sabnzbd.org>
2025-12-22 12:45:00 +00:00
SABnzbd Automation
8ada8b2fd9 Update translatable texts
[skip ci]
2025-12-19 11:41:18 +00:00
Safihre
b19bd65495 Show error in case of failed NZB upload
Closes #3233
2025-12-19 12:40:34 +01:00
Safihre
e3ea5fdd64 Update appdata file with Flathub suggestions
@jcfp
2025-12-19 11:54:53 +01:00
Safihre
4fdb89701a Add release URL's to appdata 2025-12-19 11:42:36 +01:00
SABnzbd Automation
9165c4f304 Update translatable texts
[skip ci]
2025-12-18 20:10:14 +00:00
mnightingale
4152f0ba6a Increase max pipelining (#3234) 2025-12-18 20:09:24 +00:00
SABnzbd Automation
3eaab17739 Update translatable texts
[skip ci]
2025-12-16 09:04:29 +00:00
Safihre
578bfd083d Update text files 4.6.0 Beta 1 2025-12-16 10:03:41 +01:00
renovate[bot]
dd464456e4 Update all dependencies (#3231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 02:08:17 +00:00
mnightingale
e7a0255359 Make behaviour after reset more robust (#3229)
* Make behaviour after reset more robust

* Remove use of hasattr and rename to generation

* I had a feeling this would be a circular reference

* Reset and increment generation under lock
2025-12-14 22:39:48 +01:00
mnightingale
2e1281d9e8 Fix nzb types (#3230) 2025-12-14 15:46:14 +01:00
SABnzbd Automation
efecefdd3b Update translatable texts
[skip ci]
2025-12-09 20:22:55 +00:00
Safihre
a91e718ef5 Split nzbstuff into separate files for Article, NzbFile and NzbObject (#3221) 2025-12-09 21:21:51 +01:00
mnightingale
b420975267 Fix read/write actions after reset_nw (#3223) 2025-12-09 19:39:49 +01:00
SABnzbd Automation
c4211df8dc Update translatable texts
[skip ci]
2025-12-08 21:37:36 +00:00
Safihre
e182707d3a Update text files for 4.6.0Alpha2 2025-12-08 22:36:51 +01:00
Safihre
05cbd9d7c4 Correct process_nzb_only_download and add tests 2025-12-08 11:42:22 +01:00
Safihre
6e8683349f Keep NZB name prefix when processing multiple NZBs
Closes #3217
2025-12-08 10:27:16 +01:00
Safihre
adb4816552 Update to Python 3.14.2 2025-12-08 10:27:16 +01:00
renovate[bot]
3914290c11 Update all dependencies (#3219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 01:00:54 +00:00
Safihre
f76bf55b4a Handle aborted Direct Unpack better
Closes #3212
2025-12-05 16:18:17 +01:00
SABnzbd Automation
1cde764336 Update translatable texts
[skip ci]
2025-12-05 12:34:16 +00:00
mnightingale
44d94226ec Pipelining and performance optimisations (#3199)
* Pipelining and performance optimisations

* Refactor to remove handle_remainder and add on_response callback to allow inspecting of nntp messages

* Logic fix if there are sockets but nothing to read/write

* Fix logic errors for failed article requests

* Fix logic for reconfiguring servers

* Add guard_restart callback to pipelining_requests

* Fix article download stats

* Fix current article request shown via api

* Removal of DecodingStatus

* Fix circular reference

* Cleanup imports

* Handle reset_nw and hard_reset for inflight requests

* Improve __request_article behaviour using discard helper

* Article should be None here (before auth) but just in case

* Remove command_queue_condition unnecessary with the pull rather than push queue system

* During reset discard any data received prior to sending quit request

* Circular references again

* Revert to using bytearray

* Revert "During reset discard any data received prior to sending quit request"

This reverts commit ed522e3e80.

* Simpler interaction with sabctools

* Temporarily use the sabctools streaming decoder branch

* Fix most uu tests

* Reduce maximum pipelining requests

* Fix the squiggly line

* Remove some LOG_ALL debug code

* Make get_articles return consistent (None) - it now populates the server deque

* Reduce NNTP_BUFFER_SIZE

* Rename PIPELINING_REQUESTS to DEF_PIPELINING_REQUESTS

* A little refactoring

* Reduce default pipelining until it is dynamic

* Use BoundedSemaphore and fix the unacquired release

* Use crc from sabctools for uu and make filename logic consistent wit yenc

* Use sabctools 9.0.0

* Fix Check Before Download

* Move lock to NzbFile

* Use sabctools 9.1.0

* Minor change

* Fix 430 on check before download

* Update sabnews to work reliably with pipelining

* Minor tidy up

* Why does only Linux complain about this

* Leave this as it was

* Remove unused import

* Compare enum by identity

* Remove command_queue and just prepare a single request
Check if it should be sent and discard when paused

* Kick-start idle connections

* Modify events sockets are monitored for
2025-12-05 13:33:35 +01:00
Safihre
e8e8fff5bf Prevent filepath creation before first article is processed (#3215) 2025-12-05 13:18:27 +01:00
SABnzbd Automation
1b04e07d40 Update translatable texts
[skip ci]
2025-12-04 14:14:01 +00:00
Safihre
54db889f05 Update sfv help text
Closes #3214 and #3213
2025-12-04 15:13:07 +01:00
Safihre
777d279267 Only clear work-flag for post processing when needed 2025-12-01 16:40:59 +01:00
Safihre
75be6b5850 Use Event's to handle Post Processing queue
See #3209
2025-12-01 15:28:05 +01:00
Safihre
a4657e2bd3 Correct rar-version logging line 2025-12-01 11:36:10 +01:00
renovate[bot]
095b48ca47 Update all dependencies (#3210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 01:32:06 +00:00
renovate[bot]
d459f69113 Update all dependencies (#3204)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:49:05 +00:00
mnightingale
2ecdd0b940 Optimise bpsmeter to avoid repeat lookups and try/except (#3203) 2025-11-22 14:46:21 +01:00
Safihre
73a4ad50e5 Allow longer build timeout for Snap amr64 build 2025-11-21 12:02:41 +01:00
Safihre
9b59e24961 Lower macOS build version to support older clients 2025-11-21 11:50:45 +01:00
Safihre
27e164763e Remove unused imports and shorten build timeouts 2025-11-21 11:29:53 +01:00
Safihre
eb544d85c7 Update text files for 4.6.0Alpha1 2025-11-21 11:02:24 +01:00
Safihre
ad85a241df Enable verify_xff_header by default 2025-11-21 10:12:47 +01:00
Safihre
e4d8642b4f Correct mobile layout if Full Width is enabled 2025-11-21 10:12:19 +01:00
Safihre
77b35e7904 Re-enable all Python versions for CI tests 2025-11-21 10:05:01 +01:00
Safihre
f8a0b3db52 Remove hostname resolution in get_webhost
#3131
2025-11-21 10:00:12 +01:00
Safihre
9c8b26ab4e Use new removeprefix and removesuffix 2025-11-21 10:00:11 +01:00
Safihre
67a5a552fd Add missing typing hints to several files 2025-11-21 10:00:10 +01:00
Safihre
80f57a2b9a Drop support for Python 3.8 2025-11-21 10:00:09 +01:00
Safihre
baaf7edc89 Windows tray icon disappears after Explorer restart
Closes #3200
2025-11-20 16:05:52 +01:00
Safihre
2d9f480af1 Only measure real write time during disk speed test 2025-11-20 15:34:34 +01:00
L-Cie
2266ac33aa Address low throughput reporting in diskspeed.py (#3197)
* increased buffer, mesaurement time, changed file management and calcucation of result

* Write smaller chunks first, abort if time exceeds

* Move urandom dump to diskspeedmeasure, reduced buffer size to 16MB and recycled buffer for more efficient resource usage during writes

* fixed formatting issues

* fixed formatting issues

* fixed formatting issues

---------

Co-authored-by: L-Cie <lcie@sturmklinge.ch>
2025-11-20 06:49:18 +01:00
renovate[bot]
1ba479398c Update all dependencies (develop) (#3195)
* Update all dependencies

* Pin tavern due to failure in newer versions

* User SABnzbd User-agent in wiki test

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Safihre <safihre@sabnzbd.org>
2025-11-18 13:30:07 +01:00
Safihre
f71a81f7a8 Stop endless loop in edge-case of no STAT or HEAD support
Closes #3191
2025-11-13 13:33:11 +01:00
Safihre
1916c01bd9 Use general failure flag for pre-check result check
Closes #3190
2025-11-11 16:48:05 +01:00
Safihre
699d97965c Make Assembler queue configurable and auto increase on high bw-limit 2025-11-10 15:54:26 +01:00
renovate[bot]
399935ad21 Update all dependencies (develop) (#3186)
* Update all dependencies

* Allow older markdown for Python 3.9 and below

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Safihre <safihre@sabnzbd.org>
2025-11-10 14:19:10 +00:00
Sander
0824fdc7c7 STAT: tell on which server an article is present (#3185)
* STAT: tell on which server an article is present

* Update logging format for article presence: old format
2025-11-10 11:08:31 +01:00
SABnzbd Automation
a3f8e89af8 Update translatable texts
[skip ci]
2025-11-05 21:37:39 +00:00
Safihre
f9f17731c8 Certificate validation should also be Strict in Wizard
Closes ##3183
2025-11-05 22:36:49 +01:00
SABnzbd Automation
b052325ea7 Update translatable texts
[skip ci]
2025-11-03 13:29:35 +00:00
Safihre
daca14f97e Update Apprise texts 2025-11-03 14:28:47 +01:00
renovate[bot]
daa26bc1a6 Update dependency cheroot to v11.1.0 (#3180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 06:43:18 +00:00
renovate[bot]
70d5134d28 Update all dependencies (#3174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 01:46:12 +00:00
Safihre
a32458d9a9 Resolve another PyGithub deprecation 2025-10-24 13:22:07 +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
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
SABnzbd Automation
d7fa3e1f7b Update translatable texts
[skip ci]
2025-10-13 14:24:54 +00: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
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
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
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
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
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
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
152 changed files with 7894 additions and 8619 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

@@ -23,9 +23,15 @@
"jaraco.collections",
"sabctools",
"paho-mqtt",
"werkzeug"
"werkzeug",
"tavern"
],
"packageRules": [
{
"matchManagers": ["github-actions"],
"matchPackageNames": ["windows", "macos"],
"enabled": false
},
{
"matchPackagePatterns": [
"*"

View File

@@ -8,16 +8,26 @@ env:
jobs:
build_windows:
name: Build Windows binary
runs-on: windows-latest
timeout-minutes: 30
name: Build Windows binary (${{ matrix.architecture }})
strategy:
fail-fast: false
matrix:
include:
- architecture: x64
runs-on: windows-2022
- architecture: arm64
runs-on: windows-11-arm
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
architecture: "x64"
python-version: "3.14"
architecture: ${{ matrix.architecture }}
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Install Python dependencies
@@ -27,44 +37,87 @@ 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
uses: actions/upload-artifact@v4
- name: Build Windows standalone binary
id: windows_binary
run: python builder/package.py binary
- name: Upload Windows standalone binary (unsigned)
uses: actions/upload-artifact@v6
id: upload-unsigned-binary
with:
path: "*-win64-bin.zip"
name: Windows standalone binary
path: "*-win*-bin.zip"
name: Windows standalone binary (${{ matrix.architecture }})
- name: Sign Windows standalone binary
uses: signpath/github-action-submit-signing-request@v2
if: contains(github.ref, 'refs/tags/')
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@v6
if: contains(github.ref, 'refs/tags/')
with:
name: Windows standalone binary (${{ matrix.architecture }}, signed)
path: "signed"
- name: Build Windows installer
if: matrix.architecture == 'x64'
run: python builder/package.py installer
- name: Upload Windows installer
uses: actions/upload-artifact@v4
if: matrix.architecture == 'x64'
uses: actions/upload-artifact@v6
id: upload-unsigned-installer
with:
path: "*-win-setup.exe"
name: Windows installer
name: Windows installer (${{ matrix.architecture }})
- name: Sign Windows installer
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: signpath/github-action-submit-signing-request@v2
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: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v6
with:
name: Windows installer (${{ matrix.architecture }}, signed)
path: "signed/*-win-setup.exe"
build_macos:
name: Build macOS binary
runs-on: macos-14
timeout-minutes: 30
timeout-minutes: 15
env:
# 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.2"
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@v6
- 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
id: cache-python-download
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/python.pkg
key: cache-macOS-Python-${{ env.PYTHON_VERSION }}
@@ -89,8 +142,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
@@ -99,7 +152,7 @@ jobs:
# Run this on macOS so the line endings are correct by default
run: python builder/package.py source
- name: Upload source distribution
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
path: "*-src.tar.gz"
name: Source distribution
@@ -112,27 +165,88 @@ jobs:
python3 builder/package.py app
python3 builder/make_dmg.py
- name: Upload macOS binary
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
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: x64
- os: ubuntu-24.04-arm
linux_arch: arm64
steps:
- uses: actions/checkout@v6
- name: Cache par2cmdline-turbo tarball
uses: actions/cache@v5
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@v6
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@v6
- 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@v7
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@v7
with:
name: macOS binary
- name: Download Windows artifacts
uses: actions/download-artifact@v7
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 +254,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@v6
- name: Black Code Formatter
uses: lgeiger/black-action@master
with:
@@ -20,7 +20,7 @@ jobs:
builder/SABnzbd.spec
tests
--line-length=120
--target-version=py38
--target-version=py39
--check
--diff
@@ -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"
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@v6
- 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
@@ -26,7 +26,7 @@ jobs:
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
- uses: dessant/lock-threads@v6
with:
log-output: true
issue-inactive-days: 60

View File

@@ -12,7 +12,7 @@ jobs:
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
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.1.0
if: env.TX_TOKEN
with:
commit_message: |

View File

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

View File

@@ -16,7 +16,7 @@ If you want to know more you can head over to our website: https://sabnzbd.org.
SABnzbd has a few dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages, then you likely already have all the needed dependencies. If not, here's what you're looking for:
- `python` (Python 3.8 and above, often called `python3`)
- `python` (Python 3.9 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt -U`
- `par2` (Multi-threaded par2 installation guide can be found [here](https://sabnzbd.org/wiki/installation/multicore-par2))
- `unrar` (make sure you get the "official" non-free version of unrar)
@@ -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,23 +1,31 @@
Release Notes - SABnzbd 4.5.0 Release Candidate 1
Release Notes - SABnzbd 4.6.0 Beta 2
=========================================================
This is the first Release Candidate for the 4.5.0 release.
This is the second beta release of version 4.6.
## New features in 4.5.0
## New features in 4.6.0
* Improved failure detection by downloading additional par2 files right away.
* Added more diagnostic information about the system.
* Use XFF headers for login validation if `verify_xff_header` is enabled.
* Added Turkish translation (by @cardpuncher).
* Added `unrar_parameters` option to supply custom Unrar parameters.
* Windows: Removed MultiPar support.
* Windows and macOS: Updated Python to 3.13.2, 7zip to 24.09,
Unrar to 7.10 and par2cmdline-turbo to 1.2.0.
* Added support for NNTP Pipelining which eliminates idle waiting between
requests, significantly improving speeds on high-latency connections.
Read more here: https://sabnzbd.org/wiki/advanced/nntp-pipelining
* Dynamically increase Assembler limits on faster connections.
* Improved disk speed measurement in Status window.
* Enable `verify_xff_header` by default.
* Reduce delays between jobs during post-processing.
* If a download only has `.nzb` files inside, the new downloads
will include the name of the original download.
* Dropped support for Python 3.8.
* Windows: Added Windows ARM (portable) release.
## Bug fixes since 4.4.0
## Bug fixes since 4.5.0
* `Check before download` could get stuck or fail to reject.
* No error was shown in case NZB upload failed.
* Correct mobile layout if `Full Width` is enabled.
* Aborted Direct Unpack could result in no files being unpacked.
* Windows: Tray icon disappears after Explorer restart.
* macOS: Slow to start on some network setups.
* Handle filenames that exceed maximum filesystem lengths.
* Directly decompress gzip responses when retrieving NZB's.
## Upgrade notices
@@ -30,7 +38,12 @@ This is the first Release Candidate for the 4.5.0 release.
* 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

@@ -19,8 +19,8 @@ import sys
# Trick to show a better message on older Python
# releases that don't support walrus operator
if Python_38_is_required_to_run_SABnzbd := sys.hexversion < 0x03080000:
print("Sorry, requires Python 3.8 or above")
if Python_39_is_required_to_run_SABnzbd := sys.hexversion < 0x03090000:
print("Sorry, requires Python 3.9 or above")
print("You can read more at: https://sabnzbd.org/wiki/installation/install-off-modules")
sys.exit(1)
@@ -40,7 +40,7 @@ import re
import gc
import threading
import http.cookies
from typing import List, Dict, Any
from typing import Any
try:
import sabctools
@@ -142,7 +142,7 @@ class GUIHandler(logging.Handler):
"""Initializes the handler"""
logging.Handler.__init__(self)
self._size: int = size
self.store: List[Dict[str, Any]] = []
self.store: list[dict[str, Any]] = []
def emit(self, record: logging.LogRecord):
"""Emit a record by adding it to our private queue"""
@@ -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(
@@ -543,21 +540,19 @@ def get_webhost(web_host, web_port, https_port):
# If only APIPA's or IPV6 are found, fall back to localhost
ipv4 = ipv6 = False
localhost = hostip = "localhost"
try:
info = socket.getaddrinfo(socket.gethostname(), None)
# Valid user defined name?
info = socket.getaddrinfo(web_host, None)
except socket.error:
# Hostname does not resolve
if not is_localhost(web_host):
web_host = "0.0.0.0"
try:
# Valid user defined name?
info = socket.getaddrinfo(web_host, None)
info = socket.getaddrinfo(localhost, None)
except socket.error:
if not is_localhost(web_host):
web_host = "0.0.0.0"
try:
info = socket.getaddrinfo(localhost, None)
except socket.error:
info = socket.getaddrinfo("127.0.0.1", None)
localhost = "127.0.0.1"
info = socket.getaddrinfo("127.0.0.1", None)
localhost = "127.0.0.1"
for item in info:
ip = str(item[4][0])
if ip.startswith("169.254."):
@@ -1103,12 +1098,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,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import platform
import re
# Constants
@@ -43,11 +44,17 @@ RELEASE_VERSION_BASE = f"{RELEASE_VERSION_TUPLE[0]}.{RELEASE_VERSION_TUPLE[1]}.{
RELEASE_NAME = "SABnzbd-%s" % RELEASE_VERSION
RELEASE_TITLE = "SABnzbd %s" % RELEASE_VERSION
RELEASE_SRC = RELEASE_NAME + "-src.tar.gz"
RELEASE_BINARY = RELEASE_NAME + "-win64-bin.zip"
RELEASE_INSTALLER = RELEASE_NAME + "-win-setup.exe"
RELEASE_WIN_BIN_X64 = RELEASE_NAME + "-win64-bin.zip"
RELEASE_WIN_BIN_ARM64 = RELEASE_NAME + "-win-arm64-bin.zip"
RELEASE_WIN_INSTALLER = RELEASE_NAME + "-win-setup.exe"
RELEASE_MACOS = RELEASE_NAME + "-macos.dmg"
RELEASE_README = "README.mkd"
# Detect architecture
RELEASE_WIN_BIN = RELEASE_WIN_BIN_X64
if platform.machine() == "ARM64":
RELEASE_WIN_BIN = RELEASE_WIN_BIN_ARM64
# Used in package.py and SABnzbd.spec
EXTRA_FILES = [
RELEASE_README,

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,7 +27,7 @@ import tarfile
import urllib.request
import urllib.error
import configobj
from typing import List
import packaging.version
from constants import (
RELEASE_VERSION,
@@ -36,8 +35,8 @@ from constants import (
VERSION_FILE,
RELEASE_README,
RELEASE_NAME,
RELEASE_BINARY,
RELEASE_INSTALLER,
RELEASE_WIN_BIN,
RELEASE_WIN_INSTALLER,
ON_GITHUB_ACTIONS,
RELEASE_THIS,
RELEASE_SRC,
@@ -70,9 +69,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 +108,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 +246,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_BINARY)
safe_remove(RELEASE_NAME)
safe_remove(RELEASE_WIN_BIN)
# 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 +271,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_WIN_BIN, "SABnzbd"], cwd="dist")
shutil.move(f"dist/{RELEASE_WIN_BIN}", RELEASE_WIN_BIN)
# 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_WIN_BIN}"):
print("Using signed version of SABnzbd binaries")
safe_remove("dist/SABnzbd")
run_external_command(["win/7zip/7za.exe", "x", "-odist", f"signed/{RELEASE_WIN_BIN}"])
# 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_WIN_INSTALLER,
"NSIS_Installer.nsi.tmp",
]
)
if "app" in sys.argv:
# Must be run on macOS
@@ -271,13 +330,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 +359,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

@@ -29,8 +29,9 @@ from constants import (
RELEASE_VERSION_BASE,
PRERELEASE,
RELEASE_SRC,
RELEASE_BINARY,
RELEASE_INSTALLER,
RELEASE_WIN_BIN_X64,
RELEASE_WIN_BIN_ARM64,
RELEASE_WIN_INSTALLER,
RELEASE_MACOS,
RELEASE_README,
RELEASE_THIS,
@@ -42,8 +43,9 @@ from constants import (
# Verify we have all assets
files_to_check = (
RELEASE_SRC,
RELEASE_BINARY,
RELEASE_INSTALLER,
RELEASE_WIN_BIN_X64,
RELEASE_WIN_BIN_ARM64,
RELEASE_WIN_INSTALLER,
RELEASE_MACOS,
RELEASE_README,
)
@@ -75,7 +77,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 +88,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
@@ -112,7 +114,7 @@ if RELEASE_THIS and gh_token:
print("Removing existing asset %s " % gh_asset.name)
gh_asset.delete_asset()
# Upload the new one
print("Uploading %s to release %s" % (file_to_check, gh_release.title))
print("Uploading %s to release %s" % (file_to_check, gh_release.name))
gh_release.upload_asset(file_to_check)
# Check if we now have all files

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.17.0
packaging==25.0
pyinstaller-hooks-contrib==2025.5
altgraph==0.17.4
wrapt==1.17.2
pyinstaller-hooks-contrib==2025.10
altgraph==0.17.5
wrapt==2.0.1
setuptools==80.9.0
# For the Windows build
@@ -12,8 +12,8 @@ pefile==2024.8.26; sys_platform == 'win32'
pywin32-ctypes==0.2.3; sys_platform == 'win32'
# For the macOS build
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'
dmgbuild==1.6.6; sys_platform == 'darwin'
mac-alias==2.2.3; sys_platform == 'darwin'
macholib==1.16.4; sys_platform == 'darwin'
ds-store==1.3.2; sys_platform == 'darwin'
PyNaCl==1.6.1; 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

@@ -187,7 +187,7 @@
<td><label for="apprise_enable"> $T('opt-apprise_enable')</label></td>
</tr>
</table>
<em>$T('explain-apprise_enable')</em><br>
<p>$T('explain-apprise_enable')</p>
<p>$T('version'): ${apprise.__version__}</p>
$show_cat_box('apprise')
@@ -197,7 +197,7 @@
<div class="field-pair">
<label class="config" for="apprise_urls">$T('opt-apprise_urls')</label>
<input type="text" name="apprise_urls" id="apprise_urls" value="$apprise_urls" />
<span class="desc">$T('explain-apprise_urls'). <br>$T('readwiki')</span>
<span class="desc">$T('explain-apprise_urls')</span>
</div>
<div class="field-pair">
<span class="desc">$T('explain-apprise_extra_urls')</span>

View File

@@ -117,6 +117,12 @@
<input type="checkbox" name="optional" id="optional" value="1" />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="pipelining_requests">$T('srv-pipelining_requests')</label>
<input type="number" name="pipelining_requests" id="pipelining_requests" min="1" max="20" value="1" />
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date">$T('srv-expire_date')</label>
<input type="date" name="expire_date" id="expire_date" />
@@ -132,7 +138,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">
@@ -248,6 +254,12 @@
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="pipelining_requests$cur">$T('srv-pipelining_requests')</label>
<input type="number" name="pipelining_requests" id="pipelining_requests$cur" value="$server['pipelining_requests']" min="1" max="20" required />
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date$cur">$T('srv-expire_date')</label>
<input type="date" name="expire_date" id="expire_date$cur" value="$server['expire_date']" />
@@ -464,14 +476,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 +575,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 +586,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

@@ -6,8 +6,12 @@
<span class="glyphicon glyphicon-open"></span> $T('Glitter-notification-uploading') <span class="main-notification-box-file-count"></span>
</div>
<div class="main-notification-box-uploading-failed">
<span class="glyphicon glyphicon-exclamation-sign"></span> $T('Glitter-notification-upload-failed').replace('%s', '') <span class="main-notification-box-file-count"></span>
</div>
<div class="main-notification-box-queue-repair">
<span class="glyphicon glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
<span class="glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
</div>
<div class="main-notification-box-disconnect">
@@ -681,9 +685,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 +806,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",
@@ -727,6 +726,9 @@ function ViewModel() {
$('#nzbname').val('')
$('.btn-file em').html(glitterTranslate.chooseFile + '&hellip;')
}
}).fail(function(xhr, status, error) {
// Update the uploading notification text to show error
showNotification('.main-notification-box-uploading-failed', 0, error)
});
}
@@ -896,7 +898,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 +915,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

@@ -69,6 +69,10 @@ legend,
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.main-notification-box-uploading-failed {
color: #F95151;
}
.container,
.modal-body,
.modal-footer {

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

@@ -7,6 +7,10 @@
padding-right: 8px;
}
.container-full-width .container {
width: 100%;
}
.main-navbar {
margin-top: 0;
padding: 0;
@@ -163,7 +167,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

@@ -5,6 +5,10 @@
<metadata_license>MIT</metadata_license>
<name>SABnzbd</name>
<summary>Free and easy binary newsreader</summary>
<branding>
<color type="primary" scheme_preference="light">#e7e7e7</color>
<color type="primary" scheme_preference="dark">#444444</color>
</branding>
<description>
<p>
SABnzbd is a free and Open Source web-based binary newsreader,
@@ -17,6 +21,13 @@
and services that help automate the download process.
</p>
</description>
<keywords>
<keyword>usenet</keyword>
<keyword>nzb</keyword>
<keyword>download</keyword>
<keyword>newsreader</keyword>
<keyword>binary</keyword>
</keywords>
<categories>
<category>Network</category>
<category>FileTransfer</category>
@@ -24,30 +35,49 @@
<url type="homepage">https://sabnzbd.org</url>
<url type="bugtracker">https://github.com/sabnzbd/sabnzbd/issues</url>
<url type="vcs-browser">https://github.com/sabnzbd/sabnzbd</url>
<url type="contribute">https://github.com/sabnzbd/sabnzbd</url>
<url type="translate">https://sabnzbd.org/wiki/translate</url>
<url type="donation">https://sabnzbd.org/donate</url>
<url type="help">https://sabnzbd.org/wiki/</url>
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="4.5.2" date="2025-07-01" type="stable"/>
<release version="4.5.1" date="2025-04-11" type="stable"/>
<release version="4.5.0" date="2025-04-01" type="stable"/>
<release version="4.4.1" date="2024-12-23" type="stable"/>
<release version="4.4.0" date="2024-12-09" type="stable"/>
<release version="4.3.3" date="2024-08-01" type="stable"/>
<release version="4.3.2" date="2024-05-30" type="stable"/>
<release version="4.3.1" date="2024-05-03" type="stable"/>
<release version="4.3.0" date="2024-05-01" type="stable"/>
<release version="4.2.2" date="2024-02-01" type="stable"/>
<release version="4.2.1" date="2024-01-05" type="stable"/>
<release version="4.2.0" date="2024-01-03" type="stable"/>
<release version="4.1.0" date="2023-09-26" type="stable"/>
<release version="4.0.3" date="2023-06-16" type="stable"/>
<release version="4.0.2" date="2023-06-09" type="stable"/>
<release version="4.0.1" date="2023-05-01" type="stable"/>
<release version="4.0.0" date="2023-04-28" type="stable"/>
<release version="3.7.2" date="2023-02-05" type="stable"/>
<release version="4.6.0" date="2025-12-24" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.6.0</url>
</release>
<release version="4.5.5" date="2025-10-24" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.5</url>
</release>
<release version="4.5.4" date="2025-10-22" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.4</url>
</release>
<release version="4.5.3" date="2025-08-25" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.3</url>
</release>
<release version="4.5.2" date="2025-07-09" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.2</url>
</release>
<release version="4.5.1" date="2025-04-11" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.1</url>
</release>
<release version="4.5.0" date="2025-04-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.0</url>
</release>
<release version="4.4.1" date="2024-12-23" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.1</url>
</release>
<release version="4.4.0" date="2024-12-09" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.0</url>
</release>
<release version="4.3.3" date="2024-08-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.3</url>
</release>
<release version="4.3.2" date="2024-05-30" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.2</url>
</release>
<release version="4.3.1" date="2024-05-03" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.1</url>
</release>
</releases>
<launchable type="desktop-id">sabnzbd.desktop</launchable>
<provides>
@@ -70,11 +100,59 @@
<screenshots>
<screenshot type="default">
<image>https://sabnzbd.org/images/landing/screenshots/interface.png</image>
<caption>Web interface</caption>
<caption>Intuitive interface</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/night-mode.png</image>
<caption>Night mode</caption>
<caption>Also comes in Night-mode</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/add-nzb.png</image>
<caption>Add NZB's or use drag-and-drop!</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/phone-interface.png</image>
<caption>Scales to any screen size</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/history-details.png</image>
<caption>Easy overview of all history details</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/phone-extra.png</image>
<caption>Every option, on every screen size</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/file-lists.png</image>
<caption>Manage a job's individual files</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-speedlimit.png</image>
<caption>Easy speed limiting</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-options.png</image>
<caption>Quickly change settings</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/dashboard.png</image>
<caption>Easy system check</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/connections-overview.png</image>
<caption>See active connections</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/skin-settings.png</image>
<caption>Customize the interface</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/tabbed.png</image>
<caption>Tabbed-mode</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-custom-pause.png</image>
<caption>Specify any pause duration</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/config.png</image>

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)"
@@ -120,6 +125,11 @@ msgstr ""
msgid "Current umask (%o) might deny SABnzbd access to the files and folders it creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Completed Download Folder %s is on FAT file system, limiting maximum file size to 4GB"
@@ -274,7 +284,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr ""
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr ""
@@ -293,6 +303,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 ""
@@ -490,11 +513,6 @@ msgstr ""
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr ""
@@ -512,11 +530,6 @@ msgstr ""
msgid "Connecting %s@%s failed, message=%s"
msgstr ""
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr ""
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr ""
@@ -670,6 +683,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 +700,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"
@@ -876,7 +889,7 @@ msgid "Update Available!"
msgstr ""
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -996,10 +1009,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 ""
@@ -1113,6 +1122,16 @@ msgstr ""
msgid "left"
msgstr ""
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr ""
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr ""
@@ -1286,103 +1305,18 @@ msgstr ""
msgid "NZB added to queue"
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr ""
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr ""
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr ""
@@ -2239,6 +2173,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"
@@ -3108,7 +3047,7 @@ msgid "Enable SFV-based checks"
msgstr ""
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgid "If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
@@ -3296,10 +3235,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 +3332,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
@@ -3467,6 +3402,14 @@ msgstr ""
msgid "Enable"
msgstr ""
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid "Request multiple articles per connection without waiting for each response first.<br />This can improve download speeds, especially on connections with higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -3870,17 +3813,16 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid "Send notifications directly to any notification service you use.<br>For example: Slack, Discord, Telegram, or any service from over 100 supported services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid "Apprise defines service connection information using URLs.<br>Read the Apprise wiki how to define the URL for each service.<br>Use a comma and/or space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4175,6 +4117,11 @@ msgstr ""
msgid "Filename"
msgstr ""
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr ""
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr ""
@@ -4303,6 +4250,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 +4492,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 ""
@@ -4575,6 +4531,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr ""
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -137,6 +144,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -309,7 +321,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "Neočekávaná přípona v rar souboru %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Přerušeno, nalezena neočekávaná připona"
@@ -328,6 +340,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"
@@ -539,11 +565,6 @@ msgstr ""
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Příliš mnoho spojení k serveru %s [%s]"
@@ -563,11 +584,6 @@ msgstr "Přihlášení k serveru %s se nezdařilo [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr ""
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Nejspíše chyba downloaderu"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Vypínání"
@@ -723,15 +739,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 +755,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"
@@ -944,7 +960,7 @@ msgid "Update Available!"
msgstr "Dostupná aktualizace!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Nezdařilo se nahrát soubor: %s"
@@ -1069,10 +1085,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"
@@ -1190,6 +1202,16 @@ msgstr "Zkouším SFV ověření"
msgid "left"
msgstr ""
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Nejspíše chyba downloaderu"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Tento server nepovoluje SSL na tomto portu"
@@ -1371,103 +1393,18 @@ msgstr "Nelze nahrát %s, detekován porušený soubor"
msgid "NZB added to queue"
msgstr "NZB přidáno do fronty"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignoruji duplikátní NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Nezdařilo se duplikovat NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "Duplikátní NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Prázdný NZB soubor %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "Nechtěná přípona v souboru %s (%s)"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Zrušeno, nelze dokončit"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Chyba při importu %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLIKÁT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ŠIFROVANÉ"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "PŘÍLIŠ VELKÝ"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "NEKOMPLETNÍ"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "NECHTĚNÝ"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "ČEKÁNÍ %s s"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "PROPAGUJI %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Staženo do %s s průměrnou rychlostí %s B/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Stáří"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pozastavuji duplikátní NZB \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problém s"
@@ -2327,6 +2264,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"
@@ -3265,7 +3207,8 @@ msgid "Enable SFV-based checks"
msgstr ""
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
@@ -3466,10 +3409,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 +3511,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
@@ -3653,6 +3592,17 @@ msgstr ""
msgid "Enable"
msgstr ""
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4062,17 +4012,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4389,6 +4344,11 @@ msgstr ""
msgid "Filename"
msgstr ""
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Stáří"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr ""
@@ -4517,6 +4477,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 +4729,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 ""
@@ -4797,6 +4768,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Prázdný NZB soubor %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

View File

File diff suppressed because it is too large Load Diff

View File

@@ -15,14 +15,14 @@
# Stefan Rodriguez Galeano, 2024
# M Z, 2024
# Gjelbrim Haskaj, 2024
# Safihre <safihre@sabnzbd.org>, 2024
# Media Cat, 2025
# 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: Media Cat, 2025\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"
@@ -52,6 +52,15 @@ 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 ""
"OpenSSL kann nicht verknüpft werden, optimierte SSL-Verbindungsfunktionen "
"werden nicht verwendet."
#. Error message
#: SABnzbd.py
msgid ""
@@ -158,6 +167,11 @@ msgstr ""
"Die aktuellen Zugriffseinstellungen (%o) könnte SABnzbd den Zugriff auf die "
"erstellten Dateien und Ordner von SABnzbd verweigern."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -348,7 +362,7 @@ msgstr "Unerwünschter Typ \"%s\" in RAR Datei. Unerwünschte Datei ist %s "
msgid "Unwanted extension is in rar file %s"
msgstr "Unerwünschter Dateityp im RAR-Archiv %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Abgebrochen, unerwünschte Dateieindung gefunden"
@@ -369,6 +383,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 "Warnung zur Kontingentgrenze (%d%%)"
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "Download nach Kontingentzurücksetzung fortgesetzt"
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Fehlerhafter Parameter"
@@ -594,11 +622,6 @@ msgstr "Fehler %s@%s zu initialisieren, aus folgendem Grund: %s"
msgid "Fatal error in Downloader"
msgstr "Schwerer Fehler im Downloader"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s:Unbekannter Statuscode%s für Artikel erhalten %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Zu viele Verbindungen zu Server %s [%s]"
@@ -620,11 +643,6 @@ msgstr "Anmelden beim Server fehlgeschlagen. %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Fehler beim Verbinden mit %s@%s, Meldung = %s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Vermute Fehler im Downloader"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Wird beendet …"
@@ -798,15 +816,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 +832,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"
@@ -1023,7 +1041,7 @@ msgid "Update Available!"
msgstr "Neue Version verfügbar!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Hochladen fehlgeschlagen: %s"
@@ -1150,10 +1168,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."
@@ -1274,6 +1288,16 @@ msgstr "Versuche SFV-Überprüfung"
msgid "left"
msgstr "rest"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s:Unbekannter Statuscode%s für Artikel erhalten %s"
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Vermute Fehler im Downloader"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Dieser Server erlaubt kein SSL auf diesem Port"
@@ -1460,106 +1484,18 @@ msgstr "Fehler beim Laden von %s. Beschädigte Datei gefunden."
msgid "NZB added to queue"
msgstr "NZB zur Warteschlange hinzugefügt"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Doppelte NZB \"%s\" wird ignoriert"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "kopieren der NZB \"%s\" fehlgeschlagen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "Doppelte NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr "Ungültige NZB-Datei %s wird übersprungen (Fehler: %s)"
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Leere NZB-Datei %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
"Das Vorwarteschlangen (pre-queue) Skript hat die Downloadaufgabe als "
"gescheitert markiert"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "Ungewollte Dateiendung in der Datei %s (%s)"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Abgebrochen, kann nicht fertiggestellt werden"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Fehler beim Importieren von %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLIKAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr "ALTERNATIVE"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "VERSCHLÜSSELT"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "ZU GROSS"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "UNVOLLSTÄNDIG"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "UNERWÜNSCHT"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "WARTE %s Sek"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "AUSBREITUNG %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr ""
"Heruntergeladen in %s mit einer Durchschnittsgeschwindigkeit von %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Alter"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s Artikel hatten ein ungültiges Format"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s Artikel fehlten"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s Artikel hatten nicht übereinstimmende Duplikate"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Doppelt vorhandene NZB \"%s\" angehalten"
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problem mit"
@@ -2458,6 +2394,11 @@ msgstr "Name"
msgid "Retry"
msgstr "Erneut versuchen"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr "Als abgeschlossen markieren und temporäre Dateien entfernen"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3495,8 +3436,9 @@ msgid "Enable SFV-based checks"
msgstr "SFV-basierte Überprüfung aktivieren"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Zusätzliche Überprüfung mittels SFV-Dateien durchführen"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3722,10 +3664,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 +3777,13 @@ 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."
"Kontingent für diesen Server, gezählt ab dem Zeitpunkt der Festlegung. In "
"Bytes, optional gefolgt von K,M,G.<br />Wird alle paar Minuten überprüft. "
"Benachrichtigung wird gesendet, wenn das Kontingent aufgebraucht ist."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -3933,6 +3871,17 @@ msgstr "Für unzuverlässige Server, wird bei Fehlern länger ignoriert"
msgid "Enable"
msgstr "Aktivieren"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4352,22 +4301,30 @@ msgid "Enable Apprise notifications"
msgstr "Aktivieren Sie Info-Benachrichtigungen"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
"Senden Sie Benachrichtigungen mit Anfragen an fast jeden "
"Benachrichtigungsdienst"
"Senden Sie Benachrichtigungen direkt an jeden von Ihnen genutzten "
"Benachrichtigungsdienst.<br>Zum Beispiel: Slack, Discord, Telegram oder "
"jeden anderen Dienst aus über 100 unterstützten Diensten!"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr "Standard Apprise URLs"
msgid "Use default Apprise URLs"
msgstr "Standard-Apprise-URLs verwenden"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
"Verwenden Sie ein Komma und/oder ein Leerzeichen, um mehr als eine URL zu "
"kennzeichnen."
"Apprise definiert Dienstverbindungsinformationen über URLs.<br>Lesen Sie das"
" Apprise-Wiki, um zu erfahren, wie Sie die URL für jeden Dienst "
"definieren.<br>Verwenden Sie ein Komma und/oder Leerzeichen, um mehr als "
"eine URL anzugeben."
#: sabnzbd/skintext.py
msgid ""
@@ -4708,6 +4665,11 @@ msgstr "Löschen"
msgid "Filename"
msgstr "Dateiname"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Alter"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Freier Speicherplatz"
@@ -4836,6 +4798,14 @@ 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 ""
"Sind Sie sicher, dass Sie alle Ordner in Ihrem temporären Download-Ordner "
"löschen möchten? Dies kann nicht rückgängig gemacht werden!"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "NZB aus URL laden"
@@ -5091,6 +5061,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 "Klicken Sie auf \"Server testen\", bevor Sie fortfahren"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Backup wiederherstellen"
@@ -5129,6 +5104,10 @@ msgstr "Datei nicht auf dem Server"
msgid "Server could not complete request"
msgstr "Server konnte nicht vollständig antworten"
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Leere NZB-Datei %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -139,6 +146,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -308,7 +320,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "Ei toivottu tiedostopääte on rar arkistossa %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Peruutettu, ei toivottu tiedostopääte havaittu"
@@ -327,6 +339,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"
@@ -535,11 +561,6 @@ msgstr "Alustaminen epäonnistui kohteessa %s@%s syy: %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Liikaa yhteyksiä palvelimelle %s [%s]"
@@ -559,11 +580,6 @@ msgstr "Kirjautuminen palvelimelle %s epäonnistui [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Yhdistäminen %s@%s epäonnistui, viesti=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Mahdollinen virhe lataajassa"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Sammutetaan"
@@ -729,15 +745,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 +761,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"
@@ -950,7 +966,7 @@ msgid "Update Available!"
msgstr "Päivitys saatavilla!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1072,10 +1088,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"
@@ -1191,6 +1203,16 @@ msgstr "Yritetään SFV varmennusta"
msgid "left"
msgstr "jäljellä"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Mahdollinen virhe lataajassa"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Tämä palvelin ei salli SSL yhteyksiä tähän porttiin"
@@ -1370,103 +1392,18 @@ msgstr "Virhe ladattaessa %s, korruptoitunut tiedosto havaittu"
msgid "NZB added to queue"
msgstr "NZB lisätty jonoon"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ohitetaan kaksoiskappale NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Tyhjä NZB tiedosto %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Peruutettu, ei voi valmistua"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Virhe tuotaessa %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "KAKSOISKAPPALE"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "SALATTU"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "LIIAN SUURI"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "KESKENERÄINEN"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "EI TOIVOTTU"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "ODOTA %s sekuntia"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "LEVITETÄÄN %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Ladattiin ajassa %s keskilatausnopeudella %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Ikä"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s artikkelia oli väärin muotoiltuja"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s artikkelia puuttui"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s artikkelissa oli ei-vastaavia kaksoiskappaleita"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Keskeytetään kaksoiskappale NZB \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Ongelma"
@@ -2354,6 +2291,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"
@@ -3341,8 +3283,9 @@ msgid "Enable SFV-based checks"
msgstr "SFV-pohjaiset tarkistukset käytössä"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Suorittaa ylimääräisen varmennuksen SFV tiedostojen avulla."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3554,10 +3497,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 +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
@@ -3744,6 +3683,17 @@ msgstr ""
msgid "Enable"
msgstr "Ota käyttöön"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4157,17 +4107,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4495,6 +4450,11 @@ msgstr "Poista"
msgid "Filename"
msgstr "Tiedostonimi"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Ikä"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Vapaa tila"
@@ -4624,6 +4584,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 +4842,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 ""
@@ -4914,6 +4885,10 @@ msgstr "Tiedostoa ei ole palvelimella"
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Tyhjä NZB tiedosto %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -148,6 +157,11 @@ msgstr ""
"L'umask actuel (%o) pourrait refuser à SABnzbd l'accès aux fichiers et "
"dossiers qu'il crée."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -338,7 +352,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "L'extension indésirable est dans le fichier rar %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Interrompu, extension indésirable détectée"
@@ -361,6 +375,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"
@@ -583,11 +611,6 @@ msgstr "Échec d'initialisation de %s@%s pour la raison suivante : %s"
msgid "Fatal error in Downloader"
msgstr "Erreur fatale dans le Téléchargeur"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s a reçu le code d'état inconnu %s pour l'article %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Trop de connexions au serveur %s [%s]"
@@ -609,11 +632,6 @@ msgstr "Échec de la connexion au serveur %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "La connexion à %s@%s a échoué, message=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Erreur suspecte dans le téléchargeur"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Arrêt en cours..."
@@ -787,15 +805,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 +821,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"
@@ -1012,7 +1030,7 @@ msgid "Update Available!"
msgstr "Mise à Jour disponible!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Échec de l'upload du fichier : %s"
@@ -1142,10 +1160,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"
@@ -1263,6 +1277,16 @@ msgstr "Essai vérification SFV"
msgid "left"
msgstr "restant"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s a reçu le code d'état inconnu %s pour l'article %s"
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Erreur suspecte dans le téléchargeur"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Ce serveur n'authorise pas de connexion SSL sur ce port"
@@ -1447,103 +1471,18 @@ msgstr "Erreur lors du chargement de %s, fichier corrompu détecté"
msgid "NZB added to queue"
msgstr "NZB ajouté à la file d'attente"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Doublon NZB ignoré \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Échec de duplication du NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "Dupliquer NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr "Fichier NZB %s invalide, sera ignoré (erreur : %s)"
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Fichier NZB %s vide"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr "Le script de pré-file d'attente a marqué la tâche comme échouée"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "Extension non souhaitée dans le fichier %s (%s)"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Interrompu, ne peut être achevé"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Erreur lors de l'importation de %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DOUBLON"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr "ALTERNATIVE"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "CHIFFRÉ"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "TROP VOLUMINEUX"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "INCOMPLET"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "INDÉSIRABLE"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "PATIENTER %s sec"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "PROPAGATION %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Téléchargé en %s à %sB/s de moyenne"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Âge"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s articles malformés"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s articles manquants"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s articles avec doublons sans correspondance"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Mise en pause du doublon NZB \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problème avec"
@@ -2442,6 +2381,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"
@@ -3478,8 +3422,11 @@ msgid "Enable SFV-based checks"
msgstr "Activer les contrôles SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Fait une vérification supplémentaire basée sur les fichiers SFV."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
"Si aucun fichier par2 n'est disponible, utiliser les fichiers sfv (si "
"présents) pour vérifier les fichiers"
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3704,10 +3651,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 +3761,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
@@ -3914,6 +3857,17 @@ msgstr ""
msgid "Enable"
msgstr "Activer"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4333,20 +4287,30 @@ msgid "Enable Apprise notifications"
msgstr "Activer les notifications Apprise"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
"Envoyer des notifications en utilisant Apprise vers presque n'importe quel "
"service de notification"
"Envoyez des notifications directement vers n'importe quel service de "
"notification que vous utilisez.<br>Par exemple : Slack, Discord, Telegram ou"
" tout autre service parmi plus de 100 services pris en charge !"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr "URLs par défaut d'Apprise"
msgid "Use default Apprise URLs"
msgstr "Utiliser les URLs Apprise par défaut"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgstr "Utilisez une virgule et/ou un espace pour identifier plusieurs URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
"Apprise définit les informations de connexion au service à l'aide "
"d'URL.<br>Consultez le wiki Apprise pour savoir comment définir l'URL de "
"chaque service.<br>Utilisez une virgule et/ou un espace pour identifier "
"plusieurs URL."
#: sabnzbd/skintext.py
msgid ""
@@ -4689,6 +4653,11 @@ msgstr "Supprimer"
msgid "Filename"
msgstr "Nom de fichier"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Âge"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Espace libre"
@@ -4818,6 +4787,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 +5053,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"
@@ -5114,6 +5096,10 @@ msgstr "Fichier introuvable sur le serveur"
msgid "Server could not complete request"
msgstr "Le serveur n'a pas pu terminer la requête"
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Fichier NZB %s vide"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# ION, 2024
# ION, 2025
# 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: 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"
@@ -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 "לא ניתן לקשר ל-OpenSSL, פונקציות חיבור SSL מותאמות לא יהיו בשימוש."
#. Error message
#: SABnzbd.py
msgid ""
@@ -136,6 +143,11 @@ msgstr ""
"פקודת umask נוכחית (%o) עשויה לדחות גישה מן SABnzbd אל הקבצים והתיקיות שהוא "
"יוצר."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -203,12 +215,16 @@ msgid ""
"Could not connect to %s on port %s. Use the default usenet settings: port "
"563 and SSL turned on"
msgstr ""
"לא ניתן להתחבר ל-%s בפורט %s. השתמש בהגדרות ברירת המחדל של usenet: פורט 563 "
"ו-SSL מופעל"
#: sabnzbd/api.py
msgid ""
"Could not connect to %s on port %s. Use the default usenet settings: port "
"119 and SSL turned off"
msgstr ""
"לא ניתן להתחבר ל-%s בפורט %s. השתמש בהגדרות ברירת המחדל של usenet: פורט 119 "
"ו-SSL כבוי"
#: sabnzbd/api.py, sabnzbd/interface.py
msgid "Server address \"%s:%s\" is not valid."
@@ -309,7 +325,7 @@ msgstr "בעבודה \"%s\" יש סיומת בלתי רצויה בתוך קוב
msgid "Unwanted extension is in rar file %s"
msgstr "סיומת בלתי רצויה בקובץ rar %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "בוטל, סיומת בלתי רצויה התגלתה"
@@ -328,6 +344,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 "אזהרת מגבלת מכסה (%d%%)"
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "ההורדה התחדשה לאחר איפוס מכסה"
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "פרמטר שגוי"
@@ -390,7 +420,7 @@ msgstr ""
#: sabnzbd/cfg.py
msgid ""
"The par2 application was switched, any custom par2 parameters were removed"
msgstr ""
msgstr "יישום par2 הוחלף, כל פרמטרי par2 מותאמים אישית הוסרו"
#. Warning message
#: sabnzbd/config.py
@@ -466,7 +496,7 @@ msgstr "אי־האפלה שינתה שם של %d קבצים"
#: sabnzbd/deobfuscate_filenames.py
msgid "Deobfuscate renamed %d subtitle file(s)"
msgstr ""
msgstr "בוצע ביטול ערפול של %d קבצי כתוביות ששמם שונה"
#: sabnzbd/directunpacker.py, sabnzbd/skintext.py
msgid "Direct Unpack"
@@ -542,11 +572,6 @@ msgstr "כישלון באתחול %s@%s עם סיבה: %s"
msgid "Fatal error in Downloader"
msgstr "שגיאה גורלית במורידן"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: קוד בלתי ידוע של מעמד התקבל %s עבור מאמר %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "יותר מדי חיבורים לשרת %s [%s]"
@@ -568,11 +593,6 @@ msgstr "כניסה נכשלה עבור שרת %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "התחברות אל %s@%s נכשלה, הודעה=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "הורדה חשודה במורידן"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "מכבה"
@@ -738,15 +758,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 +772,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"
@@ -958,7 +978,7 @@ msgid "Update Available!"
msgstr "עדכון זמין!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "כישלון בהעלאת קובץ: %s"
@@ -1080,11 +1100,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"
@@ -1200,6 +1216,16 @@ msgstr "מנסה וידוא SFV"
msgid "left"
msgstr "נותר"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: קוד בלתי ידוע של מעמד התקבל %s עבור מאמר %s"
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "הורדה חשודה במורידן"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "שרת זה אינו מתיר SSL על פתחה זו"
@@ -1218,6 +1244,8 @@ msgid ""
" locally injected certificate (for example by firewall or virus scanner). "
"Try setting Certificate verification to Medium."
msgstr ""
"לא ניתן לאמת את האישור. זה יכול להיות בעיית שרת או בגלל אישור מוזרק מקומית "
"(לדוגמה על ידי חומת אש או סורק וירוסים). נסה להגדיר את אימות האישור לבינוני."
#: sabnzbd/newswrapper.py
msgid "Server %s uses an untrusted certificate [%s]"
@@ -1298,7 +1326,7 @@ msgstr "כישלון בשליחת הודעת Prowl"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send Apprise message - no URLs defined"
msgstr ""
msgstr "שליחת הודעת Apprise נכשלה - לא הוגדרו כתובות URL"
#. Warning message
#: sabnzbd/notifier.py
@@ -1381,103 +1409,18 @@ msgstr "שגיאה בטעינת %s, קובץ פגום התגלה"
msgid "NZB added to queue"
msgstr "NZB התווסף לתור"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "מתעלם מן NZB כפול \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "מכשיל NZB כפול \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "NZB כפול"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr "קובץ NZB בלתי תקף %s, מדלג (שגיאה: %s)"
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "קובץ NZB ריק %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr "תסריט קדם־תור סומן כנכשל"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "סיומת בלתי רצויה בקובץ %s (%s)"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "בוטל, לא יכול להיות שלם"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "שגיאה ביבוא %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "כפול"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr "חלופה"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "מוצפן"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "גדול מדי"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "בלתי שלם"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "בלתי רצוי"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "המתן %s שניות"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "מפיץ %s דקות"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "ירד תוך %s בממוצע של %s ב/ש"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "גיל"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s מאמרים עוותו"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s מאמרים היו חסרים"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "אל %s מאמרים יש כפילויות בלתי תואמות"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "משהה NZB כפול \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "בעיה עם"
@@ -2367,6 +2310,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 +2586,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 ""
@@ -2911,7 +2859,7 @@ msgstr "העבר עבודות אל הארכיון אם ההיסטוריה חור
#: sabnzbd/skintext.py
msgid ""
"Delete jobs if the history and archive exceeds specified number of jobs"
msgstr ""
msgstr "מחק עבודות אם ההיסטוריה והארכיון עוברים את מספר העבודות שצוין"
#: sabnzbd/skintext.py
msgid "Move jobs to the archive after specified number of days"
@@ -2920,7 +2868,7 @@ msgstr "העבר עבודות אל הארכיון לאחר מספר מצוין
#: sabnzbd/skintext.py
msgid ""
"Delete jobs from the history and archive after specified number of days"
msgstr ""
msgstr "מחק עבודות מההיסטוריה והארכיון לאחר מספר הימים שצוין"
#: sabnzbd/skintext.py
msgid "Move all completed jobs to archive"
@@ -3349,8 +3297,9 @@ msgid "Enable SFV-based checks"
msgstr "אפשר בדיקות מבוססות SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "בצע וידוא נוסף שמבוסס על קבצי SFV."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3558,10 +3507,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 +3611,12 @@ 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, היא נבדקת כל כמה דקות."
"מכסה לשרת זה, נספרת מהרגע שהיא נקבעה. בבייטים, באופן אופציונלי ניתן להוסיף "
"K,M,G.<br />נבדקת כל כמה דקות. הודעה נשלחת כאשר המכסה מוצתה."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -3702,6 +3647,11 @@ msgid ""
"used. - Disabled: no certification verification. This is not secure at all, "
"anyone could intercept your connection. "
msgstr ""
"כאשר SSL מופעל: - מחמיר: אכוף אימות אישור מלא. זוהי ההגדרה המאובטחת ביותר. -"
" בינוני: אמת שהאישור תקף ותואם לכתובת השרת, אך אפשר אישורים המוזרקים מקומית "
"(למשל על ידי חומת אש או סורק וירוסים). - מינימלי: אמת שהאישור תקף. זה לא "
"מאובטח, כל אישור תקף יכול לשמש. - מושבת: ללא אימות אישור. זה לא מאובטח כלל, "
"כל אחד יכול ליירט את החיבור שלך."
#: sabnzbd/skintext.py
msgid "Disabled"
@@ -3713,7 +3663,7 @@ msgstr "מזערי"
#: sabnzbd/skintext.py
msgid "Medium"
msgstr ""
msgstr "בינוני"
#: sabnzbd/skintext.py
msgid "Strict"
@@ -3751,6 +3701,17 @@ msgstr "עבור שרתים בלתי מהימנים, ייתקל בהתעלמות
msgid "Enable"
msgstr "אפשר"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4165,18 +4126,28 @@ msgid "Enable Apprise notifications"
msgstr "אפשר התראות Apprise"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgstr "שלח התראות ע״י שימוש בשירות Apprise אל כמעט כל שירות התראות"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
"שלח הודעות ישירות לכל שירות הודעות שאתה משתמש בו.<br>לדוגמה: Slack, Discord,"
" Telegram או כל שירות מתוך למעלה מ-100 שירותים נתמכים!"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr "כתובות Apprise ברירות מחדל"
msgid "Use default Apprise URLs"
msgstr "השתמש בכתובות URL של Apprise המוגדרות כברירת מחדל"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgstr "השתמש בפסיק, ברווח או בשניהם כדי לזהות יותר מכתובת אחת."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
"Apprise מגדיר מידע על חיבור שירות באמצעות כתובות URL.<br>קרא את הוויקי של "
"Apprise כדי ללמוד כיצד להגדיר את כתובת ה-URL עבור כל שירות.<br>השתמש בפסיק "
"ו/או רווח כדי לזהות יותר מכתובת URL אחת."
#: sabnzbd/skintext.py
msgid ""
@@ -4506,6 +4477,11 @@ msgstr "מחק"
msgid "Filename"
msgstr "שם קובץ"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "גיל"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "שטח פנוי"
@@ -4634,6 +4610,14 @@ 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 +4872,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 "שחזר גיבוי"
@@ -4926,6 +4915,10 @@ msgstr "קובץ לא על השרת"
msgid "Server could not complete request"
msgstr "השרת לא היה יכול להשלים בקשה"
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "קובץ NZB ריק %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

View File

@@ -36,6 +36,15 @@ 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 ""
"Impossibile collegarsi a OpenSSL, le funzioni di connessione SSL ottimizzate"
" non verranno utilizzate."
#. Error message
#: SABnzbd.py
msgid ""
@@ -141,6 +150,11 @@ msgstr ""
"L'umask corrente (%o) potrebbe negare a SABnzbd l'accesso ai file e alle "
"cartelle che crea."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -331,7 +345,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "L'estensione non desiderata è nel file rar %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Annullato, rilevata estensione non desiderata"
@@ -354,6 +368,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 "Avviso limite quota (%d%%)"
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "Download ripreso dopo il ripristino della quota"
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Parametro non corretto"
@@ -576,11 +604,6 @@ msgstr "Inizializzazione di %s@%s fallita con motivo: %s"
msgid "Fatal error in Downloader"
msgstr "Errore fatale nel Downloader"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: Ricevuto codice di stato sconosciuto %s per l'articolo %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Troppe connessioni al server %s [%s]"
@@ -602,11 +625,6 @@ msgstr "Accesso fallito per il server %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Connessione a %s@%s fallita, messaggio=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Sospetto errore nel downloader"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Spegnimento in corso"
@@ -778,15 +796,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 +812,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"
@@ -1003,7 +1021,7 @@ msgid "Update Available!"
msgstr "Aggiornamento disponibile!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Caricamento del file %s fallito"
@@ -1127,10 +1145,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"
@@ -1250,6 +1264,16 @@ msgstr "Tentativo di verifica SFV"
msgid "left"
msgstr "rimanente"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: Ricevuto codice di stato sconosciuto %s per l'articolo %s"
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Sospetto errore nel downloader"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Questo server non permette SSL su questa porta"
@@ -1435,103 +1459,18 @@ msgstr "Errore durante il caricamento di %s, rilevato file corrotto"
msgid "NZB added to queue"
msgstr "NZB aggiunto alla coda"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorando NZB duplicato \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Fallimento NZB duplicato \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "NZB duplicato"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr "File NZB non valido %s, saltato (errore: %s)"
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "File NZB vuoto %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr "Lo script pre-coda ha contrassegnato il processo come fallito"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "Estensione non desiderata nel file %s (%s)"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Annullato, non può essere completato"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Errore durante l'importazione di %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLICATO"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr "ALTERNATIVO"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "CRITTOGRAFATO"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "TROPPO GRANDE"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "INCOMPLETO"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "NON DESIDERATO"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "ATTENDI %s sec"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "PROPAGAZIONE %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Scaricato in %s a una media di %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Età"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s articoli erano malformati"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s articoli erano mancanti"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s articoli avevano duplicati non corrispondenti"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Messa in pausa NZB duplicato \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problema con"
@@ -2422,6 +2361,11 @@ msgstr "Nome"
msgid "Retry"
msgstr "Riprova"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr "Segna come completato e rimuovi i file temporanei"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3447,8 +3391,9 @@ msgid "Enable SFV-based checks"
msgstr "Abilita controlli basati su SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Esegui una verifica extra basata sui file SFV."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3665,10 +3610,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 +3719,13 @@ 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."
"Quota per questo server, contata dal momento in cui viene impostata. In "
"byte, opzionalmente seguito da K,M,G.<br />Controllato ogni pochi minuti. La"
" notifica viene inviata quando la quota è esaurita."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -3873,6 +3814,17 @@ msgstr ""
msgid "Enable"
msgstr "Abilita"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4289,18 +4241,29 @@ msgid "Enable Apprise notifications"
msgstr "Abilita notifiche Apprise"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgstr "Invia notifiche usando Apprise a quasi tutti i servizi di notifica"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
"Invia notifiche direttamente a qualsiasi servizio di notifica che "
"utilizzi.<br>Ad esempio: Slack, Discord, Telegram o qualsiasi servizio tra "
"oltre 100 servizi supportati!"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr "URL predefiniti di Apprise"
msgid "Use default Apprise URLs"
msgstr "Usa URL Apprise predefiniti"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgstr "Usa una virgola e/o uno spazio per identificare più di un URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
"Apprise definisce le informazioni di connessione del servizio utilizzando "
"URL.<br>Leggi il wiki di Apprise per sapere come definire l'URL per ogni "
"servizio.<br>Usa una virgola e/o uno spazio per identificare più di un URL."
#: sabnzbd/skintext.py
msgid ""
@@ -4643,6 +4606,11 @@ msgstr "Elimina"
msgid "Filename"
msgstr "Nome file"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Età"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Spazio libero"
@@ -4772,6 +4740,14 @@ 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 ""
"Sei sicuro di voler eliminare tutte le cartelle nella tua cartella di "
"download temporanei? Questo non può essere annullato!"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Recupera NZB da URL"
@@ -5028,6 +5004,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 "Fai clic su Prova server prima di continuare"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Ripristina backup"
@@ -5066,6 +5047,10 @@ msgstr "File non presente sul server"
msgid "Server could not complete request"
msgstr "Il server non ha potuto completare la richiesta"
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "File NZB vuoto %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -135,6 +142,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -306,7 +318,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "Uønsket forlenging finnes i rar fil %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Avbryt, uønsket forlenging oppdaget"
@@ -325,6 +337,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"
@@ -532,11 +558,6 @@ msgstr "Feilet å starte %s@%s grunnet: %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "For mange tilkoblinger til server %s [%s]"
@@ -556,11 +577,6 @@ msgstr "Kunne ikke logge inn på server %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Kontaker %s@%s feilet, feilmelding=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Mistenker feil i nedlaster"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Starter avslutning av SABnzbd.."
@@ -726,15 +742,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 +758,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"
@@ -947,7 +963,7 @@ msgid "Update Available!"
msgstr "Oppdatering tilgjengelig"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1069,10 +1085,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"
@@ -1189,6 +1201,16 @@ msgstr "Prøver SFV-verifisering"
msgid "left"
msgstr "gjenstår"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Mistenker feil i nedlaster"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Denne serveren tillater ikke SSL på denne porten"
@@ -1368,103 +1390,18 @@ msgstr "Lastingsfeil %s, feilaktig fil oppdaget"
msgid "NZB added to queue"
msgstr "NZB er lagt til i køen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerer duplikatfil \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Tom NZB-fil %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Avbrutt, kan ikke fullføres"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Kunne ikke importere %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLIKAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "KRYPTERT"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "FOR STOR"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "UFULLSTENDIG"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "UØNSKET"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "VENT %s sek"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Hentet filer på %s med gjenomsnitts hastighet på %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Tid"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s artikler var korrupte"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s artikler manglet"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s artikler hadde ulike duplikater"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Stanser duplikatfil \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problem med"
@@ -2352,6 +2289,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"
@@ -3324,8 +3266,9 @@ msgid "Enable SFV-based checks"
msgstr "Aktiver SFV-baserte sjekker"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Utfør ekstra verifisering basert på SFV filer"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3531,10 +3474,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 +3581,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
@@ -3723,6 +3662,17 @@ msgstr ""
msgid "Enable"
msgstr "Aktivere"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4136,17 +4086,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4469,6 +4424,11 @@ msgstr "Fjern"
msgid "Filename"
msgstr "Filnavn"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Tid"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Ledig plass"
@@ -4598,6 +4558,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 +4814,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 ""
@@ -4886,6 +4857,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Tom NZB-fil %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

View File

@@ -38,6 +38,15 @@ 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 ""
"Kan niet koppelen aan OpenSSL, geoptimaliseerde SSL-verbindingsfuncties "
"worden niet gebruikt."
#. Error message
#: SABnzbd.py
msgid ""
@@ -143,6 +152,11 @@ msgstr ""
"Huidige umask (%o) zou kunnen beletten dat SABnzbd toegang heeft tot de "
"aangemaakte bestanden en mappen."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -328,7 +342,7 @@ msgstr "Ongewenste extensie ontdekt in \"%s\". Het ongewenste bestand is \"%s\"
msgid "Unwanted extension is in rar file %s"
msgstr "De ongewenste extensie zit in RAR-bestand %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Afgebroken, ongewenste extensie ontdekt"
@@ -351,6 +365,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 "Waarschuwing quotumlimiet (%d%%)"
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "Downloaden hervat na quotumreset"
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
msgstr "Incorrecte parameter"
@@ -577,11 +605,6 @@ msgstr "Initialisatie van %s@%s mislukt, vanwege: %s"
msgid "Fatal error in Downloader"
msgstr "Onherstelbare fout in de Downloader"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: Onbekende statuscode %s ontvangen voor artikel %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Te veel verbindingen met server %s [%s]"
@@ -603,11 +626,6 @@ msgstr "Aanmelden bij server %s mislukt [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Verbinding %s@%s mislukt, bericht=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Vedachte fout in downloader"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Afsluiten"
@@ -781,15 +799,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 +815,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"
@@ -1006,7 +1024,7 @@ msgid "Update Available!"
msgstr "Update beschikbaar!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Kon het volgende bestand niet uploaden: %s"
@@ -1131,10 +1149,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"
@@ -1253,6 +1267,16 @@ msgstr "Probeer SFV-verificatie"
msgid "left"
msgstr "over"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: Onbekende statuscode %s ontvangen voor artikel %s"
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Vedachte fout in downloader"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "De server staat geen SSL toe op deze poort"
@@ -1438,103 +1462,18 @@ msgstr "Fout bij inladen van %s, corrupt bestand gevonden"
msgid "NZB added to queue"
msgstr "Download aan wachtrij toegevoegd"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" overgeslagen"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Download '%s' geweigerd omdat het een dubbele is"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "Dubbele download"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr "Corrupte NZB %s wordt overgeslagen (foutmelding: %s)"
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "NZB-bestand %s is leeg"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr "Wachtrij filter script heeft de download afgekeurd"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "Ongewenste extensie gevonden in %s (%s) "
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Afgebroken, kan niet voltooid worden"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Fout bij importeren van %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUBBEL"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr "ALTERNATIEF"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "VERSLEUTELD"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "TE GROOT"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "ONVOLLEDIG"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "ONGEWENST"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "WACHT %s sec"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "VERSPREIDINGSWACHTTIJD %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Gedownload in %s met een gemiddelde snelheid van %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Leeftijd"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s artikelen zijn misvormd"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s artikelen ontbreken"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s artikelen hadden afwijkende duplicaten"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Dubbele download \"%s\" gepauzeerd"
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Probleem met"
@@ -2425,6 +2364,11 @@ msgstr "Naam"
msgid "Retry"
msgstr "Opnieuw"
#. History page button
#: sabnzbd/skintext.py
msgid "Mark as Completed & Remove Temporary Files"
msgstr "Markeer als voltooid en verwijder tijdelijke bestanden"
#. Queue page table, script selection menu
#: sabnzbd/skintext.py
msgid "Scripts"
@@ -3445,8 +3389,9 @@ msgid "Enable SFV-based checks"
msgstr "Voer SFV-gebaseerde controles uit"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Doe een extra verificatie m.b.v. SFV-bestanden"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3665,10 +3610,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 +3721,13 @@ 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."
"Quotum voor deze server, geteld vanaf het moment dat het is ingesteld. In "
"bytes, optioneel gevolgd door K,M,G.<br />Wordt om de paar minuten "
"gecontroleerd. Melding wordt verzonden wanneer het quotum is opgebruikt."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -3874,6 +3814,17 @@ msgstr ""
msgid "Enable"
msgstr "Inschakelen"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4292,19 +4243,30 @@ msgid "Enable Apprise notifications"
msgstr "Apprise-meldingen activeren"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
"Stuur meldingen met behulp van Apprise naar bijna elke bestaande service."
"Stuur meldingen rechtstreeks naar elke meldingsservice die u "
"gebruikt.<br>Bijvoorbeeld: Slack, Discord, Telegram of elke andere service "
"uit meer dan 100 ondersteunde services!"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr "Standaard Apprise-URL's"
msgid "Use default Apprise URLs"
msgstr "Gebruik standaard Apprise-URL's"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgstr "Gebruik een komma en/of spatie om meer dan één URL op te geven."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
"Apprise definieert serviceverbindingsinformatie met behulp van "
"URL's.<br>Lees de Apprise-wiki om te leren hoe u de URL voor elke service "
"definieert.<br>Gebruik een komma en/of spatie om meer dan één URL te "
"identificeren."
#: sabnzbd/skintext.py
msgid ""
@@ -4642,6 +4604,11 @@ msgstr "Verwijder"
msgid "Filename"
msgstr "Bestandsnaam"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Leeftijd"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Vrije ruimte"
@@ -4770,6 +4737,14 @@ 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 ""
"Weet u zeker dat u alle mappen in uw tijdelijke downloadmap wilt "
"verwijderen? Dit kan niet ongedaan worden gemaakt!"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Haal NZB op via URL"
@@ -5025,6 +5000,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 "Klik op Test server voordat u doorgaat"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Backup herstellen"
@@ -5063,6 +5043,10 @@ msgstr "Bestand bestaat niet op de server"
msgid "Server could not complete request"
msgstr "De server kon de opdracht niet uitvoeren"
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "NZB-bestand %s is leeg"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -131,6 +138,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -305,7 +317,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "Niepożądane rozszerzenie w pliku RAR %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Przerwano, wykryto niepożądane rozszerzenie"
@@ -324,6 +336,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"
@@ -533,11 +559,6 @@ msgstr "Błąd podczas inicjalizacji %s@%s: %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Zbyt wiele połączeń do serwera %s [%s]"
@@ -557,11 +578,6 @@ msgstr "Błąd logowania do serwera %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Błąd połączenia %s@%s, komunikat=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Nieobsługiwany błąd w module pobierania"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Wyłączanie"
@@ -729,15 +745,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 +761,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"
@@ -950,7 +966,7 @@ msgid "Update Available!"
msgstr "Dostępna aktualizacja!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1072,10 +1088,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"
@@ -1194,6 +1206,16 @@ msgstr "Próba weryfikacji SFV"
msgid "left"
msgstr "pozostało"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Nieobsługiwany błąd w module pobierania"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Serwer nie obsługuje SSL na tym porcie"
@@ -1373,103 +1395,18 @@ msgstr "Błąd ładowania %s, wykryto uszkodzony plik"
msgid "NZB added to queue"
msgstr "NZB dodany do kolejki"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignoruję zduplikowany NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Pusty plik NZB %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Przerwano, nie można ukończyć"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Błąd importu %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLIKAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ZASZYFROWANY"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "ZA DUŻY"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "NIEKOMPLETNY"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "NIEPOŻĄDANY"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "CZEKAM %s s"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Pobrano w %s ze średnią %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Wiek"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s artykułów było uszkodzonych"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "Brakowało %s artykułów"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s artykułów posiadało niepasujące duplikaty"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Wstrzymuję zduplikowany NZB \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problem z"
@@ -2361,6 +2298,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"
@@ -3335,8 +3277,9 @@ msgid "Enable SFV-based checks"
msgstr "Włącz sprawdzanie przy użyciu SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Wykonuj dodatkową weryfikację na podstawie plików SFV"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3542,10 +3485,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 +3593,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
@@ -3735,6 +3674,17 @@ msgstr ""
msgid "Enable"
msgstr "Włączony"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4148,17 +4098,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4481,6 +4436,11 @@ msgstr "Usuń"
msgid "Filename"
msgstr "Nazwa pliku"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Wiek"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Wolne miejsce"
@@ -4610,6 +4570,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 +4824,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 ""
@@ -4896,6 +4867,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Pusty plik NZB %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -140,6 +147,11 @@ msgstr ""
"Mascara atual (%o) pode negar ao SABnzbd acesso aos arquivos e diretórios "
"criados."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -317,7 +329,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "A extensão indesejada está no arquivo rar %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Cancelado, extensão indesejada detectada"
@@ -336,6 +348,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"
@@ -547,11 +573,6 @@ msgstr "Falha ao iniciar %s@%s devido as seguintes razões: %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Excesso de conexões ao servidor %s [%s]"
@@ -571,11 +592,6 @@ msgstr "Falha de logon ao servidor %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "A conexão a %s@%s falhou. Mensagem=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Erro suspeito no downloader"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Encerrando"
@@ -741,15 +757,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 +773,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"
@@ -962,7 +978,7 @@ msgid "Update Available!"
msgstr "Atualização Disponível!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1084,10 +1100,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"
@@ -1203,6 +1215,16 @@ msgstr "Tentando verificação SFV"
msgid "left"
msgstr "restantes"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Erro suspeito no downloader"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Este servidor não permite SSL nesta porta"
@@ -1382,103 +1404,18 @@ msgstr "Erro ao carregar %s. Arquivo corrompido detectado"
msgid "NZB added to queue"
msgstr "NZB adicionado à fila"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorando NZB duplicado \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Arquivo NZB %s vazio"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Cancelado, não é possível concluir"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Erro ao importar %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLICADO"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "CRIPTOGRAFADO"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "MUITO GRANDE"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "INCOMPLETO"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "INDESEJADO"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "Espere %s segundo(s)"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Baixado em %s a uma média de %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Idade"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s artigos estavam malformados"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s artigos estavam faltando"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s artigos tinham duplicatas não-correspondentes"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausando NZB duplicado \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problema com"
@@ -2372,6 +2309,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"
@@ -3345,8 +3287,9 @@ msgid "Enable SFV-based checks"
msgstr "Habilitar verificações baseadas em SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Fazer uma verificação extra baseada em arquivos SFV."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3554,10 +3497,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 +3604,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
@@ -3746,6 +3685,17 @@ msgstr ""
msgid "Enable"
msgstr "Habilitar"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4159,17 +4109,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4492,6 +4447,11 @@ msgstr "Eliminar"
msgid "Filename"
msgstr "Nome do arquivo"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Idade"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Espaço Disponível"
@@ -4621,6 +4581,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 +4835,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 ""
@@ -4907,6 +4878,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Arquivo NZB %s vazio"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -140,6 +147,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -319,7 +331,7 @@ msgstr "Extensie nedorită în fișierul RAR al „%s”. Fișierul nedorit este
msgid "Unwanted extension is in rar file %s"
msgstr "Extensii fișier nedorite în fișierul rar %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Oprit, extensii nedorite detectate"
@@ -340,6 +352,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"
@@ -555,11 +581,6 @@ msgstr "Nu am putu inițializa %s@%s din cauza următorului motiv: %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Prea multe conexiuni la serverul %s [%s]"
@@ -579,11 +600,6 @@ msgstr "Autentificare nereuşită la serverul %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Conectare %s@%s eșuată, mesaj=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Eroare suspectă în sistemul de descprcare"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Închidere"
@@ -749,15 +765,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 +781,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"
@@ -972,7 +988,7 @@ msgid "Update Available!"
msgstr "Actualizare Disponibilă!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Eșuare la încărcarea fișierului: %s"
@@ -1097,10 +1113,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ă"
@@ -1219,6 +1231,16 @@ msgstr "Încerc verificare SFV"
msgid "left"
msgstr "rămas"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Eroare suspectă în sistemul de descprcare"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Acest server nu permite SSL pe acest port"
@@ -1400,103 +1422,18 @@ msgstr "Eroare încărcare %s, fişier corupt detectat"
msgid "NZB added to queue"
msgstr "NZB adăugat în coadă"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorăm duplicat NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "Eșuare duplicat NZB „%s”"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "NZB duplicat"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Fişier NZB gol %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr "Scriptul pre-coadă a marcat sarcina ca nereușită"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "Extensie nedorită în fișierul %s (%s)"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Anulat nu poate fi finalizat"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Eroare importare %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUPLICAT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ENCRIPTAT"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "PREA MARE"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "INCOMPLET"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "NEDORIT"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "AŞTEAPTĂ %s sec"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "SE PROPAGHEAZĂ %s min"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Descărcat în %s cu o medie de %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Vârsta"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s articolele au fost incorecte"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s articolele au fost lipsă"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s articolele au avut duplicate diferite"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Întrerupem duplicat NZB \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problemă cu"
@@ -2390,6 +2327,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"
@@ -3363,8 +3305,9 @@ msgid "Enable SFV-based checks"
msgstr "Activează verficări SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Fă o verificare extra bazată pe fişiere SFV"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3573,10 +3516,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 +3624,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
@@ -3767,6 +3706,17 @@ msgstr ""
msgid "Enable"
msgstr "Activează"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4180,17 +4130,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4512,6 +4467,11 @@ msgstr "Şterge"
msgid "Filename"
msgstr "Nume de fișier"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Vârsta"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Spațiu liber"
@@ -4640,6 +4600,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 +4857,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 ""
@@ -4929,6 +4900,10 @@ msgstr "Fișierul nu este pe server"
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Fişier NZB gol %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -135,6 +142,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -305,7 +317,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr ""
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr ""
@@ -324,6 +336,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 "Неправильный параметр"
@@ -531,11 +557,6 @@ msgstr ""
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr ""
@@ -555,11 +576,6 @@ msgstr "Ошибка входа на сервер %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr ""
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr ""
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Завершение работы"
@@ -725,15 +741,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 +757,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"
@@ -946,7 +962,7 @@ msgid "Update Available!"
msgstr "Доступно обновление!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1068,10 +1084,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 "Ошибка распаковки: архив защищён паролем"
@@ -1189,6 +1201,16 @@ msgstr "Проверка SFV-суммы"
msgid "left"
msgstr "осталось"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr ""
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr ""
@@ -1368,103 +1390,18 @@ msgstr "Ошибка загрузки %s: обнаружен повреждён
msgid "NZB added to queue"
msgstr "NZB-файл добавлен в очередь"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Пропущен повторяющийся NZB-файл «%s»"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Пустой NZB-файл %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr ""
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Ошибка импорта %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "ПОВТОР"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ЗАШИФРОВАН"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "СЛИШКОМ БОЛЬШОЙ"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "НЕПОЛНЫЙ"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "ОЖИДАНИЕ %s с"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Загружено за %s со средней скоростью %sБ/с"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Возраст"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s статей с ошибками"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s статей отсутствует"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s статей содержат несовпадающие повторы"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Приостановлен повторяющийся NZB-файл «%s»"
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Проблема с"
@@ -2354,6 +2291,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"
@@ -3327,8 +3269,9 @@ msgid "Enable SFV-based checks"
msgstr "Использовать проверку по SFV"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Выполнять дополнительную проверку по SFV-файлам."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3533,10 +3476,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 +3582,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
@@ -3724,6 +3663,17 @@ msgstr ""
msgid "Enable"
msgstr "Включить"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4144,17 +4094,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4476,6 +4431,11 @@ msgstr "Удалить"
msgid "Filename"
msgstr "Название файла"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Возраст"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "свободно на диске"
@@ -4604,6 +4564,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 +4820,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 ""
@@ -4892,6 +4863,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Пустой NZB-файл %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -133,6 +140,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -302,7 +314,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "Neželjena ekstenzija je u rar datoteci %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Prekinuto, detektovana neželjena ekstenzija"
@@ -321,6 +333,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 "Погрешан параметар"
@@ -529,11 +555,6 @@ msgstr "Neuspešna inicijalizacija %s@%s iz razloga: %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "Previše konekcija ka serveru %s [%s]"
@@ -553,11 +574,6 @@ msgstr "Неуспешно пријављивање на сервер %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Povezivanje na %s@%s neuspešno, poruka=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Sumnja u grešku u programu za download"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Гашење"
@@ -723,15 +739,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 +753,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"
@@ -942,7 +958,7 @@ msgid "Update Available!"
msgstr "Нова верзија доступна!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1064,10 +1080,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"
@@ -1184,6 +1196,16 @@ msgstr "Pokušaj SFV provere"
msgid "left"
msgstr "остало"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Sumnja u grešku u programu za download"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Ovaj server ne dozvoljava SSL na ovom portu"
@@ -1363,103 +1385,18 @@ msgstr "Грешка учитавање %s, покварена датотека
msgid "NZB added to queue"
msgstr "NZB додат у ред"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Игнорисање дуплог NZB-а \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Празан NZB %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Поништено, не може да се заврши"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Грешка увоза %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "ДУПЛИКАТ"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ШИФРИРАНО"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "ПРЕВЕЛИКО"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "НЕПОТПУНО"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "NEŽELJENI"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "Чекање %s сек"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Преузето за %s на просек од %sБ/с"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Старост"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s артикла нису добро формирани"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s артикла недостају"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s артикла нису дупликате"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Паузирам због дуплог NZB-а \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Проблем са"
@@ -2347,6 +2284,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"
@@ -3313,8 +3255,9 @@ msgid "Enable SFV-based checks"
msgstr "Упали SFV провере"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Уради још једну проверу базирану на SFV датотеке."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3518,10 +3461,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 +3568,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
@@ -3710,6 +3649,17 @@ msgstr ""
msgid "Enable"
msgstr "Омогући"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4122,17 +4072,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4454,6 +4409,11 @@ msgstr "Обриши"
msgid "Filename"
msgstr "Име датотеке"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Старост"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Слободан простор"
@@ -4583,6 +4543,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 +4797,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 ""
@@ -4869,6 +4840,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Празан NZB %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

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 ""
@@ -133,6 +140,11 @@ msgid ""
"creates."
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -302,7 +314,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "Oönskad filändelse i RAR-fil %s"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "Avbruten, oönskad filändelse detekterad"
@@ -321,6 +333,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"
@@ -529,11 +555,6 @@ msgstr "Misslyckades att initiera %s@%s med orsak %s"
msgid "Fatal error in Downloader"
msgstr ""
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "För många anslutningar till servern %s [%s]"
@@ -553,11 +574,6 @@ msgstr "Det gick inte att logga in på server %s [%s]"
msgid "Connecting %s@%s failed, message=%s"
msgstr "Anslutning %s@%s misslyckades, meddelande=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "Misstänker fel i nedladdare"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Påbörjar nedstängning av SABnzbd.."
@@ -723,15 +739,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 +755,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"
@@ -944,7 +960,7 @@ msgid "Update Available!"
msgstr "Uppdatering tillgänglig"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr ""
@@ -1066,10 +1082,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"
@@ -1188,6 +1200,16 @@ msgstr "Försöker verifiera SFV"
msgid "left"
msgstr "kvar"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr ""
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "Misstänker fel i nedladdare"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Den här servern tillåter in SSL på denna port"
@@ -1367,103 +1389,18 @@ msgstr "Laddningsfel %s, felaktig fil detekterad"
msgid "NZB added to queue"
msgstr "NZB tillagd i kön"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Ignorerar dubblett för NZB \"%s\""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr ""
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "NZB filen %s är tom"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr ""
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "Avbrutet, kan inte slutföras"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "Det gick inte att importera %s"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "DUBLETT"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "KRYPTERAT"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "FÖR STOR"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "INKOMPLETT"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "OÖNSKAD"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "VÄNTA %s SEKUNDER"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr ""
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "Hämtade i %s vid ett genomsnitt på %sB/s"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Ålder"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s artiklar var felaktiga"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s artiklar saknades"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s artiklar hade icke-matchande dubletter"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Pausar dubblett för NZB \"%s\""
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Problem med"
@@ -2353,6 +2290,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"
@@ -3323,8 +3265,9 @@ msgid "Enable SFV-based checks"
msgstr "Använd SFV-baserade kontroller"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "Gör en extra kontroll med SFV filer"
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3530,10 +3473,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 +3580,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
@@ -3722,6 +3661,17 @@ msgstr ""
msgid "Enable"
msgstr "Aktivera"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4135,17 +4085,22 @@ msgid "Enable Apprise notifications"
msgstr ""
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgid "Use default Apprise URLs"
msgstr ""
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
#: sabnzbd/skintext.py
@@ -4467,6 +4422,11 @@ msgstr "Ta bort"
msgid "Filename"
msgstr "Filnamn"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Ålder"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Ledigt diskutrymme"
@@ -4595,6 +4555,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 +4811,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 ""
@@ -4883,6 +4854,10 @@ msgstr ""
msgid "Server could not complete request"
msgstr ""
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "NZB filen %s är tom"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

View File

@@ -3,6 +3,7 @@
#
# Translators:
# Taylan Tatlı, 2025
# Safihre <safihre@sabnzbd.org>, 2025
# mauron, 2025
#
msgid ""
@@ -37,6 +38,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 ""
@@ -141,6 +151,11 @@ msgstr ""
"Güncel umask (%o), SABnzbd'nin oluşturduğu dosya ve dizinlere erişimini "
"reddedebilir."
#. Warning message
#: sabnzbd/__init__.py
msgid "Windows ARM version of SABnzbd is available from our Downloads page!"
msgstr ""
#. Warning message
#: sabnzbd/__init__.py
msgid ""
@@ -332,7 +347,7 @@ msgstr ""
msgid "Unwanted extension is in rar file %s"
msgstr "İstenmeyen uzantı %s rar dosyasındadır"
#: sabnzbd/assembler.py, sabnzbd/nzbstuff.py
#: sabnzbd/assembler.py
msgid "Aborted, unwanted extension detected"
msgstr "İptal edildi, istenmeyen uzantı tespit edildi"
@@ -353,6 +368,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"
@@ -573,11 +602,6 @@ msgstr "%s@%s başlatması şu sebepten dolayı başarısız oldu: %s"
msgid "Fatal error in Downloader"
msgstr "İndirici'de ölümcül hata"
#. Warning message
#: sabnzbd/downloader.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: bilinmeyen durum kodu %s, şu makale için alındı: %s"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
msgstr "%s [%s] sunucusuna çok fazla bağlantı"
@@ -599,11 +623,6 @@ msgstr "%s [%s] sunucusunda oturum açılışı başarısız oldu"
msgid "Connecting %s@%s failed, message=%s"
msgstr "%s@%s bağlantısı başarısız oldu, mesaj=%s"
#. Error message
#: sabnzbd/downloader.py
msgid "Suspect error in downloader"
msgstr "İndiricide şüpheli hata"
#: sabnzbd/downloader.py, sabnzbd/skintext.py
msgid "Shutting down"
msgstr "Kapatılıyor"
@@ -771,15 +790,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 +806,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"
@@ -994,7 +1013,7 @@ msgid "Update Available!"
msgstr "Güncelleme Mevcut!"
#. Error message
#: sabnzbd/misc.py
#: sabnzbd/misc.py, sabnzbd/skintext.py
msgid "Failed to upload file: %s"
msgstr "Dosyanın gönderilmesi başarısız oldu: %s"
@@ -1118,10 +1137,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"
@@ -1240,6 +1255,16 @@ msgstr "SFV doğrulaması deneniyor"
msgid "left"
msgstr "kaldı"
#. Warning message
#: sabnzbd/newswrapper.py
msgid "%s@%s: Received unknown status code %s for article %s"
msgstr "%s@%s: bilinmeyen durum kodu %s, şu makale için alındı: %s"
#. Error message
#: sabnzbd/newswrapper.py
msgid "Suspect error in downloader"
msgstr "İndiricide şüpheli hata"
#: sabnzbd/newswrapper.py
msgid "This server does not allow SSL on this port"
msgstr "Bu sunucu, bu bağlantı noktasında SSL kullanımına izin vermiyor"
@@ -1425,103 +1450,18 @@ msgstr "%s yüklenirken hata, bozuk dosya tespit edildi"
msgid "NZB added to queue"
msgstr "NZB kuyruğa ilave edildi"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Ignoring duplicate NZB \"%s\""
msgstr "Yinelenmiş NZB \"%s\" dikkate alınmıyor"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Failing duplicate NZB \"%s\""
msgstr "\"%s\" NSB dosyasının yinelenmesi başarısız"
#: sabnzbd/nzbqueue.py, sabnzbd/nzbstuff.py
#: sabnzbd/nzbqueue.py
msgid "Duplicate NZB"
msgstr "Yinelenmiş NZB"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Invalid NZB file %s, skipping (error: %s)"
msgstr "Geçersiz NZB dosyası %s, atlanıyor (hata: %s)"
#. Warning message
#: sabnzbd/nzbstuff.py, sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Boş NZB dosyası %s"
#: sabnzbd/nzbstuff.py
msgid "Pre-queue script marked job as failed"
msgstr "Kuyruk öncesi betiği işi başarısız oldu olarak işaretlemiş"
#. Warning message
#: sabnzbd/nzbstuff.py
msgid "Unwanted Extension in file %s (%s)"
msgstr "%s (%s) dosyasında İstenmeyen Uzantı"
#: sabnzbd/nzbstuff.py
msgid "Aborted, cannot be completed"
msgstr "İptal edildi, tamamlanamıyor"
#. Error message
#: sabnzbd/nzbstuff.py
msgid "Error importing %s"
msgstr "%s unsurunun içe aktarılmasında hata"
#: sabnzbd/nzbstuff.py
msgid "DUPLICATE"
msgstr "YİNELENMİŞ"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr "ALTERNATİF"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
msgstr "ŞİFRELENMİŞ"
#: sabnzbd/nzbstuff.py
msgid "TOO LARGE"
msgstr "ÇOK BÜYÜK"
#: sabnzbd/nzbstuff.py
msgid "INCOMPLETE"
msgstr "TAMAMLANMAMIŞ"
#: sabnzbd/nzbstuff.py
msgid "UNWANTED"
msgstr "İSTENMEYEN"
#: sabnzbd/nzbstuff.py
msgid "WAIT %s sec"
msgstr "%s saniye BEKLEYİN"
#: sabnzbd/nzbstuff.py
msgid "PROPAGATING %s min"
msgstr "YAYINLANIYOR %s dakika"
#: sabnzbd/nzbstuff.py
msgid "Downloaded in %s at an average of %sB/s"
msgstr "%s içinde ortalama %sB/s hızında indirildi"
#. Job details page, file age column header
#: sabnzbd/nzbstuff.py, sabnzbd/skintext.py
msgid "Age"
msgstr "Yaş"
#: sabnzbd/nzbstuff.py
msgid "%s articles were malformed"
msgstr "%s makale yanlış şekillendirilmişti"
#: sabnzbd/nzbstuff.py
msgid "%s articles were missing"
msgstr "%s makale eksikti"
#: sabnzbd/nzbstuff.py
msgid "%s articles had non-matching duplicates"
msgstr "%s makale eşleşmeyen yinelenmişler bulunduruyordu"
#: sabnzbd/nzbstuff.py
msgid "Pausing duplicate NZB \"%s\""
msgstr "Yinelenmiş NZB \"%s\" duraklatılıyor"
#: sabnzbd/panic.py
msgid "Problem with"
msgstr "Şununla sorun"
@@ -2413,6 +2353,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"
@@ -3433,8 +3378,11 @@ msgid "Enable SFV-based checks"
msgstr "SFV temelli kontrolleri etkinleştir"
#: sabnzbd/skintext.py
msgid "Do an extra verification based on SFV files."
msgstr "SFV dosyalarına dayalı ilave bir doğrulama yap."
msgid ""
"If no par2 files are available, use sfv files (if present) to verify files"
msgstr ""
"Eğer hiçbir par2 dosyası mevcut değilse, dosyaları kontrol etmek için "
"(mevcutsa) sfv dosyalarını kullan"
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
@@ -3651,10 +3599,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 +3706,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
@@ -3858,6 +3803,17 @@ msgstr ""
msgid "Enable"
msgstr "Etkinleştir"
#: sabnzbd/skintext.py
msgid "Articles per request"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Request multiple articles per connection without waiting for each response "
"first.<br />This can improve download speeds, especially on connections with"
" higher latency."
msgstr ""
#. Button: Remove server
#: sabnzbd/skintext.py
msgid "Remove Server"
@@ -4274,20 +4230,29 @@ msgid "Enable Apprise notifications"
msgstr "Apprise bildirimlerini etkinleştir"
#: sabnzbd/skintext.py
msgid "Send notifications using Apprise to almost any notification service"
msgid ""
"Send notifications directly to any notification service you use.<br>For "
"example: Slack, Discord, Telegram, or any service from over 100 supported "
"services!"
msgstr ""
"Apprise kullanarak neredeyse tüm bildirim hizmetlerine bildirim gönderin"
"Bildirimleri kullandığınız herhangi bir bildirim hizmetine doğrudan "
"gönderin.<br>Örneğin: Slack, Discord, Telegram veya 100'den fazla "
"desteklenen hizmetten herhangi biri!"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Default Apprise URLs"
msgstr "Varsayılan Apprise URL'leri"
msgid "Use default Apprise URLs"
msgstr "Varsayılan Apprise URL'lerini kullan"
#. Apprise settings
#: sabnzbd/skintext.py
msgid "Use a comma and/or space to identify more than one URL."
msgid ""
"Apprise defines service connection information using URLs.<br>Read the "
"Apprise wiki how to define the URL for each service.<br>Use a comma and/or "
"space to identify more than one URL."
msgstr ""
"Birden fazla URL (adres) tanımlamak için virgül ve/veya boşluk kullanın."
"Apprise, hizmet bağlantı bilgilerini URL'ler kullanarak tanımlar.<br>Her "
"hizmet için URL'nin nasıl tanımlanacağını öğrenmek için Apprise wiki'sini "
"okuyun.<br>Birden fazla URL tanımlamak için virgül ve/veya boşluk kullanın."
#: sabnzbd/skintext.py
msgid ""
@@ -4629,6 +4594,11 @@ msgstr "Sil"
msgid "Filename"
msgstr "Dosya ismi"
#. Job details page, file age column header
#: sabnzbd/skintext.py
msgid "Age"
msgstr "Yaş"
#: sabnzbd/skintext.py
msgid "Free Space"
msgstr "Boş alan"
@@ -4759,6 +4729,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 +4993,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"
@@ -5055,6 +5038,10 @@ msgstr "Dosya sunucuda yok"
msgid "Server could not complete request"
msgstr "Sunucu talebi tamamlayamadı"
#: sabnzbd/urlgrabber.py
msgid "Empty NZB file %s"
msgstr "Boş NZB dosyası %s"
#. Error message
#: sabnzbd/urlgrabber.py
msgid "URLGRABBER CRASHED"

View File

File diff suppressed because it is too large Load Diff

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
CT3==3.4.0
cffi==1.17.1
pycparser==2.22
feedparser==6.0.11
apprise==1.9.6
sabctools==9.1.0
CT3==3.4.0.post5
cffi==2.0.0
pycparser==2.23
feedparser==6.0.12
configobj==5.0.9
cheroot==10.0.1
cheroot==11.1.2
six==1.17.0
cherrypy==18.10.0
jaraco.functools==4.2.1
jaraco.functools==4.4.0
jaraco.collections==5.0.0
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
jaraco.classes==3.4.0
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,51 @@ 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.5
# 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.1; sys_platform == 'darwin'
pyobjc-framework-Cocoa==12.1; 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 # Version-less for Python 3.9 and below
markdown==3.10; python_version > '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
urllib3==2.5.0
certifi==2025.6.15
charset_normalizer==3.4.4
idna==3.11
urllib3==2.6.2
certifi==2025.11.12
oauthlib==3.3.1
PyJWT==2.10.1
blinker==1.9.0

View File

@@ -32,11 +32,12 @@ from threading import Lock, Condition
# Determine platform flags
##############################################################################
WINDOWS = MACOS = MACOSARM64 = FOUNDATION = False
WINDOWS = WINDOWSARM64 = MACOS = MACOSARM64 = FOUNDATION = False
KERNEL32 = LIBC = MACOSLIBC = PLATFORM = None
if os.name == "nt":
WINDOWS = True
WINDOWSARM64 = platform.uname().machine == "ARM64"
if platform.uname().machine not in ["AMD64", "ARM64"]:
print("SABnzbd only supports 64-bit Windows")
@@ -82,15 +83,15 @@ from sabnzbd.version import __version__, __baseline__
import sabnzbd.misc as misc
import sabnzbd.filesystem as filesystem
import sabnzbd.powersup as powersup
import sabnzbd.rss as rss
import sabnzbd.emailer as emailer
import sabnzbd.encoding as encoding
import sabnzbd.config as config
import sabnzbd.cfg as cfg
import sabnzbd.database
import sabnzbd.lang as lang
import sabnzbd.nzb
import sabnzbd.nzbparser as nzbparser
import sabnzbd.nzbstuff
import sabnzbd.rss as rss
import sabnzbd.emailer as emailer
import sabnzbd.getipaddress
import sabnzbd.newsunpack
import sabnzbd.par2file
@@ -481,6 +482,10 @@ def delayed_startup_actions():
sabnzbd.ORG_UMASK,
)
# Check if maybe we are running x64 version on ARM hardware
if sabnzbd.WINDOWSARM64 and "AMD64" in sys.version:
misc.helpful_warning(T("Windows ARM version of SABnzbd is available from our Downloads page!"))
# List the number of certificates available (can take up to 1.5 seconds)
if cfg.log_level() > 1:
logging.debug("Available certificates = %s", repr(ssl.create_default_context().cert_store_stats()))

View File

File diff suppressed because it is too large Load Diff

View File

@@ -22,12 +22,12 @@ sabnzbd.articlecache - Article cache handling
import logging
import threading
import struct
from typing import Dict, Collection
from typing import Collection
import sabnzbd
from sabnzbd.decorators import synchronized
from sabnzbd.constants import GIGI, ANFO, ASSEMBLER_WRITE_THRESHOLD
from sabnzbd.nzbstuff import Article
from sabnzbd.nzb import Article
# Operations on the article table are handled via try/except.
# The counters need to be made atomic to ensure consistency.
@@ -39,7 +39,7 @@ class ArticleCache:
self.__cache_limit_org = 0
self.__cache_limit = 0
self.__cache_size = 0
self.__article_table: Dict[Article, bytes] = {} # Dict of buffered articles
self.__article_table: dict[Article, bytes] = {} # Dict of buffered articles
self.assembler_write_trigger: int = 1

View File

@@ -25,10 +25,11 @@ import logging
import re
from threading import Thread
import ctypes
from typing import Tuple, Optional, List
from typing import Optional
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,
@@ -38,17 +39,17 @@ from sabnzbd.filesystem import (
has_unwanted_extension,
get_basename,
)
from sabnzbd.constants import Status, GIGI, MAX_ASSEMBLER_QUEUE
from sabnzbd.constants import Status, GIGI
import sabnzbd.cfg as cfg
from sabnzbd.nzbstuff import NzbObject, NzbFile
from sabnzbd.nzb import NzbFile, NzbObject
import sabnzbd.par2file as par2file
import sabnzbd.utils.rarfile as rarfile
class Assembler(Thread):
def __init__(self):
super().__init__()
self.queue: queue.Queue[Tuple[Optional[NzbObject], Optional[NzbFile], Optional[bool]]] = queue.Queue()
self.max_queue_size: int = cfg.assembler_max_queue_size()
self.queue: queue.Queue[tuple[Optional[NzbObject], Optional[NzbFile], Optional[bool]]] = queue.Queue()
def stop(self):
self.queue.put((None, None, None))
@@ -57,7 +58,7 @@ class Assembler(Thread):
self.queue.put((nzo, nzf, file_done))
def queue_level(self) -> float:
return self.queue.qsize() / MAX_ASSEMBLER_QUEUE
return self.queue.qsize() / self.max_queue_size
def run(self):
while 1:
@@ -249,7 +250,7 @@ RE_SUBS = re.compile(r"\W+sub|subs|subpack|subtitle|subtitles(?![a-z])", re.I)
SAFE_EXTS = (".mkv", ".mp4", ".avi", ".wmv", ".mpg", ".webm")
def is_cloaked(nzo: NzbObject, path: str, names: List[str]) -> bool:
def is_cloaked(nzo: NzbObject, path: str, names: list[str]) -> bool:
"""Return True if this is likely to be a cloaked encrypted post"""
fname = get_basename(get_filename(path.lower()))
for name in names:
@@ -278,7 +279,7 @@ def is_cloaked(nzo: NzbObject, path: str, names: List[str]) -> bool:
return False
def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> Tuple[bool, Optional[str]]:
def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> tuple[bool, Optional[str]]:
"""Combines check for unwanted and encrypted files to save on CPU and IO"""
encrypted = False
unwanted = None
@@ -295,7 +296,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 +323,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

@@ -22,7 +22,7 @@ sabnzbd.bpsmeter - bpsmeter
import time
import logging
import re
from typing import List, Dict, Optional
from typing import Optional
import sabnzbd
from sabnzbd.constants import BYTES_FILE_NAME, KIBI
@@ -122,6 +122,7 @@ class BPSMeter:
"q_hour",
"q_minute",
"quota_enabled",
"quota_notifications_sent",
)
def __init__(self):
@@ -131,20 +132,20 @@ class BPSMeter:
self.speed_log_time = t
self.last_update = t
self.bps = 0.0
self.bps_list: List[int] = []
self.bps_list: list[int] = []
self.server_bps: Dict[str, float] = {}
self.cached_amount: Dict[str, int] = {}
self.server_bps: dict[str, float] = {}
self.cached_amount: dict[str, int] = {}
self.sum_cached_amount: int = 0
self.day_total: Dict[str, int] = {}
self.week_total: Dict[str, int] = {}
self.month_total: Dict[str, int] = {}
self.grand_total: Dict[str, int] = {}
self.day_total: dict[str, int] = {}
self.week_total: dict[str, int] = {}
self.month_total: dict[str, int] = {}
self.grand_total: dict[str, int] = {}
self.timeline_total: Dict[str, Dict[str, int]] = {}
self.timeline_total: dict[str, dict[str, int]] = {}
self.article_stats_tried: Dict[str, Dict[str, int]] = {}
self.article_stats_failed: Dict[str, Dict[str, int]] = {}
self.article_stats_tried: dict[str, dict[str, int]] = {}
self.article_stats_failed: dict[str, dict[str, int]] = {}
self.delayed_assembler: int = 0
@@ -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"""
@@ -252,8 +254,6 @@ class BPSMeter:
self.week_total[server] = 0
if server not in self.month_total:
self.month_total[server] = 0
if server not in self.month_total:
self.month_total[server] = 0
if server not in self.grand_total:
self.grand_total[server] = 0
if server not in self.timeline_total:
@@ -300,48 +300,51 @@ class BPSMeter:
for server in sabnzbd.Downloader.servers[:]:
self.init_server_stats(server.id)
# Cache dict references for faster access
day_total = self.day_total
week_total = self.week_total
month_total = self.month_total
grand_total = self.grand_total
timeline_total = self.timeline_total
cached_amount = self.cached_amount
server_bps = self.server_bps
start_time = self.start_time
last_update = self.last_update
# Minimum epsilon to avoid division by zero
dt_total = max(t - start_time, 1e-6)
dt_last = max(last_update - start_time, 1e-6)
# Add amounts that have been stored temporarily to statistics
for srv in self.cached_amount:
if self.cached_amount[srv]:
self.day_total[srv] += self.cached_amount[srv]
self.week_total[srv] += self.cached_amount[srv]
self.month_total[srv] += self.cached_amount[srv]
self.grand_total[srv] += self.cached_amount[srv]
self.timeline_total[srv][self.day_label] += self.cached_amount[srv]
if cached := self.cached_amount[srv]:
day_total[srv] += cached
week_total[srv] += cached
month_total[srv] += cached
grand_total[srv] += cached
timeline_total[srv][self.day_label] += cached
# Reset for next time
cached_amount[srv] = 0
# Update server bps
try:
self.server_bps[srv] = (
self.server_bps[srv] * (self.last_update - self.start_time) + self.cached_amount[srv]
) / (t - self.start_time)
except ZeroDivisionError:
self.server_bps[srv] = 0.0
# Reset for next time
self.cached_amount[srv] = 0
server_bps[srv] = (server_bps[srv] * dt_last + cached) / dt_total
# Quota check
total_cached = self.sum_cached_amount
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.left -= total_cached
self.check_quota()
# Speedometer
try:
self.bps = (self.bps * (self.last_update - self.start_time) + self.sum_cached_amount) / (
t - self.start_time
)
except ZeroDivisionError:
self.bps = 0.0
self.bps = (self.bps * dt_last + total_cached) / dt_total
self.sum_cached_amount = 0
self.last_update = t
check_time = t - 5.0
if self.start_time < check_time:
if start_time < check_time:
self.start_time = check_time
if self.bps < 0.01:
@@ -383,7 +386,7 @@ class BPSMeter:
# Always trim the list to the max-length
if len(self.bps_list) > BPS_LIST_MAX:
self.bps_list = self.bps_list[len(self.bps_list) - BPS_LIST_MAX :]
self.bps_list = self.bps_list[-BPS_LIST_MAX:]
def get_sums(self):
"""return tuple of grand, month, week, day totals"""
@@ -431,15 +434,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

@@ -25,7 +25,7 @@ import re
import argparse
import socket
import ipaddress
from typing import List, Tuple, Union
from typing import Union
import sabnzbd
from sabnzbd.config import (
@@ -52,12 +52,14 @@ from sabnzbd.constants import (
DEF_STD_WEB_COLOR,
DEF_HTTPS_CERT_FILE,
DEF_HTTPS_KEY_FILE,
DEF_MAX_ASSEMBLER_QUEUE,
DEF_PIPELINING_REQUESTS,
)
from sabnzbd.filesystem import same_directory, real_path, is_valid_script, is_network_path
# Validators currently only are made for string/list-of-strings
# and return those on success or an error message.
ValidateResult = Union[Tuple[None, str], Tuple[None, List[str]], Tuple[str, None]]
ValidateResult = Union[tuple[None, str], tuple[None, list[str]], tuple[str, None]]
##############################################################################
@@ -122,21 +124,21 @@ def supported_unrar_parameters(value: str) -> ValidateResult:
return None, value
def all_lowercase(value: Union[str, List]) -> Tuple[None, Union[str, List]]:
def all_lowercase(value: Union[str, list]) -> tuple[None, Union[str, list]]:
"""Lowercase and strip everything!"""
if isinstance(value, list):
return None, [item.lower().strip() for item in value]
return None, value.lower().strip()
def lower_case_ext(value: Union[str, List]) -> Tuple[None, Union[str, List]]:
def lower_case_ext(value: Union[str, list]) -> tuple[None, Union[str, list]]:
"""Generate lower case extension(s), without dot"""
if isinstance(value, list):
return None, [item.lower().strip(" .") for item in value]
return None, value.lower().strip(" .")
def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
def validate_single_tag(value: list[str]) -> tuple[None, list[str]]:
"""Don't split single indexer tags like "TV > HD"
into ['TV', '>', 'HD']
"""
@@ -146,7 +148,7 @@ def validate_single_tag(value: List[str]) -> Tuple[None, List[str]]:
return None, value
def validate_url_base(value: str) -> Tuple[None, str]:
def validate_url_base(value: str) -> tuple[None, str]:
"""Strips the right slash and adds starting slash, if not present"""
if value and isinstance(value, str):
if not value.startswith("/"):
@@ -158,7 +160,7 @@ def validate_url_base(value: str) -> Tuple[None, str]:
RE_VAL = re.compile(r"[^@ ]+@[^.@ ]+\.[^.@ ]")
def validate_email(value: Union[List, str]) -> ValidateResult:
def validate_email(value: Union[list, str]) -> ValidateResult:
if email_endjob() or email_full() or email_rss():
if isinstance(value, list):
values = value
@@ -285,7 +287,7 @@ def validate_download_vs_complete_dir(root: str, value: str, default: str):
return validate_safedir(root, value, default)
def validate_scriptdir_not_appdir(root: str, value: str, default: str) -> Tuple[None, str]:
def validate_scriptdir_not_appdir(root: str, value: str, default: str) -> tuple[None, str]:
"""Warn users to not use the Program Files folder for their scripts"""
# Need to add separator so /mnt/sabnzbd and /mnt/sabnzbd-data are not detected as equal
if value and same_directory(sabnzbd.DIR_PROG, os.path.join(root, value)):
@@ -298,7 +300,7 @@ def validate_scriptdir_not_appdir(root: str, value: str, default: str) -> Tuple[
return None, value
def validate_default_if_empty(root: str, value: str, default: str) -> Tuple[None, str]:
def validate_default_if_empty(root: str, value: str, default: str) -> tuple[None, str]:
"""If value is empty, return default"""
if value:
return None, value
@@ -505,7 +507,7 @@ no_penalties = OptionBool("misc", "no_penalties", False)
x_frame_options = OptionBool("misc", "x_frame_options", True)
allow_old_ssl_tls = OptionBool("misc", "allow_old_ssl_tls", False)
enable_season_sorting = OptionBool("misc", "enable_season_sorting", True)
verify_xff_header = OptionBool("misc", "verify_xff_header", False)
verify_xff_header = OptionBool("misc", "verify_xff_header", True)
# Text values
rss_odd_titles = OptionList("misc", "rss_odd_titles", ["nzbindex.nl/", "nzbindex.com/", "nzbclub.com/"])
@@ -527,10 +529,12 @@ local_ranges = OptionList("misc", "local_ranges", protect=True)
max_url_retries = OptionNumber("misc", "max_url_retries", 10, minval=1)
downloader_sleep_time = OptionNumber("misc", "downloader_sleep_time", 10, minval=0)
receive_threads = OptionNumber("misc", "receive_threads", 2, minval=1)
assembler_max_queue_size = OptionNumber("misc", "assembler_max_queue_size", DEF_MAX_ASSEMBLER_QUEUE, minval=1)
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 +562,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 +579,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 +596,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 +614,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 +636,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 +655,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 +680,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 +705,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

@@ -28,7 +28,7 @@ import time
import uuid
import io
import zipfile
from typing import List, Dict, Any, Callable, Optional, Union, Tuple
from typing import Any, Callable, Optional, Union
from urllib.parse import urlparse
import configobj
@@ -42,6 +42,7 @@ from sabnzbd.constants import (
CONFIG_BACKUP_HTTPS,
DEF_INI_FILE,
DEF_SORTER_RENAME_SIZE,
DEF_PIPELINING_REQUESTS,
)
from sabnzbd.decorators import synchronized
from sabnzbd.filesystem import clip_path, real_path, create_real_path, renamer, remove_file, is_writable
@@ -101,14 +102,14 @@ class Option:
def get_string(self) -> str:
return str(self.get())
def get_dict(self, for_public_api: bool = False) -> Dict[str, Any]:
def get_dict(self, for_public_api: bool = False) -> dict[str, Any]:
"""Return value as a dictionary.
Will not show non-public options if needed for the API"""
if not self.__public and for_public_api:
return {}
return {self.__keyword: self.get()}
def set_dict(self, values: Dict[str, Any]):
def set_dict(self, values: dict[str, Any]):
"""Set value based on dictionary"""
if not self.__protect:
try:
@@ -307,7 +308,7 @@ class OptionList(Option):
self,
section: str,
keyword: str,
default_val: Union[str, List, None] = None,
default_val: Union[str, list, None] = None,
validation: Optional[Callable] = None,
add: bool = True,
public: bool = True,
@@ -318,7 +319,7 @@ class OptionList(Option):
default_val = []
super().__init__(section, keyword, default_val, add=add, public=public, protect=protect)
def set(self, value: Union[str, List]) -> Optional[str]:
def set(self, value: Union[str, list]) -> Optional[str]:
"""Set the list given a comma-separated string or a list"""
error = None
if value is not None:
@@ -341,7 +342,7 @@ class OptionList(Option):
"""Return the default list as a comma-separated string"""
return ", ".join(self.default)
def __call__(self) -> List[str]:
def __call__(self) -> list[str]:
"""get() replacement"""
return self.get()
@@ -406,7 +407,7 @@ class OptionPassword(Option):
return "*" * 10
return ""
def get_dict(self, for_public_api: bool = False) -> Dict[str, str]:
def get_dict(self, for_public_api: bool = False) -> dict[str, str]:
"""Return value a dictionary"""
if for_public_api:
return {self.keyword: self.get_stars()}
@@ -444,6 +445,7 @@ class ConfigServer:
self.enable = OptionBool(name, "enable", True, add=False)
self.required = OptionBool(name, "required", False, add=False)
self.optional = OptionBool(name, "optional", False, add=False)
self.pipelining_requests = OptionNumber(name, "pipelining_requests", DEF_PIPELINING_REQUESTS, 1, 20, add=False)
self.retention = OptionNumber(name, "retention", 0, add=False)
self.expire_date = OptionStr(name, "expire_date", add=False)
self.quota = OptionStr(name, "quota", add=False)
@@ -454,7 +456,7 @@ class ConfigServer:
self.set_dict(values)
add_to_database("servers", self.__name, self)
def set_dict(self, values: Dict[str, Any]):
def set_dict(self, values: dict[str, Any]):
"""Set one or more fields, passed as dictionary"""
# Replace usage_at_start value with most recent statistics if the user changes the quota value
# Only when we are updating it from the Config
@@ -476,6 +478,7 @@ class ConfigServer:
"enable",
"required",
"optional",
"pipelining_requests",
"retention",
"expire_date",
"quota",
@@ -491,7 +494,7 @@ class ConfigServer:
if not self.displayname():
self.displayname.set(self.__name)
def get_dict(self, for_public_api: bool = False) -> Dict[str, Any]:
def get_dict(self, for_public_api: bool = False) -> dict[str, Any]:
"""Return a dictionary with all attributes"""
output_dict = {}
output_dict["name"] = self.__name
@@ -511,6 +514,7 @@ class ConfigServer:
output_dict["enable"] = self.enable()
output_dict["required"] = self.required()
output_dict["optional"] = self.optional()
output_dict["pipelining_requests"] = self.pipelining_requests()
output_dict["retention"] = self.retention()
output_dict["expire_date"] = self.expire_date()
output_dict["quota"] = self.quota()
@@ -531,7 +535,7 @@ class ConfigServer:
class ConfigCat:
"""Class defining a single category"""
def __init__(self, name: str, values: Dict[str, Any]):
def __init__(self, name: str, values: dict[str, Any]):
self.__name = clean_section_name(name)
name = "categories," + self.__name
@@ -545,7 +549,7 @@ class ConfigCat:
self.set_dict(values)
add_to_database("categories", self.__name, self)
def set_dict(self, values: Dict[str, Any]):
def set_dict(self, values: dict[str, Any]):
"""Set one or more fields, passed as dictionary"""
for kw in ("order", "pp", "script", "dir", "newzbin", "priority"):
try:
@@ -554,7 +558,7 @@ class ConfigCat:
except KeyError:
continue
def get_dict(self, for_public_api: bool = False) -> Dict[str, Any]:
def get_dict(self, for_public_api: bool = False) -> dict[str, Any]:
"""Return a dictionary with all attributes"""
output_dict = {}
output_dict["name"] = self.__name
@@ -589,7 +593,7 @@ class ConfigSorter:
self.set_dict(values)
add_to_database("sorters", self.__name, self)
def set_dict(self, values: Dict[str, Any]):
def set_dict(self, values: dict[str, Any]):
"""Set one or more fields, passed as dictionary"""
for kw in ("order", "min_size", "multipart_label", "sort_string", "sort_cats", "sort_type", "is_active"):
try:
@@ -598,7 +602,7 @@ class ConfigSorter:
except KeyError:
continue
def get_dict(self, for_public_api: bool = False) -> Dict[str, Any]:
def get_dict(self, for_public_api: bool = False) -> dict[str, Any]:
"""Return a dictionary with all attributes"""
output_dict = {}
output_dict["name"] = self.__name
@@ -639,7 +643,7 @@ class OptionFilters(Option):
return
self.set(lst)
def update(self, pos: int, value: Tuple):
def update(self, pos: int, value: tuple):
"""Update filter 'pos' definition, value is a list
Append if 'pos' outside list
"""
@@ -659,14 +663,14 @@ class OptionFilters(Option):
return
self.set(lst)
def get_dict(self, for_public_api: bool = False) -> Dict[str, str]:
def get_dict(self, for_public_api: bool = False) -> dict[str, str]:
"""Return filter list as a dictionary with keys 'filter[0-9]+'"""
output_dict = {}
for n, rss_filter in enumerate(self.get()):
output_dict[f"filter{n}"] = rss_filter
return output_dict
def set_dict(self, values: Dict[str, Any]):
def set_dict(self, values: dict[str, Any]):
"""Create filter list from dictionary with keys 'filter[0-9]+'"""
filters = []
# We don't know how many filters there are, so just assume all values are filters
@@ -677,7 +681,7 @@ class OptionFilters(Option):
if filters:
self.set(filters)
def __call__(self) -> List[List[str]]:
def __call__(self) -> list[list[str]]:
"""get() replacement"""
return self.get()
@@ -701,7 +705,7 @@ class ConfigRSS:
self.set_dict(values)
add_to_database("rss", self.__name, self)
def set_dict(self, values: Dict[str, Any]):
def set_dict(self, values: dict[str, Any]):
"""Set one or more fields, passed as dictionary"""
for kw in ("uri", "cat", "pp", "script", "priority", "enable"):
try:
@@ -711,7 +715,7 @@ class ConfigRSS:
continue
self.filters.set_dict(values)
def get_dict(self, for_public_api: bool = False) -> Dict[str, Any]:
def get_dict(self, for_public_api: bool = False) -> dict[str, Any]:
"""Return a dictionary with all attributes"""
output_dict = {}
output_dict["name"] = self.__name
@@ -755,7 +759,7 @@ AllConfigTypes = Union[
ConfigRSS,
ConfigServer,
]
CFG_DATABASE: Dict[str, Dict[str, AllConfigTypes]] = {}
CFG_DATABASE: dict[str, dict[str, AllConfigTypes]] = {}
@synchronized(CONFIG_LOCK)
@@ -1103,7 +1107,7 @@ def restore_config_backup(config_backup_data: bytes):
@synchronized(CONFIG_LOCK)
def get_servers() -> Dict[str, ConfigServer]:
def get_servers() -> dict[str, ConfigServer]:
global CFG_DATABASE
try:
return CFG_DATABASE["servers"]
@@ -1112,7 +1116,7 @@ def get_servers() -> Dict[str, ConfigServer]:
@synchronized(CONFIG_LOCK)
def get_sorters() -> Dict[str, ConfigSorter]:
def get_sorters() -> dict[str, ConfigSorter]:
global CFG_DATABASE
try:
return CFG_DATABASE["sorters"]
@@ -1120,7 +1124,7 @@ def get_sorters() -> Dict[str, ConfigSorter]:
return {}
def get_ordered_sorters() -> List[Dict]:
def get_ordered_sorters() -> list[dict]:
"""Return sorters as an ordered list"""
database_sorters = get_sorters()
@@ -1131,7 +1135,7 @@ def get_ordered_sorters() -> List[Dict]:
@synchronized(CONFIG_LOCK)
def get_categories() -> Dict[str, ConfigCat]:
def get_categories() -> dict[str, ConfigCat]:
"""Return link to categories section.
This section will always contain special category '*'
"""
@@ -1163,7 +1167,7 @@ def get_category(cat: str = "*") -> ConfigCat:
return cats["*"]
def get_ordered_categories() -> List[Dict]:
def get_ordered_categories() -> list[dict]:
"""Return list-copy of categories section that's ordered
by user's ordering including Default-category
"""
@@ -1183,7 +1187,7 @@ def get_ordered_categories() -> List[Dict]:
@synchronized(CONFIG_LOCK)
def get_rss() -> Dict[str, ConfigRSS]:
def get_rss() -> dict[str, ConfigRSS]:
global CFG_DATABASE
try:
# We have to remove non-separator commas by detecting if they are valid URL's

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 = "9.1.0"
DB_HISTORY_VERSION = 1
DB_HISTORY_NAME = "history%s.db" % DB_HISTORY_VERSION
@@ -97,12 +97,13 @@ CONFIG_BACKUP_HTTPS = { # "basename": "associated setting"
}
# Constants affecting download performance
MAX_ASSEMBLER_QUEUE = 12
SOFT_QUEUE_LIMIT = 0.5
DEF_MAX_ASSEMBLER_QUEUE = 12
SOFT_ASSEMBLER_QUEUE_LIMIT = 0.5
# Percentage of cache to use before adding file to assembler
ASSEMBLER_WRITE_THRESHOLD = 5
NNTP_BUFFER_SIZE = int(800 * KIBI)
NNTP_BUFFER_SIZE = int(256 * KIBI)
NTTP_MAX_BUFFER_SIZE = int(10 * MEBI)
DEF_PIPELINING_REQUESTS = 1
REPAIR_PRIORITY = 3
FORCE_PRIORITY = 2
@@ -180,6 +181,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

@@ -27,7 +27,7 @@ import sys
import threading
import sqlite3
from sqlite3 import Connection, Cursor
from typing import Optional, List, Sequence, Dict, Any, Tuple, Union
from typing import Optional, Sequence, Any
import sabnzbd
import sabnzbd.cfg
@@ -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,7 +232,12 @@ class HistoryDB:
logging.info("Removing all jobs with status=%s", status)
self.execute("""DELETE FROM history WHERE name LIKE ? AND status = ?""", (search, status))
def get_failed_paths(self, search: Optional[str] = None) -> List[str]:
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)
fetch_ok = self.execute(
@@ -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)
@@ -305,10 +315,10 @@ class HistoryDB:
limit: Optional[int] = None,
archive: Optional[bool] = None,
search: Optional[str] = None,
categories: Optional[List[str]] = None,
statuses: Optional[List[str]] = None,
nzo_ids: Optional[List[str]] = None,
) -> Tuple[List[Dict[str, Any]], int]:
categories: Optional[list[str]] = None,
statuses: Optional[list[str]] = None,
nzo_ids: Optional[list[str]] = None,
) -> tuple[list[dict[str, Any]], int]:
"""Return records for specified jobs"""
command_args = [convert_search(search)]
@@ -387,7 +397,7 @@ class HistoryDB:
total = self.cursor.fetchone()["COUNT(*)"]
return total > 0
def get_history_size(self) -> Tuple[int, int, int]:
def get_history_size(self) -> tuple[int, int, int]:
"""Returns the total size of the history and
amounts downloaded in the last month and week
"""
@@ -447,7 +457,7 @@ class HistoryDB:
return path
return path
def get_other(self, nzo_id: str) -> Tuple[str, str, str, str, str]:
def get_other(self, nzo_id: str) -> tuple[str, str, str, str, str]:
"""Return additional data for job `nzo_id`"""
if self.execute("""SELECT * FROM history WHERE nzo_id = ?""", (nzo_id,)):
try:
@@ -488,9 +498,14 @@ def convert_search(search: str) -> str:
return search
def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_output: str, script_line: str):
def build_history_info(
nzo: "sabnzbd.nzb.NzbObject",
workdir_complete: str,
postproc_time: int,
script_output: str,
script_line: str,
):
"""Collects all the information needed for the database"""
nzo: sabnzbd.nzbstuff.NzbObject
completed = int(time.time())
pp = PP_LOOKUP.get(opts_to_pp(nzo.repair, nzo.unpack, nzo.delete), "X")
@@ -540,10 +555,11 @@ def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_ou
nzo.duplicate_key,
nzo.md5sum,
nzo.correct_password,
nzo.time_added,
)
def unpack_history_info(item: sqlite3.Row) -> Dict[str, Any]:
def unpack_history_info(item: sqlite3.Row) -> dict[str, Any]:
"""Expands the single line stage_log from the DB
into a python dictionary for use in the history display
"""

View File

@@ -21,14 +21,11 @@ sabnzbd.decoder - article decoder
import logging
import hashlib
import binascii
from io import BytesIO
from zlib import crc32
from typing import Optional
import sabnzbd
from sabnzbd.constants import SABCTOOLS_VERSION_REQUIRED
from sabnzbd.encoding import ubtou
from sabnzbd.nzbstuff import Article
from sabnzbd.nzb import Article
from sabnzbd.misc import match_str
# Check for correct SABCTools version
@@ -50,7 +47,7 @@ except Exception:
class BadData(Exception):
def __init__(self, data: bytes):
def __init__(self, data: bytearray):
super().__init__()
self.data = data
@@ -63,8 +60,8 @@ class BadUu(Exception):
pass
def decode(article: Article, data_view: memoryview):
decoded_data = None
def decode(article: Article, decoder: sabctools.NNTPResponse):
decoded_data: Optional[bytearray] = None
nzo = article.nzf.nzo
art_id = article.article
@@ -78,10 +75,10 @@ def decode(article: Article, data_view: memoryview):
if sabnzbd.LOG_ALL:
logging.debug("Decoding %s", art_id)
if article.nzf.type == "uu":
decoded_data = decode_uu(article, bytes(data_view))
if decoder.format is sabctools.EncodingFormat.UU:
decoded_data = decode_uu(article, decoder)
else:
decoded_data = decode_yenc(article, data_view)
decoded_data = decode_yenc(article, decoder)
article_success = True
@@ -112,28 +109,18 @@ def decode(article: Article, data_view: memoryview):
except (BadYenc, ValueError):
# Handles precheck and badly formed articles
if nzo.precheck and data_view and data_view[:4] == b"223 ":
if nzo.precheck and decoder.status_code == 223:
# STAT was used, so we only get a status code
article_success = True
else:
# Try uu-decoding
if not nzo.precheck and article.nzf.type != "yenc":
try:
decoded_data = decode_uu(article, bytes(data_view))
logging.debug("Found uu-encoded article %s in job %s", art_id, nzo.final_name)
article_success = True
except Exception:
pass
# Only bother with further checks if uu-decoding didn't work out
if not article_success:
# Convert the first 2000 bytes of raw socket data to article lines,
# and examine the headers (for precheck) or body (for download).
for line in bytes(data_view[:2000]).split(b"\r\n"):
# Examine the headers (for precheck) or body (for download).
if lines := decoder.lines:
for line in lines:
lline = line.lower()
if lline.startswith(b"message-id:"):
if lline.startswith("message-id:"):
article_success = True
# Look for DMCA clues (while skipping "X-" headers)
if not lline.startswith(b"x-") and match_str(lline, (b"dmca", b"removed", b"cancel", b"blocked")):
if not lline.startswith("x-") and match_str(lline, ("dmca", "removed", "cancel", "blocked")):
article_success = False
logging.info("Article removed from server (%s)", art_id)
break
@@ -170,164 +157,63 @@ def decode(article: Article, data_view: memoryview):
sabnzbd.NzbQueue.register_article(article, article_success)
def decode_yenc(article: Article, data_view: memoryview) -> bytearray:
def decode_yenc(article: Article, response: sabctools.NNTPResponse) -> bytearray:
# Let SABCTools do all the heavy lifting
(
decoded_data,
yenc_filename,
article.file_size,
article.data_begin,
article.data_size,
crc_correct,
) = sabctools.yenc_decode(data_view)
decoded_data = response.data
article.file_size = response.file_size
article.data_begin = response.part_begin
article.data_size = response.part_size
nzf = article.nzf
# Assume it is yenc
nzf.type = "yenc"
# Only set the name if it was found and not obfuscated
if not nzf.filename_checked and yenc_filename:
if not nzf.filename_checked and (file_name := response.file_name):
# Set the md5-of-16k if this is the first article
if article.lowest_partnum:
nzf.md5of16k = hashlib.md5(decoded_data[:16384]).digest()
nzf.md5of16k = hashlib.md5(memoryview(decoded_data)[:16384]).digest()
# Try the rename, even if it's not the first article
# For example when the first article was missing
nzf.nzo.verify_nzf_filename(nzf, yenc_filename)
nzf.nzo.verify_nzf_filename(nzf, file_name)
# CRC check
if crc_correct is None:
if (crc := response.crc) is None:
logging.info("CRC Error in %s", article.article)
raise BadData(decoded_data)
article.crc32 = crc_correct
article.crc32 = crc
return decoded_data
def decode_uu(article: Article, raw_data: bytes) -> bytes:
"""Try to uu-decode an article. The raw_data may or may not contain headers.
If there are headers, they will be separated from the body by at least one
empty line. In case of no headers, the first line seems to always be the nntp
response code (220/222) directly followed by the msg body."""
if not raw_data:
def decode_uu(article: Article, response: sabctools.NNTPResponse) -> bytearray:
"""Process a uu-decoded response"""
if not response.bytes_decoded:
logging.debug("No data to decode")
raise BadUu
# Line up the raw_data
raw_data = raw_data.split(b"\r\n")
if response.baddata:
raise BadData(response.data)
# Index of the uu payload start in raw_data
uu_start = 0
# Limit the number of lines to check for the onset of uu data
limit = min(len(raw_data), 32) - 1
if limit < 3:
logging.debug("Article too short to contain valid uu-encoded data")
raise BadUu
# Try to find an empty line separating the body from headers or response
# code and set the expected payload start to the next line.
try:
uu_start = raw_data[:limit].index(b"") + 1
except ValueError:
# No empty line, look for a response code instead
if raw_data[0].startswith(b"220 ") or raw_data[0].startswith(b"222 "):
uu_start = 1
else:
# Invalid data?
logging.debug("Failed to locate start of uu payload")
raise BadUu
def is_uu_junk(line: bytes) -> bool:
"""Determine if the line is empty or contains known junk data"""
return (not line) or line == b"-- " or line.startswith(b"Posted via ")
# Check the uu 'begin' line
if article.lowest_partnum:
try:
# Make sure the line after the uu_start one isn't empty as well or
# detection of the 'begin' line won't work. For articles other than
# lowest_partnum, filtering out empty lines (and other junk) can
# wait until the actual decoding step.
for index in range(uu_start, limit):
if is_uu_junk(raw_data[index]):
uu_start = index + 1
else:
# Bingo
break
else:
# Search reached the limit
raise IndexError
uu_begin_data = raw_data[uu_start].split(b" ")
# Filename may contain spaces
uu_filename = ubtou(b" ".join(uu_begin_data[2:]).strip())
# Sanity check the 'begin' line
if (
len(uu_begin_data) < 3
or uu_begin_data[0].lower() != b"begin"
or (not int(uu_begin_data[1], 8))
or (not uu_filename)
):
raise ValueError
# Consider this enough proof to set the type, avoiding further
# futile attempts at decoding articles in this nzf as yenc.
article.nzf.type = "uu"
# Bump the pointer for the payload to the next line
uu_start += 1
except Exception:
logging.debug("Missing or invalid uu 'begin' line: %s", raw_data[uu_start] if uu_start < limit else None)
raise BadUu
# Do the actual decoding
with BytesIO() as decoded_data:
for line in raw_data[uu_start:]:
# Ignore junk
if is_uu_junk(line):
continue
# End of the article
if line in (b"`", b"end", b"."):
break
# Remove dot stuffing
if line.startswith(b".."):
line = line[1:]
try:
decoded_line = binascii.a2b_uu(line)
except binascii.Error as msg:
try:
# Workaround for broken uuencoders by Fredrik Lundh
nbytes = (((line[0] - 32) & 63) * 4 + 5) // 3
decoded_line = binascii.a2b_uu(line[:nbytes])
except Exception as msg2:
logging.info(
"Error while uu-decoding %s: %s (line: %s; workaround: %s)", article.article, msg, line, msg2
)
raise BadData(decoded_data.getvalue())
# Store the decoded data
decoded_data.write(decoded_line)
# Set the type to uu; the latter is still needed in
# case the lowest_partnum article was damaged or slow to download.
article.nzf.type = "uu"
decoded_data = response.data
nzf = article.nzf
nzf.type = "uu"
# Only set the name if it was found and not obfuscated
if not nzf.filename_checked and (file_name := response.file_name):
# Set the md5-of-16k if this is the first article
if article.lowest_partnum:
decoded_data.seek(0)
article.nzf.md5of16k = hashlib.md5(decoded_data.read(16384)).digest()
# Handle the filename
if not article.nzf.filename_checked and uu_filename:
article.nzf.nzo.verify_nzf_filename(article.nzf, uu_filename)
nzf.md5of16k = hashlib.md5(memoryview(decoded_data)[:16384]).digest()
data = decoded_data.getvalue()
article.crc32 = crc32(data)
return data
# Try the rename, even if it's not the first article
# For example when the first article was missing
nzf.nzo.verify_nzf_filename(nzf, file_name)
article.crc32 = response.crc
return decoded_data
def search_new_server(article: Article) -> bool:

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

@@ -38,14 +38,13 @@ from sabnzbd.par2file import is_par2_file, parse_par2_file
import sabnzbd.utils.file_extension as file_extension
from sabnzbd.misc import match_str
from sabnzbd.constants import IGNORED_MOVIE_FOLDERS
from typing import List
# Files to exclude and minimal file size for renaming
EXCLUDED_FILE_EXTS = (".vob", ".rar", ".par2", ".mts", ".m2ts", ".cpi", ".clpi", ".mpl", ".mpls", ".bdm", ".bdmv")
MIN_FILE_SIZE = 10 * 1024 * 1024
def decode_par2(parfile: str) -> List[str]:
def decode_par2(parfile: str) -> list[str]:
"""Parse a par2 file and rename files listed in the par2 to their real name. Return list of generated files"""
# Check if really a par2 file
if not is_par2_file(parfile):
@@ -77,7 +76,7 @@ def decode_par2(parfile: str) -> List[str]:
return new_files
def recover_par2_names(filelist: List[str]) -> List[str]:
def recover_par2_names(filelist: list[str]) -> list[str]:
"""Find par2 files and use them for renaming"""
# Check that files exists
filelist = [f for f in filelist if os.path.isfile(f)]
@@ -168,7 +167,7 @@ def is_probably_obfuscated(myinputfilename: str) -> bool:
return True # default is obfuscated
def get_biggest_file(filelist: List[str]) -> str:
def get_biggest_file(filelist: list[str]) -> str:
"""Returns biggest file if that file is much bigger than the other files
If only one file exists, return that. If no file, return None
Note: the files in filelist must exist, because their sizes on disk are checked"""
@@ -190,7 +189,7 @@ def get_biggest_file(filelist: List[str]) -> str:
return None
def deobfuscate(nzo, filelist: List[str], usefulname: str) -> List[str]:
def deobfuscate(nzo: "sabnzbd.nzb.NzbObject", filelist: list[str], usefulname: str) -> list[str]:
"""
For files in filelist:
1. if a file has no meaningful extension, add it (for example ".txt" or ".png")
@@ -228,9 +227,6 @@ def deobfuscate(nzo, filelist: List[str], usefulname: str) -> List[str]:
"""
# Can't be imported directly due to circular import
nzo: sabnzbd.nzbstuff.NzbObject
# to be sure, only keep really existing files and remove any duplicates:
filtered_filelist = list(set(f for f in filelist if os.path.isfile(f)))
@@ -321,7 +317,7 @@ def without_extension(fullpathfilename: str) -> str:
return os.path.splitext(fullpathfilename)[0]
def deobfuscate_subtitles(nzo, filelist: List[str]):
def deobfuscate_subtitles(nzo: "sabnzbd.nzb.NzbObject", filelist: list[str]):
"""
input:
nzo, so we can update result via set_unpack_info()
@@ -346,10 +342,6 @@ def deobfuscate_subtitles(nzo, filelist: List[str]):
Something.else.txt
"""
# Can't be imported directly due to circular import
nzo: sabnzbd.nzbstuff.NzbObject
# find .srt files
if not (srt_files := [f for f in filelist if f.endswith(".srt")]):
logging.debug("No .srt files found, so nothing to do")

View File

@@ -25,18 +25,18 @@ import subprocess
import time
import threading
import logging
from typing import Optional, Dict, List, Tuple
from typing import Optional
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.nzbstuff import NzbObject, NzbFile
from sabnzbd.filesystem import remove_all, real_path, remove_file, get_basename, clip_path
from sabnzbd.nzb import NzbFile, NzbObject
from sabnzbd.encoding import platform_btou
from sabnzbd.decorators import synchronized
from sabnzbd.newsunpack import RAR_EXTRACTFROM_RE, RAR_EXTRACTED_RE, rar_volumelist, add_time_left
from sabnzbd.postproc import prepare_extraction_path
from sabnzbd.utils.rarfile import RarFile
from sabnzbd.misc import SABRarFile
from sabnzbd.utils.diskspeed import diskspeedmeasure
# Need a lock to make sure start and stop is handled correctly
@@ -61,11 +61,11 @@ class DirectUnpacker(threading.Thread):
self.rarfile_nzf: Optional[NzbFile] = None
self.cur_setname: Optional[str] = None
self.cur_volume: int = 0
self.total_volumes: Dict[str, int] = {}
self.total_volumes: dict[str, int] = {}
self.unpack_time: float = 0.0
self.success_sets: Dict[str, Tuple[List[str], List[str]]] = {}
self.next_sets: List[NzbFile] = []
self.success_sets: dict[str, tuple[list[str], list[str]]] = {}
self.next_sets: list[NzbFile] = []
self.duplicate_lines: int = 0
@@ -415,40 +415,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 +440,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 +494,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

@@ -23,7 +23,7 @@ import asyncio
import os
import logging
import threading
from typing import Generator, Set, Optional, Tuple
from typing import Generator, Optional
import sabnzbd
from sabnzbd.constants import SCAN_FILE_NAME, VALID_ARCHIVES, VALID_NZB_FILES, AddNzbFileResult
@@ -128,7 +128,7 @@ class DirScanner(threading.Thread):
def get_suspected_files(
self, folder: str, catdir: Optional[str] = None
) -> Generator[Tuple[str, Optional[str], Optional[os.stat_result]], None, None]:
) -> Generator[tuple[str, Optional[str], Optional[os.stat_result]], None, None]:
"""Generator listing possible paths to NZB files"""
if catdir is None:
@@ -222,17 +222,15 @@ class DirScanner(threading.Thread):
async def scan_async(self, dirscan_dir: str):
"""Do one scan of the watched folder"""
# On Python 3.8 we first need an event loop before we can create a asyncio.Lock
if not self.lock:
with DIR_SCANNER_LOCK:
self.lock = asyncio.Lock()
with DIR_SCANNER_LOCK:
self.lock = asyncio.Lock()
async with self.lock:
if sabnzbd.PAUSED_ALL:
return
files: Set[str] = set()
futures: Set[asyncio.Task] = set()
files: set[str] = set()
futures: set[asyncio.Task] = set()
for path, catdir, stat_tuple in self.get_suspected_files(dirscan_dir):
files.add(path)

View File

@@ -19,15 +19,18 @@
sabnzbd.downloader - download engine
"""
import select
import logging
import selectors
from collections import deque
from threading import Thread, RLock, current_thread
import socket
import sys
import ssl
import time
from datetime import date
from typing import List, Dict, Optional, Union, Set
from typing import Optional, Union, Deque
import sabctools
import sabnzbd
from sabnzbd.decorators import synchronized, NzbQueueLocker, DOWNLOADER_CV, DOWNLOADER_LOCK
@@ -35,8 +38,8 @@ 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.constants import SOFT_QUEUE_LIMIT
from sabnzbd.get_addrinfo import get_fastest_addrinfo, AddrInfo
from sabnzbd.constants import SOFT_ASSEMBLER_QUEUE_LIMIT
# Timeout penalty in minutes for each cause
@@ -82,6 +85,7 @@ class Server:
"retention",
"username",
"password",
"pipelining_requests",
"busy_threads",
"next_busy_threads_check",
"idle_threads",
@@ -110,6 +114,7 @@ class Server:
use_ssl,
ssl_verify,
ssl_ciphers,
pipelining_requests,
username=None,
password=None,
required=False,
@@ -123,7 +128,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
@@ -134,10 +139,11 @@ class Server:
self.retention: int = retention
self.username: Optional[str] = username
self.password: Optional[str] = password
self.pipelining_requests: Callable[[], int] = pipelining_requests
self.busy_threads: Set[NewsWrapper] = set()
self.busy_threads: set[NewsWrapper] = set()
self.next_busy_threads_check: float = 0
self.idle_threads: Set[NewsWrapper] = set()
self.idle_threads: set[NewsWrapper] = set()
self.next_article_search: float = 0
self.active: bool = True
self.bad_cons: int = 0
@@ -148,7 +154,7 @@ class Server:
self.request: bool = False # True if a getaddrinfo() request is pending
self.have_body: bool = True # Assume server has "BODY", until proven otherwise
self.have_stat: bool = True # Assume server has "STAT", until proven otherwise
self.article_queue: List[sabnzbd.nzbstuff.Article] = []
self.article_queue: Deque[sabnzbd.nzb.Article] = deque()
# Skip during server testing
if threads:
@@ -173,19 +179,19 @@ class Server:
self.reset_article_queue()
@synchronized(DOWNLOADER_LOCK)
def get_article(self):
def get_article(self, peek: bool = False):
"""Get article from pre-fetched and pre-fetch new ones if necessary.
Articles that are too old for this server are immediately marked as tried"""
if self.article_queue:
return self.article_queue.pop(0)
return self.article_queue[0] if peek else self.article_queue.popleft()
if self.next_article_search < time.time():
# Pre-fetch new articles
self.article_queue = sabnzbd.NzbQueue.get_articles(self, sabnzbd.Downloader.servers, _ARTICLE_PREFETCH)
sabnzbd.NzbQueue.get_articles(self, sabnzbd.Downloader.servers, _ARTICLE_PREFETCH)
if self.article_queue:
article = self.article_queue.pop(0)
article = self.article_queue[0] if peek else self.article_queue.popleft()
# Mark expired articles as tried on this server
if self.retention and article.nzf.nzo.avg_stamp < time.time() - self.retention:
if not peek and self.retention and article.nzf.nzo.avg_stamp < time.time() - self.retention:
sabnzbd.Downloader.decode(article)
while self.article_queue:
sabnzbd.Downloader.decode(self.article_queue.pop())
@@ -201,12 +207,15 @@ class Server:
"""Reset articles queued for the Server. Locked to prevent
articles getting stuck in the Server when enabled/disabled"""
logging.debug("Resetting article queue for %s (%s)", self, self.article_queue)
for article in self.article_queue:
article.allow_new_fetcher()
self.article_queue = []
while self.article_queue:
try:
article = self.article_queue.popleft()
article.allow_new_fetcher()
except IndexError:
pass
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 +223,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 +231,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
@@ -250,7 +259,7 @@ class Downloader(Thread):
"shutdown",
"server_restarts",
"force_disconnect",
"read_fds",
"selector",
"servers",
"timers",
"last_max_chunk_size",
@@ -290,10 +299,10 @@ class Downloader(Thread):
self.force_disconnect: bool = False
self.read_fds: Dict[int, NewsWrapper] = {}
self.selector: selectors.DefaultSelector = selectors.DefaultSelector()
self.servers: List[Server] = []
self.timers: Dict[str, List[float]] = {}
self.servers: list[Server] = []
self.timers: dict[str, list[float]] = {}
for server in config.get_servers():
self.init_server(None, server)
@@ -319,6 +328,7 @@ class Downloader(Thread):
ssl = srv.ssl()
ssl_verify = srv.ssl_verify()
ssl_ciphers = srv.ssl_ciphers()
pipelining_requests = srv.pipelining_requests
username = srv.username()
password = srv.password()
required = srv.required()
@@ -349,6 +359,7 @@ class Downloader(Thread):
ssl,
ssl_verify,
ssl_ciphers,
pipelining_requests,
username,
password,
required,
@@ -361,15 +372,34 @@ class Downloader(Thread):
self.servers.sort(key=lambda svr: "%02d%s" % (svr.priority, svr.displayname.lower()))
@synchronized(DOWNLOADER_LOCK)
def add_socket(self, fileno: int, nw: NewsWrapper):
"""Add a socket ready to be used to the list to be watched"""
self.read_fds[fileno] = nw
def add_socket(self, nw: NewsWrapper):
"""Add a socket to be watched for read or write availability"""
if nw.nntp:
try:
self.selector.register(nw.nntp.fileno, selectors.EVENT_READ | selectors.EVENT_WRITE, nw)
nw.selector_events = selectors.EVENT_READ | selectors.EVENT_WRITE
except KeyError:
pass
@synchronized(DOWNLOADER_LOCK)
def modify_socket(self, nw: NewsWrapper, events: int):
"""Modify the events socket are watched for"""
if nw.nntp and nw.selector_events != events:
try:
self.selector.modify(nw.nntp.fileno, events, nw)
nw.selector_events = events
except KeyError:
pass
@synchronized(DOWNLOADER_LOCK)
def remove_socket(self, nw: NewsWrapper):
"""Remove a socket to be watched"""
if nw.nntp:
self.read_fds.pop(nw.nntp.fileno, None)
try:
self.selector.unregister(nw.nntp.fileno)
nw.selector_events = 0
except KeyError:
pass
@NzbQueueLocker
def set_paused_state(self, state: bool):
@@ -409,8 +439,9 @@ class Downloader(Thread):
@NzbQueueLocker
def resume_from_postproc(self):
logging.info("Post-processing finished, resuming download")
self.paused_for_postproc = False
if self.paused_for_postproc:
logging.info("Post-processing finished, resuming download")
self.paused_for_postproc = False
@NzbQueueLocker
def disconnect(self):
@@ -451,6 +482,15 @@ class Downloader(Thread):
self.bandwidth_perc = 0
self.bandwidth_limit = 0
# Increase limits for faster connections
if limit > from_units("150M"):
if cfg.receive_threads() == cfg.receive_threads.default:
cfg.receive_threads.set(4)
logging.info("Receive threads set to 4")
if cfg.assembler_max_queue_size() == cfg.assembler_max_queue_size.default:
cfg.assembler_max_queue_size.set(30)
logging.info("Assembler max_queue_size set to 30")
def sleep_time_set(self):
self.sleep_time = cfg.downloader_sleep_time() * 0.0001
logging.debug("Sleep time: %f seconds", self.sleep_time)
@@ -490,7 +530,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()
@@ -499,26 +539,30 @@ class Downloader(Thread):
# Remove all connections to server
for nw in server.idle_threads | server.busy_threads:
self.__reset_nw(nw, "Forcing disconnect", warn=False, wait=False, retry_article=False)
self.reset_nw(nw, "Forcing disconnect", warn=False, wait=False, retry_article=False)
# Make sure server address resolution is refreshed
server.addrinfo = None
@staticmethod
def decode(article, data_view: Optional[memoryview] = None):
def decode(article: "sabnzbd.nzb.Article", response: Optional[sabctools.NNTPResponse] = None):
"""Decode article"""
# Need a better way of draining requests
if article.nzf.nzo.removed_from_queue:
return
# Article was requested and fetched, update article stats for the server
sabnzbd.BPSMeter.register_server_article_tried(article.fetcher.id)
# Handle broken articles directly
if not data_view:
if not response or not response.bytes_decoded and not article.nzf.nzo.precheck:
if not article.search_new_server():
article.nzf.nzo.increase_bad_articles_counter("missing_articles")
sabnzbd.NzbQueue.register_article(article, success=False)
return
# Decode and send to article cache
sabnzbd.decoder.decode(article, data_view)
sabnzbd.decoder.decode(article, response)
def run(self):
# Warn if there are servers defined, but none are valid
@@ -538,7 +582,7 @@ class Downloader(Thread):
for _ in range(cfg.receive_threads()):
# Started as daemon, so we don't need any shutdown logic in the worker
# The Downloader code will make sure shutdown is handled gracefully
Thread(target=self.process_nw_worker, args=(self.read_fds, process_nw_queue), daemon=True).start()
Thread(target=self.process_nw_worker, args=(process_nw_queue,), daemon=True).start()
# Catch all errors, just in case
try:
@@ -560,9 +604,9 @@ class Downloader(Thread):
if (nw.nntp and nw.nntp.error_msg) or (nw.timeout and now > nw.timeout):
if nw.nntp and nw.nntp.error_msg:
# Already showed error
self.__reset_nw(nw)
self.reset_nw(nw)
else:
self.__reset_nw(nw, "Timed out", warn=True)
self.reset_nw(nw, "Timed out", warn=True)
server.bad_cons += 1
self.maybe_block_server(server)
@@ -602,15 +646,14 @@ class Downloader(Thread):
server.request_addrinfo()
break
nw.article = server.get_article()
if not nw.article:
if not server.get_article(peek=True):
break
server.idle_threads.remove(nw)
server.busy_threads.add(nw)
if nw.connected:
self.__request_article(nw)
self.add_socket(nw)
else:
try:
logging.info("%s@%s: Initiating connection", nw.thrdnum, server.host)
@@ -622,14 +665,14 @@ class Downloader(Thread):
server.host,
sys.exc_info()[1],
)
self.__reset_nw(nw, "Failed to initialize", warn=True)
self.reset_nw(nw, "Failed to initialize", warn=True)
if self.force_disconnect or self.shutdown:
for server in self.servers:
for nw in server.idle_threads | server.busy_threads:
# Send goodbye if we have open socket
if nw.nntp:
self.__reset_nw(nw, "Forcing disconnect", wait=False, count_article_try=False)
self.reset_nw(nw, "Forcing disconnect", wait=False, count_article_try=False)
# Make sure server address resolution is refreshed
server.addrinfo = None
server.reset_article_queue()
@@ -653,10 +696,13 @@ class Downloader(Thread):
self.last_max_chunk_size = 0
# Use select to find sockets ready for reading/writing
if readkeys := self.read_fds.keys():
read, _, _ = select.select(readkeys, (), (), 1.0)
if self.selector.get_map():
if events := self.selector.select(timeout=1.0):
for key, ev in events:
nw = key.data
process_nw_queue.put((nw, ev, nw.generation))
else:
read = []
events = []
BPSMeter.reset()
time.sleep(0.1)
self.max_chunk_size = _DEFAULT_CHUNK_SIZE
@@ -675,58 +721,75 @@ class Downloader(Thread):
next_bpsmeter_update = now + _BPSMETER_UPDATE_DELAY
self.check_assembler_levels()
if not read:
if not events:
continue
# Submit all readable sockets to be processed and wait for completion
process_nw_queue.put_multiple(read)
# Wait for socket operation completion
process_nw_queue.join()
except Exception:
logging.error(T("Fatal error in Downloader"), exc_info=True)
def process_nw_worker(self, read_fds: Dict[int, NewsWrapper], nw_queue: MultiAddQueue):
def process_nw_worker(self, nw_queue: MultiAddQueue):
"""Worker for the daemon thread to process results.
Wrapped in try/except because in case of an exception, logging
might get lost and the queue.join() would block forever."""
try:
logging.debug("Starting Downloader receive thread: %s", current_thread().name)
while True:
# The read_fds is passed by reference, so we can access its items!
self.process_nw(read_fds[nw_queue.get()])
self.process_nw(*nw_queue.get())
nw_queue.task_done()
except Exception:
# We cannot break out of the Downloader from here, so just pause
logging.error(T("Fatal error in Downloader"), exc_info=True)
self.pause()
def process_nw(self, nw: NewsWrapper):
def process_nw(self, nw: NewsWrapper, event: int, generation: int):
"""Receive data from a NewsWrapper and handle the response"""
try:
bytes_received, end_of_line, article_done = nw.recv_chunk()
except ssl.SSLWantReadError:
# Drop stale items
if nw.generation != generation:
return
except (ConnectionError, ConnectionAbortedError):
# The ConnectionAbortedError is also thrown by sabctools in case of fatal SSL-layer problems
self.__reset_nw(nw, "Server closed connection", wait=False)
return
except BufferError:
# The BufferError is thrown when exceeding maximum buffer size
# Make sure to discard the article
self.__reset_nw(nw, "Maximum data buffer size exceeded", wait=False, retry_article=False)
if event & selectors.EVENT_READ:
self.process_nw_read(nw, generation)
# If read caused a reset, don't proceed to write
if nw.generation != generation:
return
if event & selectors.EVENT_WRITE:
nw.write()
def process_nw_read(self, nw: NewsWrapper, generation: int) -> None:
bytes_received: int = 0
bytes_pending: int = 0
while nw.decoder and nw.generation == generation:
try:
n, bytes_pending = nw.read(nbytes=bytes_pending, generation=generation)
bytes_received += n
except ssl.SSLWantReadError:
return
except (ConnectionError, ConnectionAbortedError):
# The ConnectionAbortedError is also thrown by sabctools in case of fatal SSL-layer problems
self.reset_nw(nw, "Server closed connection", wait=False)
return
except BufferError:
# The BufferError is thrown when exceeding maximum buffer size
# Make sure to discard the article
self.reset_nw(nw, "Maximum data buffer size exceeded", wait=False, retry_article=False)
return
if not bytes_pending:
break
# Ignore metrics for reset connections
if nw.generation != generation:
return
article = nw.article
server = nw.server
with DOWNLOADER_LOCK:
sabnzbd.BPSMeter.update(server.id, bytes_received)
if bytes_received > self.last_max_chunk_size:
self.last_max_chunk_size = bytes_received
# Update statistics only when we fetched a whole article
# The side effect is that we don't count things like article-not-available messages
if article_done:
article.nzf.nzo.update_download_stats(sabnzbd.BPSMeter.bps, server.id, nw.data_position)
# Check speedlimit
if (
self.bandwidth_limit
@@ -737,97 +800,10 @@ class Downloader(Thread):
time.sleep(0.01)
sabnzbd.BPSMeter.update()
# If we are not at the end of a line, more data will follow
if not end_of_line:
return
# Response code depends on request command:
# 220 = ARTICLE, 222 = BODY
if nw.status_code not in (220, 222) and not article_done:
if not nw.connected or nw.status_code == 480:
if not self.__finish_connect_nw(nw):
return
if nw.connected:
logging.info("Connecting %s@%s finished", nw.thrdnum, nw.server.host)
self.__request_article(nw)
elif nw.status_code == 223:
article_done = True
logging.debug("Article <%s> is present", article.article)
elif nw.status_code in (411, 423, 430, 451):
article_done = True
logging.debug(
"Thread %s@%s: Article %s missing (error=%s)",
nw.thrdnum,
nw.server.host,
article.article,
nw.status_code,
)
nw.reset_data_buffer()
elif nw.status_code == 500:
if article.nzf.nzo.precheck:
# Assume "STAT" command is not supported
server.have_stat = False
logging.debug("Server %s does not support STAT", server.host)
else:
# Assume "BODY" command is not supported
server.have_body = False
logging.debug("Server %s does not support BODY", server.host)
nw.reset_data_buffer()
self.__request_article(nw)
else:
# Don't warn for (internal) server errors during downloading
if nw.status_code not in (400, 502, 503):
logging.warning(
T("%s@%s: Received unknown status code %s for article %s"),
nw.thrdnum,
nw.server.host,
nw.status_code,
article.article,
)
# Ditch this thread, we don't know what data we got now so the buffer can be bad
self.__reset_nw(nw, f"Server error or unknown status code: {nw.status_code}", wait=False)
return
if article_done:
# Successful data, clear "bad" counter
server.bad_cons = 0
server.errormsg = server.warning = ""
# Decode
self.decode(article, nw.data_view[: nw.data_position])
if sabnzbd.LOG_ALL:
logging.debug("Thread %s@%s: %s done", nw.thrdnum, server.host, article.article)
# Reset connection for new activity
nw.soft_reset()
# Request a new article immediately if possible
if (
nw.connected
and server.active
and not server.restart
and not (self.paused or self.shutdown or self.paused_for_postproc)
):
nw.article = server.get_article()
if nw.article:
self.__request_article(nw)
return
# Make socket available again
server.busy_threads.discard(nw)
server.idle_threads.add(nw)
self.remove_socket(nw)
def check_assembler_levels(self):
"""Check the Assembler queue to see if we need to delay, depending on queue size"""
if (assembler_level := sabnzbd.Assembler.queue_level()) > SOFT_QUEUE_LIMIT:
time.sleep(min((assembler_level - SOFT_QUEUE_LIMIT) / 4, 0.15))
if (assembler_level := sabnzbd.Assembler.queue_level()) > SOFT_ASSEMBLER_QUEUE_LIMIT:
time.sleep(min((assembler_level - SOFT_ASSEMBLER_QUEUE_LIMIT) / 4, 0.15))
sabnzbd.BPSMeter.delayed_assembler += 1
logged_counter = 0
@@ -849,13 +825,12 @@ class Downloader(Thread):
logged_counter += 1
@synchronized(DOWNLOADER_LOCK)
def __finish_connect_nw(self, nw: NewsWrapper) -> bool:
def finish_connect_nw(self, nw: NewsWrapper, response: sabctools.NNTPResponse) -> bool:
server = nw.server
try:
nw.finish_connect(nw.status_code)
nw.finish_connect(response.status_code, response.message)
if sabnzbd.LOG_ALL:
logging.debug("%s@%s last message -> %s", nw.thrdnum, server.host, nw.nntp_msg)
nw.reset_data_buffer()
logging.debug("%s@%s last message -> %d", nw.thrdnum, server.host, response.status_code)
except NNTPPermanentError as error:
# Handle login problems
block = False
@@ -868,9 +843,8 @@ class Downloader(Thread):
errormsg = T("Too many connections to server %s [%s]") % (server.host, error.msg)
if server.active:
# Don't count this for the tries (max_art_tries) on this server
self.__reset_nw(nw)
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(
@@ -919,7 +893,7 @@ class Downloader(Thread):
if penalty and (block or server.optional):
self.plan_server(server, penalty)
# Note that the article is discard for this server if the server is not required
self.__reset_nw(nw, retry_article=retry_article)
self.reset_nw(nw, retry_article=retry_article)
return False
except Exception as err:
logging.error(
@@ -930,11 +904,11 @@ class Downloader(Thread):
)
logging.info("Traceback: ", exc_info=True)
# No reset-warning needed, above logging is sufficient
self.__reset_nw(nw, retry_article=False)
self.reset_nw(nw, retry_article=False)
return True
@synchronized(DOWNLOADER_LOCK)
def __reset_nw(
def reset_nw(
self,
nw: NewsWrapper,
reset_msg: Optional[str] = None,
@@ -942,6 +916,7 @@ class Downloader(Thread):
wait: bool = True,
count_article_try: bool = True,
retry_article: bool = True,
article: Optional["sabnzbd.nzb.Article"] = None,
):
# Some warnings are errors, and not added as server.warning
if warn and reset_msg:
@@ -957,20 +932,8 @@ class Downloader(Thread):
# Make sure it is not in the readable sockets
self.remove_socket(nw)
if nw.article and not nw.article.nzf.nzo.removed_from_queue:
# Only some errors should count towards the total tries for each server
if count_article_try:
nw.article.tries += 1
# Do we discard, or try again for this server
if not retry_article or (not nw.server.required and nw.article.tries > cfg.max_art_tries()):
# Too many tries on this server, consider article missing
self.decode(nw.article)
nw.article.tries = 0
else:
# Allow all servers again for this article
# Do not use the article_queue, as the server could already have been disabled when we get here!
nw.article.allow_new_fetcher()
# Discard the article request which failed
nw.discard(article, count_article_try=count_article_try, retry_article=retry_article)
# Reset connection object
nw.hard_reset(wait)
@@ -978,21 +941,6 @@ class Downloader(Thread):
# Empty SSL info, it might change on next connect
nw.server.ssl_info = ""
def __request_article(self, nw: NewsWrapper):
try:
if sabnzbd.LOG_ALL:
logging.debug("Thread %s@%s: BODY %s", nw.thrdnum, nw.server.host, nw.article.article)
nw.body()
# Mark as ready to be read
self.add_socket(nw.nntp.fileno, nw)
except socket.error as err:
logging.info("Looks like server closed connection: %s", err)
self.__reset_nw(nw, "Server broke off connection", warn=True)
except Exception:
logging.error(T("Suspect error in downloader"))
logging.info("Traceback: ", exc_info=True)
self.__reset_nw(nw, "Server broke off connection", warn=True)
# ------------------------------------------------------------------------------
# Timed restart of servers admin.
# For each server all planned events are kept in a list.
@@ -1144,6 +1092,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,8 +33,7 @@ import fnmatch
import stat
import ctypes
import random
import functools
from typing import Union, List, Tuple, Any, Dict, Optional, BinaryIO
from typing import Union, Any, Optional, BinaryIO
try:
import win32api
@@ -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
@@ -296,10 +295,10 @@ def sanitize_and_trim_path(path: str) -> str:
if sabnzbd.WINDOWS:
if path.startswith("\\\\?\\UNC\\"):
new_path = "\\\\?\\UNC\\"
path = path[8:]
path = path.removeprefix("\\\\?\\UNC\\")
elif path.startswith("\\\\?\\"):
new_path = "\\\\?\\"
path = path[4:]
path = path.removeprefix("\\\\?\\")
path = path.replace("\\", "/")
parts = path.split("/")
@@ -315,7 +314,7 @@ def sanitize_and_trim_path(path: str) -> str:
return os.path.abspath(os.path.normpath(new_path))
def sanitize_files(folder: Optional[str] = None, filelist: Optional[List[str]] = None) -> List[str]:
def sanitize_files(folder: Optional[str] = None, filelist: Optional[list[str]] = None) -> list[str]:
"""Sanitize each file in the folder or list of filepaths, return list of new names"""
logging.info("Checking if any resulting filenames need to be sanitized")
if folder:
@@ -331,7 +330,7 @@ def sanitize_files(folder: Optional[str] = None, filelist: Optional[List[str]] =
return output_filelist
def strip_extensions(name: str, ext_to_remove: Tuple[str, ...] = (".nzb", ".par", ".par2")):
def strip_extensions(name: str, ext_to_remove: tuple[str, ...] = (".nzb", ".par", ".par2")) -> str:
"""Strip extensions from a filename, without sanitizing the filename"""
name_base, ext = os.path.splitext(name)
while ext.lower() in ext_to_remove:
@@ -379,7 +378,7 @@ def real_path(loc: str, path: str) -> str:
def create_real_path(
name: str, loc: str, path: str, apply_permissions: bool = False, writable: bool = True
) -> Tuple[bool, str, Optional[str]]:
) -> tuple[bool, str, Optional[str]]:
"""When 'path' is relative, create join of 'loc' and 'path'
When 'path' is absolute, create normalized path
'name' is used for logging.
@@ -485,7 +484,7 @@ TS_RE = re.compile(r"\.(\d+)\.(ts$)", re.I)
def build_filelists(
workdir: Optional[str], workdir_complete: Optional[str] = None, check_both: bool = False, check_rar: bool = True
) -> Tuple[List[str], List[str], List[str], List[str]]:
) -> tuple[list[str], list[str], list[str], list[str]]:
"""Build filelists, if workdir_complete has files, ignore workdir.
Optionally scan both directories.
Optionally test content to establish RAR-ness
@@ -536,7 +535,7 @@ def safe_fnmatch(f: str, pattern: str) -> bool:
return False
def globber(path: str, pattern: str = "*") -> List[str]:
def globber(path: str, pattern: str = "*") -> list[str]:
"""Return matching base file/folder names in folder `path`"""
# Cannot use glob.glob() because it doesn't support Windows long name notation
if os.path.exists(path):
@@ -544,7 +543,7 @@ def globber(path: str, pattern: str = "*") -> List[str]:
return []
def globber_full(path: str, pattern: str = "*") -> List[str]:
def globber_full(path: str, pattern: str = "*") -> list[str]:
"""Return matching full file/folder names in folder `path`"""
# Cannot use glob.glob() because it doesn't support Windows long name notation
if os.path.exists(path):
@@ -573,7 +572,7 @@ def is_valid_script(basename: str) -> bool:
return basename in list_scripts(default=False, none=False)
def list_scripts(default: bool = False, none: bool = True) -> List[str]:
def list_scripts(default: bool = False, none: bool = True) -> list[str]:
"""Return a list of script names, optionally with 'Default' added"""
lst = []
path = sabnzbd.cfg.script_dir.get_path()
@@ -614,7 +613,7 @@ def make_script_path(script: str) -> Optional[str]:
return script_path
def get_admin_path(name: str, future: bool):
def get_admin_path(name: str, future: bool) -> str:
"""Return news-style full path to job-admin folder of names job
or else the old cache path
"""
@@ -661,7 +660,7 @@ def set_permissions(path: str, recursive: bool = True):
UNWANTED_FILE_PERMISSIONS = stat.S_ISUID | stat.S_ISGID | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
def removexbits(path: str, custom_permissions: int = None):
def removexbits(path: str, custom_permissions: Optional[int] = None):
"""Remove all the x-bits from files, respecting current or custom permissions"""
if os.path.isfile(path):
# Use custom permissions as base
@@ -784,7 +783,7 @@ def get_unique_filename(path: str) -> str:
@synchronized(DIR_LOCK)
def listdir_full(input_dir: str, recursive: bool = True) -> List[str]:
def listdir_full(input_dir: str, recursive: bool = True) -> list[str]:
"""List all files in dirs and sub-dirs"""
filelist = []
for root, dirs, files in os.walk(input_dir):
@@ -798,7 +797,7 @@ def listdir_full(input_dir: str, recursive: bool = True) -> List[str]:
@synchronized(DIR_LOCK)
def move_to_path(path: str, new_path: str) -> Tuple[bool, Optional[str]]:
def move_to_path(path: str, new_path: str) -> tuple[bool, Optional[str]]:
"""Move a file to a new path, optionally give unique filename
Return (ok, new_path)
"""
@@ -991,51 +990,7 @@ 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]:
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
x = "x"
@@ -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)
def diskspace(force: bool = False) -> Dict[str, Tuple[float, float]]:
"""Wrapper to keep results cached by cache_maintainer
@conditional_cache(cache_time=10)
def diskspace(force: bool = False) -> dict[str, tuple[float, float]]:
"""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()),
@@ -1083,7 +1033,7 @@ def diskspace(force: bool = False) -> Dict[str, Tuple[float, float]]:
}
def get_new_id(prefix, folder, check_list=None):
def get_new_id(prefix: str, folder: str, check_list: Optional[list] = None) -> str:
"""Return unique prefixed admin identifier within folder
optionally making sure that id is not in the check_list.
"""
@@ -1104,7 +1054,7 @@ def get_new_id(prefix, folder, check_list=None):
raise IOError
def save_data(data, _id, path, do_pickle=True, silent=False):
def save_data(data: Any, _id: str, path: str, do_pickle: bool = True, silent: bool = False):
"""Save data to a diskfile"""
if not silent:
logging.debug("[%s] Saving data for %s in %s", sabnzbd.misc.caller_name(), _id, path)
@@ -1131,7 +1081,7 @@ def save_data(data, _id, path, do_pickle=True, silent=False):
time.sleep(0.1)
def load_data(data_id, path, remove=True, do_pickle=True, silent=False):
def load_data(data_id: str, path: str, remove: bool = True, do_pickle: bool = True, silent: bool = False) -> Any:
"""Read data from disk file"""
path = os.path.join(path, data_id)
@@ -1179,7 +1129,7 @@ def save_admin(data: Any, data_id: str):
save_data(data, data_id, sabnzbd.cfg.admin_dir.get_path())
def load_admin(data_id: str, remove=False, silent=False) -> Any:
def load_admin(data_id: str, remove: bool = False, silent: bool = False) -> Any:
"""Read data in admin folder in specified format"""
logging.debug("[%s] Loading data for %s", sabnzbd.misc.caller_name(), data_id)
return load_data(data_id, sabnzbd.cfg.admin_dir.get_path(), remove=remove, silent=silent)
@@ -1246,7 +1196,7 @@ def purge_log_files():
logging.debug("Finished puring log files")
def directory_is_writable_with_file(mydir, myfilename):
def directory_is_writable_with_file(mydir: str, myfilename: str) -> bool:
filename = os.path.join(mydir, myfilename)
if os.path.exists(filename):
try:
@@ -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
)
@@ -1299,7 +1253,7 @@ def check_filesystem_capabilities(test_dir: str) -> bool:
return allgood
def get_win_drives() -> List[str]:
def get_win_drives() -> list[str]:
"""Return list of detected drives, adapted from:
http://stackoverflow.com/questions/827371/is-there-a-way-to-list-all-the-available-drive-letters-in-python/827490
"""
@@ -1327,7 +1281,7 @@ PATHBROWSER_JUNKFOLDERS = (
)
def pathbrowser(path: str, show_hidden: bool = False, show_files: bool = False) -> List[Dict[str, str]]:
def pathbrowser(path: str, show_hidden: bool = False, show_files: bool = False) -> list[dict[str, str]]:
"""Returns a list of dictionaries with the folders and folders contained at the given path
Give the empty string as the path to list the contents of the root path
under Unix this means "/", on Windows this will be a list of drive letters
@@ -1413,3 +1367,54 @@ def pathbrowser(path: str, show_hidden: bool = False, show_files: bool = False)
)
return file_list
def create_work_name(name: str) -> str:
"""Remove ".nzb" and ".par(2)" and sanitize, skip URL's"""
if name.find("://") < 0:
# Invalid charters need to be removed before and after (see unit-tests)
return sanitize_foldername(strip_extensions(sanitize_foldername(name)))
else:
return name.strip()
def nzf_cmp_name(nzf1, nzf2):
"""Comparison function for sorting NZB files.
The comparison will sort .par2 files to the top of the queue followed by .rar files,
they will then be sorted by name.
Note: nzf1 and nzf2 should be NzbFile objects, but we can't import that here
to avoid circular dependencies.
"""
nzf1_name = nzf1.filename.lower()
nzf2_name = nzf2.filename.lower()
# Determine vol-pars
is_par1 = ".vol" in nzf1_name and ".par2" in nzf1_name
is_par2 = ".vol" in nzf2_name and ".par2" in nzf2_name
# mini-par2 in front
if not is_par1 and nzf1_name.endswith(".par2"):
return -1
if not is_par2 and nzf2_name.endswith(".par2"):
return 1
# vol-pars go to the back
if is_par1 and not is_par2:
return 1
if is_par2 and not is_par1:
return -1
# Prioritize .rar files above any other type of file (other than vol-par)
m1 = RAR_RE.search(nzf1_name)
m2 = RAR_RE.search(nzf2_name)
if m1 and not (is_par2 or m2):
return -1
elif m2 and not (is_par1 or m1):
return 1
# Force .rar to come before 'r00'
if m1 and m1.group(1) == ".rar":
nzf1_name = nzf1_name.replace(".rar", ".r//")
if m2 and m2.group(1) == ".rar":
nzf2_name = nzf2_name.replace(".rar", ".r//")
return sabnzbd.misc.cmp(nzf1_name, nzf2_name)

View File

@@ -16,31 +16,23 @@
# 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 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.
@@ -68,9 +60,10 @@ class AddrInfo:
type: socket.SocketKind
proto: int
canonname: str
sockaddr: Union[Tuple[str, int], Tuple[str, int, int, int]]
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 +83,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 +114,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 +172,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

@@ -34,7 +34,7 @@ import copy
from random import randint
from xml.sax.saxutils import escape
from Cheetah.Template import Template
from typing import Optional, Callable, Union, Any, Dict, List
from typing import Optional, Callable, Union, Any
from guessit.api import properties as guessit_properties
import sabnzbd
@@ -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,
@@ -264,7 +264,7 @@ def check_hostname():
COOKIE_SECRET = str(randint(1000, 100000) * os.getpid())
def remote_ip_from_xff(xff_ips: List[str]) -> str:
def remote_ip_from_xff(xff_ips: list[str]) -> str:
# Per MDN docs, the first non-local/non-trusted IP (rtl) is our "client"
# However, it's possible that all IPs are local/trusted, so we may also
# return the first ip in the list as it "should" be the client
@@ -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():
@@ -402,7 +399,7 @@ def check_apikey(kwargs):
return _MSG_APIKEY_INCORRECT
def template_filtered_response(file: str, search_list: Dict[str, Any]):
def template_filtered_response(file: str, search_list: dict[str, Any]):
"""Wrapper for Cheetah response"""
# We need a copy, because otherwise source-dicts might be modified
search_list_copy = copy.deepcopy(search_list)
@@ -561,7 +558,7 @@ class Wizard:
info["password"] = ""
info["connections"] = ""
info["ssl"] = 1
info["ssl_verify"] = 2
info["ssl_verify"] = 3
else:
# Sort servers to get the first enabled one
server_names = sorted(
@@ -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"):
@@ -907,11 +906,13 @@ SPECIAL_VALUE_LIST = (
"max_foldername_length",
"url_base",
"receive_threads",
"assembler_max_queue_size",
"switchinterval",
"direct_unpack_threads",
"selftest_host",
"ssdp_broadcast_interval",
"unrar_parameters",
"outgoing_nntp_ip",
)
SPECIAL_LIST_LIST = (
"rss_odd_titles",
@@ -1174,7 +1175,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 +2030,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 +2047,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 +2064,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 +2082,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 +2102,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 +2123,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 +2148,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 +2173,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

@@ -26,12 +26,11 @@ import socket
import ssl
import time
import threading
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
@@ -44,7 +43,7 @@ NR_CONNECTIONS = 5
TIME_LIMIT = 3
def internetspeed_worker(secure_sock: ssl.SSLSocket, socket_speed: Dict[ssl.SSLSocket, float]):
def internetspeed_worker(secure_sock: ssl.SSLSocket, socket_speed: dict[ssl.SSLSocket, float]):
"""Worker to perform the requests in parallel"""
secure_sock.sendall(TEST_REQUEST.encode())
empty_buffer = memoryview(sabctools.bytearray_malloc(BUFFER_SIZE))
@@ -88,8 +87,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,9 +38,10 @@ 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
from typing import Union, Any, AnyStr, Optional, Collection
import sabnzbd
import sabnzbd.getipaddress
@@ -55,9 +55,9 @@ 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
from sabnzbd.filesystem import userxbit, make_script_path, remove_file, strip_extensions
if sabnzbd.WINDOWS:
try:
@@ -85,6 +85,10 @@ RE_SAMPLE = re.compile(r"((^|[\W_])(sample|proof))", re.I) # something-sample o
RE_IP4 = re.compile(r"inet\s+(addr:\s*)?(\d+\.\d+\.\d+\.\d+)")
RE_IP6 = re.compile(r"inet6\s+(addr:\s*)?([0-9a-f:]+)", re.I)
# Name patterns for NZB parsing
RE_SUBJECT_FILENAME_QUOTES = re.compile(r'"([^"]*)"')
RE_SUBJECT_BASIC_FILENAME = re.compile(r"\b([\w\-+()' .,]+(?:\[[\w\-/+()' .,]*][\w\-+()' .,]*)*\.[A-Za-z0-9]{2,4})\b")
# Check if strings are defined for AM and PM
HAVE_AMPM = bool(time.strftime("%p"))
@@ -178,7 +182,7 @@ def is_none(inp: Any) -> bool:
return not inp or (isinstance(inp, str) and inp.lower() == "none")
def clean_comma_separated_list(inp: Any) -> List[str]:
def clean_comma_separated_list(inp: Any) -> list[str]:
"""Return a list of stripped values from a string or list, empty ones removed"""
result_ids = []
if isinstance(inp, str):
@@ -190,7 +194,7 @@ def clean_comma_separated_list(inp: Any) -> List[str]:
return result_ids
def cmp(x, y):
def cmp(x: Any, y: Any) -> int:
"""
Replacement for built-in function cmp that was removed in Python 3
@@ -217,7 +221,7 @@ def cat_pp_script_sanitizer(
cat: Optional[str] = None,
pp: Optional[Union[int, str]] = None,
script: Optional[str] = None,
) -> Tuple[Optional[Union[int, str]], Optional[str], Optional[str]]:
) -> tuple[Optional[Union[int, str]], Optional[str], Optional[str]]:
"""Basic sanitizer from outside input to a bit more predictable values"""
# * and Default are valid values
if safe_lower(cat) in ("", "none"):
@@ -234,7 +238,7 @@ def cat_pp_script_sanitizer(
return cat, pp, script
def name_to_cat(fname, cat=None):
def name_to_cat(fname: str, cat: Optional[str] = None) -> tuple[str, Optional[str]]:
"""Retrieve category from file name, but only if "cat" is None."""
if cat is None and fname.startswith("{{"):
n = fname.find("}}")
@@ -246,7 +250,9 @@ def name_to_cat(fname, cat=None):
return fname, cat
def cat_to_opts(cat, pp=None, script=None, priority=None) -> Tuple[str, int, str, int]:
def cat_to_opts(
cat: Optional[str], pp: Optional[int] = None, script: Optional[str] = None, priority: Optional[int] = None
) -> tuple[str, int, str, int]:
"""Derive options from category, if options not already defined.
Specified options have priority over category-options.
If no valid category is given, special category '*' will supply default values
@@ -279,7 +285,7 @@ def cat_to_opts(cat, pp=None, script=None, priority=None) -> Tuple[str, int, str
return cat, pp, script, priority
def pp_to_opts(pp: Optional[int]) -> Tuple[bool, bool, bool]:
def pp_to_opts(pp: Optional[int]) -> tuple[bool, bool, bool]:
"""Convert numeric processing options to (repair, unpack, delete)"""
# Convert the pp to an int
pp = int_conv(pp)
@@ -331,12 +337,12 @@ _wildcard_to_regex = {
}
def wildcard_to_re(text):
def wildcard_to_re(text: str) -> str:
"""Convert plain wildcard string (with '*' and '?') to regex."""
return "".join([_wildcard_to_regex.get(ch, ch) for ch in text])
def convert_filter(text):
def convert_filter(text: str) -> Optional[re.Pattern]:
"""Return compiled regex.
If string starts with re: it's a real regex
else quote all regex specials, replace '*' by '.*'
@@ -353,7 +359,7 @@ def convert_filter(text):
return None
def cat_convert(cat):
def cat_convert(cat: Optional[str]) -> Optional[str]:
"""Convert indexer's category/group-name to user categories.
If no match found, but indexer-cat equals user-cat, then return user-cat
If no match found, but the indexer-cat starts with the user-cat, return user-cat
@@ -397,7 +403,7 @@ _SERVICE_KEY = "SYSTEM\\CurrentControlSet\\services\\"
_SERVICE_PARM = "CommandLine"
def get_serv_parms(service):
def get_serv_parms(service: str) -> list[str]:
"""Get the service command line parameters from Registry"""
service_parms = []
try:
@@ -416,7 +422,7 @@ def get_serv_parms(service):
return service_parms
def set_serv_parms(service, args):
def set_serv_parms(service: str, args: list) -> bool:
"""Set the service command line parameters in Registry"""
serv = []
for arg in args:
@@ -444,7 +450,7 @@ def get_from_url(url: str) -> Optional[str]:
return None
def convert_version(text):
def convert_version(text: str) -> tuple[int, bool]:
"""Convert version string to numerical value and a testversion indicator"""
version = 0
test = True
@@ -551,7 +557,7 @@ def check_latest_version():
)
def upload_file_to_sabnzbd(url, fp):
def upload_file_to_sabnzbd(url: str, fp: str):
"""Function for uploading nzbs to a running SABnzbd instance"""
try:
fp = urllib.parse.quote_plus(fp)
@@ -644,7 +650,7 @@ def to_units(val: Union[int, float], postfix="") -> str:
return f"{sign}{val:.{decimals}f}{units}"
def caller_name(skip=2):
def caller_name(skip: int = 2) -> str:
"""Get a name of a caller in the format module.method
Originally used: https://gist.github.com/techtonik/2151727
Adapted for speed by using sys calls directly
@@ -682,7 +688,7 @@ def exit_sab(value: int):
os._exit(value)
def split_host(srv):
def split_host(srv: Optional[str]) -> tuple[Optional[str], Optional[int]]:
"""Split host:port notation, allowing for IPV6"""
if not srv:
return None, None
@@ -704,7 +710,7 @@ def split_host(srv):
return out[0], port
def get_cache_limit():
def get_cache_limit() -> str:
"""Depending on OS, calculate cache limits.
In ArticleCache it will make sure we stay
within system limits for 32/64 bit
@@ -742,7 +748,7 @@ def get_cache_limit():
return ""
def get_windows_memory():
def get_windows_memory() -> int:
"""Use ctypes to extract available memory"""
class MEMORYSTATUSEX(ctypes.Structure):
@@ -768,15 +774,14 @@ def get_windows_memory():
return stat.ullTotalPhys
def get_macos_memory():
def get_macos_memory() -> float:
"""Use system-call to extract total memory on macOS"""
system_output = run_command(["sysctl", "hw.memsize"])
return float(system_output.split()[1])
@cache_maintainer(clear_time=3600)
@functools.lru_cache(maxsize=None)
def get_cpu_name():
@conditional_cache(cache_time=3600)
def get_cpu_name() -> Optional[str]:
"""Find the CPU name (which needs a different method per OS), and return it
If none found, return platform.platform()"""
@@ -876,7 +881,7 @@ def on_cleanup_list(filename: str, skip_nzb: bool = False) -> bool:
return False
def memory_usage():
def memory_usage() -> Optional[str]:
try:
# Probably only works on Linux because it uses /proc/<pid>/statm
with open("/proc/%d/statm" % os.getpid()) as t:
@@ -898,7 +903,7 @@ except Exception:
_HAVE_STATM = _PAGE_SIZE and memory_usage()
def loadavg():
def loadavg() -> str:
"""Return 1, 5 and 15 minute load average of host or "" if not supported"""
p = ""
if not sabnzbd.WINDOWS and not sabnzbd.MACOS:
@@ -973,7 +978,7 @@ def bool_conv(value: Any) -> bool:
return bool(int_conv(value))
def create_https_certificates(ssl_cert, ssl_key):
def create_https_certificates(ssl_cert: str, ssl_key: str) -> bool:
"""Create self-signed HTTPS certificates and store in paths 'ssl_cert' and 'ssl_key'"""
try:
from sabnzbd.utils.certgen import generate_key, generate_local_cert
@@ -989,7 +994,7 @@ def create_https_certificates(ssl_cert, ssl_key):
return True
def get_all_passwords(nzo) -> List[str]:
def get_all_passwords(nzo) -> list[str]:
"""Get all passwords, from the NZB, meta and password file. In case a working password is
already known, try it first."""
passwords = []
@@ -1052,7 +1057,7 @@ def is_sample(filename: str) -> bool:
return bool(re.search(RE_SAMPLE, filename))
def find_on_path(targets):
def find_on_path(targets: Union[str, tuple[str, ...]]) -> Optional[str]:
"""Search the PATH for a program and return full path"""
if sabnzbd.WINDOWS:
paths = os.getenv("PATH").split(";")
@@ -1171,7 +1176,7 @@ def is_local_addr(ip: str) -> bool:
return is_lan_addr(ip)
def ip_extract() -> List[str]:
def ip_extract() -> list[str]:
"""Return list of IP addresses of this system"""
ips = []
program = find_on_path("ip")
@@ -1216,7 +1221,7 @@ def get_base_url(url: str) -> str:
return ""
def match_str(text: AnyStr, matches: Tuple[AnyStr, ...]) -> Optional[AnyStr]:
def match_str(text: AnyStr, matches: tuple[AnyStr, ...]) -> Optional[AnyStr]:
"""Return first matching element of list 'matches' in 'text', otherwise None"""
text = text.lower()
for match in matches:
@@ -1225,7 +1230,7 @@ def match_str(text: AnyStr, matches: Tuple[AnyStr, ...]) -> Optional[AnyStr]:
return None
def recursive_html_escape(input_dict_or_list: Union[Dict[str, Any], List], exclude_items: Tuple[str, ...] = ()):
def recursive_html_escape(input_dict_or_list: Union[dict[str, Any], list], exclude_items: tuple[str, ...] = ()):
"""Recursively update the input_dict in-place with html-safe values"""
if isinstance(input_dict_or_list, (dict, list)):
if isinstance(input_dict_or_list, dict):
@@ -1246,7 +1251,7 @@ def recursive_html_escape(input_dict_or_list: Union[Dict[str, Any], List], exclu
raise ValueError("Expected dict or str, got %s" % type(input_dict_or_list))
def list2cmdline_unrar(lst: List[str]) -> str:
def list2cmdline_unrar(lst: list[str]) -> str:
"""convert list to a unrar.exe-compatible command string
Unrar uses "" instead of \" to escape the double quote"""
nlst = []
@@ -1260,7 +1265,9 @@ def list2cmdline_unrar(lst: List[str]) -> str:
return " ".join(nlst)
def build_and_run_command(command: List[str], windows_unrar_command: bool = False, text_mode: bool = True, **kwargs):
def build_and_run_command(
command: list[str], windows_unrar_command: bool = False, text_mode: bool = True, **kwargs
) -> subprocess.Popen:
"""Builds and then runs command with necessary flags and optional
IONice and Nice commands. Optional Popen arguments can be supplied.
On Windows we need to run our own list2cmdline for Unrar.
@@ -1327,7 +1334,7 @@ def build_and_run_command(command: List[str], windows_unrar_command: bool = Fals
return subprocess.Popen(command, **popen_kwargs)
def run_command(cmd: List[str], **kwargs):
def run_command(cmd: list[str], **kwargs) -> str:
"""Run simple external command and return output as a string."""
with build_and_run_command(cmd, **kwargs) as p:
txt = p.stdout.read()
@@ -1360,7 +1367,7 @@ def set_socks5_proxy():
socket.socket = socks.socksocket
def set_https_verification(value):
def set_https_verification(value: bool) -> bool:
"""Set HTTPS-verification state while returning current setting
False = disable verification
"""
@@ -1382,7 +1389,7 @@ def request_repair():
pass
def check_repair_request():
def check_repair_request() -> bool:
"""Return True if repair request found, remove afterwards"""
path = os.path.join(cfg.admin_dir.get_path(), REPAIR_REQUEST)
if os.path.exists(path):
@@ -1515,8 +1522,8 @@ def convert_sorter_settings():
min_size: Union[str|int] = "50M"
multipart_label: Optional[str] = ""
sort_string: str
sort_cats: List[str]
sort_type: List[int]
sort_cats: list[str]
sort_type: list[int]
is_active: bool = 1
}
@@ -1576,7 +1583,7 @@ def convert_sorter_settings():
def convert_history_retention():
"""Convert single-option to the split history retention setting"""
if "d" in cfg.history_retention():
days_to_keep = int_conv(cfg.history_retention().strip()[:-1])
days_to_keep = int_conv(cfg.history_retention().strip().removesuffix("d"))
cfg.history_retention_option.set("days-delete")
cfg.history_retention_number.set(days_to_keep)
else:
@@ -1586,3 +1593,100 @@ def convert_history_retention():
cfg.history_retention_number.set(to_keep)
elif to_keep < 0:
cfg.history_retention_option.set("all-delete")
def scan_password(name: str) -> tuple[str, Optional[str]]:
"""Get password (if any) from the title"""
if "http://" in name or "https://" in name:
return name, None
# Strip any unwanted usenet-related extensions
name = strip_extensions(name)
# Identify any braces
braces = name[1:].find("{{")
if braces < 0:
braces = len(name)
else:
braces += 1
slash = name.find("/")
# Look for name/password, but make sure that '/' comes before any {{
if 0 < slash < braces and "password=" not in name:
# Is it maybe in 'name / password' notation?
if slash == name.find(" / ") + 1 and name[: slash - 1].strip(". "):
# Remove the extra space after name and before password
return name[: slash - 1].strip(". "), name[slash + 2 :]
if name[:slash].strip(". "):
return name[:slash].strip(". "), name[slash + 1 :]
# Look for "name password=password"
pw = name.find("password=")
if pw > 0 and name[:pw].strip(". "):
return name[:pw].strip(". "), name[pw + 9 :]
# Look for name{{password}}
if braces < len(name):
closing_braces = name.rfind("}}")
if closing_braces > braces and name[:braces].strip(". "):
return name[:braces].strip(". "), name[braces + 2 : closing_braces]
# Look again for name/password
if slash > 0 and name[:slash].strip(". "):
return name[:slash].strip(". "), name[slash + 1 :]
# No password found
return name, None
def subject_name_extractor(subject: str) -> str:
"""Try to extract a file name from a subject line, return `subject` if in doubt"""
# Filename nicely wrapped in quotes
for name in re.findall(RE_SUBJECT_FILENAME_QUOTES, subject):
if name := name.strip(' "'):
return name
# Found nothing? Try a basic filename-like search
for name in re.findall(RE_SUBJECT_BASIC_FILENAME, subject):
if name := name.strip():
return name
# Return the subject
return subject
##
## SABnzbd patched rarfile classes
## Patch for https://github.com/markokr/rarfile/issues/56#issuecomment-711146569
##
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) -> list[str]:
"""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
from typing import Tuple, List, BinaryIO, Optional, Dict, Any, Union, Set
import rarfile
from typing import BinaryIO, Optional, Any, Union
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,
@@ -63,10 +64,11 @@ from sabnzbd.filesystem import (
SEVENMULTI_RE,
is_size,
get_basename,
create_all_dirs,
)
from sabnzbd.nzbstuff import NzbObject
from sabnzbd.nzb import NzbObject
import sabnzbd.cfg as cfg
from sabnzbd.constants import Status, JOB_ADMIN
from sabnzbd.constants import Status
# Regex globals
@@ -105,18 +107,25 @@ 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:
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "win/par2/par2.exe")
if sabnzbd.WINDOWSARM64:
# ARM64 version of par2
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "win/par2/arm64/par2.exe")
else:
# Regular x64 version
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, "win/par2/par2.exe")
# UnRAR has no arm64 version, so we skip it also for 7zip
sabnzbd.newsunpack.RAR_COMMAND = check(curdir, "win/unrar/UnRAR.exe")
sabnzbd.newsunpack.SEVENZIP_COMMAND = check(curdir, "win/7zip/7za.exe")
else:
@@ -199,7 +208,7 @@ ENV_NZO_FIELDS = [
def external_processing(
extern_proc: str, nzo: NzbObject, complete_dir: str, nicename: str, status: int
) -> Tuple[str, int]:
) -> tuple[str, int]:
"""Run a user postproc script, return console output and exit value"""
failure_url = nzo.nzo_info.get("failure", "")
# Items can be bool or null, causing POpen to fail
@@ -261,12 +270,12 @@ def unpacker(
nzo: NzbObject,
workdir_complete: str,
one_folder: bool,
joinables: List[str] = [],
rars: List[str] = [],
sevens: List[str] = [],
ts: List[str] = [],
joinables: list[str] = [],
rars: list[str] = [],
sevens: list[str] = [],
ts: list[str] = [],
depth: int = 0,
) -> Tuple[Union[int, bool], List[str]]:
) -> tuple[Union[int, bool], list[str]]:
"""Do a recursive unpack from all archives in 'download_path' to 'workdir_complete'"""
if depth > 2:
# Prevent going to deep down the rabbit-hole
@@ -358,7 +367,7 @@ def unpacker(
##############################################################################
# Filejoin Functions
##############################################################################
def match_ts(file: str) -> Tuple[str, int]:
def match_ts(file: str) -> tuple[str, int]:
"""Return True if file is a joinable TS file"""
match = TS_RE.search(file)
if not match:
@@ -373,7 +382,7 @@ def match_ts(file: str) -> Tuple[str, int]:
return setname, num
def clean_up_joinables(names: List[str]):
def clean_up_joinables(names: list[str]):
"""Remove joinable files and their .1 backups"""
for name in names:
if os.path.exists(name):
@@ -402,7 +411,7 @@ def get_seq_number(name: str) -> int:
return 0
def file_join(nzo: NzbObject, workdir_complete: str, joinables: List[str]) -> Tuple[bool, List[str]]:
def file_join(nzo: NzbObject, workdir_complete: str, joinables: list[str]) -> tuple[bool, list[str]]:
"""Join and joinable files in 'workdir' to 'workdir_complete' and
when successful, delete originals
"""
@@ -493,7 +502,7 @@ def file_join(nzo: NzbObject, workdir_complete: str, joinables: List[str]) -> Tu
##############################################################################
# (Un)Rar Functions
##############################################################################
def rar_unpack(nzo: NzbObject, workdir_complete: str, one_folder: bool, rars: List[str]) -> Tuple[int, List[str]]:
def rar_unpack(nzo: NzbObject, workdir_complete: str, one_folder: bool, rars: list[str]) -> tuple[int, list[str]]:
"""Unpack multiple sets 'rars' of RAR files from 'download_path' to 'workdir_complete.
When 'delete' is set, originals will be deleted.
When 'one_folder' is set, all files will be in a single folder
@@ -615,7 +624,7 @@ def rar_unpack(nzo: NzbObject, workdir_complete: str, one_folder: bool, rars: Li
def rar_extract(
rarfile_path: str, numrars: int, one_folder: bool, nzo: NzbObject, setname: str, extraction_path: str
) -> Tuple[int, List[str], List[str]]:
) -> tuple[int, list[str], list[str]]:
"""Unpack single rar set 'rarfile' to 'extraction_path',
with password tries
Return fail==0(ok)/fail==1(error)/fail==2(wrong password)/fail==3(crc-error), new_files, rars
@@ -625,6 +634,12 @@ def rar_extract(
rars = []
passwords = get_all_passwords(nzo)
# Sanity check, does the folder exist? Could be removed by aborted Direct Unpack
if not os.path.exists(extraction_path):
# Similar to prepare_extraction_path
extraction_path = create_all_dirs(extraction_path, apply_permissions=True)
logging.info("Extraction path (re)created because it was missing: %s", extraction_path)
for password in passwords:
if password:
logging.debug('Trying unrar with password "%s"', password)
@@ -641,21 +656,20 @@ def rar_extract(
def rar_extract_core(
rarfile_path: str, numrars: int, one_folder: bool, nzo: NzbObject, setname: str, extraction_path: str, password: str
) -> Tuple[int, List[str], List[str]]:
) -> tuple[int, list[str], list[str]]:
"""Unpack single rar set 'rarfile_path' to 'extraction_path'
Return fail==0(ok)/fail==1(error)/fail==2(wrong password)/fail==3(crc-error), new_files, rars
"""
start = time.time()
logging.debug("rar_extract(): Extractionpath: %s", extraction_path)
logging.debug("Extraction path: %s", extraction_path)
logging.debug("Found rar version: %s", rarfile.get_rar_version(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 +681,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 +690,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 +705,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 +791,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
@@ -872,7 +880,7 @@ def rar_extract_core(
##############################################################################
# 7Zip Functions
##############################################################################
def unseven(nzo: NzbObject, workdir_complete: str, one_folder: bool, sevens: List[str]):
def unseven(nzo: NzbObject, workdir_complete: str, one_folder: bool, sevens: list[str]) -> tuple[bool, list[str]]:
"""Unpack multiple sets '7z' of 7Zip files from 'download_path' to 'workdir_complete.
When 'delete' is set, originals will be deleted.
"""
@@ -920,7 +928,7 @@ def unseven(nzo: NzbObject, workdir_complete: str, one_folder: bool, sevens: Lis
def seven_extract(
nzo: NzbObject, seven_path: str, seven_set: str, extraction_path: str, one_folder: bool
) -> Tuple[int, List[str]]:
) -> tuple[int, list[str]]:
"""Unpack single set 'sevenset' to 'extraction_path', with password tries
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, sevens
"""
@@ -944,7 +952,7 @@ def seven_extract(
def seven_extract_core(
nzo: NzbObject, seven_path: str, extraction_path: str, seven_set: str, one_folder: bool, password: str
) -> Tuple[int, List[str]]:
) -> tuple[int, list[str]]:
"""Unpack single 7Z set 'sevenset' to 'extraction_path'
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, message
"""
@@ -1010,7 +1018,7 @@ def seven_extract_core(
##############################################################################
# PAR2 Functions
##############################################################################
def par2_repair(nzo: NzbObject, setname: str) -> Tuple[bool, bool]:
def par2_repair(nzo: NzbObject, setname: str) -> tuple[bool, bool]:
"""Try to repair a set, return readd and correctness"""
# Check which of the files exists
for new_par in nzo.extrapars[setname]:
@@ -1123,8 +1131,8 @@ def par2_repair(nzo: NzbObject, setname: str) -> Tuple[bool, bool]:
def par2cmdline_verify(
parfile: str, nzo: NzbObject, setname: str, joinables: List[str]
) -> Tuple[bool, bool, List[str], List[str]]:
parfile: str, nzo: NzbObject, setname: str, joinables: list[str]
) -> tuple[bool, bool, list[str], list[str]]:
"""Run par2 on par-set"""
used_joinables = []
used_for_repair = []
@@ -1409,7 +1417,7 @@ def par2cmdline_verify(
return finished, readd, used_joinables, used_for_repair
def create_env(nzo: Optional[NzbObject] = None, extra_env_fields: Dict[str, Any] = {}) -> Optional[Dict[str, Any]]:
def create_env(nzo: Optional[NzbObject] = None, extra_env_fields: dict[str, Any] = {}) -> Optional[dict[str, Any]]:
"""Modify the environment for pp-scripts with extra information
macOS: Return copy of environment without PYTHONPATH and PYTHONHOME
other: return None
@@ -1466,7 +1474,7 @@ def create_env(nzo: Optional[NzbObject] = None, extra_env_fields: Dict[str, Any]
return env
def rar_volumelist(rarfile_path: str, password: str, known_volumes: List[str]) -> List[str]:
def rar_volumelist(rarfile_path: str, password: str, known_volumes: list[str]) -> list[str]:
"""List volumes that are part of this rarset
and merge them with parsed paths list, removing duplicates.
We assume RarFile is right and use parsed paths as backup.
@@ -1474,7 +1482,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:
@@ -1522,7 +1530,7 @@ def quick_check_set(setname: str, nzo: NzbObject) -> bool:
result = True
nzf_list = nzo.finished_files
renames = {}
found_paths: Set[str] = set()
found_paths: set[str] = set()
# Files to ignore
ignore_ext = cfg.quick_check_ext_ignore()
@@ -1596,7 +1604,7 @@ def quick_check_set(setname: str, nzo: NzbObject) -> bool:
return result
def unrar_check(rar: str) -> Tuple[int, bool]:
def unrar_check(rar: str) -> tuple[int, bool]:
"""Return version number of unrar, where "5.01" returns 501
Also return whether an original version is found
(version, original)
@@ -1684,7 +1692,7 @@ def is_sfv_file(myfile: str) -> bool:
return sfv_info_line_counter >= 1
def sfv_check(sfvs: List[str], nzo: NzbObject) -> bool:
def sfv_check(sfvs: list[str], nzo: NzbObject) -> bool:
"""Verify files using SFV files"""
# Update status
nzo.status = Status.VERIFYING
@@ -1768,7 +1776,7 @@ def sfv_check(sfvs: List[str], nzo: NzbObject) -> bool:
return result
def parse_sfv(sfv_filename):
def parse_sfv(sfv_filename: str) -> dict[str, bytes]:
"""Parse SFV file and return dictionary of crc32's and filenames"""
results = {}
with open(sfv_filename, mode="rb") as sfv_list:
@@ -1793,12 +1801,12 @@ def add_time_left(perc: float, start_time: Optional[float] = None, time_used: Op
return ""
def pre_queue(nzo: NzbObject, pp, cat):
def pre_queue(nzo: NzbObject, pp: str, cat: str) -> list[Any]:
"""Run pre-queue script (if any) and process results.
pp and cat are supplied separate since they can change.
"""
def fix(p):
def fix(p: Any) -> str:
# If added via API, some items can still be "None" (as a string)
if is_none(p):
return ""
@@ -1892,7 +1900,7 @@ class SevenZip:
if not is_sevenfile(self.path):
raise TypeError("File is not a 7zip file")
def namelist(self) -> List[str]:
def namelist(self) -> list[str]:
"""Return list of names in 7Zip"""
names = []
command = [SEVENZIP_COMMAND, "l", "-p", "-y", "-slt", "-sccUTF-8", self.path]
@@ -1915,6 +1923,6 @@ class SevenZip:
p.wait()
return data
def close(self):
def close(self) -> None:
"""Close file"""
pass

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