mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-30 19:21:16 -05:00
Compare commits
46 Commits
feature/pr
...
4.2.0RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
68
README.mkd
68
README.mkd
@@ -1,33 +1,55 @@
|
||||
Release Notes - SABnzbd 4.2.0 Alpha 2
|
||||
Release Notes - SABnzbd 4.2.0 Release Candidate 1
|
||||
=========================================================
|
||||
|
||||
## 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 a 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.
|
||||
|
||||
* **Performance and usability improvements:**
|
||||
* Numerous smaller performance improvements were made.
|
||||
* Server IP-address selection was optimized.
|
||||
* The `Internet Bandwidth` test was made more reliable.
|
||||
* Windows/macOS: 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.
|
||||
* 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)
|
||||
|
||||
@@ -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
|
||||
@@ -5,11 +5,11 @@ 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'
|
||||
|
||||
|
||||
@@ -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.0Beta1\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.0Beta1\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
|
||||
@@ -1369,6 +1364,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"
|
||||
@@ -1642,6 +1642,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 +2019,6 @@ msgstr ""
|
||||
msgid "Scheduling"
|
||||
msgstr ""
|
||||
|
||||
#. Main menu item
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "RSS"
|
||||
msgstr ""
|
||||
|
||||
#. Main menu item
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notifications"
|
||||
@@ -2427,7 +2427,6 @@ msgstr ""
|
||||
msgid "Backup"
|
||||
msgstr ""
|
||||
|
||||
#. Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Read the Wiki Help on this!"
|
||||
msgstr ""
|
||||
@@ -2904,19 +2903,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 +2923,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.0Beta1\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."
|
||||
@@ -1441,6 +1436,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 ""
|
||||
@@ -1717,6 +1717,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 +2094,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 +2522,6 @@ msgstr ""
|
||||
msgid "Backup"
|
||||
msgstr "Záloha"
|
||||
|
||||
#. Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Read the Wiki Help on this!"
|
||||
msgstr ""
|
||||
@@ -3047,23 +3046,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 +3067,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.0Beta1\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."
|
||||
@@ -1473,6 +1468,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 ""
|
||||
@@ -1749,6 +1749,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 +2126,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 +2563,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 +3114,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 +3135,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.0Beta1\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."
|
||||
@@ -1535,6 +1529,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 ""
|
||||
@@ -1814,6 +1814,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 +2191,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 +2641,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 +3233,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 +3254,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.0Beta1\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."
|
||||
@@ -1525,6 +1520,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 ""
|
||||
@@ -1805,6 +1805,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 +2182,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 +2629,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 +3196,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 +3217,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.0Beta1\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."
|
||||
@@ -1467,6 +1462,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 ""
|
||||
@@ -1743,6 +1743,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 +2120,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 +2559,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 +3121,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 +3142,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.0Beta1\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."
|
||||
@@ -1249,7 +1244,7 @@ msgstr "DOUBLON"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
msgstr "ALTERNATIVE"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
@@ -1531,6 +1526,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 ""
|
||||
@@ -1812,6 +1812,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 +2189,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 +2641,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 +3234,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 +3256,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
|
||||
|
||||
@@ -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:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2023\n"
|
||||
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
|
||||
@@ -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 ""
|
||||
|
||||
#: sabnzbd/cfg.py
|
||||
msgid "Queue not empty, cannot change folder."
|
||||
@@ -1472,6 +1467,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 "עבור מונח חיפוש regex נכשל בליקוט: %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
msgid ""
|
||||
@@ -1750,6 +1750,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 +2127,6 @@ msgstr "מתגים"
|
||||
msgid "Scheduling"
|
||||
msgstr "תזמון"
|
||||
|
||||
#. Main menu item
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#. Main menu item
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Notifications"
|
||||
@@ -2568,7 +2568,6 @@ msgstr "זמן פעולה"
|
||||
msgid "Backup"
|
||||
msgstr "גיבוי"
|
||||
|
||||
#. Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Read the Wiki Help on this!"
|
||||
msgstr "קרא את עזרת וויקי על זה!"
|
||||
@@ -3126,25 +3125,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 "גלה הורדות כפולות"
|
||||
|
||||
#: 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 ""
|
||||
"גלה קבצי NZB זהים (על סמך פריטים בהיסטוריה שלך או קבצים בתיקיית גיבוי .nzb)"
|
||||
|
||||
#: 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)"
|
||||
msgstr "גלה פרקים זהים בסדרות (על סמך \"שם/עונה/פרק\" של פריטים בהיסטוריה שלך)"
|
||||
msgid "Smart duplicate detection"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Detect duplicates based on analysis of the filename."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Allow proper releases"
|
||||
@@ -3152,9 +3146,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 ""
|
||||
|
||||
#. 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.0Beta1\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."
|
||||
@@ -1463,6 +1458,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 ""
|
||||
@@ -1739,6 +1739,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 +2116,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 +2553,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 +3102,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 +3123,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
|
||||
|
||||
117
po/main/nl.po
117
po/main/nl.po
@@ -8,7 +8,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: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
|
||||
@@ -1235,7 +1237,7 @@ msgstr "DUBBEL"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ALTERNATIVE"
|
||||
msgstr ""
|
||||
msgstr "ALTERNATIEF"
|
||||
|
||||
#: sabnzbd/nzbstuff.py
|
||||
msgid "ENCRYPTED"
|
||||
@@ -1511,6 +1513,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 ""
|
||||
@@ -1790,6 +1797,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 +2174,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 +2607,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 +2623,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 +2990,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 +3076,7 @@ msgstr "Systeemmappen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Hidden Folders"
|
||||
msgstr ""
|
||||
msgstr "Verborgen mappen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Administrative Folder"
|
||||
@@ -3113,7 +3123,7 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Purge Logs"
|
||||
msgstr ""
|
||||
msgstr "Logs wissen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ".nzb Backup Folder"
|
||||
@@ -3192,28 +3202,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 +3225,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 +3321,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 +4297,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 +4321,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 +4358,7 @@ msgstr "Minimale bestandsgrootte"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Affected Job Types"
|
||||
msgstr ""
|
||||
msgstr "Type downloads"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "All"
|
||||
@@ -4362,15 +4366,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 +4386,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 +4665,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 +4844,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.0Beta1\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."
|
||||
@@ -1469,6 +1464,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 ""
|
||||
@@ -1745,6 +1745,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 +2122,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 +2558,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 +3110,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 +3131,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.0Beta1\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."
|
||||
@@ -1470,6 +1476,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 ""
|
||||
@@ -1748,6 +1759,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 +2136,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 +2572,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 +3123,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 +3144,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.0Beta1\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."
|
||||
@@ -1497,6 +1492,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 ""
|
||||
@@ -1775,6 +1775,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 +2152,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 +2591,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 +3139,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 +3160,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.0Beta1\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."
|
||||
@@ -1466,6 +1461,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 ""
|
||||
@@ -1742,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"
|
||||
@@ -2114,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"
|
||||
@@ -2555,7 +2555,6 @@ msgstr "Время работы"
|
||||
msgid "Backup"
|
||||
msgstr "Резервный"
|
||||
|
||||
#. Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Read the Wiki Help on this!"
|
||||
msgstr "Описание см. на вики-странице."
|
||||
@@ -3107,23 +3106,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 +3127,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.0Beta1\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."
|
||||
@@ -1460,6 +1455,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 ""
|
||||
@@ -1736,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"
|
||||
@@ -2108,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"
|
||||
@@ -2547,7 +2547,6 @@ msgstr "; Ради"
|
||||
msgid "Backup"
|
||||
msgstr "Резервно"
|
||||
|
||||
#. Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Read the Wiki Help on this!"
|
||||
msgstr "За више информација, читајте Вики!"
|
||||
@@ -3094,23 +3093,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 +3114,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.0Beta1\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."
|
||||
@@ -1466,6 +1461,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 ""
|
||||
@@ -1742,6 +1742,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 +2119,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 +2555,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 +3103,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 +3124,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.0Beta1\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."
|
||||
@@ -1453,6 +1449,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 ""
|
||||
@@ -1729,6 +1730,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 +2107,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 +2535,6 @@ msgstr "启动时间"
|
||||
msgid "Backup"
|
||||
msgstr "备份"
|
||||
|
||||
#. Notification Script settings
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Read the Wiki Help on this!"
|
||||
msgstr "关于该项请参阅 Wiki 帮助!"
|
||||
@@ -2875,11 +2875,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 +2963,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 +2987,7 @@ msgstr "SABnzbd 日志文件的位置。<br /><i>需要重启 SABnzbd 才能生
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Purge Logs"
|
||||
msgstr ""
|
||||
msgstr "清除日志"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ".nzb Backup Folder"
|
||||
@@ -3057,24 +3057,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 +3078,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 +3436,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 +3616,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.0Beta1\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.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.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"
|
||||
|
||||
@@ -30,7 +30,7 @@ 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
|
||||
|
||||
@@ -285,6 +285,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,20 +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!
|
||||
@@ -78,12 +78,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,7 +106,9 @@ 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"""
|
||||
@@ -165,7 +167,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 +181,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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
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()
|
||||
@@ -36,6 +36,7 @@ import sabnzbd.utils.rarfile as rarfile
|
||||
from sabnzbd.misc import (
|
||||
format_time_string,
|
||||
find_on_path,
|
||||
int_conv,
|
||||
get_all_passwords,
|
||||
calc_age,
|
||||
cmp,
|
||||
@@ -44,6 +45,7 @@ from sabnzbd.misc import (
|
||||
format_time_left,
|
||||
)
|
||||
from sabnzbd.filesystem import (
|
||||
make_script_path,
|
||||
real_path,
|
||||
globber,
|
||||
globber_full,
|
||||
@@ -2132,6 +2134,89 @@ def add_time_left(perc: float, start_time: Optional[float] = None, time_used: Op
|
||||
return " - %s %s" % (format_time_left(int((100 - perc) / (perc / time_used)), short_format=True), T("left"))
|
||||
return ""
|
||||
|
||||
|
||||
def pre_queue(nzo: NzbObject, pp, cat):
|
||||
"""Run pre-queue script (if any) and process results.
|
||||
pp and cat are supplied separate since they can change.
|
||||
"""
|
||||
|
||||
def fix(p):
|
||||
# If added via API, some items can still be "None" (as a string)
|
||||
if not p or str(p).lower() == "none":
|
||||
return ""
|
||||
return str(p)
|
||||
|
||||
values = [1, nzo.final_name_with_password, pp, cat, nzo.script, nzo.priority, None]
|
||||
script_path = make_script_path(cfg.pre_script())
|
||||
if script_path:
|
||||
# Basic command-line parameters
|
||||
command = [
|
||||
script_path,
|
||||
nzo.final_name_with_password,
|
||||
pp,
|
||||
cat,
|
||||
nzo.script,
|
||||
nzo.priority,
|
||||
str(nzo.bytes),
|
||||
" ".join(nzo.groups),
|
||||
]
|
||||
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),
|
||||
"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:
|
||||
p = build_and_run_command(command, env=create_env(nzo, extra_env_fields))
|
||||
except:
|
||||
logging.debug("Failed script %s, Traceback: ", script_path, exc_info=True)
|
||||
return values
|
||||
|
||||
output = p.stdout.read()
|
||||
ret = p.wait()
|
||||
logging.info("Pre-queue script returned %s and output=\n%s", ret, output)
|
||||
if ret == 0:
|
||||
split_output = output.splitlines()
|
||||
try:
|
||||
# Extract category line from pre-queue output
|
||||
pre_queue_category = split_output[3].strip(" '\"")
|
||||
except IndexError:
|
||||
pre_queue_category = None
|
||||
|
||||
for index, line in enumerate(split_output):
|
||||
line = line.strip(" '\"")
|
||||
if index < len(values):
|
||||
if line:
|
||||
values[index] = line
|
||||
elif pre_queue_category and index in (2, 4, 5):
|
||||
# Preserve empty pp, script, and priority lines to prevent
|
||||
# pre-existing values from overriding category-based settings
|
||||
values[index] = ""
|
||||
|
||||
accept = int_conv(values[0])
|
||||
if accept < 1:
|
||||
logging.info("Pre-Q refuses %s", nzo.final_name)
|
||||
elif accept == 2:
|
||||
logging.info("Pre-Q accepts&fails %s", nzo.final_name)
|
||||
else:
|
||||
logging.info("Pre-Q accepts %s", nzo.final_name)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def is_sevenfile(path: str) -> bool:
|
||||
"""Return True if path has 7Zip-signature and 7Zip is detected"""
|
||||
with open(path, "rb") as sevenzip:
|
||||
|
||||
@@ -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
|
||||
@@ -826,15 +828,64 @@ class NzbObject(TryList):
|
||||
# Determine category and find pp/script values
|
||||
self.cat, pp_tmp, self.script, priority = cat_to_opts(cat, pp, script, self.priority)
|
||||
self.set_priority(priority)
|
||||
self.set_pp(pp_tmp)
|
||||
self.repair, self.unpack, self.delete = pp_to_opts(pp_tmp)
|
||||
|
||||
# Show first meta-password (if any), when there's no explicit password
|
||||
if not self.password and self.meta.get("password"):
|
||||
self.password = self.meta.get("password", [None])[0]
|
||||
|
||||
# Run user pre-queue script
|
||||
if not reuse:
|
||||
self.run_pre_queue(input_priority, input_pp, input_script)
|
||||
# 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
|
||||
accept, name, pq_pp, pq_cat, pq_script, pq_priority, pq_group = sabnzbd.newsunpack.pre_queue(self, pp, cat)
|
||||
|
||||
if pq_cat:
|
||||
# An explicit pp/script/priority set upon adding the job takes precedence
|
||||
# over an implicit setting based on the category set by pre-queue
|
||||
if input_priority and not pq_priority:
|
||||
pq_priority = input_priority
|
||||
if input_pp and not pq_pp:
|
||||
pq_pp = input_pp
|
||||
if input_script and not pq_script:
|
||||
pq_script = input_script
|
||||
|
||||
# Accept or reject
|
||||
accept = int_conv(accept)
|
||||
if accept < 1:
|
||||
self.purge_data()
|
||||
raise NzbRejected
|
||||
if accept == 2:
|
||||
raise NzbRejectToHistory(self, T("Pre-queue script marked job as failed"))
|
||||
|
||||
# Process all options, only over-write if set by script
|
||||
# Beware that cannot do "if priority/pp", because those can
|
||||
# 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:
|
||||
pp = None
|
||||
if pq_cat:
|
||||
cat = pq_cat
|
||||
try:
|
||||
priority = int(pq_priority)
|
||||
except:
|
||||
priority = DEFAULT_PRIORITY
|
||||
if pq_script and is_valid_script(pq_script):
|
||||
script = pq_script
|
||||
if pq_group:
|
||||
self.groups = [str(pq_group)]
|
||||
|
||||
# Re-evaluate results from pre-queue script
|
||||
self.cat, pp, self.script, priority = cat_to_opts(cat, pp, script, priority)
|
||||
self.set_priority(priority)
|
||||
self.repair, self.unpack, self.delete = pp_to_opts(pp)
|
||||
|
||||
# Pause if requested by the NZB-adding or the pre-queue script
|
||||
if self.priority == PAUSED_PRIORITY:
|
||||
@@ -1247,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)
|
||||
@@ -1300,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"))
|
||||
@@ -1320,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
|
||||
|
||||
@@ -1521,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)
|
||||
@@ -1841,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
|
||||
@@ -1872,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
|
||||
|
||||
@@ -1913,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()
|
||||
@@ -1932,101 +2017,13 @@ 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()
|
||||
|
||||
def run_pre_queue(
|
||||
self,
|
||||
input_priority: Optional[Union[int, str]],
|
||||
input_pp: Optional[int],
|
||||
input_script: Optional[str],
|
||||
):
|
||||
"""Run pre-queue script (if any) and process results."""
|
||||
if script_path := make_script_path(cfg.pre_script()):
|
||||
|
||||
def fix_parameter(parameter: Any) -> str:
|
||||
# If added via API, some items can still be "None" (as a string)
|
||||
if not parameter or str(parameter).lower() == "none":
|
||||
return ""
|
||||
return str(parameter)
|
||||
|
||||
# Basic parameters
|
||||
command = [script_path, self.final_name_with_password, self.cat, self.priority, self.pp, self.script]
|
||||
command = [fix_parameter(arg) for arg in command]
|
||||
|
||||
# Fields not in the NZO directly
|
||||
extra_env_fields = sabnzbd.newsunpack.analyse_show(self.final_name_with_password)
|
||||
extra_env_fields["groups"] = " ".join(self.groups)
|
||||
|
||||
try:
|
||||
p = sabnzbd.newsunpack.build_and_run_command(
|
||||
command, env=sabnzbd.newsunpack.create_env(self, extra_env_fields)
|
||||
)
|
||||
except:
|
||||
logging.debug("Failed script %s, Traceback: ", script_path, exc_info=True)
|
||||
return
|
||||
|
||||
output = p.stdout.read()
|
||||
ret = p.wait()
|
||||
logging.info("Pre-queue script returned %s and output=\n%s", ret, output)
|
||||
if ret == 0:
|
||||
# Base values
|
||||
pq_cat = pq_pp = pq_script = pq_priority = None
|
||||
for index, line in enumerate(output.splitlines(), start=1):
|
||||
# Make sure to keep this in line with the documentation!
|
||||
# 1: Accept
|
||||
# 2: Name
|
||||
# 3: Category
|
||||
# 4: Priority
|
||||
# 5: Post-processing
|
||||
# 6: Script
|
||||
# 7: Duplicate
|
||||
# 8: Duplicate key
|
||||
if line := line.strip(" '\""):
|
||||
if index == 1:
|
||||
# Accept or reject
|
||||
accept = int_conv(line)
|
||||
if accept < 1:
|
||||
logging.info("Pre-queue script refuses %s", self.final_name)
|
||||
self.purge_data()
|
||||
raise NzbRejected
|
||||
if accept == 2:
|
||||
logging.info("Pre-queue marking as failed %s", self.final_name)
|
||||
raise NzbRejectToHistory(self, T("Pre-queue script marked job as failed"))
|
||||
logging.info("Pre-queue accepts %s", self.final_name)
|
||||
elif index == 2:
|
||||
self.set_final_name_and_scan_password(line)
|
||||
elif index == 3:
|
||||
pq_cat = line
|
||||
elif index == 4:
|
||||
pq_priority = int_conv(line, default=DEFAULT_PRIORITY)
|
||||
elif index == 5:
|
||||
pq_pp = int_conv(line, default=None)
|
||||
elif index == 6:
|
||||
if is_valid_script(line):
|
||||
pq_script = line
|
||||
elif index == 7:
|
||||
self.duplicate = line
|
||||
elif index == 8:
|
||||
self.duplicate_series_key = line
|
||||
|
||||
if pq_cat:
|
||||
# An explicit pp/script/priority set upon adding the job takes precedence
|
||||
# over an implicit setting based on the category set by pre-queue
|
||||
if input_priority and pq_priority is None:
|
||||
pq_priority = input_priority
|
||||
if input_pp and pq_pp is None:
|
||||
pq_pp = input_pp
|
||||
if input_script and not pq_script:
|
||||
pq_script = input_script
|
||||
|
||||
# Re-evaluate results from pre-queue script
|
||||
self.cat, pp, self.script, priority = cat_to_opts(pq_cat, pq_pp, pq_script, pq_priority)
|
||||
self.set_priority(priority)
|
||||
self.set_pp(pp)
|
||||
|
||||
def __getstate__(self):
|
||||
"""Save to pickle file, selecting attributes"""
|
||||
dict_ = {}
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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 + "*"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.0RC1"
|
||||
__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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -174,17 +174,9 @@ class TestAddingNZBs:
|
||||
try:
|
||||
script_path = os.path.join(VAR.SCRIPT_DIR, script_name)
|
||||
with open(script_path, "w") as f:
|
||||
# Lines:
|
||||
# 1: Accept
|
||||
# 2: Name
|
||||
# 3: Category
|
||||
# 4: Priority
|
||||
# 5: Post-processing
|
||||
# 6: Script
|
||||
# 7: Duplicate
|
||||
# 8: Duplicate key
|
||||
# line 1 = accept; 4 = category; 6 = priority
|
||||
f.write(
|
||||
"#!%s\n\nprint('1\\n\\n%s\\n%s\\n')"
|
||||
"#!%s\n\nprint('1\\n\\n\\n%s\\n\\n%s\\n')"
|
||||
% (
|
||||
sys.executable,
|
||||
(category if category else ""),
|
||||
@@ -414,8 +406,8 @@ class TestAddingNZBs:
|
||||
@pytest.mark.parametrize("prio_def_cat", sample(VALID_DEFAULT_PRIORITIES, 2))
|
||||
@pytest.mark.parametrize("prio_add", sample(PRIO_OPTS_ADD, 3))
|
||||
@pytest.mark.parametrize("prio_add_cat", sample(PRIO_OPTS_ADD_CAT, 2))
|
||||
@pytest.mark.parametrize("prio_preq", PRIO_OPTS_PREQ)
|
||||
@pytest.mark.parametrize("prio_preq_cat", PRIO_OPTS_PREQ_CAT)
|
||||
@pytest.mark.parametrize("prio_preq", sample(PRIO_OPTS_PREQ, 2))
|
||||
@pytest.mark.parametrize("prio_preq_cat", sample(PRIO_OPTS_PREQ_CAT, 2))
|
||||
def test_adding_nzbs_priority_sample(
|
||||
self, prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat
|
||||
):
|
||||
|
||||
@@ -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,9 +86,13 @@ 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
|
||||
(
|
||||
"single-ep_sort_s06e66_4k_uhd-SABnzbd",
|
||||
|
||||
@@ -54,7 +54,7 @@ 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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -219,7 +219,7 @@ class FakeHistoryDB(db.HistoryDB):
|
||||
nzo.repair, nzo.unpack, nzo.delete = pp_to_opts(choice(list(PP_LOOKUP.keys()))) # for "pp"
|
||||
nzo.nzo_info = {"download_time": randint(1, 10**4)}
|
||||
nzo.unpack_info = {"unpack_info": "placeholder unpack_info line\r\n" * 3}
|
||||
nzo.duplicate_series_key = "show/season/episode"
|
||||
nzo.duplicate_key = "show/season/episode"
|
||||
nzo.futuretype = False # for "report", only True when fetching an URL
|
||||
nzo.download_path = os.path.join(os.path.dirname(db.HistoryDB.db_path), "placeholder_downpath")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user