mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-27 17:49:20 -05:00
Compare commits
65 Commits
feature/pr
...
4.2.0RC2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
521b97b7b7 | ||
|
|
58c8601067 | ||
|
|
36609376e8 | ||
|
|
32a1c8264e | ||
|
|
06754f4ef1 | ||
|
|
99d9b3bf94 | ||
|
|
ec71d20d37 | ||
|
|
2d1e88bb39 | ||
|
|
c9d30bb422 | ||
|
|
cd448082e3 | ||
|
|
46239dddac | ||
|
|
81177fda35 | ||
|
|
983d623d7f | ||
|
|
bdda8f4abf | ||
|
|
94fc804394 | ||
|
|
e00d8c09e7 | ||
|
|
70a40b4bdd | ||
|
|
f806a62f01 | ||
|
|
71a9281b8f | ||
|
|
a34747fbd5 | ||
|
|
6b0380199b | ||
|
|
39d2f90a84 | ||
|
|
7bff7651f3 | ||
|
|
44bd15d519 | ||
|
|
1ca93b03a0 | ||
|
|
3295142d81 | ||
|
|
f12fdc46dc | ||
|
|
fc01254fe6 | ||
|
|
8fb3368601 | ||
|
|
58facc2512 | ||
|
|
b43c2b308b | ||
|
|
1e89a0af56 | ||
|
|
acd3cbbf49 | ||
|
|
a806521745 | ||
|
|
0dddaf26e0 | ||
|
|
cdf63a005b | ||
|
|
ca422a0af3 | ||
|
|
a682371a91 | ||
|
|
26ef146526 | ||
|
|
936ee58abb | ||
|
|
71d8c208bc | ||
|
|
2200ffa88e | ||
|
|
4453316516 | ||
|
|
b947207571 | ||
|
|
25d29deae6 | ||
|
|
9abe6d6d71 | ||
|
|
77dbc0a37f | ||
|
|
659117512b | ||
|
|
b1dbbc6a69 | ||
|
|
424a1c626e | ||
|
|
522666191b | ||
|
|
78055ef794 | ||
|
|
0fe534c202 | ||
|
|
257179de31 | ||
|
|
65b57112b9 | ||
|
|
27f0b1d1f2 | ||
|
|
6e31476c45 | ||
|
|
bc7f0f3fb3 | ||
|
|
13eeb5164f | ||
|
|
fc756ed23d | ||
|
|
c150365462 | ||
|
|
58d209059e | ||
|
|
506179b517 | ||
|
|
f0f4eb75df | ||
|
|
6c1c025668 |
2
.github/workflows/build_release.yml
vendored
2
.github/workflows/build_release.yml
vendored
@@ -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
|
||||
|
||||
73
README.mkd
73
README.mkd
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
<div class="col-sm-6">$T('dashboard-systemPerformance') </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') </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') </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') </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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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])
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 l’interface"
|
||||
|
||||
#: 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
|
||||
|
||||
197
po/main/he.po
197
po/main/he.po
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
131
po/main/nl.po
131
po/main/nl.po
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
@@ -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())
|
||||
|
||||
109
sabnzbd/api.py
109
sabnzbd/api.py
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
110
sabnzbd/internetspeed.py
Normal 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()
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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 + "*"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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))
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user