Compare commits

...

65 Commits

Author SHA1 Message Date
Safihre
521b97b7b7 Update text files for 4.2.0RC2 2023-12-14 21:48:18 +01:00
SABnzbd Automation
58c8601067 Update translatable texts
[skip ci]
2023-12-14 20:38:57 +00:00
Safihre
36609376e8 Add button to macOS notification
Closes #2749
2023-12-14 21:36:55 +01:00
Safihre
32a1c8264e Add buttons to Windows Notifications
Closes #2641
2023-12-14 21:36:13 +01:00
Safihre
06754f4ef1 Mark second test_download_sorting_single as xfail on macOS and Windows 2023-12-13 14:40:02 +01:00
Safihre
99d9b3bf94 "Disk Full" notification type was not actually used 2023-12-13 14:18:53 +01:00
Safihre
ec71d20d37 Remove alternative IPv6-address mapping
Due to provider concerns
2023-12-13 11:16:51 +01:00
Safihre
2d1e88bb39 Ignore warning about old SSL/TLS settings in test_newswrapper 2023-12-12 14:28:05 +01:00
Safihre
c9d30bb422 Update sabnews for current asyncio behaviour 2023-12-12 14:28:05 +01:00
SABnzbd Automation
cd448082e3 Update translatable texts
[skip ci]
2023-12-12 11:00:30 +00:00
Safihre
46239dddac Update code for deprection warnings 2023-12-12 11:59:43 +01:00
SABnzbd Automation
81177fda35 Update translatable texts
[skip ci]
2023-12-11 20:35:26 +00:00
Michael Nightingale
983d623d7f Fix dirscanner async tests (#2748)
* Fix dirscanner async tests

* Use root of fake filesystem to test dirscanner

* Revert minor change
2023-12-11 21:34:42 +01:00
Safihre
bdda8f4abf Correct Direct Unpack unrar output logging 2023-12-11 13:25:46 +01:00
SABnzbd Automation
94fc804394 Update translatable texts
[skip ci]
2023-12-11 01:01:21 +00:00
renovate[bot]
e00d8c09e7 Update all dependencies 2023-12-11 01:00:42 +00:00
Safihre
70a40b4bdd Add duplicate_key to script environment variables 2023-12-08 21:10:47 +01:00
Safihre
f806a62f01 Add change of Pre-queue parameters to changelog 2023-12-08 21:10:47 +01:00
SABnzbd Automation
71a9281b8f Update translatable texts
[skip ci]
2023-12-08 13:25:25 +00:00
Safihre
a34747fbd5 Update text files for 4.2.0RC1
Yes, I used AI to generate the new release notes
2023-12-08 14:24:40 +01:00
Safihre
6b0380199b Mark test_download_sorting_single as xfail on macOS and Windows 2023-12-07 21:43:43 +01:00
Safihre
39d2f90a84 Trigger duplicate analysis if pre-queue script sets a new name 2023-12-07 21:42:43 +01:00
Safihre
7bff7651f3 Only auto-enable Direct Unpack for >100MB/s drives 2023-12-07 21:34:09 +01:00
Safihre
44bd15d519 Small change to internetspeed measurement 2023-12-07 15:49:07 +01:00
SABnzbd Automation
1ca93b03a0 Update translatable texts
[skip ci]
2023-12-06 15:56:06 +00:00
Safihre
3295142d81 Use sabctools for Internet Bandwidth measurement
Closes #2737
2023-12-06 13:44:12 +01:00
Safihre
f12fdc46dc Improve stability of test_adding_nzbs_nzoids 2023-12-06 13:22:59 +01:00
Safihre
fc01254fe6 Mark test_download_sorting_single as xfail on macOS and Windows 32bit 2023-12-06 12:58:47 +01:00
Safihre
8fb3368601 Add guestimate of performance test duration to hint 2023-12-06 12:46:44 +01:00
SABnzbd Automation
58facc2512 Update translatable texts
[skip ci]
2023-12-05 09:56:24 +00:00
Safihre
b43c2b308b Use correct keys for Season and Episode in Smart Duplicate detection 2023-12-05 10:55:34 +01:00
renovate[bot]
1e89a0af56 Update all dependencies 2023-12-04 02:19:58 +00:00
Safihre
acd3cbbf49 Correct test_validate_safedir 2023-12-03 20:02:41 +01:00
SABnzbd Automation
a806521745 Update translatable texts
[skip ci]
2023-12-02 20:20:33 +00:00
Safihre
0dddaf26e0 Stricter validation on Windows to prevent network drives as Incomplete 2023-12-02 21:19:48 +01:00
Safihre
cdf63a005b Python 3.8 doesn't have functools.cache so use lru_cache 2023-12-02 21:09:44 +01:00
SABnzbd Automation
ca422a0af3 Update translatable texts
[skip ci]
2023-12-02 19:40:31 +00:00
Safihre
a682371a91 Cache result of HappyEyeBalls 10 seconds 2023-12-02 20:39:38 +01:00
Safihre
26ef146526 Use decorator to maintain diskspace cache and HappyEyeBalls cache 2023-12-02 20:36:14 +01:00
Safihre
936ee58abb Reduce waiting time if there are no sockets to read 2023-12-01 15:11:05 +01:00
Safihre
71d8c208bc Micro-optimization of NzbQueue.is_empty 2023-12-01 14:50:07 +01:00
Safihre
2200ffa88e Use Server-specific timeout in final attempt 2023-12-01 14:34:07 +01:00
Safihre
4453316516 Server warnings were not always shown 2023-11-30 22:15:32 +01:00
Safihre
b947207571 Use Server-specific timeout during HappyEyeBalls 2023-11-30 19:50:31 +01:00
SABnzbd Automation
25d29deae6 Update translatable texts
[skip ci]
2023-11-30 12:26:53 +00:00
Safihre
9abe6d6d71 Skip empty HTTP-headers in URLGrabber and skip invalid categories 2023-11-30 13:25:43 +01:00
Safihre
77dbc0a37f Check nzb backup folder only if the job is not still in the queue 2023-11-30 13:19:57 +01:00
Safihre
659117512b Give RSS feed it's own history-stage 2023-11-30 13:04:12 +01:00
SABnzbd Automation
b1dbbc6a69 Update translatable texts
[skip ci]
2023-11-29 20:47:52 +00:00
Safihre
424a1c626e Add name of RSS feed to history Source
Closes #2206
2023-11-29 21:46:19 +01:00
Safihre
522666191b Indexer category was not used anymore 2023-11-29 21:46:19 +01:00
Safihre
78055ef794 Do not show propagation label in case job is Forced 2023-11-29 21:46:19 +01:00
SABnzbd Automation
0fe534c202 Update translatable texts
[skip ci]
2023-11-29 13:58:08 +00:00
Safihre
257179de31 Add ability to search Queue/History for status
Closes #2376
2023-11-29 14:57:15 +01:00
Safihre
65b57112b9 Optimize handling of propagation_delay 2023-11-29 14:57:15 +01:00
renovate[bot]
27f0b1d1f2 Update dependency cryptography to v41.0.6 [SECURITY] 2023-11-29 01:54:31 +00:00
SABnzbd Automation
6e31476c45 Update translatable texts
[skip ci]
2023-11-28 21:07:35 +00:00
Safihre
bc7f0f3fb3 Update text files for 4.2.0Beta1 2023-11-28 22:06:43 +01:00
SABnzbd Automation
13eeb5164f Update translatable texts
[skip ci]
2023-11-28 14:31:50 +00:00
Safihre
fc756ed23d Add smarter duplicate detection (#2736)
Restore pre-queue
2023-11-28 15:30:46 +01:00
SABnzbd Automation
c150365462 Update translatable texts
[skip ci]
2023-11-27 12:18:43 +00:00
renovate[bot]
58d209059e Update dependency setuptools to v69 2023-11-27 13:12:00 +01:00
Safihre
506179b517 Remove unused sort_type from guess_what 2023-11-24 21:17:29 +01:00
SABnzbd Automation
f0f4eb75df Update translatable texts
[skip ci]
2023-11-22 15:55:34 +00:00
Safihre
6c1c025668 Update text files 4.2.0Alpha3 2023-11-22 16:54:50 +01:00
102 changed files with 1698 additions and 1396 deletions

View File

@@ -81,7 +81,7 @@ jobs:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: "3.12.0"
PYTHON_VERSION: "3.12.1"
MACOSX_DEPLOYMENT_TARGET: "10.9"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64

View File

@@ -1,33 +1,60 @@
Release Notes - SABnzbd 4.2.0 Alpha 2
Release Notes - SABnzbd 4.2.0 Release Candidate 2
=========================================================
## Changes since 4.1.0
- Numerous smaller performance improvements were made.
- Reduced recursive unpacking to 2 levels, instead of 5.
- IPv6 addresses are preferred during server address selection.
- Stricter check if `Complete Folder` is inside `Download Folder`.
- Windows: Reduced size of installer.
- Windows/macOS: Updated to Python 3.12.
This is the second pre-release build of SABnzbd 4.2.0, which includes several new features and bug fixes.
## Bugfixes since 4.1.0
- Multi-select in the queue was broken for some users.
- Prevent crash during saving of configuration.
- Removing a failed download from the history could break active downloads.
## Key changes since 4.1.0
* **Duplicate detection workflow was overhauled:**
* `Series Duplicate Detection` was replaced by `Smart Duplicate Detection`
that can also detect `Movie` and `Daily Show` duplicates.
* Additionally, duplicates will also be detected if they are still in the queue.
* More information: https://sabnzbd.org/wiki/duplicate-detection
* **Interface changes:**
* Added ability to filter the Queue and History by `status`.
* RSS-feed that provided the download is shown in History details.
* macOS/Windows 10 & 11: Added `Open Folder` button to `Job/Queue finished` notifications.
Clicking any type of notification will now open a browser with SABnzbd.
* **Performance and usability improvements:**
* Numerous smaller performance improvements were made.
* Server IP-address selection was optimized.
* The `Internet Bandwidth` test was made more reliable.
* macOS/Windows: Updated to Python 3.12.
* **Configuration changes:**
* The `On queue finish script` is now set in Switches.
* Reduced recursive unpacking to 2 levels, instead of 5.
* Duplicate detection related Pre-queue script input parameters were removed.
You will need to update your Pre-queue script.
More information: https://sabnzbd.org/wiki/configuration/4.2/scripts/pre-queue-scripts
* Stricter check if `Complete Folder` is inside `Download Folder`.
* Windows: Prevent use of network drive as `Download Folder`.
## Bug fixes since 4.1.0
* Fixed an issue where the multi-select option in the queue was not working for some users.
* Prevented a crash that would occur during the saving of configuration settings.
* Ensured that server warnings are always displayed to users.
* If `weblogging` was enabled, output was also written to regular log.
* Fixed an issue where removing a failed download from the History could break active downloads.
## Upgrade notices
- Direct upgrade is possible from version 3.0.0 and newer.
Upgrading from older versions will require `Queue repair`.
- Downgrading from version 4.2.0 or newer to 3.7.2 or older will
require `Queue repair` due to changes in the internal data format.
* You can directly upgrade from version 3.0.0 and newer.
* Upgrading from older versions will require performing a `Queue repair`.
* Downgrading from version 4.2.0 or newer to 3.7.2 or older will require
performing a `Queue repair` due to changes in the internal data format.
## Known problems and solutions
- Read the file "ISSUES.txt"
* Read `ISSUES.txt` or https://sabnzbd.org/wiki/introduction/known-issues
## 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, extract and clean up posts downloaded
from Usenet.
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,
extract and clean up posts downloaded from Usenet.
(c) Copyright 2007-2023 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2023 by The SABnzbd-Team (sabnzbd.org)

View File

@@ -1762,10 +1762,10 @@ if __name__ == "__main__":
# Initialize the menu
shared_app = NSApplication.sharedApplication()
sabnzbd_menu = SABnzbdDelegate.alloc().init()
shared_app.setDelegate_(sabnzbd_menu)
sabnzbd.MACOSTRAY = SABnzbdDelegate.alloc().init()
shared_app.setDelegate_(sabnzbd.MACOSTRAY)
# Build the menu
sabnzbd_menu.awakeFromNib()
sabnzbd.MACOSTRAY.awakeFromNib()
# Run the main eventloop
AppHelper.runEventLoop()
else:

View File

@@ -1,3 +1,3 @@
# Special requirements for macOS universal2 binary release
# This way dependabot can auto-update them
cryptography==41.0.5
cryptography==41.0.7

View File

@@ -1,15 +1,15 @@
# Basic build requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.2.0
pyinstaller==6.3.0
packaging==23.2
pyinstaller-hooks-contrib==2023.10
altgraph==0.17.4
wrapt==1.16.0
setuptools==68.2.2
setuptools==69.0.2
certifi
# Required on 32bit Windows, exclude it based on Python-version
importlib_metadata==6.8.0; python_version < '3.10'
importlib_metadata==7.0.0; python_version < '3.10'
importlib_resources==6.1.1; python_version < '3.10'
zipp==3.17.0; python_version < '3.10'

View File

@@ -200,9 +200,14 @@ Section "SABnzbd" SecDummy
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "URLUpdateInfo" 'https://sabnzbd.org/'
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "Comments" 'The automated Usenet download tool'
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "DisplayIcon" '$INSTDIR\icons\sabnzbd.ico'
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 25674
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 40674
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoRepair" -1
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoModify" -1
WriteRegStr HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd" "DisplayName" "SABnzbd"
WriteRegStr HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd" "IconUri" '$INSTDIR\icons\sabnzbd16_32.ico'
; write out uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
@@ -348,6 +353,8 @@ Section "un.$(MsgDelProgram)" Uninstall
Delete "$INSTDIR\uninstall.exe"
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\SABnzbd"
DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd"
DeleteRegKey HKEY_CURRENT_USER "Software\Classes\AppUserModelId\SABnzbd"
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
${RemovePrev} "$INSTDIR"
@@ -383,8 +390,6 @@ Section "un.$(MsgDelProgram)" Uninstall
Delete "$DESKTOP\SABnzbd.lnk"
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
${unregisterExtension} ".nzb" "NZB File"
${RefreshShellIcons}

View File

@@ -202,7 +202,7 @@
<div class="field-pair">
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
<span class="desc">$T('Optional') - $T('readwiki')</span>
</div>
$show_notify_checkboxes('nscript')
<div class="field-pair no-field-pair-bg">

View File

@@ -96,23 +96,29 @@
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">$T('explain-no_dupes')</span>
<span class="desc">
$T('explain-no_dupes')<br>
<a href="https://sabnzbd.org/wiki/duplicate-detection" target="_blank">https://sabnzbd.org/wiki/duplicate-detection</a>
</span>
</div>
<div class="field-pair">
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
<select name="no_series_dupes" id="no_series_dupes">
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_series_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_series_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
<label class="config" for="no_smart_dupes">$T('opt-no_smart_dupes')</label>
<select name="no_smart_dupes" id="no_smart_dupes">
<option value="0" <!--#if int($no_smart_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_smart_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_smart_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_smart_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_smart_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">$T('explain-no_series_dupes')</span>
<span class="desc">
$T('explain-no_smart_dupes')<br>
<a href="https://sabnzbd.org/wiki/duplicate-detection" target="_blank">https://sabnzbd.org/wiki/duplicate-detection</a>
</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="series_propercheck">$T('opt-series_propercheck')</label>
<input type="checkbox" name="series_propercheck" id="series_propercheck" value="1" <!--#if int($series_propercheck) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-series_propercheck')</span>
<label class="config" for="dupes_propercheck">$T('opt-dupes_propercheck')</label>
<input type="checkbox" name="dupes_propercheck" id="dupes_propercheck" value="1" <!--#if int($dupes_propercheck) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-dupes_propercheck')</span>
</div>
<div class="field-pair">
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
@@ -187,12 +193,12 @@
<div class="field-pair advanced-settings <!--#if not $have_nice then "disabled" else "" #-->">
<label class="config" for="nice">$T('opt-nice')</label>
<input type="text" name="nice" id="nice" value="$nice" <!--#if not $have_nice then 'readonly="readonly" disabled="disabled"' else "" #--> />
<span class="desc">$T('explain-nice')</span>
<span class="desc">$T('readwiki')</span>
</div>
<div class="field-pair advanced-settings <!--#if not $have_ionice then "disabled" else "" #-->">
<label class="config" for="ionice">$T('opt-ionice')</label>
<input type="text" name="ionice" id="ionice" value="$ionice" <!--#if not $have_ionice then 'readonly="readonly" disabled="disabled"' else "" #--> />
<span class="desc">$T('explain-ionice')</span>
<span class="desc">$T('readwiki')</span>
</div>
<!--#else#-->
<div class="field-pair advanced-settings">
@@ -204,13 +210,13 @@
<option value="2" <!--#if int($win_process_prio) == 2 then 'selected="selected"' else ""#-->>$T('win_process_prio-low')</option>
<option value="1" <!--#if int($win_process_prio) == 1 then 'selected="selected"' else ""#-->>$T('win_process_prio-idle')</option>
</select>
<span class="desc">$T('explain-win_process_prio')</span>
<span class="desc">$T('readwiki')</span>
</div>
<!--#end if#-->
<div class="field-pair advanced-settings">
<label class="config" for="par_option">$T('opt-par_option')</label>
<input type="text" name="par_option" id="par_option" value="$par_option" />
<span class="desc">$T('explain-par_option')</span>
<span class="desc">$T('readwiki')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="sfv_check">$T('opt-sfv_check')</label>

View File

@@ -134,7 +134,7 @@
<div class="col-sm-6">$T('dashboard-systemPerformance') &nbsp; </div>
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.pystone"></span>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<small title="$cpumodel $cpusimd" data-tooltip="true">$cpumodel $cpusimd</small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
@@ -143,7 +143,7 @@
<div class="col-sm-6">$T('dashboard-downloadDirSpeed') &nbsp; </div>
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.downloaddirspeed()"></span> MB/s
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<small data-bind="text: statusInfo.downloaddir, attr: { 'data-original-title': statusInfo.downloaddir }" data-tooltip="true"></small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
@@ -152,7 +152,7 @@
<div class="col-sm-6">$T('dashboard-completeDirSpeed') &nbsp; </div>
<div class="col-sm-6 col-dot-overflow" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.completedirspeed()"></span> MB/s
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<small data-bind="text: statusInfo.completedir, attr: { 'data-original-title': statusInfo.completedir }" data-tooltip="true"></small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
@@ -161,7 +161,7 @@
<div class="col-sm-6">$T('dashboard-internetBandwidth') &nbsp; </div>
<div class="col-sm-6" data-bind="visible: hasPerformanceInfo">
<span data-bind="text: statusInfo.internetbandwidth()"></span> MB/s
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest')"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="diskspeed-button" data-bind="click: loadStatusInfo" data-tooltip="true" data-placement="right" title="$T('dashboard-repeatTest') (~10 $T('seconds'))"><span class="glyphicon glyphicon-repeat"></span></a>
<small><span data-bind="text: statusInfo.internetbandwidth()*8"></span> Mbps</small>
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>

View File

@@ -97,6 +97,7 @@
glitterTranslate.status['Unpack'] = "$T('stage-unpack')";
glitterTranslate.status['Deobfuscate'] = "$T('stage-deobfuscate')";
glitterTranslate.status['Script'] = "$T('stage-script')";
glitterTranslate.status['RSS'] = "$T('stage-rss')";
glitterTranslate.status['Source'] = "$T('stage-source')";
glitterTranslate.status['Servers'] = "$T('stage-servers')";
glitterTranslate.status['INFO'] = "$T('log-info')".replace('+', '').toUpperCase();

View File

@@ -338,7 +338,7 @@ function ViewModel() {
limit: parseInt(self.queue.paginationLimit())
}
if (self.queue.searchTerm()) {
parseSearchQuery(api_call, self.queue.searchTerm(), ["cat", "category", "priority"])
parseSearchQuery(api_call, self.queue.searchTerm(), ["cat", "category", "priority", "status"])
}
var queueApi = callAPI(api_call)
.done(self.updateQueue)
@@ -367,7 +367,7 @@ function ViewModel() {
last_history_update: self.history.lastUpdate
}
if (self.history.searchTerm()) {
parseSearchQuery(history_call, self.history.searchTerm(), ["cat", "category"])
parseSearchQuery(history_call, self.history.searchTerm(), ["cat", "category", "status"])
}
// History
@@ -397,7 +397,6 @@ function ViewModel() {
if (keyword === "priority" && api_request["priority"]) {
for (const prio_name in self.queue.priorityName) {
api_request["priority"] = api_request["priority"].replace(prio_name, self.queue.priorityName[prio_name])
}
}
}

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"

View File

@@ -3,7 +3,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: ION, 2020\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -162,11 +162,6 @@ msgstr ""
msgid "Default"
msgstr ""
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr ""
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -259,7 +254,7 @@ msgid "Permissions setting of %s might deny SABnzbd access to the files and fold
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
@@ -1002,6 +997,16 @@ msgstr ""
msgid "Other Messages"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr ""
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr ""
@@ -1369,6 +1374,11 @@ msgstr ""
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr ""
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr ""
#. Warning message
#: sabnzbd/postproc.py
msgid "Completed Download Folder %s is on FAT file system, limiting maximum file size to 4GB"
@@ -1551,10 +1561,6 @@ msgstr ""
msgid "Show interface"
msgstr ""
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr ""
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1642,6 +1648,11 @@ msgstr ""
msgid "Script"
msgstr ""
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr ""
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2014,11 +2025,6 @@ msgstr ""
msgid "Scheduling"
msgstr ""
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr ""
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2427,7 +2433,6 @@ msgstr ""
msgid "Backup"
msgstr ""
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr ""
@@ -2904,19 +2909,19 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect identical NZB files (based on items in your History or files in .nzb Backup Folder)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect identical episodes in series (based on \"name/season/episode\" of items in your History)"
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -2924,7 +2929,7 @@ msgid "Allow proper releases"
msgstr ""
#: sabnzbd/skintext.py
msgid "Bypass series duplicate detection if PROPER, REAL or REPACK is detected in the download name"
msgid "Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
@@ -179,11 +179,6 @@ msgstr "Žádný"
msgid "Default"
msgstr "Výchozí"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Nepodařilo se zkompilovat regex pro hledaný výraz: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -286,8 +281,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC cesta \"%s\" zde není povolena"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1069,6 +1064,16 @@ msgstr "Fronta dokončena"
msgid "Other Messages"
msgstr "Ostatní zprávy"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Otevřít složku s kompletními soubory"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Nedostupné"
@@ -1441,6 +1446,11 @@ msgstr ""
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Stará fronta nalezena, použijte Status->Repair pro konverzi fronty"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Nepodařilo se zkompilovat regex pro hledaný výraz: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1625,10 +1635,6 @@ msgstr "Prázdný RSS záznam nalezen (%s)"
msgid "Show interface"
msgstr "Zobrazit rozhraní"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Otevřít složku s kompletními soubory"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1717,6 +1723,11 @@ msgstr ""
msgid "Script"
msgstr "Skript"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2089,11 +2100,6 @@ msgstr "Přepínače"
msgid "Scheduling"
msgstr "Plánování"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2522,7 +2528,6 @@ msgstr ""
msgid "Backup"
msgstr "Záloha"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr ""
@@ -3047,23 +3052,19 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Detekovat duplicitní stahování"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3072,8 +3073,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
@@ -179,11 +179,6 @@ msgstr "Ingen"
msgid "Default"
msgstr "Standard"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Det lykkedes ikke at kompilere regex for søgestreng: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -286,8 +281,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC søgning \"%s\" er ikke tilladt her"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1073,6 +1068,16 @@ msgstr "Kø færdig"
msgid "Other Messages"
msgstr "Andre beskeder"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Åben færdig mappe"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Ikke tilgængelig"
@@ -1473,6 +1478,11 @@ msgstr "Fejl %s: Du skal angive et gyldigt brugernavn og adgangskode."
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Gamle kø opdaget, brug Status->Reparation for at konvertere kø"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Det lykkedes ikke at kompilere regex for søgestreng: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1657,10 +1667,6 @@ msgstr "Tom RSS post blev fundet (%s)"
msgid "Show interface"
msgstr "Vis grænseflade"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Åben færdig mappe"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1749,6 +1755,11 @@ msgstr ""
msgid "Script"
msgstr "Script"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2121,11 +2132,6 @@ msgstr "Parameter"
msgid "Scheduling"
msgstr "Planlægning"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2563,7 +2569,6 @@ msgstr "Oppetid"
msgid "Backup"
msgstr "Sikkerhedskopi"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Læs mere om dette på Wiki Help!"
@@ -3115,28 +3120,20 @@ msgstr ""
"I tilfælde af \"Pause\", skal du angive en adgangskode og genoptage jobbet."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Find identiske downloads"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
"Fundet identiske NZB filer (baseret på elementer i din historik eller filer "
"i. nzb Backup mappe)"
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Opdage identiske episoder i serier"
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
"Fundet identiske episoder i serie (baseret på \"navn /sæson /episode\" af "
"elementer i din historik)"
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3144,8 +3141,8 @@ msgstr "Tillad reelle udgivelser"
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -15,7 +15,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
@@ -195,12 +195,6 @@ msgstr "Nichts"
msgid "Default"
msgstr "Standard"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr ""
"Kompilieren des regulären Ausdrucks für den Suchbegriff %s fehlgeschlagen."
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -307,8 +301,8 @@ msgstr ""
"erstellten Dateien und Ordner von SABnzbd verweigern."
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-Pfad \"%s\" ist hier nicht erlaubt"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1123,6 +1117,16 @@ msgstr "Warteschlange abgearbeitet"
msgid "Other Messages"
msgstr "Andere Nachrichten"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Öffne Zielverzeichnis"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Nicht verfügbar"
@@ -1535,6 +1539,12 @@ msgstr ""
"Alte Warteschlangen-Version erkannt, über Status->Reparieren ins neue Format"
" konvertieren"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr ""
"Kompilieren des regulären Ausdrucks für den Suchbegriff %s fehlgeschlagen."
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1722,10 +1732,6 @@ msgstr "Leerer RSS-Feed gefunden: %s"
msgid "Show interface"
msgstr "Interface anzeigen"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Öffne Zielverzeichnis"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1814,6 +1820,11 @@ msgstr "Entschleiern"
msgid "Script"
msgstr "Skript"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2186,11 +2197,6 @@ msgstr "Schalter"
msgid "Scheduling"
msgstr "Planung"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2641,7 +2647,6 @@ msgstr "Zeit seit Start"
msgid "Backup"
msgstr "Sicherheitskopie"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Lesen Sie dazu die Hilfe im Wiki!"
@@ -3234,28 +3239,20 @@ msgstr ""
"fortsetzen."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Doppelte Downloads erkennen"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
"Doppelte NZB Datei entdeckt (basierend auf den Eintragungen in der Historie "
"oder den .nzb Dateien im NZB-Backup-Ordner)"
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Doppelte Episoden in Serien erkennen"
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
"Identische Episoden in den Serien entdeckt (basierend auf "
"\"name/season/episode\") der Einträge in der Historie"
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3263,11 +3260,9 @@ msgstr "Erlaube \"Proper\" Releases"
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
"Umgehe Serien Duplikat-Erkennung, wenn PROPER, REAL oder REPACK im Download-"
"Namen erkannt wird"
#. Four way switch for duplicates
#: sabnzbd/skintext.py

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
@@ -188,11 +188,6 @@ msgstr "Ninguno"
msgid "Default"
msgstr "Predeterminado"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Compilación de regex para término fallo: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -301,8 +296,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Ruta de acceso UNC \"%s\" no permitido aqui"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1113,6 +1108,16 @@ msgstr "Cola terminada"
msgid "Other Messages"
msgstr "Otros mensajes"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Abrir todo el folder"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "No disponible"
@@ -1525,6 +1530,11 @@ msgstr ""
"Se ha encontrado una cola antigua, utilice Estado->Reparar para convertir la"
" cola"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Compilación de regex para término fallo: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1713,10 +1723,6 @@ msgstr "Entrada RSS vacía (%s)"
msgid "Show interface"
msgstr "Mostrar interfaz"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Abrir todo el folder"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1805,6 +1811,11 @@ msgstr ""
msgid "Script"
msgstr "Script"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2177,11 +2188,6 @@ msgstr "Switches"
msgid "Scheduling"
msgstr "Planificación"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2629,7 +2635,6 @@ msgstr "Tiempo en Activo"
msgid "Backup"
msgstr "Copia de seguridad"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Lee la ayuda en la Wiki (inglés) acerca de esto!"
@@ -3197,28 +3202,20 @@ msgstr ""
"trabajo."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Detectar descargas duplicadas"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
"Detecta archivos NZB idénticos (basándose en los elementos de su historial o"
" los archivos en el directorio de copia de seguridad .nzb)"
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Detectar episodios duplicadas en serie"
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
"Detecta episodios idénticos en series (basándose en "
"\"nombre/temporada/episodio\" de los elementos de su historial)"
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3226,11 +3223,9 @@ msgstr "Permitir comunicados adecuados"
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
"Evitar detección de duplicado de series si se detecta PROPER, REAL o REPACK "
"en el nombre de la descarga"
#. Four way switch for duplicates
#: sabnzbd/skintext.py

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
@@ -181,11 +181,6 @@ msgstr "Ei mitään"
msgid "Default"
msgstr "Oletus"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Regex käännös epäonnistui hakutermille: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -284,8 +279,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "TUNT polku \"%s\" ei ole sallittu"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1067,6 +1062,16 @@ msgstr "Jono valmistunut"
msgid "Other Messages"
msgstr "Muut viestit"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Avaa valmistuneet-kansio"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Ei saatavilla"
@@ -1467,6 +1472,11 @@ msgstr "Virhe %s: Syötä kelvollinen käyttäjänimi ja salasana."
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Vanhan version jono havaittiin, käytä Tila->Korjaa muuntaaksesi jonon"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Regex käännös epäonnistui hakutermille: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1651,10 +1661,6 @@ msgstr "Tyhjä RSS kohde löytyi (%s)"
msgid "Show interface"
msgstr "Näytä käyttöliittymä"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Avaa valmistuneet-kansio"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1743,6 +1749,11 @@ msgstr ""
msgid "Script"
msgstr "Skripti"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2115,11 +2126,6 @@ msgstr "Muuttujat"
msgid "Scheduling"
msgstr "Ajastus"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2559,7 +2565,6 @@ msgstr "Käynnissäoloaika"
msgid "Backup"
msgstr "Varmuuskopioi"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Lue Wikin ohjeet tähän!"
@@ -3122,23 +3127,19 @@ msgstr ""
" lataamista."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Tunnista päällekkäiset lataukset"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Tunnista identtiset jaksot sarjassa"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3147,8 +3148,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2023\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
@@ -190,11 +190,6 @@ msgstr "Aucun"
msgid "Default"
msgstr "Par défaut"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Echec de la compilation de regex pour la recherche du terme : %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -305,8 +300,8 @@ msgstr ""
"fichiers et dossiers qu'il crée."
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Le chemin UNC \"%s\" n'est pas autorisé ici"
msgid "Network path \"%s\" is not allowed here"
msgstr "Le chemin réseau \"%s\" n'est pas autorisé ici"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1123,6 +1118,16 @@ msgstr "File d'attente terminée"
msgid "Other Messages"
msgstr "Autres messages"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Ouvrir le dossier des complets"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Non disponible"
@@ -1249,7 +1254,7 @@ msgstr "DOUBLON"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
msgstr "ALTERNATIVE"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
@@ -1531,6 +1536,11 @@ msgstr ""
"Ancienne file d'attente détectée, utiliser Statut-> Réparation pour "
"convertir la file d'attente"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Echec de la compilation de regex pour la recherche du terme : %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1720,10 +1730,6 @@ msgstr "Entrée vide de flux RSS trouvée (%s)"
msgid "Show interface"
msgstr "Afficher linterface"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Ouvrir le dossier des complets"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1812,6 +1818,11 @@ msgstr "Désobfuscation"
msgid "Script"
msgstr "Script"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2184,11 +2195,6 @@ msgstr "Switches"
msgid "Scheduling"
msgstr "Planification"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2641,7 +2647,6 @@ msgstr "Temps de fonctionnement"
msgid "Backup"
msgstr "Secours"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Consultez le Wiki pour plus d'info à ce sujet (en anglais) !"
@@ -3235,28 +3240,21 @@ msgstr ""
"tâche."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Détecter les doublons de téléchargement"
msgid "Identical download detection"
msgstr "Détection des téléchargements identiques"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
"Détecter les fichiers NZB identiques (en fonction des éléments de votre "
"historique ou des fichiers .nzb du dossier de backup)"
"Détecter les téléchargements identiques basés sur le nom ou le contenu NZB."
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Détecter les doublons d'épisodes de séries"
msgid "Smart duplicate detection"
msgstr "Détection intelligente des doublons"
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgstr ""
"Détecter les épisodes de série identiques (en fonction du modèle "
"\"nom/saison/épisode\" des éléments de votre historique)"
msgid "Detect duplicates based on analysis of the filename."
msgstr "Détecter les doublons basés sur l'analyse du nom de fichier."
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3264,11 +3262,11 @@ msgstr "Autoriser les versions corrigées (proper)"
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
"Contourner la détection des doublons si PROPER, REAL ou REPACK est détecté "
"dans l'intitulé du téléchargement"
"dans l'intitulé du téléchargement."
#. Four way switch for duplicates
#: sabnzbd/skintext.py

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# ION, 2023
# Safihre <safihre@sabnzbd.org>, 2023
# ION, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Last-Translator: ION, 2023\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"
@@ -30,7 +30,7 @@ msgstr "שגיאה"
#. Error message
#: SABnzbd.py
msgid "Failed to start web-interface"
msgstr "נכשל בהתחלת ממשק רשת"
msgstr "כישלון בהתחלת ממשק רשת"
#. Warning message
#: SABnzbd.py
@@ -116,7 +116,7 @@ msgstr "HTTPS הושבת בגלל קבצים בלתי תקפים של CERT ו־K
#. Error message
#: SABnzbd.py
msgid "Failed to start web-interface: "
msgstr "נכשל בהתחלת ממשק רשת: "
msgstr "כישלון בהתחלת ממשק רשת: "
#: SABnzbd.py
msgid "SABnzbd %s started"
@@ -178,11 +178,6 @@ msgstr "אין"
msgid "Default"
msgstr "ברירת מחדל"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "עבור מונח חיפוש regex נכשל בליקוט: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -283,8 +278,8 @@ msgstr ""
"הגדרת הרשאות של %s עשויה לדחות גישה מן SABnzbd אל הקבצים והתיקיות שהוא יוצר."
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "נתיב UNC \"%s\" אינו מותר כאן"
msgid "Network path \"%s\" is not allowed here"
msgstr "נתיב הרשת \"%s\" אינו מותר כאן"
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -304,6 +299,8 @@ msgid ""
"Do not use a folder in the application folder as your Scripts Folder, it "
"might be emptied during updates."
msgstr ""
"אל תשתמש בתיקייה בתוך תיקיית היישום כתיקיית התסריטים שלך, היא עשויה להתרוקן "
"במהלך עדכונים."
#. Warning message
#: sabnzbd/config.py
@@ -348,7 +345,7 @@ msgstr "פקודת SQL נכשלה, ראה יומן"
#. Error message
#: sabnzbd/database.py
msgid "Failed to close database, see log"
msgstr "נכשל בסגירת מסד נתונים, ראה יומן"
msgstr "כישלון בסגירת מסד נתונים, ראה יומן"
#. Warning message
#: sabnzbd/database.py
@@ -444,12 +441,12 @@ msgstr "אין שרתים פעילים!"
#. Error message
#: sabnzbd/downloader.py
msgid "Failed to initialize %s@%s with reason: %s"
msgstr "נכשל באתחול %s@%s עם סיבה: %s"
msgstr "כישלון באתחול %s@%s עם סיבה: %s"
#. Error message
#: sabnzbd/downloader.py
msgid "Fatal error in Downloader"
msgstr ""
msgstr "שגיאה גורלית במורידן"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
@@ -460,10 +457,12 @@ msgid ""
"Login from too many different IP addresses to server %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
msgstr ""
"כניסה מיותר מדי כתובות IP שונות אל שרת %s [%s] - "
"https://sabnzbd.org/multiple-adresses"
#: sabnzbd/downloader.py
msgid "Failed login for server %s [%s]"
msgstr "נכשל בכניסה אל השרת %s [%s]"
msgstr "כניסה נכשלה עבור שרת %s [%s]"
#. Error message
#: sabnzbd/downloader.py
@@ -491,11 +490,11 @@ msgstr "השרת %s השתמש במכסה המצויינת"
#: sabnzbd/emailer.py
msgid "Failed to connect to mail server"
msgstr "נכשל בהתחברות אל שרת דוא״ל"
msgstr "כישלון בהתחברות אל שרת דוא״ל"
#: sabnzbd/emailer.py
msgid "Failed to initiate TLS connection"
msgstr "נכשל ביזימת חיבור TLS"
msgstr "כישלון ביזימת חיבור TLS"
#: sabnzbd/emailer.py
msgid "The server didn't reply properly to the helo greeting"
@@ -503,7 +502,7 @@ msgstr "השרת לא הגיב כראוי לברכת השלום"
#: sabnzbd/emailer.py
msgid "Failed to authenticate to mail server"
msgstr "נכשל באימות שרת הדוא״ל"
msgstr "כישלון באימות שרת הדוא״ל"
#: sabnzbd/emailer.py
msgid "No suitable authentication method was found"
@@ -515,11 +514,11 @@ msgstr "כישלון בלתי ידוע של אימות בשרת דוא״ל"
#: sabnzbd/emailer.py
msgid "Failed to send e-mail"
msgstr "נכשל בשליחת דוא״ל"
msgstr "כישלון בשליחת דוא״ל"
#: sabnzbd/emailer.py
msgid "Failed to close mail connection"
msgstr "נכשל בסגירת חיבור דוא״ל"
msgstr "כישלון בסגירת חיבור דוא״ל"
#: sabnzbd/emailer.py, sabnzbd/notifier.py
msgid "Cannot send, missing required data"
@@ -583,12 +582,12 @@ msgstr "לא ניתן לשנות הרשאות של %s"
#. Error message
#: sabnzbd/filesystem.py
msgid "Failed making (%s)"
msgstr "נכשל בעשייה (%s)"
msgstr "כישלון בעשייה (%s)"
#. Error message
#: sabnzbd/filesystem.py, sabnzbd/postproc.py
msgid "Failed moving %s to %s"
msgstr "נכשל בהעברת %s אל %s"
msgstr "כישלון בהעברת %s אל %s"
#. Error message
#: sabnzbd/filesystem.py
@@ -618,12 +617,12 @@ msgstr "%s אינו בר־כתיבה בכלל. זה חוסם הורדות."
#. Warning message
#: sabnzbd/filesystem.py
msgid "Cannot write a long filename to %s. This can cause problems."
msgstr ""
msgstr "בלתי ניתן לכתוב שם ארוך של קובץ אל %s. זה עשוי לגרום לבעיות."
#. Warning message
#: sabnzbd/filesystem.py
msgid "Cannot write a unicode filename to %s. This can cause problems."
msgstr ""
msgstr "בלתי ניתן לכתוב שם יוניקוד של קובץ אל %s. זה עשוי לגרום לבעיות."
#. Warning message
#: sabnzbd/filesystem.py
@@ -757,12 +756,12 @@ msgstr "עדכון זמין!"
#. Error message
#: sabnzbd/misc.py
msgid "Failed to upload file: %s"
msgstr "נכשל בהעלאת קובץ: %s"
msgstr "כישלון בהעלאת קובץ: %s"
#. Error message
#: sabnzbd/misc.py
msgid "Error creating SSL key and certificate"
msgstr "נכשל ביצירה של מפתח ותעודה של SSL"
msgstr "כישלון ביצירה של מפתח ותעודה של SSL"
#. Warning message
#: sabnzbd/misc.py
@@ -776,7 +775,7 @@ msgstr ""
#. Warning message
#: sabnzbd/misc.py
msgid "Failed to read the password file %s"
msgstr "נכשל בקריאת קובץ הסיסמה %s"
msgstr "כישלון בקריאת קובץ הסיסמה %s"
#. Error message
#: sabnzbd/misc.py
@@ -1070,18 +1069,28 @@ msgstr "תור הסתיים"
msgid "Other Messages"
msgstr "הודעות אחרות"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "פתח תיקיית השלמה"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "לא זמין"
#: sabnzbd/notifier.py
msgid "Failed to send macOS notification"
msgstr "נכשל בשליחת התראת macOS"
msgstr "כישלון בשליחת התראת macOS"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send Prowl message"
msgstr "נכשל בשליחת הודעת Prowl"
msgstr "כישלון בשליחת הודעת Prowl"
#. Error message
#: sabnzbd/notifier.py
@@ -1091,7 +1100,7 @@ msgstr "תגובה רעה מאת Pushover (%s): %s"
#. Error message - Warning message
#: sabnzbd/notifier.py
msgid "Failed to send pushover message"
msgstr "נכשל בשליחת הודעת Pushover"
msgstr "כישלון בשליחת הודעת Pushover"
#. Error message
#: sabnzbd/notifier.py
@@ -1101,7 +1110,7 @@ msgstr "תגובה רעה מאת Pushbullet (%s): %s"
#. Warning message
#: sabnzbd/notifier.py
msgid "Failed to send pushbullet message"
msgstr "Pushbullet נכשל בשליחת הודעת"
msgstr "כישלון בשליחת הודעת Pushbullet"
#. Error message
#: sabnzbd/notifier.py
@@ -1114,7 +1123,7 @@ msgstr "תסריט ההתראה \"%s\" אינו קיים"
#: sabnzbd/notifier.py
msgid "Failed to send Windows notification"
msgstr "נכשל בשליחת התראת Windows"
msgstr "כישלון בשליחת התראת Windows"
#. Error message
#: sabnzbd/nzbparser.py
@@ -1134,7 +1143,7 @@ msgstr "שגיאה בהסרת %s"
#. Warning message
#: sabnzbd/nzbparser.py
msgid "Failed to import %s files from %s"
msgstr "נכשל ביבוא %s קבצים מן %s"
msgstr "כישלון ביבוא %s קבצים מן %s"
#. Error message
#: sabnzbd/nzbqueue.py
@@ -1196,7 +1205,7 @@ msgstr "כפול"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
msgstr "חלופה"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
@@ -1472,6 +1481,11 @@ msgstr "שגיאה %s: אתה צריך לספק שם משתמש וסיסמה ת
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "תור ישן התגלה, השתמש במעמד->תיקון כדי להמיר את התור"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "כישלון בליקוט ביטוי רגולרי עבור מונח חיפוש: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1508,7 +1522,7 @@ msgstr "שגיאה בשינוי שם \"%s\" אל \"%s\""
#: sabnzbd/postproc.py
msgid "Failed to move files"
msgstr "נכשל בהעברת קבצים"
msgstr "כישלון בהעברת קבצים"
#: sabnzbd/postproc.py
msgid "Running user script %s"
@@ -1602,12 +1616,12 @@ msgstr "הסרת %s נכשלה"
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to hibernate system"
msgstr "נכשל בחריפת מערכת"
msgstr "כישלון בחריפת מערכת"
#. Error message
#: sabnzbd/powersup.py
msgid "Failed to standby system"
msgstr "נכשל בהיכוננות מערכת"
msgstr "כישלון בהיכוננות מערכת"
#. Error message
#: sabnzbd/powersup.py
@@ -1634,7 +1648,7 @@ msgstr "שגיאת צד שרת (קוד שרת %s); לא היה ניתן להשי
#: sabnzbd/rss.py
msgid "Failed to retrieve RSS from %s: %s"
msgstr "נכשל באחזור RSS מן %s: %s"
msgstr "כישלון באחזור RSS מן %s: %s"
#: sabnzbd/rss.py, sabnzbd/urlgrabber.py
msgid "Server %s uses an untrusted HTTPS certificate"
@@ -1658,10 +1672,6 @@ msgstr "כניסת RSS ריקה נמצאה (%s)"
msgid "Show interface"
msgstr "הראה ממשק"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "פתח תיקיית השלמה"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1750,6 +1760,11 @@ msgstr "אי־האפלה"
msgid "Script"
msgstr "תסריט"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2122,11 +2137,6 @@ msgstr "מתגים"
msgid "Scheduling"
msgstr "תזמון"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2554,6 +2564,7 @@ msgid ""
"Speed up repairs by installing par2cmdline-turbo, it is available for many "
"platforms."
msgstr ""
"האץ תיקונים ע״י התקנת par2cmdline-turbo, הוא זמין עבור פלטפורמות רבות."
#: sabnzbd/skintext.py
msgid "Version"
@@ -2568,7 +2579,6 @@ msgstr "זמן פעולה"
msgid "Backup"
msgstr "גיבוי"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "קרא את עזרת וויקי על זה!"
@@ -2927,7 +2937,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Use Sorting to automatically organize and rename your completed downloads."
msgstr ""
msgstr "השתמש במיון כדי לארגן ולשנות שם באופן אוטומטי את ההורדות השלמות שלך."
#: sabnzbd/skintext.py
msgid "Minimum Free Space for Completed Download Folder"
@@ -3010,7 +3020,7 @@ msgstr "תיקיות מערכת"
#: sabnzbd/skintext.py
msgid "Hidden Folders"
msgstr ""
msgstr "תיקיות מוסתרות"
#: sabnzbd/skintext.py
msgid "Administrative Folder"
@@ -3053,7 +3063,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Purge Logs"
msgstr ""
msgstr "טהר יומנים"
#: sabnzbd/skintext.py
msgid ".nzb Backup Folder"
@@ -3126,25 +3136,20 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr "במקרה של \"השהיה\", תצטרך לקבוע סיסמה ולהמשיך את העבודה."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "גלה הורדות כפולות"
msgid "Identical download detection"
msgstr "גילוי הורדה זהה"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgstr ""
"גלה קבצי NZB זהים (על סמך פריטים בהיסטוריה שלך או קבצים בתיקיית גיבוי .nzb)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr "גלה הורדות זהות על סמך שם או תכני NZB."
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "גלה פרקים כפולים בסדרות"
msgid "Smart duplicate detection"
msgstr "גילוי שכפולים חכם"
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgstr "גלה פרקים זהים בסדרות (על סמך \"שם/עונה/פרק\" של פריטים בהיסטוריה שלך)"
msgid "Detect duplicates based on analysis of the filename."
msgstr "גלה שכפולים על סמך ניתוח של שם הקובץ."
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3152,9 +3157,9 @@ msgstr "התר שחרורים תקינים"
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
msgstr "עקוף גילוי כפילויות סדרה אם PROPER, REAL או REPACK מתגלים בשם ההורדה"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr "עקוף גילוי שכפולים חכם אם PROPER, REAL או REPACK מתגלים בשם ההורדה."
#. Four way switch for duplicates
#: sabnzbd/skintext.py
@@ -3214,14 +3219,13 @@ msgstr "בצע וידוא נוסף שמבוסס על קבצי SFV."
#: sabnzbd/skintext.py
msgid "User script can flag job as failed"
msgstr "תסריט משתמש יכול לסמן בדגל עבודה כנכשלה"
msgstr "תסריט משתמש יכול לדגל עבודה כנכשלה"
#: sabnzbd/skintext.py
msgid ""
"When the user script returns a non-zero exit code, the job will be flagged "
"as failed."
msgstr ""
"כאשר תסריט המשתמש מחזיר קוד יציאה בלתי אפסי, העבודה תסומן בדגל כנכשלה."
msgstr "כאשר תסריט המשתמש מחזיר קוד יציאה בלתי אפסי, העבודה תדוגל כנכשלה."
#: sabnzbd/skintext.py
msgid "Enable folder rename"
@@ -3245,11 +3249,11 @@ msgstr "בשימוש לפני ש־NZB נכנס לתור."
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
msgstr "תסריט בסיום תור"
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
msgstr "מבוצע לאחר שהתור יסיים להוריד."
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
@@ -4121,7 +4125,7 @@ msgstr "שם_סרט"
#: sabnzbd/skintext.py
msgid "Show Name"
msgstr "הראה שם"
msgstr "שם סדרה"
#: sabnzbd/skintext.py
msgid "Show.Name"
@@ -4129,7 +4133,7 @@ msgstr "שם.סדרה"
#: sabnzbd/skintext.py
msgid "Show_Name"
msgstr "הראה_שם"
msgstr "שם_סדרה"
#: sabnzbd/skintext.py
msgid "Season Number"
@@ -4193,15 +4197,15 @@ msgstr "מחרוזת מיון"
#: sabnzbd/skintext.py
msgid "Multi-part Label"
msgstr ""
msgstr "תווית מרובת־חלקים"
#: sabnzbd/skintext.py
msgid "Show folder"
msgstr ""
msgstr "תיקיית סדרה"
#: sabnzbd/skintext.py
msgid "Season folder"
msgstr ""
msgstr "תיקיית עונה"
#: sabnzbd/skintext.py
msgid "In folders"
@@ -4217,7 +4221,7 @@ msgstr "שם עבודה בתור שם קובץ"
#: sabnzbd/skintext.py
msgid "Series"
msgstr ""
msgstr "סדרות"
#. Note for title expression in Sorting that does case adjustment
#: sabnzbd/skintext.py
@@ -4250,27 +4254,27 @@ msgstr "GuessIt_Property"
#: sabnzbd/skintext.py
msgid "Minimum Filesize"
msgstr ""
msgstr "גודל מזערי של קובץ"
#: sabnzbd/skintext.py
msgid "Affected Job Types"
msgstr ""
msgstr "סוגי עבודה מושפעים"
#: sabnzbd/skintext.py
msgid "All"
msgstr "הכל"
msgstr "הכול"
#: sabnzbd/skintext.py
msgid "Series with air dates"
msgstr ""
msgstr "סדרות עם תאריכי שידור"
#: sabnzbd/skintext.py
msgid "Movies"
msgstr ""
msgstr "סרטים"
#: sabnzbd/skintext.py
msgid "Other / Unknown"
msgstr ""
msgstr "אחר / בלתי ידוע"
#: sabnzbd/skintext.py
msgid ""
@@ -4282,34 +4286,39 @@ msgid ""
"applied.</p><p>More options are available when Advanced Settings is "
"checked.<br/>Detailed information can be found on the Wiki.</p>"
msgstr ""
"<p>השתמש בממיינים כדי לארגן באופן אוטומטי את ההורדות השלמות שלך. לדוגמה, שים"
" את כל הפרקים מסדרה בתיקייה של עונה מסויימת.</p><p>ממיינים מנוסים לפי סדר "
"הופעתם ויכולים להסתדר מחדש על ידי גרירה ושחרור.<br/>הממיין הפעיל הראשון "
"שתואם אל הקטגוריה המושפעת ואל סוג העבודה הוא זה שמוחל.</p><p>עוד אפשרויות "
"זמינות כאשר האפשרות הגדרות מתקדמות מסומנת.<br/>מידע מפורט נמצא בוויקי.</p>"
#: sabnzbd/skintext.py
msgid "Add Sorter"
msgstr ""
msgstr "הוסף ממיין"
#: sabnzbd/skintext.py
msgid "Remove Sorter"
msgstr ""
msgstr "הסר ממיין"
#: sabnzbd/skintext.py
msgid "Test Data"
msgstr ""
msgstr "בחן נתונים"
#: sabnzbd/skintext.py
msgid "Quick start"
msgstr ""
msgstr "התחלה זריזה"
#: sabnzbd/skintext.py
msgid ""
"Move and rename all episodes in the \"tv\" category to a show-specific "
"folder"
msgstr ""
msgstr "העבר ושנה שם את כל הפרקים בקטגוריה \"טלוויזיה\" אל תיקייה של סדרה מסויימת"
#: sabnzbd/skintext.py
msgid ""
"Move and rename all movies in the \"movies\" category to a movie-specific "
"folder"
msgstr ""
msgstr "העבר ושנה שם את כל הסרטים בקטגוריה \"סרטים\" אל תיקייה של סרט מסויים"
#: sabnzbd/skintext.py
msgid ""
@@ -4551,7 +4560,7 @@ msgstr "קיצורי דרך במקלדת"
#: sabnzbd/skintext.py
msgid "Shift+Arrow key: Browse Queue and History pages"
msgstr ""
msgstr "Shift+מקש חץ: עיין בתור ובדפי היסטוריה"
#: sabnzbd/skintext.py
msgid "How long or untill when do you want to pause? (in English!)"
@@ -4730,12 +4739,12 @@ msgstr ""
#. Error message
#: sabnzbd/sorting.py
msgid "Failed to rename %s to %s"
msgstr ""
msgstr "כישלון בשינוי שם %s אל %s"
#. Error message
#: sabnzbd/sorting.py
msgid "Failed to rename similar file: %s to %s"
msgstr "נכשל בשינוי שם של קובץ דומה: %s אל %s"
msgstr "כישלון בשינוי שם של קובץ דומה: %s אל %s"
#: sabnzbd/urlgrabber.py
msgid "Unauthorized access"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
@@ -177,11 +177,6 @@ msgstr "Ingen"
msgid "Default"
msgstr "Standard"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Kunne ikke lage regex for søkestreng: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -280,8 +275,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-sti \"%s\" er ikke tillatt her"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1063,6 +1058,16 @@ msgstr "Køen er ferdig"
msgid "Other Messages"
msgstr "Andre meldinger"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Åpne fullført mappe"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Ikke tilgjengelig"
@@ -1463,6 +1468,11 @@ msgstr "Feil %s: Du må oppgi et gyldig brukernavn og passord."
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Gammel kø oppdaget. Bruk Status -> Reparer for å konvertere køen"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Kunne ikke lage regex for søkestreng: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1647,10 +1657,6 @@ msgstr "Tom RSS post funnet (%s)"
msgid "Show interface"
msgstr "Vis grensesnitt"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Åpne fullført mappe"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1739,6 +1745,11 @@ msgstr ""
msgid "Script"
msgstr "Skript"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2111,11 +2122,6 @@ msgstr "Svitsjer"
msgid "Scheduling"
msgstr "Nedlastingsplan"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2553,7 +2559,6 @@ msgstr "Oppetid"
msgid "Backup"
msgstr "Sikkerhetskopi"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Les Wiki Help fer dette!"
@@ -3103,23 +3108,19 @@ msgstr ""
"I tilfelle \"Pause\", så trenger du å sette et passord og gjenoppta jobben."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Oppdag duplikatnedlastinger"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Oppdag duplikat episoder i serie"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3128,8 +3129,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -8,7 +8,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
@@ -185,11 +185,6 @@ msgstr "Geen"
msgid "Default"
msgstr "Standaard"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Het compileren van 'regex' voor de zoekterm lukt niet: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -298,8 +293,8 @@ msgstr ""
"tot de aangemaakte bestanden en mappen."
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-pad '%s' hier niet toegestaan."
msgid "Network path \"%s\" is not allowed here"
msgstr "Netwerk-pad \"%s\" hier niet toegestaan."
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -319,6 +314,9 @@ msgid ""
"Do not use a folder in the application folder as your Scripts Folder, it "
"might be emptied during updates."
msgstr ""
"Als de Map voor scripts zich in de SABnzbd installatie-map bevindt kan deze "
"automatisch verwijderd worden tijdens updates. We adviseren een andere "
"locatie te gebruiken voor je scripts."
#. Warning message
#: sabnzbd/config.py
@@ -472,7 +470,7 @@ msgstr "Initialisatie van %s@%s mislukt, vanwege: %s"
#. Error message
#: sabnzbd/downloader.py
msgid "Fatal error in Downloader"
msgstr ""
msgstr "Onherstelbare fout in de Downloader"
#: sabnzbd/downloader.py
msgid "Too many connections to server %s [%s]"
@@ -646,11 +644,15 @@ msgstr ""
#: sabnzbd/filesystem.py
msgid "Cannot write a long filename to %s. This can cause problems."
msgstr ""
"Bestanden met lange bestandsnamen kunnen niet worden opgeslagen in %s. Dit "
"kan voor problemen zorgen tijdens het downloaden."
#. Warning message
#: sabnzbd/filesystem.py
msgid "Cannot write a unicode filename to %s. This can cause problems."
msgstr ""
"Bestanden met speciale karakters in de bestandsnaam kunnen niet worden "
"opgeslagen in %s. Dit kan voor problemen zorgen tijdens het downloaden."
#. Warning message
#: sabnzbd/filesystem.py
@@ -1109,6 +1111,16 @@ msgstr "Wachtrij voltooid"
msgid "Other Messages"
msgstr "Andere berichten"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Open map met voltooide downloads"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Niet beschikbaar"
@@ -1235,7 +1247,7 @@ msgstr "DUBBEL"
#: sabnzbd/nzbstuff.py
msgid "ALTERNATIVE"
msgstr ""
msgstr "ALTERNATIEF"
#: sabnzbd/nzbstuff.py
msgid "ENCRYPTED"
@@ -1511,6 +1523,11 @@ msgstr "Fout %s: Je moet een geldige gebruikersnaam en wachtwoord invullen."
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Oude wachtrij gevonden, gebruik Status->Reparatie om te converteren"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Het compileren van 'regex' voor de zoekterm lukt niet: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1698,10 +1715,6 @@ msgstr "Lege RSS-feed gevonden (%s)"
msgid "Show interface"
msgstr "Toon webinterface"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Open map met voltooide downloads"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1790,6 +1803,11 @@ msgstr "Bestandsnaam verbeteren"
msgid "Script"
msgstr "Script"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2162,11 +2180,6 @@ msgstr "Opties"
msgid "Scheduling"
msgstr "Taakplanner"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2600,6 +2613,8 @@ msgid ""
"Speed up repairs by installing par2cmdline-turbo, it is available for many "
"platforms."
msgstr ""
"Versnel reparaties door par2cmdline-turbo te installeren. Beschikbaar voor "
"veel besturingssystemen."
#: sabnzbd/skintext.py
msgid "Version"
@@ -2614,7 +2629,6 @@ msgstr "Tijd in de lucht"
msgid "Backup"
msgstr "Reserve"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Lees de Wiki pagina over dit onderwerp"
@@ -2982,6 +2996,8 @@ msgstr "(kan aangepast worden door de categorieën)."
msgid ""
"Use Sorting to automatically organize and rename your completed downloads."
msgstr ""
"Gebruik Sorteren om automatisch je voltooide downloads te organiseren en "
"hernoemen."
#: sabnzbd/skintext.py
msgid "Minimum Free Space for Completed Download Folder"
@@ -3066,7 +3082,7 @@ msgstr "Systeemmappen"
#: sabnzbd/skintext.py
msgid "Hidden Folders"
msgstr ""
msgstr "Verborgen mappen"
#: sabnzbd/skintext.py
msgid "Administrative Folder"
@@ -3113,7 +3129,7 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Purge Logs"
msgstr ""
msgstr "Logs wissen"
#: sabnzbd/skintext.py
msgid ".nzb Backup Folder"
@@ -3192,28 +3208,22 @@ msgstr ""
"download vrij te geven"
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Detecteer dubbele downloads"
msgid "Identical download detection"
msgstr "Identieke downloaddetectie"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
"Detecteer identieke downloads (op basis van downloads in je Geschiedenis of "
"bestanden in je .nzb backup map)."
"Detecteer identieke downloads op basis van de naam of de inhoud van de NZB."
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Detecteer dubbele afleveringen in series"
msgid "Smart duplicate detection"
msgstr "Slimme detectie van dubbele downloads"
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
"Detecteer identieke afleveringen in series (gebaseerd op "
"\"naam/seizoen/aflevering\" van downloads in je Geschiedenis)."
"Detecteer dubbele downloads op basis van de analyse van de bestandsnaam."
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3221,11 +3231,11 @@ msgstr "Sta verbeterde downloads toe"
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
"Sla dubbele download detectie over als er in de naam van de download PROPER,"
" REAL of REPACK bevat"
"Sla slimme detectie van dubbele downloads over als de naam van de download "
"PROPER, REAL of REPACK bevat."
#. Four way switch for duplicates
#: sabnzbd/skintext.py
@@ -3317,11 +3327,11 @@ msgstr "Word uitgevoerd vóór een download aan de wachtrij word toegevoegd"
#: sabnzbd/skintext.py
msgid "On queue finish script"
msgstr ""
msgstr "Script voor na het afronden van de wachtrij"
#: sabnzbd/skintext.py
msgid "Executed after the queue finishes downloading."
msgstr ""
msgstr "Script wordt uitgevoerd nadat de wachtrij is gedownload."
#: sabnzbd/skintext.py
msgid "Extra PAR2 Parameters"
@@ -4293,15 +4303,15 @@ msgstr "Sorteertekst"
#: sabnzbd/skintext.py
msgid "Multi-part Label"
msgstr ""
msgstr "Meervoudig label"
#: sabnzbd/skintext.py
msgid "Show folder"
msgstr ""
msgstr "Map per serie"
#: sabnzbd/skintext.py
msgid "Season folder"
msgstr ""
msgstr "Map per seizoen"
#: sabnzbd/skintext.py
msgid "In folders"
@@ -4317,7 +4327,7 @@ msgstr "Downloadnaam als Bestandsnaam"
#: sabnzbd/skintext.py
msgid "Series"
msgstr ""
msgstr "Series"
#. Note for title expression in Sorting that does case adjustment
#: sabnzbd/skintext.py
@@ -4354,7 +4364,7 @@ msgstr "Minimale bestandsgrootte"
#: sabnzbd/skintext.py
msgid "Affected Job Types"
msgstr ""
msgstr "Type downloads"
#: sabnzbd/skintext.py
msgid "All"
@@ -4362,15 +4372,15 @@ msgstr "alles"
#: sabnzbd/skintext.py
msgid "Series with air dates"
msgstr ""
msgstr "Series met datums"
#: sabnzbd/skintext.py
msgid "Movies"
msgstr ""
msgstr "Films"
#: sabnzbd/skintext.py
msgid "Other / Unknown"
msgstr ""
msgstr "Anders / Onbekend"
#: sabnzbd/skintext.py
msgid ""
@@ -4382,34 +4392,43 @@ msgid ""
"applied.</p><p>More options are available when Advanced Settings is "
"checked.<br/>Detailed information can be found on the Wiki.</p>"
msgstr ""
"<p>Gebruik Sorteren om automatisch je voltooide downloads te organiseren. Bijvoorbeeld het automatisch verplaatsen van alle afleveringen van een serie in een seizoensspecifieke map. Of plaats films in een map met de naam van de film.</p>\n"
"\n"
"<p>Sorteringen worden in de getoonde volgorde geprobeerd en kunnen worden herschikt door ze te slepen.<br/> De eerste actieve Sortering die overeenkomt met zowel de betreffende categorie als het type taak wordt toegepast.</p>\n"
"\n"
"<p>Meer opties zijn beschikbaar wanneer Geavanceerde instellingen zijn aangevinkt.<br/> Gedetailleerde informatie is te vinden op de Wiki.</p>"
#: sabnzbd/skintext.py
msgid "Add Sorter"
msgstr ""
msgstr "Sortering toevoegen"
#: sabnzbd/skintext.py
msgid "Remove Sorter"
msgstr ""
msgstr "Sortering verwijderen"
#: sabnzbd/skintext.py
msgid "Test Data"
msgstr ""
msgstr "Testgegevens"
#: sabnzbd/skintext.py
msgid "Quick start"
msgstr ""
msgstr "Snel beginnen"
#: sabnzbd/skintext.py
msgid ""
"Move and rename all episodes in the \"tv\" category to a show-specific "
"folder"
msgstr ""
"Verplaats en hernoem alle afleveringen in de \"tv\" categorie naar een "
"specifieke map voor de serie."
#: sabnzbd/skintext.py
msgid ""
"Move and rename all movies in the \"movies\" category to a movie-specific "
"folder"
msgstr ""
"Verplaats en hernoem alle films in de categorie \"films\" naar een "
"specifieke map voor de film."
#: sabnzbd/skintext.py
msgid ""
@@ -4652,7 +4671,7 @@ msgstr "Sneltoetsen"
#: sabnzbd/skintext.py
msgid "Shift+Arrow key: Browse Queue and History pages"
msgstr ""
msgstr "Shift+Pijltoets: Blader door de wachtrij- en geschiedenispagina's"
#: sabnzbd/skintext.py
msgid "How long or untill when do you want to pause? (in English!)"
@@ -4831,7 +4850,7 @@ msgstr ""
#. Error message
#: sabnzbd/sorting.py
msgid "Failed to rename %s to %s"
msgstr ""
msgstr "Hernoemen van %s naar %s mislukt"
#. Error message
#: sabnzbd/sorting.py

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
@@ -173,11 +173,6 @@ msgstr "Brak"
msgid "Default"
msgstr "Domyślne"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Błąd kompilacji wyrażenia regularnego dla wyszukiwania: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -276,8 +271,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "Ścieżka UNC \"%s\" niedozwolona"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1065,6 +1060,16 @@ msgstr "Kolejka ukończona"
msgid "Other Messages"
msgstr "Inne komunikaty"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Otwórz katalog zakończonych"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Niedostępne"
@@ -1469,6 +1474,11 @@ msgstr ""
"Wykryto kolejkę w starszej wersji, użyj funkcji Status->Naprawa, aby ją "
"przekonwertować"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Błąd kompilacji wyrażenia regularnego dla wyszukiwania: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1653,10 +1663,6 @@ msgstr "Znaleziono pusty wpis RSS (%s)"
msgid "Show interface"
msgstr "Pokaż interfejs"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Otwórz katalog zakończonych"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1745,6 +1751,11 @@ msgstr ""
msgid "Script"
msgstr "Skrypt"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2117,11 +2128,6 @@ msgstr "Przełączniki"
msgid "Scheduling"
msgstr "Harmonogram"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2558,7 +2564,6 @@ msgstr "Czas działania"
msgid "Backup"
msgstr "Zapasowy"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Przeczytaj o tym w Wiki!"
@@ -3111,23 +3116,19 @@ msgstr ""
"Jeśli wybrano \"Wstrzymaj\", będzie trzeba ustawić hasło i wznowić zadanie"
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Działanie dla duplikatów"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Wykryj zduplikowane odcinki seriali"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3136,8 +3137,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -2,11 +2,12 @@
# Copyright 2007-2023 The SABnzbd-Team
#
# Translators:
# Henrique Moreno, 2023
# Safihre <safihre@sabnzbd.org>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
@@ -67,7 +68,7 @@ msgstr "aplicativo 7za... NÃO encontrado!"
#. Error message
#: SABnzbd.py
msgid "Essential modules are missing, downloading cannot start."
msgstr ""
msgstr "Módulos essenciais estão faltando, não é possível baixar."
#. Warning message
#: SABnzbd.py
@@ -89,6 +90,8 @@ msgid ""
"SABnzbd was started with encoding %s, this should be UTF-8. Expect problems "
"with Unicoded file and directory names in downloads."
msgstr ""
"SABnzbd iniciou com codificado %s, deveria ser UFT-8. Esperado problemas com"
" arquivos e nomes de diretórios Unicode nos downloades."
#. Warning message
#: SABnzbd.py
@@ -96,11 +99,13 @@ msgid ""
"Current umask (%o) might deny SABnzbd access to the files and folders it "
"creates."
msgstr ""
"Mascara atual (%o) pode negar ao SABnzbd acesso aos arquivos e diretórios "
"criados."
#. Warning message
#: SABnzbd.py
msgid "Could not load additional certificates from certifi package"
msgstr ""
msgstr "Não foi possível carregar certificado do pacote certifi."
#. Warning message
#: SABnzbd.py
@@ -110,7 +115,7 @@ msgstr "HTTPS desabilitado pela falta de arquivos CERT e KEY"
#. Warning message
#: SABnzbd.py
msgid "Disabled HTTPS because of invalid CERT and KEY files"
msgstr ""
msgstr "HTTPs desabilitado por caus de arquivo CERT e KEY invalidos"
#. Error message
#: SABnzbd.py
@@ -138,22 +143,22 @@ msgstr "Erro fatal ao salvar estado"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed postprocessor"
msgstr ""
msgstr "Reiniciado por falha de pós processamento."
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed downloader"
msgstr ""
msgstr "Reiniciado por falha de download"
#. Warning message
#: sabnzbd/__init__.py
msgid "Restarting because of crashed assembler"
msgstr ""
msgstr "Reiniciado por falha de assembler"
#. Warning message
#: sabnzbd/__init__.py
msgid "Cannot access PID file %s"
msgstr ""
msgstr "Não é possível acessar arquivo PID %s"
#: sabnzbd/api.py, sabnzbd/emailer.py
msgid "Email succeeded"
@@ -177,11 +182,6 @@ msgstr "Nenhum"
msgid "Default"
msgstr "Padrão"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Falha ao compilar a expressão para o termo pesquisado: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -208,6 +208,8 @@ msgid ""
"Paused job \"%s\" because of encrypted RAR file (if supplied, all passwords "
"were tried)"
msgstr ""
"Tarefa \"%s\" pausado por causa de arquivo RAR encripitado (se fornecido, "
"todos as senhas foram tentadas)"
#. Warning message
#: sabnzbd/assembler.py
@@ -215,6 +217,8 @@ msgid ""
"Aborted job \"%s\" because of encrypted RAR file (if supplied, all passwords"
" were tried)"
msgstr ""
"Tarefa \"%s\" abortado por causa de arquivo RAR encripitado (se fornecido, "
"todos as senhas foram tentadas)"
#: sabnzbd/assembler.py
msgid "Aborted, encryption detected"
@@ -224,6 +228,8 @@ msgstr "Cancelado, criptografia detectada"
#: sabnzbd/assembler.py
msgid "In \"%s\" unwanted extension in RAR file. Unwanted file is %s "
msgstr ""
"Em \"%s\" extensão não necessária em arquivo RAR. Arquivo não necessário é "
"%s "
#: sabnzbd/assembler.py
msgid "Unwanted extension is in rar file %s"
@@ -280,8 +286,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "O caminho UNC \"%s\" não é permitido aqui"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1066,6 +1072,16 @@ msgstr "Fila concluída"
msgid "Other Messages"
msgstr "Outras Mensagens"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Abrir pasta de finalizados"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Não disponível"
@@ -1470,6 +1486,11 @@ msgstr ""
"Fila antiga detectada, use \"Situação -> Reparação da fila\" para converter "
"a fila"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Falha ao compilar a expressão para o termo pesquisado: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1656,10 +1677,6 @@ msgstr "Entrada RSS vazia encontrada (%s)"
msgid "Show interface"
msgstr "Exibir interface"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Abrir pasta de finalizados"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1748,6 +1765,11 @@ msgstr ""
msgid "Script"
msgstr "Script"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2120,11 +2142,6 @@ msgstr "Opções"
msgid "Scheduling"
msgstr "Agendamento"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2561,7 +2578,6 @@ msgstr "Tempo ativo"
msgid "Backup"
msgstr "Backup"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Leia a sessão ajuda no Wiki sobre isso!"
@@ -3113,23 +3129,19 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr "Em caso de \"Pausa\", você precisa definir uma senha e retomar a tarefa."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Detectar Downloads Duplicados"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Detecta episódios duplicados em séries"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3138,8 +3150,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
@@ -182,11 +182,6 @@ msgstr "Niciunul"
msgid "Default"
msgstr "Implicit"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Compilarea unei căutări regex nereuşită: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -291,8 +286,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "cale UNC \"%s\" nu este premisă aici"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1091,6 +1086,16 @@ msgstr "Coadă finalizată"
msgid "Other Messages"
msgstr "Alte Mesaje"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Deschide dosar descărcări complete"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Indisponibil"
@@ -1497,6 +1502,11 @@ msgstr ""
"Coadă de descărcare veche detectată, utilizează Stare->Reparare pentru a "
"converti coada"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Compilarea unei căutări regex nereuşită: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1683,10 +1693,6 @@ msgstr "Valoare RSS gasită a fost goală (%s)"
msgid "Show interface"
msgstr "Arată interfața"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Deschide dosar descărcări complete"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1775,6 +1781,11 @@ msgstr ""
msgid "Script"
msgstr "Script"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2147,11 +2158,6 @@ msgstr "Comutatoare"
msgid "Scheduling"
msgstr "Planificare"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2591,7 +2597,6 @@ msgstr "Durata Funcţionării"
msgid "Backup"
msgstr "Server Secundar"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Citeşte Ajutorul Wiki despre asta !"
@@ -3140,23 +3145,19 @@ msgstr ""
"reluați sarcina."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Detectează Descărcări Duplicate"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Detectează episoade duplicate în seriale"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3165,8 +3166,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
@@ -177,11 +177,6 @@ msgstr "Ничего"
msgid "Default"
msgstr "по умолчанию"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Не удалось составить регулярное выражение поиска: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -280,8 +275,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC-путь «%s» здесь не допускается"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1064,6 +1059,16 @@ msgstr ""
msgid "Other Messages"
msgstr "Другие сообщения"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr ""
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr ""
@@ -1466,6 +1471,11 @@ msgstr "Ошибка %s: укажите действительное имя по
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr ""
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Не удалось составить регулярное выражение поиска: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1650,10 +1660,6 @@ msgstr "Обнаружена пустая запись RSS (%s)"
msgid "Show interface"
msgstr "Показать интерфейс"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr ""
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1742,6 +1748,11 @@ msgstr ""
msgid "Script"
msgstr "Сценарий"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2114,11 +2125,6 @@ msgstr "Переключатели"
msgid "Scheduling"
msgstr "Расписание"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2555,7 +2561,6 @@ msgstr "Время работы"
msgid "Backup"
msgstr "Резервный"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Описание см. на вики-странице."
@@ -3107,23 +3112,19 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Обнаруживать повторяющиеся загрузки"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3132,8 +3133,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
@@ -175,11 +175,6 @@ msgstr "Ниједно"
msgid "Default"
msgstr "Подразумевано"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Neuspešna kompilacija regularne ekspresije za termin pretrage: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -278,8 +273,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC путања \"%s\" није дозвољена"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1060,6 +1055,16 @@ msgstr "Ред завршен"
msgid "Other Messages"
msgstr "Остале поруке"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Otvori fasciklu završenih"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Недоступно"
@@ -1460,6 +1465,11 @@ msgstr "Грешка %s: Требате да унесете важеће име/
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Стари ред је нађен, употребити Статус->Поправи за претварање реда"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Neuspešna kompilacija regularne ekspresije za termin pretrage: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1644,10 +1654,6 @@ msgstr "Nađen prazan RSS unos (%s)"
msgid "Show interface"
msgstr "Pokaži interfejs"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Otvori fasciklu završenih"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1736,6 +1742,11 @@ msgstr ""
msgid "Script"
msgstr "Скрипт"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2108,11 +2119,6 @@ msgstr "Прекидачи"
msgid "Scheduling"
msgstr "Планирање"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2547,7 +2553,6 @@ msgstr "; Ради"
msgid "Backup"
msgstr "Резервно"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "За више информација, читајте Вики!"
@@ -3094,23 +3099,19 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr "Ако је \"Пауза\", требате да поставите лозинку и да наставите рад."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Откриј дупликатна преузимања"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Откриј дупле епизоде у серије"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3119,8 +3120,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
@@ -175,11 +175,6 @@ msgstr "Ingen"
msgid "Default"
msgstr "Standard"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "Det gick inte att kompilera regex för sök-sträng: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -278,8 +273,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "UNC sökväg \"%s\" är inte tillåten här"
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1064,6 +1059,16 @@ msgstr "Kön färdig"
msgid "Other Messages"
msgstr "Andra meddelanden"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Öppna färdig mapp"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "Ej tillgänglig"
@@ -1466,6 +1471,11 @@ msgstr "Error %s: Du måste ange ett giltigt användarnamn och lösenord."
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "Gammal kö hittad, använd Status -> Reparera för att konvertera kön"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "Det gick inte att kompilera regex för sök-sträng: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1650,10 +1660,6 @@ msgstr "Tom RSS post hittades (%s)"
msgid "Show interface"
msgstr "Visa gränssnitt"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "Öppna färdig mapp"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1742,6 +1748,11 @@ msgstr ""
msgid "Script"
msgstr "Skript"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2114,11 +2125,6 @@ msgstr "Switchar"
msgid "Scheduling"
msgstr "Schemaläggare"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2555,7 +2561,6 @@ msgstr "Upptid"
msgid "Backup"
msgstr "Säkerhetskopiera"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "Läs Wiki Help för detta!"
@@ -3104,23 +3109,19 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr "Om \"Pausad\", så behöver du ange ett lösenord för att återuppta jobbet."
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "Upptäck dubbletter av nedladdningar"
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "Hitta dublettavsnitt i serier"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
@@ -3129,8 +3130,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates

View File

@@ -3,12 +3,13 @@
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2023
# Kangwei Li <lkw20010211@gmail.com>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
"Last-Translator: Kangwei Li <lkw20010211@gmail.com>, 2023\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"
@@ -173,11 +174,6 @@ msgstr "无"
msgid "Default"
msgstr "默认"
#. Error message
#: sabnzbd/api.py
msgid "Failed to compile regex for search term: %s"
msgstr "为搜索关键词编译正则表达式失败: %s"
#. Error message
#: sabnzbd/assembler.py
msgid "Disk full! Forcing Pause"
@@ -276,8 +272,8 @@ msgid ""
msgstr ""
#: sabnzbd/cfg.py
msgid "UNC path \"%s\" not allowed here"
msgstr "此处不允许使用 UNC 路径 \"%s\""
msgid "Network path \"%s\" is not allowed here"
msgstr ""
#: sabnzbd/cfg.py
msgid "Queue not empty, cannot change folder."
@@ -1053,6 +1049,16 @@ msgstr "队列已完成"
msgid "Other Messages"
msgstr "其他信息"
#. Notification action
#: sabnzbd/notifier.py
msgid "Open folder"
msgstr ""
#. Notification action
#: sabnzbd/notifier.py, sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "打开完成文件夹"
#: sabnzbd/notifier.py, sabnzbd/skintext.py
msgid "Not available"
msgstr "不可用"
@@ -1453,6 +1459,11 @@ msgstr "错误 %s: 您需要提供有效的用户名与密码。"
msgid "Old queue detected, use Status->Repair to convert the queue"
msgstr "侦测到旧版队列,请使用“状态”→“修复”转换队列"
#. Error message
#: sabnzbd/postproc.py
msgid "Failed to compile regex for search term: %s"
msgstr "为搜索关键词编译正则表达式失败: %s"
#. Warning message
#: sabnzbd/postproc.py
msgid ""
@@ -1637,10 +1648,6 @@ msgstr "发现空的 RSS 条目 (%s)"
msgid "Show interface"
msgstr "显示界面"
#: sabnzbd/sabtray.py, sabnzbd/sabtraylinux.py
msgid "Open complete folder"
msgstr "打开完成文件夹"
#. Queue page button or entry box
#: sabnzbd/sabtray.py, sabnzbd/skintext.py
msgid "Pause for"
@@ -1729,6 +1736,11 @@ msgstr ""
msgid "Script"
msgstr "脚本"
#. PP RSS feed of the NZB - Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. PP Source of the NZB (path or URL) - Where to find the SABnzbd sourcecode
#: sabnzbd/skintext.py
msgid "Source"
@@ -2101,11 +2113,6 @@ msgstr "参数"
msgid "Scheduling"
msgstr "定时任务"
#. Main menu item
#: sabnzbd/skintext.py
msgid "RSS"
msgstr "RSS"
#. Main menu item
#: sabnzbd/skintext.py
msgid "Notifications"
@@ -2534,7 +2541,6 @@ msgstr "启动时间"
msgid "Backup"
msgstr "备份"
#. Notification Script settings
#: sabnzbd/skintext.py
msgid "Read the Wiki Help on this!"
msgstr "关于该项请参阅 Wiki 帮助!"
@@ -2875,11 +2881,11 @@ msgstr ""
#: sabnzbd/skintext.py
msgid "Minimum Free Space for Completed Download Folder"
msgstr ""
msgstr "完成下载文件夹的最小剩余空间"
#: sabnzbd/skintext.py
msgid "Will not work if a category folder is on a different disk."
msgstr ""
msgstr "当某分类的路径位于另一磁盘上时不生效。"
#. Auto-resume download on the reset day
#: sabnzbd/skintext.py
@@ -2963,14 +2969,14 @@ msgstr "队列管理及历史数据库的存放位置。<br /><i>仅当队列为
#: sabnzbd/skintext.py
msgid "Backup Folder"
msgstr ""
msgstr "备份文件夹"
#: sabnzbd/skintext.py
msgid ""
"Location where the backups of the configuration file and databases are "
"stored.<br />If left empty, the backup will be created in the Completed "
"Download Folder."
msgstr ""
msgstr "备份配置文件和数据库的位置。<br />如果留空,备份将存放于完成下载文件夹中。"
#: sabnzbd/skintext.py
msgid "<i>Data will <b>not</b> be moved. Requires SABnzbd restart!</i>"
@@ -2987,7 +2993,7 @@ msgstr "SABnzbd 日志文件的位置。<br /><i>需要重启 SABnzbd 才能生
#: sabnzbd/skintext.py
msgid "Purge Logs"
msgstr ""
msgstr "清除日志"
#: sabnzbd/skintext.py
msgid ".nzb Backup Folder"
@@ -3057,24 +3063,20 @@ msgid "In case of \"Pause\", you'll need to set a password and resume the job."
msgstr "若选择“暂停”,您将需要设置密码并手动续传对应任务。"
#: sabnzbd/skintext.py
msgid "Detect Duplicate Downloads"
msgstr "侦测重复下载"
msgid "Identical download detection"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical NZB files (based on items in your History or files in .nzb "
"Backup Folder)"
msgstr "检测相同的 NZB 文件 (基于您的历史项目或 .nzb 备份文件夹中的文件)"
msgid "Detect identical downloads based on name or NZB contents."
msgstr ""
#: sabnzbd/skintext.py
msgid "Detect duplicate episodes in series"
msgstr "侦测同季的重复剧集"
msgid "Smart duplicate detection"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Detect identical episodes in series (based on \"name/season/episode\" of "
"items in your History)"
msgstr "在剧目中检测相同的剧集 (基于您的历史项目,参照 \"name/season/episode\" 的规则)"
msgid "Detect duplicates based on analysis of the filename."
msgstr ""
#: sabnzbd/skintext.py
msgid "Allow proper releases"
@@ -3082,8 +3084,8 @@ msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name"
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in "
"the download name."
msgstr ""
#. Four way switch for duplicates
@@ -3440,7 +3442,7 @@ msgstr "超时"
#: sabnzbd/skintext.py
msgid "Account expiration date"
msgstr ""
msgstr "账户到期时间"
#: sabnzbd/skintext.py
msgid "Warn 5 days in advance of account expiration date."
@@ -3620,7 +3622,7 @@ msgstr "应用过滤器"
#. Config->RSS edit button
#: sabnzbd/skintext.py
msgid "Edit"
msgstr ""
msgstr "编辑"
#. Config->RSS when will be the next RSS scan
#: sabnzbd/skintext.py

View File

@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Pavel C <quoing_transifex@mess.cz>, 2022\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: reloxx13 <reloxx@interia.pl>, 2022\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Ester Molla Aragones <moarages@gmail.com>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2021\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"

View File

@@ -7,7 +7,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: ION, 2021\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0RC1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2021\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"

View File

@@ -6,7 +6,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.2.0Alpha2\n"
"Project-Id-Version: SABnzbd-4.2.0Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"

View File

@@ -4,13 +4,13 @@ sabctools==8.0.0
cheetah3==3.2.6.post1
cffi==1.16.0
pycparser==2.21
feedparser==6.0.10
feedparser==6.0.11
configobj==5.0.8
cheroot==10.0.0
six==1.16.0
cherrypy==18.8.0
jaraco.functools==4.0.0
jaraco.collections==4.3.0
jaraco.collections==5.0.0
jaraco.text==3.8.1 # Newer version introduces irrelevant extra dependencies
jaraco.classes==3.3.0
jaraco.context==4.3.0
@@ -30,18 +30,20 @@ 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==41.0.5
cryptography==41.0.7
# 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.8.0
ujson==5.9.0
# Windows system integration
pywin32==306; sys_platform == 'win32'
toasts-winrt==1.0.0; sys_platform == 'win32'
windows-toasts==1.0.1; sys_platform == 'win32'
# macOS system calls
pyobjc-core==10.0; sys_platform == 'darwin'
pyobjc-framework-Cocoa==10.0; sys_platform == 'darwin'
pyobjc-core==10.1; sys_platform == 'darwin'
pyobjc-framework-Cocoa==10.1; sys_platform == 'darwin'
# Linux notifications
notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
@@ -55,4 +57,4 @@ notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
# Optional support for system power management on *nix.
# Requires libdbus-1-dev to be installed.
# Uncomment line below or manually install after installing requirements.
# dbus-python; sys_platform != 'win32' and sys_platform != 'darwin'
# dbus-python; sys_platform != 'win32' and sys_platform != 'darwin'

View File

@@ -162,6 +162,7 @@ RESTART_REQ = False
PAUSED_ALL = False
TRIGGER_RESTART = False # To trigger restart for Scheduler, WinService and Mac
WINTRAY = None # Thread for the Windows SysTray icon
MACOSTRAY = None # Thread for the macOS tray icon
WEBUI_READY = False
LAST_HISTORY_UPDATE = 1
RESTORE_DATA = None
@@ -285,6 +286,11 @@ def initialize(pause_downloader=False, clean_up=False, repair=0):
misc.convert_sorter_settings()
cfg.sorters_converted.set(True)
# Convert duplicate settings
if cfg.no_series_dupes():
cfg.no_smart_dupes.set(cfg.no_series_dupes())
cfg.no_series_dupes.set(0)
# Add hostname to the whitelist
if not cfg.host_whitelist():
cfg.host_whitelist.set(socket.gethostname())

View File

@@ -58,7 +58,7 @@ import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.skintext import SKIN_TEXT
from sabnzbd.utils.diskspeed import diskspeedmeasure
from sabnzbd.utils.internetspeed import internetspeed
from sabnzbd.internetspeed import internetspeed
from sabnzbd.utils.pathbrowser import folders_at_path
from sabnzbd.utils.getperformance import getpystone
from sabnzbd.misc import (
@@ -270,6 +270,7 @@ def _api_queue_default(value, kwargs):
search = kwargs.get("search")
categories = kwargs.get("cat") or kwargs.get("category")
priorities = kwargs.get("priority")
statuses = kwargs.get("status")
nzo_ids = kwargs.get("nzo_ids")
if categories and not isinstance(categories, list):
@@ -277,13 +278,21 @@ def _api_queue_default(value, kwargs):
if priorities and not isinstance(priorities, list):
# Make sure it's an integer
priorities = [int_conv(prio) for prio in priorities.split(",")]
if statuses and not isinstance(statuses, list):
statuses = statuses.split(",")
if nzo_ids and not isinstance(nzo_ids, list):
nzo_ids = nzo_ids.split(",")
return report(
keyword="queue",
data=build_queue(
start=start, limit=limit, search=search, categories=categories, priorities=priorities, nzo_ids=nzo_ids
start=start,
limit=limit,
search=search,
categories=categories,
priorities=priorities,
statuses=statuses,
nzo_ids=nzo_ids,
),
)
@@ -483,8 +492,9 @@ def _api_history(name, kwargs):
limit = int_conv(kwargs.get("limit"))
last_history_update = int_conv(kwargs.get("last_history_update", 0))
search = kwargs.get("search")
failed_only = int_conv(kwargs.get("failed_only"))
categories = kwargs.get("cat") or kwargs.get("category")
statuses = kwargs.get("status")
failed_only = int_conv(kwargs.get("failed_only"))
nzo_ids = kwargs.get("nzo_ids")
# Do we need to send anything?
@@ -494,6 +504,13 @@ def _api_history(name, kwargs):
if categories and not isinstance(categories, list):
categories = categories.split(",")
if statuses and not isinstance(statuses, list):
statuses = statuses.split(",")
if failed_only:
# We ignore any other statuses, having both doesn't make sense
statuses = [Status.FAILED]
if nzo_ids and not isinstance(nzo_ids, list):
nzo_ids = nzo_ids.split(",")
@@ -537,7 +554,12 @@ def _api_history(name, kwargs):
to_units(day),
)
history["slots"], history["ppslots"], history["noofslots"] = build_history(
start=start, limit=limit, search=search, failed_only=failed_only, categories=categories, nzo_ids=nzo_ids
start=start,
limit=limit,
search=search,
categories=categories,
statuses=statuses,
nzo_ids=nzo_ids,
)
history["last_history_update"] = sabnzbd.LAST_HISTORY_UPDATE
history["version"] = sabnzbd.__version__
@@ -1250,9 +1272,6 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
# Calculate performance measures, if requested
if int_conv(calculate_performance):
# Perform the internetspeed measure in separate thread
internetspeed_future = sabnzbd.THREAD_POOL.submit(internetspeed)
# PyStone
sabnzbd.PYSTONE_SCORE = getpystone()
@@ -1261,7 +1280,7 @@ def build_status(calculate_performance: bool = False, skip_dashboard: bool = Fal
sabnzbd.COMPLETE_DIR_SPEED = round(diskspeedmeasure(sabnzbd.cfg.complete_dir.get_path()), 1)
# Internet bandwidth
sabnzbd.INTERNET_BANDWIDTH = round(internetspeed_future.result(), 1)
sabnzbd.INTERNET_BANDWIDTH = round(internetspeed(), 1)
# How often did we delay?
info["delayed_assembler"] = sabnzbd.BPSMeter.delayed_assembler
@@ -1343,6 +1362,7 @@ def build_queue(
search: Optional[str] = None,
categories: Optional[List[str]] = None,
priorities: Optional[List[str]] = None,
statuses: Optional[List[str]] = None,
nzo_ids: Optional[List[str]] = None,
):
info = build_header(for_template=False)
@@ -1354,7 +1374,13 @@ def build_queue(
queue_fullsize,
nzos_matched,
) = sabnzbd.NzbQueue.queue_info(
search=search, categories=categories, priorities=priorities, nzo_ids=nzo_ids, start=start, limit=limit
search=search,
categories=categories,
priorities=priorities,
statuses=statuses,
nzo_ids=nzo_ids,
start=start,
limit=limit,
)
info["kbpersec"] = "%.2f" % (sabnzbd.BPSMeter.bps / KIBI)
@@ -1384,7 +1410,6 @@ def build_queue(
for nzo in nzo_list:
mbleft = nzo.remaining / MEBI
mb = nzo.bytes / MEBI
is_propagating = (nzo.avg_stamp + float(cfg.propagation_delay() * 60)) > time.time()
slot = {}
slot["index"] = n
@@ -1405,8 +1430,8 @@ def build_queue(
slot["direct_unpack"] = nzo.direct_unpack_progress
if not sabnzbd.Downloader.paused and nzo.status not in (Status.PAUSED, Status.FETCHING, Status.GRABBING):
if is_propagating:
slot["status"] = Status.PROP
if nzo.propagation_delay_left:
slot["status"] = Status.PROPAGATING
elif nzo.status == Status.CHECKING:
slot["status"] = Status.CHECKING
else:
@@ -1420,7 +1445,7 @@ def build_queue(
if (
sabnzbd.Downloader.paused
or sabnzbd.Downloader.paused_for_postproc
or is_propagating
or nzo.propagation_delay_left
or nzo.status not in (Status.DOWNLOADING, Status.FETCHING, Status.QUEUED)
) and nzo.priority != FORCE_PRIORITY:
slot["timeleft"] = "0:00:00"
@@ -1631,36 +1656,20 @@ def build_header(webdir: str = "", for_template: bool = True, trans_functions: b
def build_history(
start: int = 0,
limit: int = 0,
limit: int = 1000000,
search: Optional[str] = None,
failed_only: int = 0,
categories: Optional[List[str]] = None,
statuses: Optional[List[str]] = None,
nzo_ids: Optional[List[str]] = None,
) -> Tuple[List[Dict[str, Any]], int, int]:
"""Combine the jobs still in post-processing and the database history"""
if not limit:
limit = 1000000
# Grab any items that are active or queued in postproc
postproc_queue = sabnzbd.PostProcessor.get_queue()
# Filter out any items that don't match the search term or category
if postproc_queue:
# It would be more efficient to iterate only once, but we accept the penalty for code clarity
if isinstance(categories, list):
postproc_queue = [nzo for nzo in postproc_queue if nzo.cat in categories]
if isinstance(search, str):
# Replace * with .* and ' ' with .
search_text = search.strip().replace("*", ".*").replace(" ", ".*") + ".*?"
try:
re_search = re.compile(search_text, re.I)
postproc_queue = [nzo for nzo in postproc_queue if re_search.search(nzo.final_name)]
except:
logging.error(T("Failed to compile regex for search term: %s"), search_text)
if nzo_ids:
postproc_queue = [nzo for nzo in postproc_queue if nzo.nzo_id in nzo_ids]
postproc_queue = sabnzbd.PostProcessor.get_queue(
search=search,
categories=categories,
statuses=statuses,
nzo_ids=nzo_ids,
)
# Multi-page support for postproc items
postproc_queue_size = len(postproc_queue)
@@ -1669,13 +1678,10 @@ def build_history(
postproc_queue = []
database_history_limit = limit
else:
try:
if limit:
postproc_queue = postproc_queue[start : start + limit]
else:
postproc_queue = postproc_queue[start:]
except:
pass
if limit:
postproc_queue = postproc_queue[start : start + limit]
else:
postproc_queue = postproc_queue[start:]
# Remove the amount of postproc items from the db request for history items
database_history_limit = max(limit - len(postproc_queue), 0)
database_history_start = max(start - postproc_queue_size, 0)
@@ -1692,12 +1698,22 @@ def build_history(
# Fetch history items
if not database_history_limit:
items, total_items = history_db.fetch_history(
database_history_start, 1, search, failed_only, categories, nzo_ids
start=database_history_start,
limit=1,
search=search,
categories=categories,
statuses=statuses,
nzo_ids=nzo_ids,
)
items = []
else:
items, total_items = history_db.fetch_history(
database_history_start, database_history_limit, search, failed_only, categories, nzo_ids
start=database_history_start,
limit=database_history_limit,
search=search,
categories=categories,
statuses=statuses,
nzo_ids=nzo_ids,
)
# Add the postproc items to the top of the history
@@ -1742,6 +1758,7 @@ def add_active_history(postproc_queue: List[NzbObject], items: List[Dict[str, An
"size": to_units(nzo.bytes_downloaded, "B"),
"meta": None,
"series": "",
"duplicate_key": nzo.duplicate_key,
"md5sum": "",
"password": nzo.correct_password,
"action_line": nzo.action_line,

View File

@@ -157,6 +157,7 @@ class Assembler(Thread):
sabnzbd.Downloader.pause()
if cfg.fulldisk_autoresume():
sabnzbd.Scheduler.plan_diskspace_resume(full_dir, required_space)
sabnzbd.notifier.send_notification("SABnzbd", T("Too little diskspace forcing PAUSE"), "disk_full")
sabnzbd.emailer.diskfull_mail()
@staticmethod

View File

@@ -51,7 +51,7 @@ from sabnzbd.constants import (
DEF_HTTPS_CERT_FILE,
DEF_HTTPS_KEY_FILE,
)
from sabnzbd.filesystem import same_directory, real_path
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.
@@ -188,7 +188,7 @@ def validate_host(value: str) -> ValidateResult:
def validate_script(value: str) -> ValidateResult:
"""Check if value is a valid script"""
if not sabnzbd.__INITIALIZED__ or (value and sabnzbd.filesystem.is_valid_script(value)):
if not sabnzbd.__INITIALIZED__ or (value and is_valid_script(value)):
return None, value
elif (value and value == "None") or not value:
return None, "None"
@@ -217,10 +217,10 @@ def validate_permissions(value: str) -> ValidateResult:
def validate_safedir(root: str, value: str, default: str) -> ValidateResult:
"""Allow only when queues are empty and no UNC"""
"""Allow only when queues are empty and not a network-path"""
if not sabnzbd.__INITIALIZED__ or (sabnzbd.PostProcessor.empty() and sabnzbd.NzbQueue.is_empty()):
if value.startswith(r"\\"):
return T('UNC path "%s" not allowed here') % value, None
if is_network_path(real_path(root, value)):
return T('Network path "%s" is not allowed here') % value, None
else:
return validate_default_if_empty(root, value, default)
else:
@@ -378,14 +378,15 @@ autodisconnect = OptionBool("misc", "auto_disconnect", True)
pre_script = OptionStr("misc", "pre_script", "None", validation=validate_script)
end_queue_script = OptionStr("misc", "end_queue_script", "None", validation=validate_script)
no_dupes = OptionNumber("misc", "no_dupes", 0)
no_series_dupes = OptionNumber("misc", "no_series_dupes", 0)
series_propercheck = OptionBool("misc", "series_propercheck", True)
no_series_dupes = OptionNumber("misc", "no_series_dupes", 0) # Kept for converting to no_smart_dupes
no_smart_dupes = OptionNumber("misc", "no_smart_dupes", 0)
dupes_propercheck = OptionBool("misc", "dupes_propercheck", True)
pause_on_pwrar = OptionNumber("misc", "pause_on_pwrar", 1)
ignore_samples = OptionBool("misc", "ignore_samples", False)
deobfuscate_final_filenames = OptionBool("misc", "deobfuscate_final_filenames", True)
auto_sort = OptionStr("misc", "auto_sort")
direct_unpack = OptionBool("misc", "direct_unpack", False)
propagation_delay = OptionNumber("misc", "propagation_delay", 0)
propagation_delay = OptionNumber("misc", "propagation_delay", 0, minval=0)
folder_rename = OptionBool("misc", "folder_rename", True)
replace_spaces = OptionBool("misc", "replace_spaces", False)
replace_underscores = OptionBool("misc", "replace_underscores", False)

View File

@@ -120,14 +120,15 @@ INTERFACE_PRIORITIES = {
}
STAGES = {
"Source": 0,
"Download": 1,
"Servers": 2,
"Repair": 3,
"Filejoin": 4,
"Unpack": 5,
"Deobfuscate": 6,
"Script": 7,
"RSS": 0,
"Source": 1,
"Download": 2,
"Servers": 3,
"Repair": 4,
"Filejoin": 5,
"Unpack": 6,
"Deobfuscate": 7,
"Script": 8,
}
VALID_ARCHIVES = (".zip", ".rar", ".7z")
@@ -162,14 +163,14 @@ class Status:
RUNNING = "Running" # PP: User's post processing script is running
VERIFYING = "Verifying" # PP: Job is being verified (by par2)
DELETED = "Deleted" # Q: Job has been deleted (and is almost gone)
PROP = "Propagating" # Q: Delayed download
PROPAGATING = "Propagating" # Q: Delayed download
class DuplicateStatus:
DUPLICATE = "Duplicate" # Simple duplicate
DUPLICATE_ALTERNATIVE = "Duplicate Alternative" # Alternative duplicate for a queued job
SERIES_DUPLICATE = "Series Duplicate" # Simple Series duplicate
SERIES_DUPLICATE_ALTERNATIVE = "Series Duplicate Alternative" # Alternative duplicate for a queued job
SMART_DUPLICATE = "Smart Duplicate" # Simple Series duplicate
SMART_DUPLICATE_ALTERNATIVE = "Smart Duplicate Alternative" # Alternative duplicate for a queued job
class AddNzbFileResult:

View File

@@ -82,19 +82,25 @@ class HistoryDB:
version = self.cursor.fetchone()["user_version"]
except IndexError:
version = 0
# Add any new columns added since last DB version
# Use "and" to stop when database has been reset due to corruption
if version < 1:
# Add any missing columns added since first DB version
# Use "and" to stop when database has been reset due to corruption
_ = (
self.execute("PRAGMA user_version = 1;")
and self.execute('ALTER TABLE "history" ADD COLUMN series TEXT;')
and self.execute('ALTER TABLE "history" ADD COLUMN md5sum TEXT;')
and self.execute("ALTER TABLE history ADD COLUMN series TEXT;")
and self.execute("ALTER TABLE history ADD COLUMN md5sum TEXT;")
)
if version < 2:
# Add any missing columns added since second DB version
# Use "and" to stop when database has been reset due to corruption
_ = self.execute("PRAGMA user_version = 2;") and self.execute(
'ALTER TABLE "history" ADD COLUMN password TEXT;'
"ALTER TABLE history ADD COLUMN password TEXT;"
)
if version < 3:
# Transfer data to new column (requires WHERE-hack), original column should be removed later
_ = (
self.execute("PRAGMA user_version = 3;")
and self.execute("ALTER TABLE history ADD COLUMN duplicate_key TEXT;")
and self.execute("UPDATE history SET duplicate_key = series WHERE 1 = 1;")
)
def execute(self, command: str, args: Sequence = (), save: bool = False) -> bool:
@@ -142,7 +148,7 @@ class HistoryDB:
"""Create a new (empty) database file"""
self.execute(
"""
CREATE TABLE "history" (
CREATE TABLE history (
"id" INTEGER PRIMARY KEY,
"completed" INTEGER NOT NULL,
"name" TEXT NOT NULL,
@@ -169,11 +175,12 @@ class HistoryDB:
"meta" TEXT,
"series" TEXT,
"md5sum" TEXT,
"password" TEXT
"password" TEXT,
"duplicate_key" TEXT
)
"""
)
self.execute("PRAGMA user_version = 2;")
self.execute("PRAGMA user_version = 3;")
def close(self):
"""Close database connection"""
@@ -263,7 +270,7 @@ 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, series, md5sum, password)
downloaded, fail_message, url_info, bytes, duplicate_key, md5sum, password)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
t,
save=True,
@@ -275,8 +282,8 @@ class HistoryDB:
start: Optional[int] = None,
limit: Optional[int] = None,
search: Optional[str] = None,
failed_only: int = 0,
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"""
@@ -285,18 +292,20 @@ class HistoryDB:
post = ""
if categories:
categories = ["*" if c == "Default" else c for c in categories]
post = " AND (CATEGORY = ?"
post += " OR CATEGORY = ? " * (len(categories) - 1)
post = " AND (category = ?"
post += " OR category = ? " * (len(categories) - 1)
post += ")"
command_args.extend(categories)
if statuses:
post += " AND (status = ?"
post += " OR status = ? " * (len(statuses) - 1)
post += ")"
command_args.extend(statuses)
if nzo_ids:
post += " AND (NZO_ID = ?"
post += " OR NZO_ID = ? " * (len(nzo_ids) - 1)
post += " AND (nzo_id = ?"
post += " OR nzo_id = ? " * (len(nzo_ids) - 1)
post += ")"
command_args.extend(nzo_ids)
if failed_only:
post += " AND STATUS = ?"
command_args.append(Status.FAILED)
cmd = "SELECT COUNT(*) FROM history WHERE name LIKE ?"
total_items = -1
@@ -321,11 +330,17 @@ class HistoryDB:
return items, total_items
def have_episode(self, series_key: str) -> bool:
"""Check whether History contains this series episode"""
def have_duplicate_key(self, duplicate_key: str) -> bool:
"""Check whether History contains this duplicate key"""
total = 0
if self.execute(
"""SELECT COUNT(*) FROM History WHERE series = ? AND STATUS != ?""", (series_key, Status.FAILED)
"""
SELECT COUNT(*)
FROM History
WHERE
duplicate_key = ? AND
STATUS != ?""",
(duplicate_key, Status.FAILED),
):
total = self.cursor.fetchone()["COUNT(*)"]
return total > 0
@@ -334,7 +349,12 @@ class HistoryDB:
"""Check whether this name or md5sum is already in History"""
total = 0
if self.execute(
"""SELECT COUNT(*) FROM History WHERE ( LOWER(name) = LOWER(?) OR md5sum = ? ) AND STATUS != ?""",
"""
SELECT COUNT(*)
FROM History
WHERE
( LOWER(name) = LOWER(?) OR md5sum = ? ) AND
STATUS != ?""",
(name, md5sum, Status.FAILED),
):
total = self.cursor.fetchone()["COUNT(*)"]
@@ -422,7 +442,7 @@ class HistoryDB:
def convert_search(search: str) -> str:
"""Convert classic wildcard to SQL wildcard"""
if not search:
if not search or not isinstance(search, str):
# Default value
search = ""
else:
@@ -466,7 +486,7 @@ def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_ou
report = "future" if nzo.futuretype else ""
# Make sure we have the duplicate key
nzo.set_duplicate_series_key()
nzo.set_duplicate_key()
return (
completed,
@@ -490,7 +510,7 @@ def build_history_info(nzo, workdir_complete: str, postproc_time: int, script_ou
nzo.fail_msg,
url_info,
nzo.bytes_downloaded,
nzo.duplicate_series_key,
nzo.duplicate_key,
nzo.md5sum,
nzo.correct_password,
)

View File

@@ -18,6 +18,8 @@
##############################################################################
# Decorators
##############################################################################
import time
import functools
from typing import Union, Callable
from threading import Lock, RLock, Condition
@@ -60,3 +62,24 @@ def NzbQueueLocker(func: Callable):
DOWNLOADER_CV.release()
return call_func
def cache_maintainer(clear_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)
"""
def inner(func):
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)
return wrapper
return inner

View File

@@ -252,7 +252,7 @@ class DirectUnpacker(threading.Thread):
self.nzo.set_unpack_info("Unpack", msg, self.cur_setname)
# Write current log and clear
logging.debug("DirectUnpack Unrar output %s", "\n".join(unrar_log))
logging.debug("DirectUnpack Unrar output: \n%s", "\n".join(unrar_log))
unrar_log = []
rarfiles = []
extracted = []
@@ -344,7 +344,7 @@ class DirectUnpacker(threading.Thread):
if linebuf:
unrar_log.append(platform_btou(linebuf.strip()))
if unrar_log:
logging.debug("DirectUnpack Unrar output %s", "\n".join(unrar_log))
logging.debug("DirectUnpack Unrar output: \n%s", "\n".join(unrar_log))
# Make more space
self.reset_active()
@@ -565,9 +565,9 @@ def abort_all():
def test_disk_performance():
"""Test the incomplete-dir performance and enable
Direct Unpack if good enough (> 40MB/s)
Direct Unpack if good enough (> 100MB/s)
"""
if diskspeedmeasure(sabnzbd.cfg.download_dir.get_path()) > 40:
if diskspeedmeasure(sabnzbd.cfg.download_dir.get_path()) > 100:
cfg.direct_unpack.set(True)
logging.warning(
T("Direct Unpack was automatically enabled.")

View File

@@ -223,7 +223,7 @@ class Server:
def request_addrinfo_blocking(self):
"""Blocking attempt to run getaddrinfo() and Happy Eyeballs for specified server"""
logging.debug("Retrieving server address information for %s", self.host)
self.addrinfo = happyeyeballs(self.host, self.port)
self.addrinfo = happyeyeballs(self.host, self.port, self.timeout)
if not self.addrinfo:
self.bad_cons += self.threads
# Notify next call to maybe_block_server
@@ -666,7 +666,7 @@ class Downloader(Thread):
else:
read = []
BPSMeter.reset()
time.sleep(1.0)
time.sleep(0.1)
self.max_chunk_size = _DEFAULT_CHUNK_SIZE
with DOWNLOADER_CV:
while (
@@ -888,6 +888,13 @@ class Downloader(Thread):
errormsg = T("Cannot connect to server %s [%s]") % (server.host, error.msg)
penalty = _PENALTY_UNKNOWN
block = True
# Set error for server and warn user if it was first time thrown
if errormsg and server.active and server.errormsg != errormsg:
server.errormsg = errormsg
logging.warning(errormsg)
# Take action on the problem
if block or (penalty and server.optional):
retry_article = False
if server.active:
@@ -900,11 +907,6 @@ class Downloader(Thread):
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)
# Set error for server and warn user if it was first time thrown
if errormsg and server.active and server.errormsg != errormsg:
server.errormsg = errormsg
logging.warning(errormsg)
return False
except Exception as err:
logging.error(

View File

@@ -33,6 +33,7 @@ import fnmatch
import stat
import ctypes
import random
import functools
from typing import Union, List, Tuple, Any, Dict, Optional, BinaryIO
try:
@@ -42,7 +43,7 @@ except ImportError:
pass
import sabnzbd
from sabnzbd.decorators import synchronized
from sabnzbd.decorators import synchronized, cache_maintainer
from sabnzbd.constants import FUTURE_Q_FOLDER, JOB_ADMIN, GIGI, DEF_FILE_MAX, IGNORED_FILES_AND_FOLDERS, DEF_LOG_FILE
from sabnzbd.encoding import correct_unknown_encoding, utob, ubtou
from sabnzbd.utils import rarfile
@@ -452,9 +453,23 @@ def same_directory(a: str, b: str) -> int:
return is_subfolder
def check_mount(path: str) -> bool:
"""Return False if volume isn't mounted on Linux or macOS
Retry 6 times with an interval of 1 sec.
def is_network_path(path: str) -> bool:
"""Check weither a path is a network path.
On Windows, use win32 functions to detect users that try to avoid this detection by using a mapped drive letter.
We don't check on Linux for mnt or media, since those could also be used for internal drives."""
path = clip_path(path)
if path.startswith(r"\\"):
return True
if sabnzbd.WIN32:
drive_letter, _ = os.path.splitdrive(path)
return win32file.GetDriveType(drive_letter) == win32file.DRIVE_REMOTE
return False
def mount_is_available(path: str) -> bool:
"""Return False if volume isn't mounted on Linux or macOS or
the network path isn't available on Windows.
Retry wait_ext_drive times with an interval of 1 sec.
"""
if sabnzbd.MACOS:
m = re.search(r"^(/Volumes/[^/]+)", path, re.I)
@@ -748,7 +763,7 @@ def create_all_dirs(path: str, apply_permissions: bool = False) -> Union[str, bo
@synchronized(DIR_LOCK)
def get_unique_dir(path: str, n: int = 0, create_dir: bool = True) -> Union[str, bool]:
"""Determine a unique folder or filename"""
if not check_mount(path):
if not mount_is_available(path):
return path
new_path = path
@@ -1068,43 +1083,15 @@ def diskspace_base(dir_to_check: str) -> Tuple[float, float]:
return 20.0, 10.0
# Store all results to speed things up
__DIRS_CHECKED = []
__DISKS_SAME = None
__LAST_DISK_RESULT = {"download_dir": (0.0, 0.0), "complete_dir": (0.0, 0.0)}
__LAST_DISK_CALL = 0
@cache_maintainer(clear_time=10)
@functools.lru_cache(maxsize=None)
def diskspace(force: bool = False) -> Dict[str, Tuple[float, float]]:
"""Wrapper to cache results"""
global __DIRS_CHECKED, __DISKS_SAME, __LAST_DISK_RESULT, __LAST_DISK_CALL
# Reset everything when folders changed
dirs_to_check = [sabnzbd.cfg.download_dir.get_path(), sabnzbd.cfg.complete_dir.get_path()]
if __DIRS_CHECKED != dirs_to_check:
__DIRS_CHECKED = dirs_to_check
__DISKS_SAME = None
__LAST_DISK_RESULT = {"download_dir": [], "complete_dir": []}
__LAST_DISK_CALL = 0
# When forced, ignore any cache to avoid problems in UI
if force:
__LAST_DISK_CALL = 0
# Check against cache
if time.time() > __LAST_DISK_CALL + 10.0:
# Same disk? Then copy-paste
__LAST_DISK_RESULT["download_dir"] = diskspace_base(sabnzbd.cfg.download_dir.get_path())
__LAST_DISK_RESULT["complete_dir"] = (
__LAST_DISK_RESULT["download_dir"] if __DISKS_SAME else diskspace_base(sabnzbd.cfg.complete_dir.get_path())
)
__LAST_DISK_CALL = time.time()
# Do we know if it's same disk?
if __DISKS_SAME is None:
__DISKS_SAME = __LAST_DISK_RESULT["download_dir"] == __LAST_DISK_RESULT["complete_dir"]
return __LAST_DISK_RESULT
"""Wrapper to keep results cached by cache_maintainer
If called with force=True, the wrapper will clear the results"""
return {
"download_dir": diskspace_base(sabnzbd.cfg.download_dir.get_path()),
"complete_dir": diskspace_base(sabnzbd.cfg.complete_dir.get_path()),
}
def get_new_id(prefix, folder, check_list=None):

View File

@@ -28,39 +28,20 @@ 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 sabnzbd import cfg as cfg
from sabnzbd.constants import DEF_TIMEOUT
from sabnzbd.decorators import cache_maintainer
# 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 minium specified in RFC 8305 is 10ms, so we use that.
CONNECTION_ATTEMPT_DELAY = 0.01
# The total time we want to wait for any result
MAXIMUM_RESOLUTION_TIME = 3
# While providers are afraid to add IPv6 to their standard hostnames
# we map a number of well known hostnames to their IPv6 alternatives.
# WARNING: Only add if the SSL-certificate allows both hostnames!
IPV6_MAPPING = {
"news.eweka.nl": "news6.eweka.nl",
"news.xlned.com": "news6.xlned.com",
"news.usenet.farm": "news6.usenet.farm",
"news.easynews.com": "news6.easynews.com",
"news.tweaknews.nl": "news6.tweaknews.nl",
"news.tweaknews.eu": "news6.tweaknews.eu",
"news.astraweb.com": "news6.astraweb.com",
"news.pureusenet.nl": "news6.pureusenet.nl",
"news.sunnyusenet.com": "news6.sunnyusenet.com",
"news.newshosting.com": "news6.newshosting.com",
"news.usenetserver.com": "news6.usenetserver.com",
"news.frugalusenet.com": "news-v6.frugalusenet.com",
"eunews.frugalusenet.com": "eunews-v6.frugalusenet.com",
}
# For typing and convenience!
@dataclass
@@ -78,12 +59,12 @@ class AddrInfo:
# Called by each thread
def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo):
def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo, timeout: int):
"""Connect to the ip, and put the result into the queue"""
try:
start = time.time()
s = socket.socket(addrinfo.family, addrinfo.type)
s.settimeout(MAXIMUM_RESOLUTION_TIME)
s.settimeout(timeout)
try:
s.connect(addrinfo.sockaddr)
result_queue.put(addrinfo)
@@ -106,50 +87,45 @@ def do_socket_connect(result_queue: queue.Queue, addrinfo: AddrInfo):
pass
def happyeyeballs(host: str, port: int) -> Optional[AddrInfo]:
@cache_maintainer(clear_time=10)
@functools.lru_cache(maxsize=None)
def happyeyeballs(host: str, port: int, timeout: int = DEF_TIMEOUT) -> 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"""
try:
# Get address information, by default both IPV4 and IPV6
check_hosts = [host]
family = socket.AF_UNSPEC
if not cfg.ipv6_servers():
family = socket.AF_INET
elif host in IPV6_MAPPING:
# See if we can add a IPv6 alternative
check_hosts.append(IPV6_MAPPING[host])
logging.info("Added alternative IPv6 address: %s", IPV6_MAPPING[host])
ipv4_addrinfo = []
ipv6_addrinfo = []
last_canonname = ""
for check_host in check_hosts:
try:
for addrinfo in socket.getaddrinfo(
check_host, port, family, socket.SOCK_STREAM, flags=socket.AI_CANONNAME
):
# Convert to AddrInfo
addrinfo = AddrInfo(*addrinfo)
# The canonname is only reported once per alias
if addrinfo.canonname:
last_canonname = addrinfo.canonname
elif last_canonname:
addrinfo.canonname = last_canonname
try:
for addrinfo in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, flags=socket.AI_CANONNAME):
# Convert to AddrInfo
addrinfo = AddrInfo(*addrinfo)
# Put it in the right list for further processing
# But prevent adding duplicate items to the lists
if addrinfo not in ipv6_addrinfo and addrinfo not in ipv4_addrinfo:
if addrinfo.family == socket.AddressFamily.AF_INET6:
ipv6_addrinfo.append(addrinfo)
else:
ipv4_addrinfo.append(addrinfo)
except:
# Did we fail on the first getaddrinfo already?
# Otherwise, we failed on the IPv6 alternative address, and those failures can be ignored
if not ipv4_addrinfo and not ipv6_addrinfo:
raise
# The canonname is only reported once per alias
if addrinfo.canonname:
last_canonname = addrinfo.canonname
elif last_canonname:
addrinfo.canonname = last_canonname
# Put it in the right list for further processing
# But prevent adding duplicate items to the lists
if addrinfo not in ipv6_addrinfo and addrinfo not in ipv4_addrinfo:
if addrinfo.family == socket.AddressFamily.AF_INET6:
ipv6_addrinfo.append(addrinfo)
else:
ipv4_addrinfo.append(addrinfo)
except:
# Did we fail on the first getaddrinfo already?
# Otherwise, we failed on the IPv6 alternative address, and those failures can be ignored
if not ipv4_addrinfo and not ipv6_addrinfo:
raise
logging.debug(
"Available addresses for %s (port=%d): %d IPv4 and %d IPv6",
@@ -165,7 +141,7 @@ def happyeyeballs(host: str, port: int) -> Optional[AddrInfo]:
addr_tried = 0
result: Optional[AddrInfo] = None
for addrinfo in roundrobin(ipv6_addrinfo, ipv4_addrinfo):
threading.Thread(target=do_socket_connect, args=(result_queue, addrinfo), daemon=True).start()
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)
@@ -179,7 +155,7 @@ def happyeyeballs(host: str, port: int) -> Optional[AddrInfo]:
if not result:
try:
# Reduce waiting time by time already spent
result = result_queue.get(timeout=MAXIMUM_RESOLUTION_TIME - addr_tried * CONNECTION_ATTEMPT_DELAY)
result = result_queue.get(timeout=timeout - addr_tried * CONNECTION_ATTEMPT_DELAY)
except queue.Empty:
raise ConnectionError("No addresses could be resolved")

View File

@@ -21,7 +21,7 @@ sabnzbd.interface - webinterface
import os
import time
from datetime import datetime
import datetime
import cherrypy
import logging
import urllib.parse
@@ -80,6 +80,7 @@ from sabnzbd.constants import (
GUESSIT_SORT_TYPES,
VALID_NZB_FILES,
VALID_ARCHIVES,
DEF_TIMEOUT,
)
from sabnzbd.lang import list_languages
from sabnzbd.api import (
@@ -781,8 +782,8 @@ SWITCH_LIST = (
"fail_hopeless_jobs",
"enable_all_par",
"enable_recursive",
"no_series_dupes",
"series_propercheck",
"no_smart_dupes",
"dupes_propercheck",
"script_can_fail",
"unwanted_extensions",
"action_on_unwanted_extensions",
@@ -1151,7 +1152,7 @@ def handle_server(kwargs, root=None, new_svr=False):
kwargs["connections"] = "1"
if kwargs.get("enable") == "1":
if not happyeyeballs(host, int_conv(port)):
if not happyeyeballs(host, int_conv(port), int_conv(kwargs.get("timeout"), default=DEF_TIMEOUT)):
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port), ajax)
# Default server name is just the host name
@@ -1485,16 +1486,24 @@ class ConfigRss:
"""Download NZB from provider (Download button)"""
feed = kwargs.get("feed")
url = kwargs.get("url")
nzbname = kwargs.get("nzbname")
att = sabnzbd.RSSReader.lookup_url(feed, url)
if att:
if att := sabnzbd.RSSReader.lookup_url(feed, url):
nzbname = kwargs.get("nzbname")
pp = att.get("pp")
cat = att.get("cat")
script = att.get("script")
prio = att.get("prio")
priority = att.get("prio")
if url:
sabnzbd.urlgrabber.add_url(url, pp, script, cat, prio, nzbname)
logging.info("Adding %s (%s) to queue", url, nzbname)
sabnzbd.urlgrabber.add_url(
url,
pp=pp,
script=script,
cat=cat,
priority=priority,
nzbname=nzbname,
nzo_info={"RSS": feed},
)
# Need to pass the title instead
sabnzbd.RSSReader.flag_downloaded(feed, url)
raise rssRaiser(self.__root, kwargs)
@@ -1935,7 +1944,7 @@ def GetRssLog(feed):
# And we add extra fields for sorting
if job.get("age", 0):
job["age_ms"] = (job["age"] - datetime.utcfromtimestamp(0)).total_seconds()
job["age_ms"] = (job["age"] - datetime.datetime(1970, 1, 1)).total_seconds()
job["age"] = calc_age(job["age"], True)
else:
job["age_ms"] = ""
@@ -2118,7 +2127,7 @@ class ConfigNotify:
@secured_expose(check_configlock=True)
def index(self, **kwargs):
conf = build_header(sabnzbd.WEB_DIR_CONFIG)
conf["notify_types"] = sabnzbd.notifier.NOTIFICATION
conf["notify_types"] = sabnzbd.notifier.NOTIFICATION_TYPES
conf["categories"] = list_cats(False)
conf["have_ntfosd"] = sabnzbd.notifier.have_ntfosd()
conf["have_ncenter"] = sabnzbd.MACOS and sabnzbd.FOUNDATION

110
sabnzbd/internetspeed.py Normal file
View File

@@ -0,0 +1,110 @@
#!/usr/bin/python3 -OO
# Copyright 2007-2023 The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
sabnzbd.internetspeed - Measure internet bandwidth using sabctools routines
"""
import sys
import logging
import socket
import ssl
import time
import threading
from typing import Dict
import sabctools
import sabnzbd
from sabnzbd.happyeyeballs import happyeyeballs
TEST_HOSTNAME = "sabnzbd.org"
TEST_PORT = 443
TEST_FILE = "/tests/internetspeed/100MB.bin"
TEST_FILE_SIZE = 100 * 1024 * 1024
TEST_REQUEST = f"GET {TEST_FILE} HTTP/1.1\nHost: {TEST_HOSTNAME}\nUser-Agent: SABnzbd/{sabnzbd.__version__}\n\n"
SOCKET_TIMEOUT = 3
BUFFER_SIZE = 5 * 1024 * 1024 # Each connection will allocate its own buffer, so mind the memory usage!
NR_CONNECTIONS = 5
TIME_LIMIT = 3
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))
start_time = time.perf_counter()
diff_time = 0
data_received = 0
while diff_time < TIME_LIMIT:
if data_received < TEST_FILE_SIZE:
try:
if new_bytes := sabctools.unlocked_ssl_recv_into(secure_sock, empty_buffer):
# Update the speed after every loop
diff_time = time.perf_counter() - start_time
data_received += new_bytes
socket_speed[secure_sock] = data_received / diff_time
else:
break
except ssl.SSLWantReadError:
time.sleep(0)
else:
break
try:
secure_sock.close()
except socket.error:
# In case socket was closed unexpectedly already
pass
def internetspeed(test_time_limit: int = TIME_LIMIT) -> float:
"""Measure internet speed from a test-download using our optimized SSL-code"""
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
socket_speed = {}
try:
for _ in range(NR_CONNECTIONS):
addrinfo = happyeyeballs(TEST_HOSTNAME, TEST_PORT, SOCKET_TIMEOUT)
sock = socket.socket(addrinfo.family, addrinfo.type)
sock.settimeout(SOCKET_TIMEOUT)
sock.connect(addrinfo.sockaddr)
secure_sock = context.wrap_socket(sock, server_hostname=TEST_HOSTNAME)
secure_sock.setblocking(False)
socket_speed[secure_sock] = 0
for secure_sock in socket_speed:
threading.Thread(target=internetspeed_worker, args=(secure_sock, socket_speed), daemon=True).start()
except Exception:
logging.info("Internet Bandwidth connection failure", exc_info=True)
return 0.0
# We let the workers finish
time.sleep(test_time_limit + 0.5)
speed = sum(socket_speed.values()) / 1024 / 1024
logging.debug("Internet Bandwidth = %.2f MB/s - %.2f Mbps", speed, speed * 8.05)
return speed
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
internetspeed()

View File

@@ -185,6 +185,7 @@ ENV_NZO_FIELDS = [
"cat",
"correct_password",
"duplicate",
"duplicate_key",
"encrypted",
"fail_msg",
"filename",
@@ -2160,23 +2161,23 @@ def pre_queue(nzo: NzbObject, pp, cat):
str(nzo.bytes),
" ".join(nzo.groups),
]
command.extend(list(sabnzbd.sorting.analyse_show(nzo.final_name).values()))
command = [fix(arg) for arg in command]
# Fields not in the NZO directly
show_analysis = sabnzbd.sorting.BasicAnalyzer(nzo.final_name)
extra_env_fields = {
"groups": " ".join(nzo.groups),
"show_name": command[8],
"show_season": command[9],
"show_episode": command[10],
"show_episode_name": command[11],
"proper": command[12],
"resolution": command[13],
"decade": command[14],
"year": command[15],
"month": command[16],
"day": command[17],
"type": command[18],
"title": show_analysis.info.get("title", ""),
"season": show_analysis.info.get("season_num", ""),
"episode": show_analysis.info.get("episode_num", ""),
"episode_name": show_analysis.info.get("ep_name", ""),
"is_proper": show_analysis.is_proper(),
"resolution": show_analysis.info.get("resolution", ""),
"decade": show_analysis.info.get("decade", ""),
"year": show_analysis.info.get("year", ""),
"month": show_analysis.info.get("month", ""),
"day": show_analysis.info.get("day", ""),
"job_type": show_analysis.type,
}
try:

View File

@@ -28,6 +28,7 @@ import urllib.parse
import http.client
import json
from threading import Thread
from typing import Optional, Dict
import sabnzbd
import sabnzbd.cfg
@@ -36,9 +37,17 @@ from sabnzbd.filesystem import make_script_path
from sabnzbd.misc import build_and_run_command
from sabnzbd.newsunpack import create_env
if sabnzbd.FOUNDATION:
import Foundation
import objc
if sabnzbd.WIN32:
try:
from win32comext.shell import shell
from windows_toasts import InteractableWindowsToaster, Toast, ToastActivatedEventArgs, ToastButton
# Set a custom AUMID to display the right icon, it is written to the registry by the installer
shell.SetCurrentProcessExplicitAppUserModelID("SABnzbd")
_HAVE_WINDOWS_TOASTER = True
except:
# Only supported on Windows 10 and above
_HAVE_WINDOWS_TOASTER = False
try:
import notify2
@@ -57,7 +66,7 @@ except:
# Define translatable message table
##############################################################################
TT = lambda x: x
NOTIFICATION = {
NOTIFICATION_TYPES = {
"startup": TT("Startup/Shutdown"), #: Notification
"pause_resume": TT("Pause") + "/" + TT("Resume"), #: Notification
"download": TT("Added NZB"), #: Notification
@@ -72,37 +81,36 @@ NOTIFICATION = {
"other": TT("Other Messages"), #: Notification
}
def get_icon():
icon = os.path.join(sabnzbd.DIR_PROG, "icons", "sabnzbd.ico")
with open(icon, "rb") as fp:
return fp.read()
NOTIFICATION_ACTIONS = {
"open_folder": TT("Open folder"), #: Notification action
"open_complete": TT("Open complete folder"), #: Notification action
}
def have_ntfosd():
def have_ntfosd() -> bool:
"""Return if any PyNotify (notify2) support is present"""
return bool(_HAVE_NTFOSD)
def check_classes(gtype, section):
"""Check if `gtype` is enabled in `section`"""
def check_classes(notification_type: str, section: str) -> bool:
"""Check if `notification_type` is enabled in `section`"""
try:
return sabnzbd.config.get_config(section, "%s_prio_%s" % (section, gtype))() > 0
return sabnzbd.config.get_config(section, "%s_prio_%s" % (section, notification_type))() > 0
except TypeError:
logging.debug("Incorrect Notify option %s:%s_prio_%s", section, section, gtype)
logging.debug("Incorrect Notify option %s:%s_prio_%s", section, section, notification_type)
return False
def get_prio(gtype, section):
"""Check prio of `gtype` in `section`"""
def get_prio(notification_type: str, section: str) -> int:
"""Check prio of `notification_type` in `section`"""
try:
return sabnzbd.config.get_config(section, "%s_prio_%s" % (section, gtype))()
return sabnzbd.config.get_config(section, "%s_prio_%s" % (section, notification_type))()
except TypeError:
logging.debug("Incorrect Notify option %s:%s_prio_%s", section, section, gtype)
logging.debug("Incorrect Notify option %s:%s_prio_%s", section, section, notification_type)
return -1000
def check_cat(section, job_cat, keyword=None):
def check_cat(section: str, job_cat: str, keyword: Optional[str] = None) -> bool:
"""Check if `job_cat` is enabled in `section`.
* = All, if no other categories selected.
"""
@@ -118,42 +126,48 @@ def check_cat(section, job_cat, keyword=None):
return True
def send_notification(title, msg, gtype, job_cat=None):
def send_notification(
title: str,
msg: str,
notification_type: str,
job_cat: Optional[str] = None,
actions: Optional[Dict[str, str]] = None,
):
"""Send Notification message"""
logging.info("Sending notification: %s - %s (type=%s, job_cat=%s)", title, msg, gtype, job_cat)
logging.info("Sending notification: %s - %s (type=%s, job_cat=%s)", title, msg, notification_type, job_cat)
# Notification Center
if sabnzbd.MACOS and sabnzbd.cfg.ncenter_enable():
if check_classes(gtype, "ncenter") and check_cat("ncenter", job_cat):
send_notification_center(title, msg, gtype)
if check_classes(notification_type, "ncenter") and check_cat("ncenter", job_cat):
send_notification_center(title, msg, notification_type, actions)
# Windows
if sabnzbd.WIN32 and sabnzbd.cfg.acenter_enable():
if check_classes(gtype, "acenter") and check_cat("acenter", job_cat):
send_windows(title, msg, gtype)
if check_classes(notification_type, "acenter") and check_cat("acenter", job_cat):
send_windows(title, msg, notification_type, actions)
# Prowl
if sabnzbd.cfg.prowl_enable() and check_cat("prowl", job_cat):
if sabnzbd.cfg.prowl_apikey():
Thread(target=send_prowl, args=(title, msg, gtype)).start()
Thread(target=send_prowl, args=(title, msg, notification_type)).start()
# Pushover
if sabnzbd.cfg.pushover_enable() and check_cat("pushover", job_cat):
if sabnzbd.cfg.pushover_token():
Thread(target=send_pushover, args=(title, msg, gtype)).start()
Thread(target=send_pushover, args=(title, msg, notification_type)).start()
# Pushbullet
if sabnzbd.cfg.pushbullet_enable() and check_cat("pushbullet", job_cat):
if sabnzbd.cfg.pushbullet_apikey() and check_classes(gtype, "pushbullet"):
Thread(target=send_pushbullet, args=(title, msg, gtype)).start()
if sabnzbd.cfg.pushbullet_apikey() and check_classes(notification_type, "pushbullet"):
Thread(target=send_pushbullet, args=(title, msg, notification_type)).start()
# Notification script.
if sabnzbd.cfg.nscript_enable() and check_cat("nscript", job_cat):
if sabnzbd.cfg.nscript_script():
Thread(target=send_nscript, args=(title, msg, gtype)).start()
Thread(target=send_nscript, args=(title, msg, notification_type)).start()
# NTFOSD
if have_ntfosd() and sabnzbd.cfg.ntfosd_enable():
if check_classes(gtype, "ntfosd") and check_cat("ntfosd", job_cat):
if check_classes(notification_type, "ntfosd") and check_cat("ntfosd", job_cat):
send_notify_osd(title, msg)
@@ -193,25 +207,26 @@ def send_notify_osd(title, message):
return error
def send_notification_center(title, msg, gtype):
"""Send message to macOS Notification Center"""
def send_notification_center(title: str, msg: str, notification_type: str, actions: Optional[Dict[str, str]] = None):
"""Send message to macOS Notification Center.
Only 1 button is possible on macOS!"""
try:
NSUserNotification = objc.lookUpClass("NSUserNotification")
NSUserNotificationCenter = objc.lookUpClass("NSUserNotificationCenter")
notification = NSUserNotification.alloc().init()
notification.setTitle_(title)
notification.setSubtitle_(T(NOTIFICATION.get(gtype, "other")))
notification.setInformativeText_(msg)
notification.setSoundName_("NSUserNotificationDefaultSoundName")
notification.setDeliveryDate_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(0, Foundation.NSDate.date()))
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
subtitle = T(NOTIFICATION_TYPES.get(notification_type, "other"))
button_text = button_action = None
if actions:
for action in actions:
button_text = NOTIFICATION_ACTIONS[action]
button_action = actions[action]
break
sabnzbd.MACOSTRAY.send_notification(title, subtitle, msg, button_text, button_action)
except:
logging.info(T("Failed to send macOS notification"))
logging.debug("Traceback: ", exc_info=True)
return T("Failed to send macOS notification")
def send_prowl(title, msg, gtype, force=False, test=None):
def send_prowl(title, msg, notification_type, force=False, test=None):
"""Send message to Prowl"""
if test:
@@ -221,10 +236,10 @@ def send_prowl(title, msg, gtype, force=False, test=None):
if not apikey:
return T("Cannot send, missing required data")
title = T(NOTIFICATION.get(gtype, "other"))
title = T(NOTIFICATION_TYPES.get(notification_type, "other"))
title = urllib.parse.quote(utob(title))
msg = urllib.parse.quote(utob(msg))
prio = get_prio(gtype, "prowl")
prio = get_prio(notification_type, "prowl")
if force:
prio = 0
@@ -244,7 +259,7 @@ def send_prowl(title, msg, gtype, force=False, test=None):
return ""
def send_pushover(title, msg, gtype, force=False, test=None):
def send_pushover(title, msg, notification_type, force=False, test=None):
"""Send message to pushover"""
if test:
@@ -260,8 +275,8 @@ def send_pushover(title, msg, gtype, force=False, test=None):
if not apikey or not userkey:
return T("Cannot send, missing required data")
title = T(NOTIFICATION.get(gtype, "other"))
prio = get_prio(gtype, "pushover")
title = T(NOTIFICATION_TYPES.get(notification_type, "other"))
prio = get_prio(notification_type, "pushover")
if force:
prio = 1
@@ -311,7 +326,7 @@ def do_send_pushover(body):
return T("Failed to send pushover message")
def send_pushbullet(title, msg, gtype, force=False, test=None):
def send_pushbullet(title, msg, notification_type, force=False, test=None):
"""Send message to Pushbullet"""
if test:
@@ -323,7 +338,7 @@ def send_pushbullet(title, msg, gtype, force=False, test=None):
if not apikey:
return T("Cannot send, missing required data")
title = "SABnzbd: " + T(NOTIFICATION.get(gtype, "other"))
title = "SABnzbd: " + T(NOTIFICATION_TYPES.get(notification_type, "other"))
try:
conn = http.client.HTTPSConnection("api.pushbullet.com:443")
@@ -346,7 +361,7 @@ def send_pushbullet(title, msg, gtype, force=False, test=None):
return ""
def send_nscript(title, msg, gtype, force=False, test=None):
def send_nscript(title, msg, notification_type, force=False, test=None):
"""Run user's notification script"""
if test:
script = test.get("nscript_script")
@@ -357,15 +372,23 @@ def send_nscript(title, msg, gtype, force=False, test=None):
if not script:
return T("Cannot send, missing required data")
title = "SABnzbd: " + T(NOTIFICATION.get(gtype, "other"))
title = "SABnzbd: " + T(NOTIFICATION_TYPES.get(notification_type, "other"))
if force or check_classes(gtype, "nscript"):
if force or check_classes(notification_type, "nscript"):
script_path = make_script_path(script)
if script_path:
ret = -1
output = None
try:
p = build_and_run_command([script_path, gtype, title, msg], env=create_env(extra_env_fields=env_params))
p = build_and_run_command(
[
script_path,
notification_type,
title,
msg,
],
env=create_env(extra_env_fields=env_params),
)
output = p.stdout.read()
ret = p.wait()
except:
@@ -382,12 +405,22 @@ def send_nscript(title, msg, gtype, force=False, test=None):
return ""
def send_windows(title, msg, gtype):
if sabnzbd.WINTRAY and not sabnzbd.WINTRAY.terminate:
try:
def send_windows(title: str, msg: str, notification_type: str, actions: Optional[Dict[str, str]] = None):
try:
if _HAVE_WINDOWS_TOASTER:
notification_sender = InteractableWindowsToaster("SABnzbd", notifierAUMID="SABnzbd")
toast_notification = Toast([title, msg], group=notification_type, launch_action=sabnzbd.BROWSER_URL)
# Add any buttons
if actions:
for action in actions:
toast_notification.AddAction(ToastButton(NOTIFICATION_ACTIONS[action], launch=actions[action]))
notification_sender.show_toast(toast_notification)
elif sabnzbd.WINTRAY and not sabnzbd.WINTRAY.terminate:
sabnzbd.WINTRAY.sendnotification(title, msg)
except:
logging.info(T("Failed to send Windows notification"))
logging.debug("Traceback: ", exc_info=True)
return T("Failed to send Windows notification")
except:
logging.info(T("Failed to send Windows notification"))
logging.debug("Traceback: ", exc_info=True)
return T("Failed to send Windows notification")
return None

View File

@@ -422,7 +422,7 @@ def nzbfile_parser(full_nzb_path: str, nzo):
# Get segments
raw_article_db = {}
file_bytes = 0
if element.find("segments"):
if len(element.find("segments")):
for segment in element.find("segments").iter("segment"):
try:
article_id = segment.text

View File

@@ -696,15 +696,13 @@ class NzbQueue:
"""Get next article for jobs in the queue
Not locked for performance, since it only reads the queue
"""
# Pre-calculate propagation delay
propagation_delay = float(cfg.propagation_delay() * 60)
for nzo in self.__nzo_list:
# Not when queue paused, individually paused, or when waiting for propagation
# Force items will always download
if (
not sabnzbd.Downloader.paused
and nzo.status not in (Status.PAUSED, Status.GRABBING)
and (not propagation_delay or (nzo.avg_stamp + propagation_delay) < time.time())
and not nzo.propagation_delay_left
) or nzo.priority == FORCE_PRIORITY:
if not nzo.server_in_try_list(server):
if articles := nzo.get_articles(server, servers, fetch_limit):
@@ -807,12 +805,12 @@ class NzbQueue:
search: Optional[str] = None,
categories: Optional[List[str]] = None,
priorities: Optional[List[str]] = None,
statuses: Optional[List[str]] = None,
nzo_ids: Optional[List[str]] = None,
start: int = 0,
limit: int = 0,
) -> Tuple[int, int, int, List[NzbObject], int, int]:
"""Return list of queued jobs,
optionally filtered by 'search' and 'nzo_ids', and limited by start and limit.
"""Return list of queued jobs, optionally filtered and limited by start and limit.
Not locked for performance, only reads the queue
"""
if search:
@@ -841,6 +839,10 @@ class NzbQueue:
continue
if priorities and nzo.priority not in priorities:
continue
if statuses and nzo.status not in statuses:
# Propagation status is set only by the API-code, so has to be filtered specially
if not (Status.PROPAGATING in statuses and nzo.propagation_delay_left):
continue
if nzo_ids and nzo.nzo_id not in nzo_ids:
continue
@@ -863,12 +865,10 @@ class NzbQueue:
return bytes_left
def is_empty(self) -> bool:
empty = True
for nzo in self.__nzo_list:
if not nzo.futuretype and nzo.status != Status.PAUSED:
empty = False
break
return empty
return False
return True
def stop_idle_jobs(self):
"""Detect jobs that have zero files left and send them to post processing"""
@@ -953,28 +953,28 @@ class NzbQueue:
lname = name.lower()
for nzo in self.__nzo_list + sabnzbd.PostProcessor.get_queue():
# Skip any jobs already marked as duplicate, to prevent double-triggers
if not nzo.duplicate:
# URL's do not have an MD5!
if nzo.final_name.lower() == lname or (nzo.md5sum and md5sum and nzo.md5sum == md5sum):
return True
# URL's do not have an MD5!
if not nzo.duplicate and (
nzo.final_name.lower() == lname or (nzo.md5sum and md5sum and nzo.md5sum == md5sum)
):
return True
return False
@NzbQueueLocker
def have_episode(self, series_key: str) -> bool:
"""Check whether this episode of the series is already
def have_duplicate_key(self, duplicate_key: str) -> bool:
"""Check whether this duplicate key is already
in the queue or the post-processing queue"""
for nzo in self.__nzo_list:
for nzo in self.__nzo_list + sabnzbd.PostProcessor.get_queue():
# Skip any jobs already marked as duplicate, to prevent double-triggers
if not nzo.duplicate:
if nzo.duplicate_series_key == series_key:
return True
if not nzo.duplicate and nzo.duplicate_key == duplicate_key:
return True
return False
@NzbQueueLocker
def handle_duplicate_alternatives(self, finished_nzo: NzbObject, success: bool):
"""Remove matching duplicates if the first job succeeded,
or start the next alternative if the job failed"""
if not cfg.no_dupes() and not cfg.no_series_dupes():
if not cfg.no_dupes() and not cfg.no_smart_dupes():
return
# Unfortunately we need a copy, since we might remove items from the list
@@ -986,16 +986,16 @@ class NzbQueue:
if (
nzo.final_name.lower() == finished_nzo.final_name.lower()
or (nzo.md5sum and finished_nzo.md5sum and nzo.md5sum == finished_nzo.md5sum)
) or (
nzo.duplicate_series_key
and finished_nzo.duplicate_series_key
and nzo.duplicate_series_key == finished_nzo.duplicate_series_key
):
) or (nzo.duplicate_key and finished_nzo.duplicate_key and nzo.duplicate_key == finished_nzo.duplicate_key):
# Start the next alternative
if not success:
logging.info("Resuming duplicate alternative %s for ", nzo.final_name, finished_nzo.final_name)
# Don't just resume if only set to tag
if (nzo.duplicate == DuplicateStatus.DUPLICATE_ALTERNATIVE and cfg.no_dupes() != 4) or (
nzo.duplicate == DuplicateStatus.SMART_DUPLICATE_ALTERNATIVE and cfg.no_smart_dupes() != 4
):
logging.info("Resuming duplicate alternative %s for ", nzo.final_name, finished_nzo.final_name)
nzo.resume()
nzo.duplicate = None
nzo.resume()
return
# Take action on the alternatives to the duplicate
@@ -1003,13 +1003,11 @@ class NzbQueue:
# 2 = Pause
# 3 = Fail (move to History)
# 4 = Tag
series_duplicate = nzo.duplicate == DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE
if (not series_duplicate and cfg.no_dupes() == 1) or (series_duplicate and cfg.no_series_dupes() == 1):
smart_duplicate = nzo.duplicate == DuplicateStatus.SMART_DUPLICATE_ALTERNATIVE
if (not smart_duplicate and cfg.no_dupes() == 1) or (smart_duplicate and cfg.no_smart_dupes() == 1):
duplicate_warning(T('Ignoring duplicate NZB "%s"'), nzo.final_name)
self.remove(nzo.nzo_id)
elif (not series_duplicate and cfg.no_dupes() == 3) or (
series_duplicate and cfg.no_series_dupes() == 3
):
elif (not smart_duplicate and cfg.no_dupes() == 3) or (smart_duplicate and cfg.no_smart_dupes() == 3):
duplicate_warning(T('Failing duplicate NZB "%s"'), nzo.final_name)
nzo.fail_msg = T("Duplicate NZB")
self.fail_to_history(nzo)
@@ -1019,7 +1017,7 @@ class NzbQueue:
if nzo.duplicate == DuplicateStatus.DUPLICATE_ALTERNATIVE:
nzo.duplicate = DuplicateStatus.DUPLICATE
else:
nzo.duplicate = DuplicateStatus.SERIES_DUPLICATE
nzo.duplicate = DuplicateStatus.SMART_DUPLICATE
return
def __repr__(self):

View File

@@ -537,6 +537,7 @@ NzbObjectSaver = (
"url",
"groups",
"avg_date",
"propagation_delay",
"md5of16k",
"extrapars",
"par2packs",
@@ -563,7 +564,7 @@ NzbObjectSaver = (
"encrypted",
"bad_articles",
"duplicate",
"duplicate_series_key",
"duplicate_key",
"oversized",
"precheck",
"incomplete",
@@ -655,6 +656,7 @@ class NzbObject(TryList):
self.groups = []
self.avg_date = datetime.datetime(1970, 1, 1, 1, 0)
self.avg_stamp = 0.0 # Avg age in seconds (calculated from avg_age)
self.propagation_delay: Optional[float] = None # Set during parsing
self.correct_password: Optional[str] = None
# Bookkeeping values
@@ -691,7 +693,7 @@ class NzbObject(TryList):
self.nzo_id: Optional[str] = None
self.duplicate: Optional[str] = None
self.duplicate_series_key: Optional[str] = None
self.duplicate_key: Optional[str] = None
self.futuretype = futuretype
self.removed_from_queue = False
@@ -832,6 +834,10 @@ class NzbObject(TryList):
if not self.password and self.meta.get("password"):
self.password = self.meta.get("password", [None])[0]
# Check if we expect propagation delay
if (propagation_delay := self.avg_stamp + float(cfg.propagation_delay() * 60)) > time.time():
self.propagation_delay = propagation_delay
# Run user pre-queue script if set and valid
if not reuse and make_script_path(cfg.pre_script()):
# Call the script
@@ -860,6 +866,7 @@ class NzbObject(TryList):
# also have a valid value of 0, which shouldn't be ignored
if name:
self.set_final_name_and_scan_password(name)
self.duplicate_check(repeat=True)
try:
pp = int(pq_pp)
except:
@@ -1291,6 +1298,17 @@ class NzbObject(TryList):
else:
return opts_to_pp(self.repair, self.unpack, self.delete)
@property
def propagation_delay_left(self) -> int:
"""Returns number of propagation minutes remaining, if any.
It could return seconds, but the numerical value is only used in the queue."""
if self.propagation_delay:
if (time_left := self.propagation_delay - time.time()) > 0:
return int(time_left / 60 + 0.5)
# We can remove the value, to skip any further calculations
self.propagation_delay = None
return 0
def set_pp(self, value: int):
self.repair, self.unpack, self.delete = pp_to_opts(value)
logging.info("Set pp=%s for job %s", value, self.final_name)
@@ -1344,9 +1362,12 @@ class NzbObject(TryList):
def labels(self):
"""Return (translated) labels of job"""
labels = []
if self.duplicate in (DuplicateStatus.DUPLICATE, DuplicateStatus.SERIES_DUPLICATE):
if self.duplicate in (DuplicateStatus.DUPLICATE, DuplicateStatus.SMART_DUPLICATE):
labels.append(T("DUPLICATE"))
if self.duplicate in (DuplicateStatus.DUPLICATE_ALTERNATIVE, DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE):
if self.duplicate in (
DuplicateStatus.DUPLICATE_ALTERNATIVE,
DuplicateStatus.SMART_DUPLICATE_ALTERNATIVE,
):
labels.append(T("ALTERNATIVE"))
if self.encrypted > 0:
labels.append(T("ENCRYPTED"))
@@ -1364,10 +1385,8 @@ class NzbObject(TryList):
labels.append(T("WAIT %s sec") % dif)
# Propagation delay label
propagation_delay = float(cfg.propagation_delay() * 60)
if propagation_delay and self.avg_stamp + propagation_delay > time.time() and self.priority != FORCE_PRIORITY:
wait_time = int((self.avg_stamp + propagation_delay - time.time()) / 60 + 0.5)
labels.append(T("PROPAGATING %s min") % wait_time) # Queue indicator while waiting for propagation of post
if self.propagation_delay_left and self.priority != FORCE_PRIORITY:
labels.append(T("PROPAGATING %s min") % self.propagation_delay_left) # Queue indicator: propagation of post
return labels
@@ -1565,6 +1584,10 @@ class NzbObject(TryList):
if dups:
download_msgs.append(T("%s articles had non-matching duplicates") % dups)
self.set_unpack_info("Download", "<br/>".join(download_msgs), unique=True)
# Add RSS source
if rss_feed := self.nzo_info.get("RSS"):
self.set_unpack_info("RSS", rss_feed, unique=True)
self.set_unpack_info("Source", self.url or self.filename, unique=True)
@synchronized(NZO_LOCK)
@@ -1885,28 +1908,45 @@ class NzbObject(TryList):
else:
nzf_ids.remove(nzf_id)
def set_duplicate_series_key(self):
def set_duplicate_key(self):
"""Shorthand to set the key once"""
if not self.duplicate_series_key:
show_analysis = sabnzbd.sorting.analyse_show(self.final_name)
if show_analysis["job_type"] == "tv":
series, season, episode, is_proper = (
show_analysis[key] for key in ("title", "season", "episode", "is_proper")
)
# Ignore proper results if not desired
if not cfg.series_propercheck():
is_proper = False
if not self.duplicate_key:
show_analysis = sabnzbd.sorting.BasicAnalyzer(self.final_name)
# We allow 1 proper result to bypass duplicate detection
self.duplicate_series_key = f"{series.lower()}/{season}/{episode}{f'/{is_proper}' if is_proper else ''}"
# We can only set a duplicate key for these types
if show_analysis.type not in ("tv", "movie", "date"):
return
def duplicate_check(self):
# The key always includes the title, for movies we don't add anything else
duplicate_key_items = [show_analysis.info.get("title", "")]
if show_analysis.type == "tv":
# For TV-shows we add the season and episode
duplicate_key_items.append(str(show_analysis.info.get("season_num", "")))
duplicate_key_items.append(str(show_analysis.info.get("episode_num", "")))
elif show_analysis.type == "date":
# Add date
duplicate_key_items.append(str(show_analysis.info.get("year", "")))
duplicate_key_items.append(str(show_analysis.info.get("month", "")))
duplicate_key_items.append(str(show_analysis.info.get("day", "")))
# We allow 1 proper result to bypass the detection, if desired
if show_analysis.is_proper() and cfg.dupes_propercheck():
duplicate_key_items.append("proper")
self.duplicate_key = "/".join(duplicate_key_items).lower()
def duplicate_check(self, repeat: bool = False):
"""Set the correct duplicate status"""
if not cfg.no_dupes() and not cfg.no_series_dupes():
if not cfg.no_dupes() and not cfg.no_smart_dupes():
return
duplicate_in_history = series_duplicate_in_history = False
duplicate_in_queue = series_duplicate_in_queue = False
# Reset status in case of a repeat analysis
if repeat:
self.duplicate = None
self.duplicate_key = None
duplicate_in_history = smart_duplicate_in_history = False
duplicate_in_queue = smart_duplicate_in_queue = False
with HistoryDB() as history_db:
# Dupe check off just name or nzb contents
@@ -1916,33 +1956,34 @@ class NzbObject(TryList):
duplicate_in_history = history_db.have_name_or_md5sum(self.final_name, self.md5sum)
logging.debug("Duplicate in history: %s", duplicate_in_history)
if not duplicate_in_history and cfg.backup_for_duplicates():
duplicate_in_history = backup_exists(self.filename)
logging.debug("Duplicate in backup: %s", duplicate_in_history)
duplicate_in_queue = sabnzbd.NzbQueue.have_name_or_md5sum(self.final_name, self.md5sum)
logging.debug("Duplicate in queue: %s", duplicate_in_queue)
# Dupe check off nzb filename
if not duplicate_in_history and not duplicate_in_queue and cfg.no_series_dupes():
logging.debug("Duplicate episode checking (%s): %s", self.final_name, self.duplicate_series_key)
self.set_duplicate_series_key()
if self.duplicate_series_key:
series_duplicate_in_history = history_db.have_episode(self.duplicate_series_key)
logging.debug("Duplicate episode in history: %s", series_duplicate_in_history)
# The nzb can already be in the backup while the job is still in the queue, so skip on repeat
if not repeat and not duplicate_in_history and not duplicate_in_queue and cfg.backup_for_duplicates():
duplicate_in_history = backup_exists(self.filename)
logging.debug("Duplicate in backup: %s", duplicate_in_history)
series_duplicate_in_queue = sabnzbd.NzbQueue.have_episode(self.duplicate_series_key)
logging.debug("Duplicate episode in queue: %s", series_duplicate_in_queue)
# Dupe check off nzb filename
if not duplicate_in_history and not duplicate_in_queue and cfg.no_smart_dupes():
self.set_duplicate_key()
logging.debug("Smart duplicate checking (%s): %s", self.final_name, self.duplicate_key)
if self.duplicate_key:
smart_duplicate_in_history = history_db.have_duplicate_key(self.duplicate_key)
logging.debug("Duplicate in history: %s", smart_duplicate_in_history)
smart_duplicate_in_queue = sabnzbd.NzbQueue.have_duplicate_key(self.duplicate_key)
logging.debug("Duplicate in queue: %s", smart_duplicate_in_queue)
else:
logging.debug("Not an episode, skipping duplicate episode check")
logging.debug("Unknown type, skipping smart duplicate check")
# Set the correct status
if series_duplicate_in_queue:
self.duplicate = DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE
if smart_duplicate_in_queue:
self.duplicate = DuplicateStatus.SMART_DUPLICATE_ALTERNATIVE
elif duplicate_in_queue:
self.duplicate = DuplicateStatus.DUPLICATE_ALTERNATIVE
elif series_duplicate_in_history:
self.duplicate = DuplicateStatus.SERIES_DUPLICATE
elif smart_duplicate_in_history:
self.duplicate = DuplicateStatus.SMART_DUPLICATE
elif duplicate_in_history:
self.duplicate = DuplicateStatus.DUPLICATE
@@ -1957,18 +1998,18 @@ class NzbObject(TryList):
# 2 = Pause
# 3 = Fail (move to History)
# 4 = Tag
if self.duplicate in (DuplicateStatus.DUPLICATE, DuplicateStatus.SERIES_DUPLICATE):
series_duplicate = self.duplicate == DuplicateStatus.SERIES_DUPLICATE
if (not series_duplicate and cfg.no_dupes() == 1) or (series_duplicate and cfg.no_series_dupes() == 1):
if self.duplicate in (DuplicateStatus.DUPLICATE, DuplicateStatus.SMART_DUPLICATE):
smart_duplicate = self.duplicate == DuplicateStatus.SMART_DUPLICATE
if (not smart_duplicate and cfg.no_dupes() == 1) or (smart_duplicate and cfg.no_smart_dupes() == 1):
# Discard
duplicate_warning(T('Ignoring duplicate NZB "%s"'), self.final_name)
self.purge_data()
raise NzbRejected
elif (not series_duplicate and cfg.no_dupes() == 3) or (series_duplicate and cfg.no_series_dupes() == 3):
elif (not smart_duplicate and cfg.no_dupes() == 3) or (smart_duplicate and cfg.no_smart_dupes() == 3):
# Fail (move to History)
duplicate_warning(T('Failing duplicate NZB "%s"'), self.final_name)
raise NzbRejectToHistory(self, T("Duplicate NZB"))
elif (not series_duplicate and cfg.no_dupes() == 2) or (series_duplicate and cfg.no_series_dupes() == 2):
elif (not smart_duplicate and cfg.no_dupes() == 2) or (smart_duplicate and cfg.no_smart_dupes() == 2):
# Pause
duplicate_warning(T('Pausing duplicate NZB "%s"'), self.final_name)
self.pause()
@@ -1976,8 +2017,10 @@ class NzbObject(TryList):
# Tag job
duplicate_warning('%s: "%s"', T("Duplicate NZB"), self.final_name)
# In case of alternative, just pause
if self.duplicate in (DuplicateStatus.DUPLICATE_ALTERNATIVE, DuplicateStatus.SERIES_DUPLICATE_ALTERNATIVE):
# In case of alternative, just pause (unless only tagging is desired)
if (self.duplicate == DuplicateStatus.DUPLICATE_ALTERNATIVE and cfg.no_dupes() != 4) or (
self.duplicate == DuplicateStatus.SMART_DUPLICATE_ALTERNATIVE and cfg.no_smart_dupes() != 4
):
logging.info("Pausing duplicate alternative %s", self.final_name)
self.pause()

View File

@@ -23,7 +23,9 @@ import os
import sys
import time
import logging
from objc import YES, NO
from typing import Optional
from objc import YES, NO, lookUpClass
from Foundation import (
NSObject,
NSDate,
@@ -34,6 +36,8 @@ from Foundation import (
NSFont,
NSImage,
NSAttributedString,
NSUserNotification,
NSUserNotificationCenter,
)
from AppKit import (
NSStatusBar,
@@ -65,6 +69,8 @@ from sabnzbd.panic import launch_a_browser
from sabnzbd.api import fast_queue
import sabnzbd.config as config
DefaultUserNotificationCenter = NSUserNotificationCenter.defaultUserNotificationCenter()
status_icons = {
"idle": "icons/sabnzbd_osx_idle.tiff",
"pause": "icons/sabnzbd_osx_pause.tiff",
@@ -83,6 +89,9 @@ class SABnzbdDelegate(NSObject):
while not sabnzbd.WEBUI_READY and not sabnzbd.SABSTOP:
time.sleep(0.5)
# Set this thread as default handler for notification actions
DefaultUserNotificationCenter.setDelegate_(self)
# Do we want the menu
if sabnzbd.cfg.tray_icon():
# Status Bar initialize
@@ -541,3 +550,45 @@ class SABnzbdDelegate(NSObject):
self.status_item.setHighlightMode_(NO)
sabnzbd.shutdown_program()
return NSTerminateNow
def send_notification(
self,
title: str,
subtitle: str,
msg: str,
button_text: Optional[str] = None,
button_action: Optional[str] = None,
):
"""Send a macOS notification, optionally with 1 action button"""
notification = NSUserNotification.alloc().init()
notification.setTitle_(title)
notification.setSubtitle_(subtitle)
notification.setInformativeText_(msg)
notification.setSoundName_("NSUserNotificationDefaultSoundName")
if button_text and button_action:
notification.setHasActionButton_(True)
notification.set_showsButtons_(True)
notification.setActionButtonTitle_(button_text)
notification.setUserInfo_({"value": button_action})
else:
notification.setHasActionButton_(False)
notification.set_showsButtons_(False)
notification.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(0, NSDate.date()))
DefaultUserNotificationCenter.scheduleNotification_(notification)
def userNotificationCenter_didActivateNotification_(self, center, notification):
"""Handler for the clicks on the notification"""
if notification.activationType() == 1:
# user clicked on the notification (not on a button)
launch_a_browser(sabnzbd.BROWSER_URL, force=True)
elif notification.activationType() == 2:
# User clicked on the action button
if os.path.exists(folder2open := notification.userInfo()["value"]):
os.system('open "%s"' % folder2open)
# Remove this notification after interaction
DefaultUserNotificationCenter._removeDisplayedNotification_(notification)

View File

@@ -216,9 +216,39 @@ class PostProcessor(Thread):
"""Return True if pp queue is empty"""
return self.slow_queue.empty() and self.fast_queue.empty() and not self.__busy
def get_queue(self) -> List[NzbObject]:
"""Return list of NZOs that still need to be processed"""
return [nzo for nzo in self.history_queue if nzo.work_name]
def get_queue(
self,
search: Optional[str] = None,
categories: Optional[List[str]] = None,
statuses: Optional[List[str]] = None,
nzo_ids: Optional[List[str]] = None,
) -> List[NzbObject]:
"""Return list of NZOs that still need to be processed.
Optionally filtered by the search terms"""
re_search = None
if isinstance(search, str):
# Replace * with .* and ' ' with .
search_text = search.strip().replace("*", ".*").replace(" ", ".*") + ".*?"
try:
re_search = re.compile(search_text, re.I)
except:
logging.error(T("Failed to compile regex for search term: %s"), search_text)
# Need a copy to prevent race conditions
filtered_queue = []
for nzo in self.history_queue[:]:
if not nzo.work_name:
continue
if re_search and not re_search.search(nzo.final_name):
continue
if categories and nzo.cat not in categories:
continue
if statuses and nzo.status not in statuses:
continue
if nzo_ids and nzo.nzo_id not in nzo_ids:
continue
filtered_queue.append(nzo)
return filtered_queue
def get_path(self, nzo_id: str) -> Optional[str]:
"""Return download path for given nzo_id or None when not found"""
@@ -627,6 +657,7 @@ def process_job(nzo: NzbObject) -> bool:
0,
)
workdir_notifcation_action = workdir_complete
if all_ok:
# If the folder only contains one file OR folder, have that as the path
# Be aware that series/generic/date sorting may move a single file into a folder containing other files
@@ -651,7 +682,13 @@ def process_job(nzo: NzbObject) -> bool:
# Show final status in history
if all_ok:
notifier.send_notification(T("Download Completed"), filename, "complete", nzo.cat)
notifier.send_notification(
T("Download Completed"),
filename,
"complete",
nzo.cat,
{"open_folder": clip_path(workdir_notifcation_action)},
)
nzo.status = Status.COMPLETED
nzo.fail_msg = ""
else:
@@ -1053,7 +1090,12 @@ def handle_empty_queue():
"""Check if empty queue calls for action"""
if not sabnzbd.NzbQueue.actives():
sabnzbd.save_state()
notifier.send_notification("SABnzbd", T("Queue finished"), "queue_done")
notifier.send_notification(
"SABnzbd",
T("Queue finished"),
"queue_done",
actions={"open_complete": cfg.complete_dir.get_clipped_path()},
)
# Perform end-of-queue script
if cfg.end_queue_script():

View File

@@ -308,8 +308,9 @@ class RSSReader:
myPrio = defPrio
n = 0
if ("F" in reTypes or "S" in reTypes) and (not season or not episode):
show_analysis = sabnzbd.sorting.analyse_show(title)
season, episode = show_analysis["season"], show_analysis["episode"]
show_analysis = sabnzbd.sorting.BasicAnalyzer(title)
season = show_analysis.info.get("season")
episode = show_analysis.info.get("episode")
# Match against all filters until an positive or negative match
logging.debug("Size %s", size)
@@ -336,12 +337,7 @@ class RSSReader:
logging.debug("Filter requirement match on rule %d", n)
result = False
break
elif (
reTypes[n] == "S"
and season
and episode
and ep_match(season, episode, regexes[n], title)
):
elif reTypes[n] == "S" and ep_match(season, episode, regexes[n], title):
logging.debug("Filter matched on rule %d", n)
result = True
break
@@ -400,6 +396,7 @@ class RSSReader:
star = first
if result:
_HandleLink(
feed,
jobs,
link,
infourl,
@@ -422,6 +419,7 @@ class RSSReader:
new_downloads.append(title)
else:
_HandleLink(
feed,
jobs,
link,
infourl,
@@ -580,6 +578,7 @@ def patch_feedparser():
def _HandleLink(
feed,
jobs,
link,
infourl,
@@ -628,8 +627,17 @@ def _HandleLink(
if download:
jobs[link]["status"] = "D"
jobs[link]["time_downloaded"] = time.localtime()
logging.info("Adding %s (%s) to queue", link, title)
sabnzbd.urlgrabber.add_url(link, pp=pp, script=script, cat=cat, priority=priority, nzbname=nzbname)
sabnzbd.urlgrabber.add_url(
link,
pp=pp,
script=script,
cat=cat,
priority=priority,
nzbname=nzbname,
nzo_info={"RSS": feed},
)
else:
if star:
jobs[link]["status"] = flag + "*"

View File

@@ -35,6 +35,7 @@ from sabnzbd.utils.systrayiconthread import SysTrayIconThread
class SABTrayThread(SysTrayIconThread):
# When updating these paths, also update them in the NSIS script!
sabicons = {
"default": "icons/sabnzbd16_32.ico",
"green": "icons/sabnzbd16_32green.ico",

View File

@@ -28,6 +28,7 @@ SKIN_TEXT = {
"stage-unpack": TT("Unpack"), #: PP phase "unpack"
"stage-deobfuscate": TT("Deobfuscate"), #: PP phase "deobfuscate"
"stage-script": TT("Script"), #: PP phase "script"
"stage-rss": TT("RSS"), #: PP RSS feed of the NZB
"stage-source": TT("Source"), #: PP Source of the NZB (path or URL)
"stage-servers": TT("Servers"), #: PP Distribution over servers
"post-Completed": TT("Completed"), #: PP status
@@ -414,17 +415,13 @@ SKIN_TEXT = {
),
"opt-pause_on_pwrar": TT("Action when encrypted RAR is downloaded"),
"explain-pause_on_pwrar": TT('In case of "Pause", you\'ll need to set a password and resume the job.'),
"opt-no_dupes": TT("Detect Duplicate Downloads"),
"explain-no_dupes": TT(
"Detect identical NZB files (based on items in your History or files in .nzb Backup Folder)"
),
"opt-no_series_dupes": TT("Detect duplicate episodes in series"),
"explain-no_series_dupes": TT(
'Detect identical episodes in series (based on "name/season/episode" of items in your History)'
),
"opt-series_propercheck": TT("Allow proper releases"),
"explain-series_propercheck": TT(
"Bypass series duplicate detection if PROPER, REAL or REPACK is detected in the download name"
"opt-no_dupes": TT("Identical download detection"),
"explain-no_dupes": TT("Detect identical downloads based on name or NZB contents."),
"opt-no_smart_dupes": TT("Smart duplicate detection"),
"explain-no_smart_dupes": TT("Detect duplicates based on analysis of the filename."),
"opt-dupes_propercheck": TT("Allow proper releases"),
"explain-dupes_propercheck": TT(
"Bypass smart duplicate detection if PROPER, REAL or REPACK is detected in the download name."
),
"nodupes-off": TT("Off"), #: Three way switch for duplicates
"nodupes-ignore": TT("Discard"), #: Four way switch for duplicates
@@ -455,13 +452,9 @@ SKIN_TEXT = {
"opt-end_queue_script": TT("On queue finish script"),
"explain-end_queue_script": TT("Executed after the queue finishes downloading."),
"opt-par_option": TT("Extra PAR2 Parameters"),
"explain-par_option": TT("Read the Wiki Help on this!"),
"opt-nice": TT("Nice Parameters"),
"explain-nice": TT("Read the Wiki Help on this!"),
"opt-ionice": TT("IONice Parameters"),
"explain-ionice": TT("Read the Wiki Help on this!"),
"opt-win_process_prio": TT("External process priority"),
"explain-win_process_prio": TT("Read the Wiki Help on this!"),
"win_process_prio-high": TT("High"),
"win_process_prio-normal": TT("Normal"),
"win_process_prio-low": TT("Low"),
@@ -690,7 +683,6 @@ SKIN_TEXT = {
"opt-nscript_parameters": TT("Parameters"), #: Notification Script settings
"explain-nscript_enable": TT("Executes a custom script"), #: Notification Scriptsettings
"explain-nscript_script": TT("Which script should we execute for notification?"), #: Notification Scriptsettings
"explain-nscript_parameters": TT("Read the Wiki Help on this!"), #: Notification Script settings
# Config->Cat
"explain-catTags": TT(
'Indexers can supply a category inside the NZB which SABnzbd will try to match to the categories defined below. Additionally, you can add terms to "Indexer Categories / Groups" to match more categories. Use commas to separate terms. Wildcards in the terms are supported. <br>More information can be found on the Wiki.'

View File

@@ -585,10 +585,12 @@ class Sorter:
return move_to_parent_directory(base_path)
class SeriesAnalyzer(Sorter):
class BasicAnalyzer(Sorter):
def __init__(self, job_name: str):
"""Very basic sorter that doesn't require a config"""
super().__init__(nzo=None, job_name=job_name)
# Directly trigger setting all values
self.get_values()
def match_sorters(self):
"""Much more basic matching"""
@@ -600,25 +602,6 @@ class SeriesAnalyzer(Sorter):
self.type = "date" if self.guess.get("date") else "tv"
def analyse_show(job_name: str) -> Dict[str, str]:
"""Use the Sorter to collect some basic info on series"""
job = SeriesAnalyzer(job_name)
job.get_values()
return {
"title": job.info.get("title", ""),
"season": job.info.get("season_num", ""),
"episode": job.info.get("episode_num", ""),
"episode_name": job.info.get("ep_name", ""),
"is_proper": job.is_proper(),
"resolution": job.info.get("resolution", ""),
"decade": job.info.get("decade", ""),
"year": job.info.get("year", ""),
"month": job.info.get("month", ""),
"day": job.info.get("day", ""),
"job_type": job.type,
}
def ends_in_file(path: str) -> bool:
"""Return True when path ends with '.%ext' or '%fn' while allowing for a lowercase marker"""
return bool(RE_ENDEXT.search(path) or RE_ENDFN.search(path))
@@ -649,9 +632,8 @@ def move_to_parent_directory(workdir: str) -> Tuple[str, bool]:
return dest, True
def guess_what(name: str, sort_type: Optional[str] = None) -> MatchesDict:
"""Guess metadata for movies or episodes from their name. The sort_type ('movie' or 'episode')
is passed as a hint to guessit, if given."""
def guess_what(name: str) -> MatchesDict:
"""Guess metadata for movies or episodes from their name."""
if not name:
raise ValueError("Need a name for guessing")
@@ -670,9 +652,6 @@ def guess_what(name: str, sort_type: Optional[str] = None) -> MatchesDict:
"excludes": EXCLUDED_GUESSIT_PROPERTIES,
"date_year_first": True, # Make sure also short-dates are detected as YY-MM-DD
}
if sort_type:
# Hint the type if known
guessit_options["type"] = sort_type
guess = guessit.api.guessit(digit_fix + name, options=guessit_options)
logging.debug("Initial guess for %s is %s", digit_fix + name, guess)
@@ -687,7 +666,7 @@ def guess_what(name: str, sort_type: Optional[str] = None) -> MatchesDict:
# Try to avoid setting the type to movie on arbitrary jobs (e.g. 'Setup.exe') just because guessit defaults to that
table = str.maketrans({char: "" for char in whitespace + "_.-()[]{}"})
if guess.get("type") == "movie" and not sort_type == "movie": # No movie hint
if guess.get("type") == "movie":
if (
guess.get("title", "").translate(table) == name.translate(table) # Check for full name used as title
or any(
@@ -697,7 +676,9 @@ def guess_what(name: str, sort_type: Optional[str] = None) -> MatchesDict:
[key in guess for key in ("year", "screen_size", "video_codec")]
) # No typical movie properties set
or (
name.lower().startswith("http://") and name.lower().endswith(".nzb") and guess.get("container" == "nzb")
name.lower().startswith(("http://", "https://"))
and name.lower().endswith(".nzb")
and guess.get("container" == "nzb")
) # URL to an nzb file, can happen when pre-queue script rejects a job
):
guess["type"] = "unknown"

View File

@@ -31,7 +31,7 @@ from http.client import IncompleteRead, HTTPResponse
from mailbox import Message
from threading import Thread
import base64
from typing import Tuple, Optional, Union, List
from typing import Tuple, Optional, Union, List, Dict, Any
import sabnzbd
from sabnzbd.constants import DEF_TIMEOUT, FUTURE_Q_FOLDER, VALID_NZB_FILES, Status, VALID_ARCHIVES, DEFAULT_PRIORITY
@@ -112,8 +112,7 @@ class URLGrabber(Thread):
continue
filename = None
category = None
nzo_info = {}
nzo_info = future_nzo.nzo_info
wait = 0
retry = True
fetch_request = None
@@ -150,11 +149,19 @@ class URLGrabber(Thread):
value = fetch_request.headers[hdr]
except:
continue
# Skip empty values
if not value:
continue
if item in ("category_id", "x-dnzb-category"):
category = value
elif item in ("x-dnzb-moreinfo",):
# Use indexer category in case no specific one was set
if value and future_nzo.cat in (None, "*"):
if indexer_cat := misc.cat_convert(value):
future_nzo.cat = indexer_cat
elif item == "x-dnzb-moreinfo":
nzo_info["more_info"] = value
elif item in ("x-dnzb-name",):
elif item == "x-dnzb-name":
filename = value
if not filename.endswith(".nzb"):
filename += ".nzb"
@@ -172,10 +179,10 @@ class URLGrabber(Thread):
nzo_info["password"] = value
elif item == "retry-after":
wait = misc.int_conv(value)
# Get filename from Content-Disposition header
if not filename and "filename" in value:
filename = filename_from_content_disposition(value)
elif item == "content-disposition":
# Get filename from Content-Disposition header
if not filename and "filename" in value:
filename = filename_from_content_disposition(value)
if wait:
# For sites that have a rate-limiting attribute
@@ -292,6 +299,10 @@ class URLGrabber(Thread):
# Failed fetch
msg = T("URL Fetching failed; %s") % msg
# Add RSS source
if rss_feed := nzo.nzo_info.get("RSS"):
nzo.set_unpack_info("RSS", rss_feed, unique=True)
# Mark as failed and set the info why
nzo.set_unpack_info("Source", url)
nzo.set_unpack_info("Source", msg)
@@ -363,11 +374,9 @@ def filename_from_content_disposition(content_disposition: str) -> Optional[str]
filename_from_content_disposition('attachment; filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz')
should return: 'jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz'
"""
filename = Message(f"Content-Disposition: attachment; {content_disposition}").get_filename()
if filename:
if filename := Message(f"Content-Disposition: attachment; {content_disposition}").get_filename():
# Basic sanitation
filename = os.path.basename(filename).lstrip(".").strip()
if filename:
if filename := os.path.basename(filename).lstrip(".").strip():
return filename
@@ -379,6 +388,7 @@ def add_url(
priority: Optional[Union[int, str]] = None,
nzbname: Optional[str] = None,
password: Optional[str] = None,
nzo_info: Optional[Dict[str, Any]] = None,
dup_check: bool = True,
) -> Tuple[AddNzbFileResult, List[str]]:
"""Add NZB based on a URL, attributes optional"""
@@ -403,6 +413,7 @@ def add_url(
password=password,
nzbname=nzbname,
status=Status.GRABBING,
nzo_info=nzo_info,
dup_check=dup_check,
)
except NzbRejected:

View File

@@ -75,8 +75,8 @@ def generate_local_cert(private_key, days_valid=3560, output_file="cert.cert", L
.subject_name(subject)
.issuer_name(issuer)
.public_key(private_key.public_key())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=days_valid))
.not_valid_before(datetime.datetime.now(datetime.timezone.utc))
.not_valid_after(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=days_valid))
.serial_number(x509.random_serial_number())
.add_extension(x509.SubjectAlternativeName(san_list), critical=True)
.sign(private_key, hashes.SHA256(), default_backend())

View File

@@ -1,96 +0,0 @@
#!/usr/bin/python3
"""
Module to measure and report Internet speed
Method: get one small and then a bigger reference file, and measure how long it takes, then calculate speed
Reports in MB/s (so mega BYTES per seconds), not to be confused with Mbps
"""
import time
import logging
import urllib.request
SIZE_URL_LIST = [
[5, "https://sabnzbd.org/tests/internetspeed/5MB.bin"],
[10, "https://sabnzbd.org/tests/internetspeed/10MB.bin"],
[20, "https://sabnzbd.org/tests/internetspeed/20MB.bin"],
[50, "https://sabnzbd.org/tests/internetspeed/50MB.bin"],
[100, "https://sabnzbd.org/tests/internetspeed/100MB.bin"],
]
def measure_speed_from_url(url: str) -> float:
"""Download the specified url (pointing to a file), and report back MB/s (as a float)"""
logging.debug("URL is %s", url)
start = time.time()
downloaded_bytes = 0 # default
try:
req = urllib.request.Request(url, data=None, headers={"User-Agent": "Mozilla/5.0 (Macintosh)"})
downloaded_bytes = len(urllib.request.urlopen(req, timeout=4).read())
except:
# No connection at all?
pass
time_granularity_worst_case = 0.008 # Windows has worst case 16 milliseconds
duration = max(time.time() - start, time_granularity_worst_case) # max() to avoid 0.0 divide error later on
logging.debug("Downloaded bytes: %d", downloaded_bytes)
logging.debug("Duration in seconds: %f", duration)
return downloaded_bytes / 1024**2 / duration
def bytes_to_bits(megabytes_per_second: float) -> float:
"""convert bytes (per second) to bits (per second), taking into a account network overhead"""
return 8.05 * megabytes_per_second # bits
def internetspeed() -> float:
"""Report Internet speed in MB/s as a float"""
# Do basic test with a small download
logging.debug("Basic measurement, with small download:")
start = time.time()
urlbasic = SIZE_URL_LIST[0][1] # get first URL, which is smallest download
base_megabytes_per_second = measure_speed_from_url(urlbasic)
logging.debug("Speed in MB/s: %.2f", base_megabytes_per_second)
if base_megabytes_per_second == 0:
# no Internet connection, or other problem
return 0.0
"""
Based on this first, small download, do a bigger download; the biggest download that still fits in total 8 seconds
Rationale: a bigger download could yield higher MB/s because the 'starting delay' is relatively less
We do two downloads, so one download must fit in 4 seconds
Note: a slow DNS lookup does influence the total time, so the measured speed (read: seemingly lower download speed)
"""
# Determine the biggest URL that can be downloaded within timeframe
maxtime = 4 # seconds
url_to_do = None
for size, sizeurl in SIZE_URL_LIST:
expectedtime = size / base_megabytes_per_second
if expectedtime < maxtime:
# ok, this one is feasible, so keep it in mind
url_to_do = sizeurl
max_megabytes_per_second = base_megabytes_per_second
# Execute it twice, and get the best result
for _ in range(2):
if url_to_do:
logging.debug(url_to_do)
measured_megabytes_per_second = measure_speed_from_url(url_to_do)
logging.debug("Speed in MB/s: %.2f", measured_megabytes_per_second)
max_megabytes_per_second = max(max_megabytes_per_second, measured_megabytes_per_second)
logging.debug("Internet Bandwidth = %.2f MB/s (in %.2f seconds)", max_megabytes_per_second, time.time() - start)
return max_megabytes_per_second
# MAIN
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
logging.debug("Log level is DEBUG")
print("Starting speed test:")
maxMBps = internetspeed()
print("Speed in MB/s: %.2f" % maxMBps)
print("Speed in Mbps: %.2f" % bytes_to_bits(maxMBps))

View File

@@ -6,5 +6,5 @@
# You MUST use double quotes (so " and not ')
# Do not forget to update the appdata file for every major release!
__version__ = "4.2.0Alpha2"
__version__ = "4.2.0RC2"
__baseline__ = "unknown"

View File

@@ -52,6 +52,7 @@ stages:
bytes: !anyint
meta: null
series: !anything
duplicate_key: !anything
md5sum: !re_match "[0-9a-fA-F]+"
password: !anystr
action_line: !anything
@@ -92,7 +93,7 @@ stages:
# so parameters match regardless of their order of appearance. Note
# that the content of 'slots' is checked in a separate expression.
expression: "^{{(?=.*'version': '{SAB_VERSION}')(?=.*'noofslots': [0-9]+)(?=.*'ppslots': [0-9]+)(?=.*'last_history_update': '?[0-9]+'?)(?=.*'total_size': '[0-9][0-9.]*.?(\ [A-Z])?')(?=.*'month_size': '[0-9][0-9.]*.?(\ [A-Z])?')(?=.*'week_size': '[0-9][0-9.]*.?(\ [A-Z])?')(?=.*'day_size': '[0-9][0-9.]*.?(\ [A-Z])?')(?=.*'slots': .+).*}}$"
expression: ".*'slots': \\[{{(?=.*'completed': [0-9]+)(?=.*'name': '.+')(?=.*'nzb_name': '.+')(?=.*'category': '.+')(?=.*'pp': '.?')(?=.*'script': '.+')(?=.*'report': '.+')(?=.*'url': '.+')(?=.*'status': '.+')(?=.*'nzo_id': 'SAB.+')(?=.*'storage': '.+')(?=.*'path': '.+')(?=.*'script_line': '.*')(?=.*'download_time': [0-9]+)(?=.*'postproc_time': [0-9]*)(?=.*'stage_log': \\[.*\\])(?=.*'downloaded': [0-9]+)(?=.*'completeness': None)(?=.*'fail_message': '.*')(?=.*'url_info': '.*')(?=.*'bytes': [0-9]+)(?=.*'meta': None)(?=.*'series': '?.*'?)(?=.*'md5sum': '[0-9a-fA-F]+')(?=.*'password': '.*')(?=.*'action_line': '.*')(?=.*'size': '[0-9].*')(?=.*'loaded': (True|False))(?=.*'retry': [0-9]+).*}}\\].*"
expression: ".*'slots': \\[{{(?=.*'completed': [0-9]+)(?=.*'name': '.+')(?=.*'nzb_name': '.+')(?=.*'category': '.+')(?=.*'pp': '.?')(?=.*'script': '.+')(?=.*'report': '.+')(?=.*'url': '.+')(?=.*'status': '.+')(?=.*'nzo_id': 'SAB.+')(?=.*'storage': '.+')(?=.*'path': '.+')(?=.*'script_line': '.*')(?=.*'download_time': [0-9]+)(?=.*'postproc_time': [0-9]*)(?=.*'stage_log': \\[.*\\])(?=.*'downloaded': [0-9]+)(?=.*'completeness': None)(?=.*'fail_message': '.*')(?=.*'url_info': '.*')(?=.*'bytes': [0-9]+)(?=.*'meta': None)(?=.*'series': '?.*'?)(?=.*'duplicate_key': '?.*'?)(?=.*'md5sum': '[0-9a-fA-F]+')(?=.*'password': '.*')(?=.*'action_line': '.*')(?=.*'size': '[0-9].*')(?=.*'loaded': (True|False))(?=.*'retry': [0-9]+).*}}\\].*"
---
@@ -150,6 +151,7 @@ stages:
<bytes>!anyint</bytes>
<meta>!anystr</meta>
<series>!anystr</series>
<duplicate_key>!anystr</duplicate_key>
<md5sum>!anystr</md5sum>
<password>!anystr</password>
<action_line>!anystr</action_line>

View File

@@ -132,10 +132,10 @@ async def serve_sabnews(hostname, port):
# Start server
logging.info("Starting SABNews on %s:%d", hostname, port)
# Needed for Python 3.5 support!
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
server = await loop.create_server(lambda: NewsServerProtocol(), hostname, port)
return server
async with server:
await server.serve_forever()
def create_nzb(nzb_file=None, nzb_dir=None, metadata=None):
@@ -209,9 +209,7 @@ def main():
# Serve if we are not creating NZB's
if not args.nzb_file and not args.nzb_dir:
loop = asyncio.get_event_loop()
loop.run_until_complete(serve_sabnews(args.hostname, args.port))
loop.run_forever()
asyncio.run(serve_sabnews(args.hostname, args.port))
else:
create_nzb(args.nzb_file, args.nzb_dir)

View File

@@ -18,6 +18,8 @@
"""
tests.test_cfg - Testing functions in cfg.py
"""
import sys
import pytest
import sabnzbd.cfg as cfg
@@ -108,7 +110,10 @@ class TestValidators:
def test_validate_safedir(self):
assert cfg.validate_safedir("", "", "def") == (None, "def")
assert cfg.validate_safedir("", "C:\\", "") == (None, "C:\\")
assert "UNC path" in cfg.validate_safedir("", "\\\\NAS\\foo", "")[0]
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows tests")
def test_validate_safedir_win(self):
assert "Network path" in cfg.validate_safedir("", "\\\\NAS\\foo", "")[0]
def test_validate_host(self):
# valid input

View File

@@ -52,7 +52,6 @@ def mock_sleep(create_mock_coroutine):
class TestDirScanner:
@set_config({"dirscan_dir": os.path.join(SAB_CACHE_DIR, "watched")})
@pytest.mark.asyncio
@pytest.mark.parametrize(
"path, catdir",
@@ -75,17 +74,16 @@ class TestDirScanner:
mocker.patch("sabnzbd.nzbparser.add_nzbfile", return_value=(AddNzbFileResult.ERROR, []))
mocker.patch("sabnzbd.config.save_config", return_value=True)
fs.create_file(os.path.join(sabnzbd.cfg.dirscan_dir.get_path(), catdir or "", path), contents="FAKEFILE")
fs.create_file(os.path.join(catdir or "", path), contents="FAKEFILE")
scanner = sabnzbd.dirscanner.DirScanner()
await scanner.scan_async(scanner.dirscan_dir)
await scanner.scan_async("")
sabnzbd.nzbparser.add_nzbfile.assert_any_call(
os.path.join(sabnzbd.cfg.dirscan_dir.get_path(), catdir or "", path), catdir=catdir, keep=False
)
@set_config({"dirscan_dir": os.path.join(SAB_CACHE_DIR, "watched")})
@pytest.mark.asyncio
@pytest.mark.parametrize(
"path",
@@ -102,15 +100,14 @@ class TestDirScanner:
mocker.patch("sabnzbd.nzbparser.add_nzbfile", return_value=(AddNzbFileResult.ERROR, []))
mocker.patch("sabnzbd.config.save_config", return_value=True)
fs.create_file(os.path.join(sabnzbd.cfg.dirscan_dir.get_path(), path))
fs.create_file(path)
scanner = sabnzbd.dirscanner.DirScanner()
await scanner.scan_async(scanner.dirscan_dir)
await scanner.scan_async("")
sabnzbd.nzbparser.add_nzbfile.assert_not_called()
@set_config({"dirscan_dir": os.path.join(SAB_CACHE_DIR, "watched")})
@pytest.mark.asyncio
@pytest.mark.parametrize(
"path",
@@ -123,10 +120,10 @@ class TestDirScanner:
mocker.patch("sabnzbd.nzbparser.add_nzbfile", return_value=(AddNzbFileResult.ERROR, []))
mocker.patch("sabnzbd.config.save_config", return_value=True)
fs.create_file(os.path.join(sabnzbd.cfg.dirscan_dir.get_path(), path), contents="FAKEFILE")
fs.create_file(path, contents="FAKEFILE")
scanner = sabnzbd.dirscanner.DirScanner()
await scanner.scan_async(scanner.dirscan_dir)
await scanner.scan_async("")
sabnzbd.nzbparser.add_nzbfile.assert_not_called()

View File

@@ -424,36 +424,36 @@ class TestCheckMountLinux(ffs.TestCase):
@set_platform("linux")
def test_bare_mountpoint_linux(self):
assert filesystem.check_mount("/media") is True
assert filesystem.check_mount("/media/") is True
assert filesystem.check_mount("/mnt") is True
assert filesystem.check_mount("/mnt/") is True
assert filesystem.mount_is_available("/media") is True
assert filesystem.mount_is_available("/media/") is True
assert filesystem.mount_is_available("/mnt") is True
assert filesystem.mount_is_available("/mnt/") is True
@set_platform("linux")
def test_existing_dir_linux(self):
assert filesystem.check_mount("/media/test") is True
assert filesystem.check_mount("/media/test/dir/") is True
assert filesystem.check_mount("/media/test/DIR/") is True
assert filesystem.check_mount("/mnt/TEST") is True
assert filesystem.check_mount("/mnt/TEST/dir/") is True
assert filesystem.check_mount("/mnt/TEST/DIR/") is True
assert filesystem.mount_is_available("/media/test") is True
assert filesystem.mount_is_available("/media/test/dir/") is True
assert filesystem.mount_is_available("/media/test/DIR/") is True
assert filesystem.mount_is_available("/mnt/TEST") is True
assert filesystem.mount_is_available("/mnt/TEST/dir/") is True
assert filesystem.mount_is_available("/mnt/TEST/DIR/") is True
@set_platform("linux")
# Cut down a bit on the waiting time
@set_config({"wait_ext_drive": 1})
def test_dir_nonexistent_linux(self):
# Filesystem is case-sensitive on this platform
assert filesystem.check_mount("/media/TEST") is False # Issue #1457
assert filesystem.check_mount("/media/TesT/") is False
assert filesystem.check_mount("/mnt/TeSt/DIR") is False
assert filesystem.check_mount("/mnt/test/DiR/") is False
assert filesystem.mount_is_available("/media/TEST") is False # Issue #1457
assert filesystem.mount_is_available("/media/TesT/") is False
assert filesystem.mount_is_available("/mnt/TeSt/DIR") is False
assert filesystem.mount_is_available("/mnt/test/DiR/") is False
@set_platform("linux")
def test_dir_outsider_linux(self):
# Outside of /media and /mnt
assert filesystem.check_mount("/test/that/") is True
assert filesystem.mount_is_available("/test/that/") is True
# Root directory
assert filesystem.check_mount("/") is True
assert filesystem.mount_is_available("/") is True
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Non-Windows tests")
@@ -470,33 +470,33 @@ class TestCheckMountMacOS(ffs.TestCase):
@set_platform("macos")
def test_bare_mountpoint_macos(self):
assert filesystem.check_mount("/Volumes") is True
assert filesystem.check_mount("/Volumes/") is True
assert filesystem.mount_is_available("/Volumes") is True
assert filesystem.mount_is_available("/Volumes/") is True
@set_platform("macos")
def test_existing_dir_macos(self):
assert filesystem.check_mount("/Volumes/test") is True
assert filesystem.check_mount("/Volumes/test/dir/") is True
assert filesystem.mount_is_available("/Volumes/test") is True
assert filesystem.mount_is_available("/Volumes/test/dir/") is True
# Filesystem is set case-insensitive for this platform
assert filesystem.check_mount("/VOLUMES/test") is True
assert filesystem.check_mount("/volumes/Test/dir/") is True
assert filesystem.mount_is_available("/VOLUMES/test") is True
assert filesystem.mount_is_available("/volumes/Test/dir/") is True
@set_platform("macos")
# Cut down a bit on the waiting time
@set_config({"wait_ext_drive": 1})
def test_dir_nonexistent_macos(self):
# Within /Volumes
assert filesystem.check_mount("/Volumes/nosuchdir") is False # Issue #1457
assert filesystem.check_mount("/Volumes/noSuchDir/") is False
assert filesystem.check_mount("/Volumes/nosuchDIR/subdir") is False
assert filesystem.check_mount("/Volumes/NOsuchdir/subdir/") is False
assert filesystem.mount_is_available("/Volumes/nosuchdir") is False # Issue #1457
assert filesystem.mount_is_available("/Volumes/noSuchDir/") is False
assert filesystem.mount_is_available("/Volumes/nosuchDIR/subdir") is False
assert filesystem.mount_is_available("/Volumes/NOsuchdir/subdir/") is False
@set_platform("macos")
def test_dir_outsider_macos(self):
# Outside of /Volumes
assert filesystem.check_mount("/test/that/") is True
assert filesystem.mount_is_available("/test/that/") is True
# Root directory
assert filesystem.check_mount("/") is True
assert filesystem.mount_is_available("/") is True
class TestCheckMountWin(ffs.TestCase):
@@ -512,39 +512,39 @@ class TestCheckMountWin(ffs.TestCase):
@set_platform("win32")
def test_existing_dir_win(self):
assert filesystem.check_mount("F:\\test") is True
assert filesystem.check_mount("F:\\test\\dir\\") is True
assert filesystem.mount_is_available("F:\\test") is True
assert filesystem.mount_is_available("F:\\test\\dir\\") is True
# Filesystem and drive letters are case-insensitive on this platform
assert filesystem.check_mount("f:\\Test") is True
assert filesystem.check_mount("f:\\test\\DIR\\") is True
assert filesystem.mount_is_available("f:\\Test") is True
assert filesystem.mount_is_available("f:\\test\\DIR\\") is True
@set_platform("win32")
def test_bare_mountpoint_win(self):
assert filesystem.check_mount("F:\\") is True
assert filesystem.check_mount("Z:\\") is False
assert filesystem.mount_is_available("F:\\") is True
assert filesystem.mount_is_available("Z:\\") is False
@set_platform("win32")
def test_dir_nonexistent_win(self):
# The existence of the drive letter is what really matters
assert filesystem.check_mount("F:\\NoSuchDir") is True
assert filesystem.check_mount("F:\\NoSuchDir\\") is True
assert filesystem.check_mount("F:\\NOsuchdir\\subdir") is True
assert filesystem.check_mount("F:\\nosuchDIR\\subdir\\") is True
assert filesystem.mount_is_available("F:\\NoSuchDir") is True
assert filesystem.mount_is_available("F:\\NoSuchDir\\") is True
assert filesystem.mount_is_available("F:\\NOsuchdir\\subdir") is True
assert filesystem.mount_is_available("F:\\nosuchDIR\\subdir\\") is True
@set_platform("win32")
# Cut down a bit on the waiting time
@set_config({"wait_ext_drive": 1})
def test_dir_on_nonexistent_drive_win(self):
# Non-existent drive-letter
assert filesystem.check_mount("H:\\NoSuchDir") is False
assert filesystem.check_mount("E:\\NoSuchDir\\") is False
assert filesystem.check_mount("L:\\NOsuchdir\\subdir") is False
assert filesystem.check_mount("L:\\nosuchDIR\\subdir\\") is False
assert filesystem.mount_is_available("H:\\NoSuchDir") is False
assert filesystem.mount_is_available("E:\\NoSuchDir\\") is False
assert filesystem.mount_is_available("L:\\NOsuchdir\\subdir") is False
assert filesystem.mount_is_available("L:\\nosuchDIR\\subdir\\") is False
@set_platform("win32")
def test_dir_outsider_win(self):
# Outside the local filesystem
assert filesystem.check_mount("//test/that/") is True
assert filesystem.mount_is_available("//test/that/") is True
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Non-Windows tests")

View File

@@ -69,9 +69,15 @@ class TestAddingNZBsClean:
mode="queue", extra_arguments={"name": "priority", "value": job1["nzo_ids"][0], "value2": STOP_PRIORITY}
)
# Wait for the job to be removed
time.sleep(2)
assert get_api_result(mode="history", extra_arguments={"nzo_ids": job1["nzo_ids"][0]})["history"]["slots"]
# Wait for the job to be removed and appear in the history
for _ in range(10):
try:
history = get_api_result(mode="history", extra_arguments={"nzo_ids": job1["nzo_ids"][0]})["history"]
assert history["slots"][0]["nzo_id"] == job1["nzo_ids"][0]
assert history["slots"][0]["status"] == "Failed"
break
except (IndexError, AssertionError):
time.sleep(1)
# Now the second job should no longer be paused and labelled
queue = get_api_result(mode="queue", extra_arguments={"nzo_ids": job2["nzo_ids"][0]})
@@ -88,9 +94,16 @@ class TestAddingNZBsClean:
job = get_api_result(mode="addlocalfile", extra_arguments={"name": nzbfile})
assert job["status"]
assert job["nzo_ids"]
time.sleep(1)
assert not get_api_result(mode="queue", extra_arguments={"nzo_ids": job["nzo_ids"][0]})["queue"]["slots"]
assert get_api_result(mode="history", extra_arguments={"nzo_ids": job["nzo_ids"][0]})["history"]["slots"]
# Wait for the job to be removed and appear in the history
for _ in range(10):
try:
assert not get_api_result(mode="queue", extra_arguments={"nzo_ids": job["nzo_ids"][0]})["queue"]
history = get_api_result(mode="history", extra_arguments={"nzo_ids": job["nzo_ids"][0]})["history"]
assert history["slots"][0]["nzo_id"] == job["nzo_ids"][0]
assert history["slots"][0]["status"] == "Failed"
except (IndexError, AssertionError):
time.sleep(1)
# Reset and clean up
get_api_result(

View File

@@ -86,13 +86,21 @@ class TestDownloadSorting(DownloadFlowBasics):
"SINGLE_sort_s23e06_480i-SABnzbd",
["Single.Sort.S23E06.1.mov"],
), # Repeat to verify a unique filename is applied
(
pytest.param(
"single-ep_sort_s06e66_4k_uhd-SABnzbd",
["Single-Ep.Sort.S06E66." + ext for ext in ("avi", "srt")],
marks=pytest.mark.xfail(
sabnzbd.MACOS or sabnzbd.WIN32,
reason="Unreliable on macOS and Windows",
),
), # Single episode with associated smaller file
(
pytest.param(
"single-ep_sort_s06e66_4k_uhd-SABnzbd",
["Single-Ep.Sort.S06E66.1." + ext for ext in ("avi", "srt")],
marks=pytest.mark.xfail(
sabnzbd.MACOS or sabnzbd.WIN32,
reason="Unreliable on macOS and Windows",
),
), # Repeat to verify unique filenames are applied
],
)

View File

@@ -25,7 +25,7 @@ import socket
import pytest
from flaky import flaky
from sabnzbd.happyeyeballs import happyeyeballs, IPV6_MAPPING
from sabnzbd.happyeyeballs import happyeyeballs
@flaky
@@ -54,18 +54,9 @@ class TestHappyEyeballs:
assert "google" in addrinfo.canonname
def test_google_unreachable_port(self):
assert happyeyeballs("www.google.com", port=33333) is None
assert happyeyeballs("www.google.com", port=33333, timeout=1) is None
@pytest.mark.xfail(reason="CI sometimes blocks this")
def test_nntp(self):
ip = happyeyeballs("news.newshosting.com", port=119).ipaddress
assert "." in ip or ":" in ip
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Resolves strangely on macOS CI")
@pytest.mark.parametrize("hostname", IPV6_MAPPING.keys())
def test_ipv6_mapping(self, hostname):
# This test will let us remove hostnames from the mapping
# once the providers add IPv6 to their main hostname
with pytest.raises(socket.gaierror):
# Print results for us to see the new information
print(socket.getaddrinfo(hostname, 119, socket.AF_INET6, socket.SOCK_STREAM))

View File

@@ -16,30 +16,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
tests.test_utils.test_internetspeed - Testing SABnzbd internetspeed
tests.test_internetspeed - Testing SABnzbd internetspeed
"""
import pytest
from sabnzbd.utils.internetspeed import internetspeed, measure_speed_from_url, SIZE_URL_LIST
from sabnzbd.internetspeed import internetspeed
@pytest.mark.usefixtures("clean_cache_dir")
class TestInternetSpeed:
"""This class contains tests to measure internet speed
with an active and inactive connection
"""
def test_measurespeed_invalid_url(self):
speed = measure_speed_from_url("www.fake-url-9999999.test")
assert not speed
def test_measurespeed_valid_url(self):
speed = measure_speed_from_url(SIZE_URL_LIST[0][1])
assert isinstance(speed, float)
assert speed > 0
def test_internet_speed(self):
curr_speed_mbps = internetspeed()

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