mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-02-23 02:07:37 -05:00
Compare commits
167 Commits
3.4.0Beta1
...
3.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7527c45cd | ||
|
|
7d5207aa67 | ||
|
|
d89b6f814b | ||
|
|
fad69356c1 | ||
|
|
2285c6e430 | ||
|
|
c1b9b727e6 | ||
|
|
a95714710b | ||
|
|
82268b58e2 | ||
|
|
0dd7e71fd1 | ||
|
|
63f1d2905f | ||
|
|
ea9b409a04 | ||
|
|
8957a8421d | ||
|
|
7c171081c6 | ||
|
|
e4520e9e16 | ||
|
|
336c373dd0 | ||
|
|
fc721f31c5 | ||
|
|
062ed9f7b8 | ||
|
|
59a915cdac | ||
|
|
191f7d2152 | ||
|
|
a76e9c2c1f | ||
|
|
9d616459b7 | ||
|
|
82fa42d182 | ||
|
|
0c36aaa5ff | ||
|
|
a9761464a0 | ||
|
|
355d02faa3 | ||
|
|
1052f37d02 | ||
|
|
654302e691 | ||
|
|
ee673b57fd | ||
|
|
2be374b841 | ||
|
|
906e1eda89 | ||
|
|
ece02cc4fa | ||
|
|
876ad60ddf | ||
|
|
862da354ac | ||
|
|
8fd477b979 | ||
|
|
2d7005655c | ||
|
|
7322f8348a | ||
|
|
e3e3a12e73 | ||
|
|
77cdd057a4 | ||
|
|
e8206fbdd9 | ||
|
|
589f15a77b | ||
|
|
7bb443678a | ||
|
|
6390415101 | ||
|
|
4abf192e11 | ||
|
|
1fed37f9da | ||
|
|
a9d86a7447 | ||
|
|
2abe4c3cef | ||
|
|
0542c25003 | ||
|
|
1b8ee4e290 | ||
|
|
51128cba55 | ||
|
|
3612432581 | ||
|
|
deca000a1b | ||
|
|
39cccb5653 | ||
|
|
f6838dc985 | ||
|
|
8cd4d92395 | ||
|
|
3bf9906f45 | ||
|
|
9f7daf96ef | ||
|
|
67de4df155 | ||
|
|
bc51a4bd1c | ||
|
|
bb54616018 | ||
|
|
6bcff5e014 | ||
|
|
8970a03a9a | ||
|
|
3ad717ca35 | ||
|
|
b14f72c67a | ||
|
|
45d036804f | ||
|
|
8f606db233 | ||
|
|
3766ba5402 | ||
|
|
e851813cef | ||
|
|
4d49ad9141 | ||
|
|
16618b3af2 | ||
|
|
0e5c0f664f | ||
|
|
7be9281431 | ||
|
|
ee0327fac1 | ||
|
|
9930de3e7f | ||
|
|
e8503e89c6 | ||
|
|
1d9ed419eb | ||
|
|
0207652e3e | ||
|
|
0f1e99c5cb | ||
|
|
f134bc7efb | ||
|
|
dcd7c7180e | ||
|
|
fbbfcd075b | ||
|
|
f42d2e4140 | ||
|
|
88882cebbc | ||
|
|
17a979675c | ||
|
|
4642850c79 | ||
|
|
e8d6eebb04 | ||
|
|
864c5160c0 | ||
|
|
99b5a00c12 | ||
|
|
85ee1f07d7 | ||
|
|
e58b4394e0 | ||
|
|
1e91a57bf1 | ||
|
|
39cee52a7e | ||
|
|
72068f939d | ||
|
|
096d0d3cad | ||
|
|
2472ab0121 | ||
|
|
00421717b8 | ||
|
|
ae96d93f94 | ||
|
|
8522c40c8f | ||
|
|
23f86e95f1 | ||
|
|
eed2045189 | ||
|
|
217785bf0f | ||
|
|
6aef50dc5d | ||
|
|
16b6e3caa7 | ||
|
|
3de4c99a8a | ||
|
|
980aa19a75 | ||
|
|
fb4b57e056 | ||
|
|
03638365ea | ||
|
|
157cb1c83d | ||
|
|
e51f11c2b1 | ||
|
|
1ad0961dd8 | ||
|
|
46ff7dd4e2 | ||
|
|
8b067df914 | ||
|
|
ef43b13272 | ||
|
|
e8e9974224 | ||
|
|
feebbb9f04 | ||
|
|
bc4f06dd1d | ||
|
|
971e4fc909 | ||
|
|
51cc765949 | ||
|
|
19c6a4fffa | ||
|
|
105ac32d2f | ||
|
|
57550675d2 | ||
|
|
e674abc5c0 | ||
|
|
f965c96f51 | ||
|
|
c76b8ed9e0 | ||
|
|
4fbd0d8a7b | ||
|
|
2186c0fff6 | ||
|
|
1adca9a9c1 | ||
|
|
9408353f2b | ||
|
|
84f4d453d2 | ||
|
|
d10209f2a1 | ||
|
|
3ae149c72f | ||
|
|
47385acc3b | ||
|
|
814eeaa900 | ||
|
|
5f2ea13aad | ||
|
|
41ca217931 | ||
|
|
b57d36e8dd | ||
|
|
9a4be70734 | ||
|
|
a8443595a6 | ||
|
|
fd0a70ac58 | ||
|
|
8a8685c968 | ||
|
|
9e6cb8da8e | ||
|
|
054ec54d51 | ||
|
|
272ce773cb | ||
|
|
050b925f7b | ||
|
|
0087940898 | ||
|
|
e323c014f9 | ||
|
|
cc465c7554 | ||
|
|
14cb37564f | ||
|
|
094db56c3b | ||
|
|
aabb709b8b | ||
|
|
0833dd2db9 | ||
|
|
cd3f912be4 | ||
|
|
665c516db6 | ||
|
|
b670da9fa0 | ||
|
|
80bee9bffe | ||
|
|
d85a70e8ad | ||
|
|
8f21533e76 | ||
|
|
89996482a1 | ||
|
|
03c10dce91 | ||
|
|
bd5331be05 | ||
|
|
46e1645289 | ||
|
|
4ce3965747 | ||
|
|
9d4af19db3 | ||
|
|
48e034f4be | ||
|
|
f8959baa2f | ||
|
|
8ed5997eae | ||
|
|
daf9f50ac8 | ||
|
|
6b11013c1a |
2
.github/workflows/build_release.yml
vendored
2
.github/workflows/build_release.yml
vendored
@@ -73,7 +73,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.9.5
|
||||
PYTHON_VERSION: 3.9.7
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.9
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
4
PKG-INFO
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 3.4.0Beta1
|
||||
Summary: SABnzbd-3.4.0Beta1
|
||||
Version: 3.4.0
|
||||
Summary: SABnzbd-3.4.0
|
||||
Home-page: https://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Release Notes - SABnzbd 3.4.0 Beta 1
|
||||
Release Notes - SABnzbd 3.4.0
|
||||
=========================================================
|
||||
|
||||
## Changes since 3.3.1
|
||||
@@ -7,13 +7,19 @@ Release Notes - SABnzbd 3.4.0 Beta 1
|
||||
not present or meaningless.
|
||||
- Added additional pattern keys that can be used in the `Sort String`
|
||||
for Sorting, by using the `guessit` package internally for parsing.
|
||||
- If unpacked files contain `.par2` files they will always be read and
|
||||
used to rename any matching files.
|
||||
- Regular expressions can be used to specify `Unwanted extensions`.
|
||||
- Not all passwords will be tried if a matching one was found.
|
||||
- Some interface-only options were added as API-call.
|
||||
- The Plush skin has been removed.
|
||||
|
||||
## Bugfixes since 3.3.1
|
||||
- Duplicate check based on `.nzb` MD5 was performed before it was calculated.
|
||||
- Enforce `local_ranges` for broadcasts (Bonjour/SSDP).
|
||||
- Correctly parse the filename in `Content-Disposition` header.
|
||||
- `Warning` instead of `Info` when there is a restart due to crashed thread.
|
||||
- Only run Direct Unpack if `enable_unrar` is enabled.
|
||||
|
||||
## Upgrade notices
|
||||
- The download statistics file `totals10.sab` is updated in 3.2.x
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Basic build requirements
|
||||
pyinstaller==4.2
|
||||
pyinstaller
|
||||
setuptools
|
||||
pkginfo
|
||||
certifi
|
||||
|
||||
@@ -153,6 +153,26 @@ msgstr ""
|
||||
msgid "Loading %s failed"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -166,6 +166,26 @@ msgstr "Chyba v tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Načítání %s selhalo"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -169,6 +169,26 @@ msgstr "Fejl i tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Downloadning af %s mislykkedes"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
# reloxx13 <reloxx@interia.pl>, 2021
|
||||
# Ben Hecht <benjamin.hecht@me.com>, 2021
|
||||
# Safihre <safihre@sabnzbd.org>, 2021
|
||||
# Manuel C. Senn, 2021
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SABnzbd-develop\n"
|
||||
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
|
||||
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2021\n"
|
||||
"Last-Translator: Manuel C. Senn, 2021\n"
|
||||
"Language-Team: German (https://www.transifex.com/sabnzbd/teams/111101/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -178,6 +179,26 @@ msgstr "Fehler in tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Fehler beim Laden von %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr "Neustart aufgrund eines abgestürzten Postprocessors"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr "Neustart aufgrund eines abgestürzten Downloaders"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr "Neustart aufgrund eines abgestürzten Dekoders"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr "Neustart aufgrund eines abgestürzten Assemblers"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
@@ -1667,7 +1688,7 @@ msgstr "RAR-Datei konnten nicht überprüft werden"
|
||||
|
||||
#: sabnzbd/postproc.py
|
||||
msgid "Trying RAR renamer"
|
||||
msgstr ""
|
||||
msgstr "Versuche RAR Umbenenner"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/postproc.py
|
||||
@@ -3585,6 +3606,8 @@ msgid ""
|
||||
"Select a mode and list all (un)wanted extensions. For example: <b>exe</b> or"
|
||||
" <b>exe, com</b>"
|
||||
msgstr ""
|
||||
"Modus auswählen, und alle (nicht-)erwünschten Erweiterungen auflisten. Zum "
|
||||
"Beispiel : <b>exe</b> oder <b>exe, com</b>"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Enable SFV-based checks"
|
||||
@@ -3780,6 +3803,9 @@ msgid ""
|
||||
"Additionally, attempts to set the correct file extension based on the file "
|
||||
"signature if the extension is not present or meaningless."
|
||||
msgstr ""
|
||||
"Zusätzlich wird versucht die korrekte Dateierweiterung mithilfe der "
|
||||
"Dateisignatur zu ermitteln, falls noch keine Dateierweiterung vorhanden, "
|
||||
"oder sie sinnlos sein sollte."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
@@ -3994,11 +4020,11 @@ msgstr "Zeitüberschreitung"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Account expiration date"
|
||||
msgstr ""
|
||||
msgstr "Account Ablaufdatum"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Warn 5 days in advance of account expiration date."
|
||||
msgstr ""
|
||||
msgstr "5 Tage vor dem Ablauf des Accounts warnen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
|
||||
@@ -177,6 +177,26 @@ msgstr "Fallo en tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Cargar de %s no se pudo completar."
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -170,6 +170,26 @@ msgstr "Virhe tiedostossa tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Kohteen %s lataaminen epäonnistui"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -178,6 +178,26 @@ msgstr "Échec dans tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Echec du chargement de %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr "Redémarrage suite au plantage du postprocesseur"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr "Redémarrage suite au plantage du téléchargeur"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr "Redémarrage suite au plantage du décodeur"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr "Redémarrage suite au plantage de l'assembleur"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -165,6 +165,26 @@ msgstr "כישלון ב־tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "טעינת %s נכשלה"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr "מפעיל מחדש בגלל בתר־מעבד שקרס"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr "מפעיל מחדש בגלל מורידן שקרס"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr "מפעיל מחדש בגלל מפענח קוד שקרס"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr "מפעיל מחדש בגלל אסמבלר שקרס"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
@@ -4604,23 +4624,23 @@ msgstr "תוצאה מעובדת"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Any property"
|
||||
msgstr ""
|
||||
msgstr "קניין כלשהו"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "property"
|
||||
msgstr ""
|
||||
msgstr "קניין"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "GuessIt Property"
|
||||
msgstr ""
|
||||
msgstr "קניין GuessIt"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "GuessIt.Property"
|
||||
msgstr ""
|
||||
msgstr "GuessIt.Property"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "GuessIt_Property"
|
||||
msgstr ""
|
||||
msgstr "GuessIt_Property"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
|
||||
@@ -167,6 +167,26 @@ msgstr "Feil i tempfil.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Lasting av %s mislyktes"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -171,6 +171,26 @@ msgstr "Probleem met tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Inlezen van %s mislukt"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr "SABnzbd wordt herstart omdat de postprocessor is gecrasht"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr "SABnzbd wordt herstart omdat de downloader is gecrasht"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr "SABnzbd wordt herstart omdat de decoder is gecrasht"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr "SABnzbd wordt herstart omdat de assembler is gecrasht"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
@@ -671,7 +691,7 @@ msgstr "Inloggen mislukt, controleer gebruikersnaam en wachtwoord."
|
||||
#. Warning message
|
||||
#: sabnzbd/interface.py
|
||||
msgid "Unsuccessful login attempt from %s"
|
||||
msgstr "Mislukte login progin bij %s"
|
||||
msgstr "Mislukte login poging van %s"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
@@ -3736,6 +3756,9 @@ msgid ""
|
||||
"Additionally, attempts to set the correct file extension based on the file "
|
||||
"signature if the extension is not present or meaningless."
|
||||
msgstr ""
|
||||
"Als de bestandsextensie van een bestand ontbreekt of verhaspelt is, zal er "
|
||||
"geprobeerd worden de correcte extensie te vinden op basis van de inhoud van "
|
||||
"het bestand."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "HTTPS certificate verification"
|
||||
@@ -4693,23 +4716,23 @@ msgstr "Bewerkt resultaat"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Any property"
|
||||
msgstr ""
|
||||
msgstr "Alle eigenschappen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "property"
|
||||
msgstr ""
|
||||
msgstr "eigenschap"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "GuessIt Property"
|
||||
msgstr ""
|
||||
msgstr "GuessIt eigenschap"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "GuessIt.Property"
|
||||
msgstr ""
|
||||
msgstr "GuessIt.Eigenschap"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "GuessIt_Property"
|
||||
msgstr ""
|
||||
msgstr "GuessIt_Eigenschap"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
|
||||
@@ -162,6 +162,26 @@ msgstr "Błąd w tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Nie udało się wczytać %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -166,6 +166,26 @@ msgstr "Falha em tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Falha ao carregar %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -172,6 +172,26 @@ msgstr "Eroare în tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Încărcarea %s nereuşită"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -166,6 +166,26 @@ msgstr "Ошибка в tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Ошибка загрузки %s"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -164,6 +164,26 @@ msgstr "Грешка у tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Učitavanje %s neuspešno"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -164,6 +164,26 @@ msgstr "Fel i tempfile.mkstemp"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "Laddning av %s misslyckades"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -162,6 +162,26 @@ msgstr "tempfile.mkstemp 出错"
|
||||
msgid "Loading %s failed"
|
||||
msgstr "加载 %s 失败"
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed postprocessor"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed downloader"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed decoder"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Restarting because of crashed assembler"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message
|
||||
#: sabnzbd/__init__.py
|
||||
msgid "Cannot access PID file %s"
|
||||
|
||||
@@ -1000,16 +1000,16 @@ def check_all_tasks():
|
||||
|
||||
# Non-restartable threads, require program restart
|
||||
if not sabnzbd.PostProcessor.is_alive():
|
||||
logging.info("Restarting because of crashed postprocessor")
|
||||
logging.warning(T("Restarting because of crashed postprocessor"))
|
||||
return False
|
||||
if not sabnzbd.Downloader.is_alive():
|
||||
logging.info("Restarting because of crashed downloader")
|
||||
logging.warning(T("Restarting because of crashed downloader"))
|
||||
return False
|
||||
if not sabnzbd.Decoder.is_alive():
|
||||
logging.info("Restarting because of crashed decoder")
|
||||
logging.warning(T("Restarting because of crashed decoder"))
|
||||
return False
|
||||
if not sabnzbd.Assembler.is_alive():
|
||||
logging.info("Restarting because of crashed assembler")
|
||||
logging.warning(T("Restarting because of crashed assembler"))
|
||||
return False
|
||||
|
||||
# Kick the downloader, in case it missed the semaphore
|
||||
|
||||
@@ -355,9 +355,8 @@ def check_encrypted_and_unwanted_files(nzo: NzbObject, filepath: str) -> Tuple[b
|
||||
|
||||
# Did any work?
|
||||
if password_hit:
|
||||
# We always trust the user's input
|
||||
if not nzo.password:
|
||||
nzo.password = password_hit
|
||||
# Record the successful password
|
||||
nzo.correct_password = password_hit
|
||||
# Don't check other files
|
||||
logging.info('Password "%s" matches for job "%s"', password_hit, nzo.final_name)
|
||||
nzo.encrypted = -1
|
||||
|
||||
@@ -190,6 +190,7 @@ replace_spaces = OptionBool("misc", "replace_spaces", False)
|
||||
replace_dots = OptionBool("misc", "replace_dots", False)
|
||||
safe_postproc = OptionBool("misc", "safe_postproc", True)
|
||||
pause_on_post_processing = OptionBool("misc", "pause_on_post_processing", False)
|
||||
enable_all_par = OptionBool("misc", "enable_all_par", False)
|
||||
sanitize_safe = OptionBool("misc", "sanitize_safe", False)
|
||||
cleanup_list = OptionList("misc", "cleanup_list")
|
||||
unwanted_extensions = OptionList("misc", "unwanted_extensions")
|
||||
@@ -199,6 +200,7 @@ new_nzb_on_failure = OptionBool("misc", "new_nzb_on_failure", False)
|
||||
history_retention = OptionStr("misc", "history_retention", "0")
|
||||
enable_meta = OptionBool("misc", "enable_meta", True)
|
||||
|
||||
|
||||
quota_size = OptionStr("misc", "quota_size")
|
||||
quota_day = OptionStr("misc", "quota_day")
|
||||
quota_resume = OptionBool("misc", "quota_resume", False)
|
||||
@@ -258,8 +260,8 @@ rss_rate = OptionNumber("misc", "rss_rate", 60, 15, 24 * 60)
|
||||
ampm = OptionBool("misc", "ampm", False)
|
||||
replace_illegal = OptionBool("misc", "replace_illegal", True)
|
||||
start_paused = OptionBool("misc", "start_paused", False)
|
||||
enable_all_par = OptionBool("misc", "enable_all_par", False)
|
||||
enable_par_cleanup = OptionBool("misc", "enable_par_cleanup", True)
|
||||
process_unpacked_par2 = OptionBool("misc", "process_unpacked_par2", True)
|
||||
enable_unrar = OptionBool("misc", "enable_unrar", True)
|
||||
enable_unzip = OptionBool("misc", "enable_unzip", True)
|
||||
enable_7zip = OptionBool("misc", "enable_7zip", True)
|
||||
|
||||
@@ -504,7 +504,7 @@ def build_history_info(nzo, workdir_complete="", postproc_time=0, script_output=
|
||||
nzo.bytes_downloaded,
|
||||
series,
|
||||
nzo.md5sum,
|
||||
nzo.password,
|
||||
nzo.correct_password,
|
||||
)
|
||||
|
||||
|
||||
|
||||
54
sabnzbd/deobfuscate_filenames.py
Normal file → Executable file
54
sabnzbd/deobfuscate_filenames.py
Normal file → Executable file
@@ -36,18 +36,19 @@ import re
|
||||
from sabnzbd.filesystem import get_unique_filename, renamer, get_ext
|
||||
from sabnzbd.par2file import is_parfile, parse_par2_file
|
||||
import sabnzbd.utils.file_extension as file_extension
|
||||
from typing import List
|
||||
|
||||
# Files to exclude and minimal file size for renaming
|
||||
EXCLUDED_FILE_EXTS = (".vob", ".rar", ".par2", ".mts", ".m2ts", ".cpi", ".clpi", ".mpl", ".mpls", ".bdm", ".bdmv")
|
||||
MIN_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
|
||||
def decode_par2(parfile):
|
||||
def decode_par2(parfile: str) -> List[str]:
|
||||
"""Parse a par2 file and rename files listed in the par2 to their real name. Resturn list of generated files"""
|
||||
# Check if really a par2 file
|
||||
if not is_parfile(parfile):
|
||||
logging.info("Par2 file %s was not really a par2 file")
|
||||
return False
|
||||
return []
|
||||
|
||||
# Parse the par2 file
|
||||
md5of16k = {}
|
||||
@@ -74,7 +75,31 @@ def decode_par2(parfile):
|
||||
return new_files
|
||||
|
||||
|
||||
def is_probably_obfuscated(myinputfilename):
|
||||
def recover_par2_names(filelist: List[str]) -> List[str]:
|
||||
"""Find par2 files and use them for renaming"""
|
||||
# Check that files exists
|
||||
filelist = [f for f in filelist if os.path.isfile(f)]
|
||||
# Search for par2 files in the filelist
|
||||
par2_files = [f for f in filelist if f.endswith(".par2")]
|
||||
# Found any par2 files we can use?
|
||||
if not par2_files:
|
||||
logging.debug("No par2 files found to process, running renamer")
|
||||
else:
|
||||
# Run par2 from SABnzbd on them
|
||||
for par2_file in par2_files:
|
||||
# Analyse data and analyse result
|
||||
logging.debug("Deobfuscate par2: handling %s", par2_file)
|
||||
new_files = decode_par2(par2_file)
|
||||
if new_files:
|
||||
logging.debug("Deobfuscate par2 repair/verify finished")
|
||||
filelist += new_files
|
||||
filelist = [f for f in filelist if os.path.isfile(f)]
|
||||
else:
|
||||
logging.debug("Deobfuscate par2 repair/verify did not find anything to rename")
|
||||
return filelist
|
||||
|
||||
|
||||
def is_probably_obfuscated(myinputfilename: str) -> bool:
|
||||
"""Returns boolean if filename is likely obfuscated. Default: True
|
||||
myinputfilename (string) can be a plain file name, or a full path"""
|
||||
|
||||
@@ -92,7 +117,7 @@ def is_probably_obfuscated(myinputfilename):
|
||||
return True
|
||||
|
||||
# 0675e29e9abfd2.f7d069dab0b853283cc1b069a25f82.6547
|
||||
if re.findall(r"^[a-f0-9\.]{40,}$", filebasename):
|
||||
if re.findall(r"^[a-f0-9.]{40,}$", filebasename):
|
||||
logging.debug("Obfuscated: starting with 40+ lower case hex digits and/or dots")
|
||||
return True
|
||||
|
||||
@@ -133,7 +158,7 @@ def is_probably_obfuscated(myinputfilename):
|
||||
return True # default not obfuscated
|
||||
|
||||
|
||||
def deobfuscate_list(filelist, usefulname):
|
||||
def deobfuscate_list(filelist: List[str], usefulname: str):
|
||||
"""Check all files in filelist, and if wanted, deobfuscate: rename to filename based on usefulname"""
|
||||
|
||||
# Methods
|
||||
@@ -144,25 +169,6 @@ def deobfuscate_list(filelist, usefulname):
|
||||
# to be sure, only keep really exsiting files:
|
||||
filelist = [f for f in filelist if os.path.isfile(f)]
|
||||
|
||||
# Search for par2 files in the filelist
|
||||
par2_files = [f for f in filelist if f.endswith(".par2")]
|
||||
# Found any par2 files we can use?
|
||||
par2_renaming_done = False
|
||||
if not par2_files:
|
||||
logging.debug("No par2 files found to process, running renamer")
|
||||
else:
|
||||
# Run par2 from SABnzbd on them
|
||||
for par2_file in par2_files:
|
||||
# Analyse data and analyse result
|
||||
logging.debug("Deobfuscate par2: handling %s", par2_file)
|
||||
new_files = decode_par2(par2_file)
|
||||
if new_files:
|
||||
logging.debug("Deobfuscate par2 repair/verify finished")
|
||||
filelist += new_files
|
||||
filelist = [f for f in filelist if os.path.isfile(f)]
|
||||
else:
|
||||
logging.debug("Deobfuscate par2 repair/verify did not find anything to rename")
|
||||
|
||||
# let's see if there are files with uncommon/unpopular (so: obfuscated) extensions
|
||||
# if so, let's give them a better extension based on their internal content/info
|
||||
# Example: if 'kjladsflkjadf.adsflkjads' is probably a PNG, rename to 'kjladsflkjadf.adsflkjads.png'
|
||||
|
||||
@@ -95,7 +95,8 @@ class DirectUnpacker(threading.Thread):
|
||||
|
||||
def check_requirements(self):
|
||||
if (
|
||||
not cfg.direct_unpack()
|
||||
not cfg.enable_unrar()
|
||||
or not cfg.direct_unpack()
|
||||
or self.killed
|
||||
or self.nzo.first_articles
|
||||
or not self.nzo.unpack
|
||||
@@ -224,10 +225,14 @@ class DirectUnpacker(threading.Thread):
|
||||
self.unpack_time += time.time() - start_time
|
||||
ACTIVE_UNPACKERS.remove(self)
|
||||
|
||||
# Take note of the correct password
|
||||
if self.nzo.password and not self.nzo.correct_password:
|
||||
self.nzo.correct_password = self.nzo.password
|
||||
|
||||
# Add to success
|
||||
rarfile_path = os.path.join(self.nzo.download_path, self.rarfile_nzf.filename)
|
||||
self.success_sets[self.cur_setname] = (
|
||||
rar_volumelist(rarfile_path, self.nzo.password, rarfiles),
|
||||
rar_volumelist(rarfile_path, self.nzo.correct_password, rarfiles),
|
||||
extracted,
|
||||
)
|
||||
logging.info("DirectUnpack completed for %s", self.cur_setname)
|
||||
@@ -364,7 +369,9 @@ class DirectUnpacker(threading.Thread):
|
||||
extraction_path, _, _, one_folder, _ = self.unpack_dir_info
|
||||
|
||||
# Set options
|
||||
if self.nzo.password:
|
||||
if self.nzo.correct_password:
|
||||
password_command = "-p%s" % self.nzo.correct_password
|
||||
elif self.nzo.password:
|
||||
password_command = "-p%s" % self.nzo.password
|
||||
else:
|
||||
password_command = "-p-"
|
||||
|
||||
@@ -852,7 +852,7 @@ def get_filepath(path: str, nzo, filename: str):
|
||||
def renamer(old: str, new: str, create_local_directories: bool = False) -> str:
|
||||
"""Rename file/folder with retries for Win32
|
||||
Optionally alows the creation of local directories if they don't exist yet
|
||||
Returns new filename (which could be changed due to sanitize_filenam) on success"""
|
||||
Returns new filename (which could be changed due to sanitize_filename) on success"""
|
||||
# Sanitize last part of new name
|
||||
path, name = os.path.split(new)
|
||||
new = os.path.join(path, sanitize_filename(name))
|
||||
|
||||
@@ -880,6 +880,7 @@ SPECIAL_BOOL_LIST = (
|
||||
"fast_fail",
|
||||
"overwrite_files",
|
||||
"enable_par_cleanup",
|
||||
"process_unpacked_par2",
|
||||
"queue_complete_pers",
|
||||
"api_warnings",
|
||||
"helpfull_warnings",
|
||||
|
||||
@@ -750,8 +750,12 @@ def create_https_certificates(ssl_cert, ssl_key):
|
||||
return True
|
||||
|
||||
|
||||
def get_all_passwords(nzo):
|
||||
"""Get all passwords, from the NZB, meta and password file"""
|
||||
def get_all_passwords(nzo) -> List[str]:
|
||||
"""Get all passwords, from the NZB, meta and password file. In case the correct password is
|
||||
already known, only that password is returned."""
|
||||
if nzo.correct_password:
|
||||
return [nzo.correct_password]
|
||||
|
||||
if nzo.password:
|
||||
logging.info("Found a password that was set by the user: %s", nzo.password)
|
||||
passwords = [nzo.password.strip()]
|
||||
@@ -1067,7 +1071,7 @@ def build_and_run_command(command: List[str], flatten_command=False, **kwargs):
|
||||
command.insert(0, "python.exe")
|
||||
if flatten_command:
|
||||
command = list2cmdline(command)
|
||||
# On some Windows platforms we need to supress a quick pop-up of the command window
|
||||
# On some Windows platforms we need to suppress a quick pop-up of the command window
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags = win32process.STARTF_USESHOWWINDOW
|
||||
startupinfo.wShowWindow = win32con.SW_HIDE
|
||||
|
||||
@@ -148,6 +148,7 @@ ENV_NZO_FIELDS = [
|
||||
"bytes_downloaded",
|
||||
"bytes_tried",
|
||||
"cat",
|
||||
"correct_password",
|
||||
"duplicate",
|
||||
"encrypted",
|
||||
"fail_msg",
|
||||
|
||||
@@ -556,6 +556,7 @@ NzbObjectSaver = (
|
||||
"nzo_info",
|
||||
"custom_name",
|
||||
"password",
|
||||
"correct_password",
|
||||
"next_save",
|
||||
"save_timeout",
|
||||
"encrypted",
|
||||
@@ -651,6 +652,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.correct_password: Optional[str] = None
|
||||
|
||||
# Bookkeeping values
|
||||
self.meta = {}
|
||||
@@ -1216,13 +1218,16 @@ class NzbObject(TryList):
|
||||
# Check if there are any files left here, so the check is inside the NZO_LOCK
|
||||
return articles_left, file_done, not self.files
|
||||
|
||||
@synchronized(NZO_LOCK)
|
||||
def add_saved_article(self, article: Article):
|
||||
self.saved_articles.append(article)
|
||||
|
||||
@synchronized(NZO_LOCK)
|
||||
def remove_saved_article(self, article: Article):
|
||||
self.saved_articles.remove(article)
|
||||
try:
|
||||
self.saved_articles.remove(article)
|
||||
except ValueError:
|
||||
# Due to racing conditions, it could already be removed
|
||||
logging.debug("Failed to remove %s from saved articles, probably already deleted", article)
|
||||
pass
|
||||
|
||||
def check_existing_files(self, wdir: str):
|
||||
"""Check if downloaded files already exits, for these set NZF to complete"""
|
||||
|
||||
@@ -26,7 +26,7 @@ import time
|
||||
import re
|
||||
import gc
|
||||
import queue
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.newsunpack import (
|
||||
@@ -65,7 +65,7 @@ from sabnzbd.filesystem import (
|
||||
get_filename,
|
||||
)
|
||||
from sabnzbd.nzbstuff import NzbObject
|
||||
from sabnzbd.sorting import Sorter, is_sample, move_to_parent_directory
|
||||
from sabnzbd.sorting import Sorter, is_sample
|
||||
from sabnzbd.constants import (
|
||||
REPAIR_PRIORITY,
|
||||
FORCE_PRIORITY,
|
||||
@@ -80,7 +80,6 @@ import sabnzbd.emailer as emailer
|
||||
import sabnzbd.downloader
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
import sabnzbd.encoding as encoding
|
||||
import sabnzbd.nzbqueue
|
||||
import sabnzbd.database as database
|
||||
import sabnzbd.notifier as notifier
|
||||
@@ -317,6 +316,8 @@ def process_job(nzo: NzbObject):
|
||||
# Signal empty download, for when 'empty_postproc' is enabled
|
||||
empty = False
|
||||
nzb_list = []
|
||||
one_folder = False
|
||||
newfiles = []
|
||||
# These need to be initialized in case of a crash
|
||||
workdir_complete = ""
|
||||
script_log = ""
|
||||
@@ -419,7 +420,6 @@ def process_job(nzo: NzbObject):
|
||||
nzo
|
||||
)
|
||||
|
||||
newfiles = []
|
||||
# Run Stage 2: Unpack
|
||||
if flag_unpack:
|
||||
# Set the current nzo status to "Extracting...". Used in History
|
||||
@@ -480,6 +480,7 @@ def process_job(nzo: NzbObject):
|
||||
|
||||
script_output = ""
|
||||
script_ret = 0
|
||||
script_error = False
|
||||
if not nzb_list:
|
||||
# Give destination its final name
|
||||
if cfg.folder_rename() and tmp_workdir_complete and not one_folder:
|
||||
@@ -511,51 +512,49 @@ def process_job(nzo: NzbObject):
|
||||
# TV/Movie/Date Renaming code part 2 - rename and move files to parent folder
|
||||
if all_ok and file_sorter.sort_file:
|
||||
if newfiles:
|
||||
workdir_complete, ok = file_sorter.rename(newfiles, workdir_complete)
|
||||
if not ok:
|
||||
workdir_complete, ok = move_to_parent_directory(workdir_complete)
|
||||
workdir_complete, ok = file_sorter.sorter.rename(newfiles, workdir_complete)
|
||||
if not ok:
|
||||
nzo.set_unpack_info("Unpack", T("Failed to move files"))
|
||||
all_ok = False
|
||||
|
||||
if cfg.deobfuscate_final_filenames() and all_ok and not nzb_list:
|
||||
# Deobfuscate the filenames
|
||||
logging.info("Running deobfuscate")
|
||||
deobfuscate.deobfuscate_list(newfiles, nzo.final_name)
|
||||
# Run further post-processing
|
||||
if (all_ok or not cfg.safe_postproc()) and not nzb_list:
|
||||
# Use par2 files to deobfuscate unpacked file names
|
||||
if cfg.process_unpacked_par2():
|
||||
newfiles = deobfuscate.recover_par2_names(newfiles)
|
||||
|
||||
# Run the user script
|
||||
script_path = make_script_path(script)
|
||||
if (all_ok or not cfg.safe_postproc()) and (not nzb_list) and script_path:
|
||||
# Set the current nzo status to "Ext Script...". Used in History
|
||||
nzo.status = Status.RUNNING
|
||||
nzo.set_action_line(T("Running script"), script)
|
||||
nzo.set_unpack_info("Script", T("Running user script %s") % script, unique=True)
|
||||
script_log, script_ret = external_processing(
|
||||
script_path, nzo, clip_path(workdir_complete), nzo.final_name, job_result
|
||||
)
|
||||
script_line = get_last_line(script_log)
|
||||
if script_log:
|
||||
script_output = nzo.nzo_id
|
||||
if script_line:
|
||||
nzo.set_unpack_info("Script", script_line, unique=True)
|
||||
else:
|
||||
nzo.set_unpack_info("Script", T("Ran %s") % script, unique=True)
|
||||
else:
|
||||
script = ""
|
||||
script_line = ""
|
||||
script_ret = 0
|
||||
if cfg.deobfuscate_final_filenames():
|
||||
# Deobfuscate the filenames
|
||||
logging.info("Running deobfuscate")
|
||||
deobfuscate.deobfuscate_list(newfiles, nzo.final_name)
|
||||
|
||||
# Maybe bad script result should fail job
|
||||
if script_ret and cfg.script_can_fail():
|
||||
script_error = True
|
||||
all_ok = False
|
||||
nzo.fail_msg = T("Script exit code is %s") % script_ret
|
||||
else:
|
||||
script_error = False
|
||||
# Run the user script
|
||||
script_path = make_script_path(script)
|
||||
if script_path:
|
||||
# Set the current nzo status to "Ext Script...". Used in History
|
||||
nzo.status = Status.RUNNING
|
||||
nzo.set_action_line(T("Running script"), script)
|
||||
nzo.set_unpack_info("Script", T("Running user script %s") % script, unique=True)
|
||||
script_log, script_ret = external_processing(
|
||||
script_path, nzo, clip_path(workdir_complete), nzo.final_name, job_result
|
||||
)
|
||||
script_line = get_last_line(script_log)
|
||||
if script_log:
|
||||
script_output = nzo.nzo_id
|
||||
if script_line:
|
||||
nzo.set_unpack_info("Script", script_line, unique=True)
|
||||
else:
|
||||
nzo.set_unpack_info("Script", T("Ran %s") % script, unique=True)
|
||||
|
||||
# Maybe bad script result should fail job
|
||||
if script_ret and cfg.script_can_fail():
|
||||
script_error = True
|
||||
all_ok = False
|
||||
nzo.fail_msg = T("Script exit code is %s") % script_ret
|
||||
|
||||
# Email the results
|
||||
if (not nzb_list) and cfg.email_endjob():
|
||||
if (cfg.email_endjob() == 1) or (cfg.email_endjob() == 2 and (unpack_error or par_error or script_error)):
|
||||
if not nzb_list and cfg.email_endjob():
|
||||
if cfg.email_endjob() == 1 or (cfg.email_endjob() == 2 and (unpack_error or par_error or script_error)):
|
||||
emailer.endjob(
|
||||
nzo.final_name,
|
||||
nzo.cat,
|
||||
@@ -578,8 +577,7 @@ def process_job(nzo: NzbObject):
|
||||
if len(script_log.rstrip().split("\n")) > 1:
|
||||
nzo.set_unpack_info(
|
||||
"Script",
|
||||
'%s%s <a href="./scriptlog?name=%s">(%s)</a>'
|
||||
% (script_ret, script_line, encoding.xml_name(script_output), T("More")),
|
||||
'%s%s <a href="./scriptlog?name=%s">(%s)</a>' % (script_ret, script_line, script_output, T("More")),
|
||||
unique=True,
|
||||
)
|
||||
else:
|
||||
@@ -672,7 +670,7 @@ def process_job(nzo: NzbObject):
|
||||
return True
|
||||
|
||||
|
||||
def prepare_extraction_path(nzo: NzbObject):
|
||||
def prepare_extraction_path(nzo: NzbObject) -> Tuple[str, str, Sorter, bool, Optional[str]]:
|
||||
"""Based on the information that we have, generate
|
||||
the extraction path and create the directory.
|
||||
Separated so it can be called from DirectUnpacker
|
||||
@@ -1193,7 +1191,7 @@ def rename_and_collapse_folder(oldpath, newpath, files):
|
||||
return files
|
||||
|
||||
|
||||
def set_marker(folder):
|
||||
def set_marker(folder: str) -> Optional[str]:
|
||||
"""Set marker file and return name"""
|
||||
name = cfg.marker_file()
|
||||
if name:
|
||||
@@ -1209,7 +1207,7 @@ def set_marker(folder):
|
||||
return name
|
||||
|
||||
|
||||
def del_marker(path):
|
||||
def del_marker(path: str):
|
||||
"""Remove marker file"""
|
||||
if path and os.path.exists(path):
|
||||
logging.debug("Removing marker file %s", path)
|
||||
|
||||
@@ -41,7 +41,6 @@ import sabnzbd.cfg as cfg
|
||||
from sabnzbd.constants import EXCLUDED_GUESSIT_PROPERTIES
|
||||
from sabnzbd.nzbstuff import NzbObject, scan_password
|
||||
|
||||
|
||||
# Do not rename .vob files as they are usually DVD's
|
||||
EXCLUDED_FILE_EXTS = (".vob", ".bin")
|
||||
|
||||
@@ -69,7 +68,7 @@ class BaseSorter:
|
||||
cats: str,
|
||||
guess: Optional[MatchesDict],
|
||||
force: Optional[bool] = False,
|
||||
) -> None:
|
||||
):
|
||||
self.matched = False
|
||||
self.original_job_name = job_name
|
||||
self.original_path = path
|
||||
@@ -88,6 +87,14 @@ class BaseSorter:
|
||||
# Check categories and do the guessing work, if necessary
|
||||
self.match()
|
||||
|
||||
def match(self):
|
||||
"""Implemented by child classes"""
|
||||
pass
|
||||
|
||||
def get_values(self):
|
||||
"""Implemented by child classes"""
|
||||
pass
|
||||
|
||||
def get_final_path(self) -> str:
|
||||
if self.matched:
|
||||
# Construct the final path
|
||||
@@ -97,7 +104,7 @@ class BaseSorter:
|
||||
# Error Sorting
|
||||
return os.path.join(self.original_path, self.original_job_name)
|
||||
|
||||
def get_names(self) -> None:
|
||||
def get_names(self):
|
||||
"""Get the show or movie name from the guess and format it"""
|
||||
# Get the formatted title and alternate title formats
|
||||
self.info["ttitle"], self.info["ttitle_two"], self.info["ttitle_three"] = get_titles(
|
||||
@@ -107,16 +114,16 @@ class BaseSorter:
|
||||
self.nzo, self.guess, self.original_job_name
|
||||
)
|
||||
|
||||
def get_resolution(self) -> None:
|
||||
def get_resolution(self):
|
||||
self.info["resolution"] = self.guess.get("screen_size", "")
|
||||
|
||||
def get_showdescriptions(self) -> None:
|
||||
def get_showdescriptions(self):
|
||||
"""Get the show descriptions based on metadata, guessit and jobname"""
|
||||
self.info["ep_name"], self.info["ep_name_two"], self.info["ep_name_three"] = get_descriptions(
|
||||
self.nzo, self.guess, self.original_job_name
|
||||
)
|
||||
|
||||
def get_year(self) -> None:
|
||||
def get_year(self):
|
||||
"""Get the year and the corresponding two and four digit decade values"""
|
||||
year = ""
|
||||
if self.nzo:
|
||||
@@ -308,8 +315,8 @@ class BaseSorter:
|
||||
class Sorter:
|
||||
"""Generic Sorter"""
|
||||
|
||||
def __init__(self, nzo: Optional[NzbObject], cat: str) -> None:
|
||||
self.sorter = None
|
||||
def __init__(self, nzo: Optional[NzbObject], cat: str):
|
||||
self.sorter: Optional[BaseSorter] = None
|
||||
self.sort_file = False
|
||||
self.nzo = nzo
|
||||
self.cat = cat
|
||||
@@ -319,20 +326,17 @@ class Sorter:
|
||||
guess = guess_what(job_name)
|
||||
|
||||
if guess["type"] == "episode":
|
||||
self.sort_file = True
|
||||
if "date" in guess:
|
||||
self.sorter = DateSorter(self.nzo, job_name, complete_dir, self.cat, guess)
|
||||
else:
|
||||
self.sorter = SeriesSorter(self.nzo, job_name, complete_dir, self.cat, guess)
|
||||
elif guess["type"] == "movie":
|
||||
self.sort_file = True
|
||||
self.sorter = MovieSorter(self.nzo, job_name, complete_dir, self.cat, guess)
|
||||
|
||||
return self.sorter.get_final_path() if self.sort_file else complete_dir
|
||||
if self.sorter and self.sorter.matched:
|
||||
self.sort_file = True
|
||||
|
||||
def rename(self, newfiles, workdir_complete) -> None:
|
||||
"""Rename files of the job"""
|
||||
return self.sorter.rename(newfiles, workdir_complete)
|
||||
return self.sorter.get_final_path() if self.sort_file else complete_dir
|
||||
|
||||
|
||||
class SeriesSorter(BaseSorter):
|
||||
@@ -346,27 +350,20 @@ class SeriesSorter(BaseSorter):
|
||||
cat: str,
|
||||
guess: Optional[MatchesDict] = None,
|
||||
force: Optional[bool] = False,
|
||||
) -> None:
|
||||
):
|
||||
|
||||
super().__init__(nzo, job_name, path, cat, cfg.tv_sort_string(), cfg.tv_categories(), guess, force)
|
||||
|
||||
def match(self) -> None:
|
||||
def match(self):
|
||||
"""Try to guess series info if config and category sort out or force is set"""
|
||||
if self.force or (cfg.enable_tv_sorting() and cfg.tv_sort_string()):
|
||||
if (
|
||||
self.force
|
||||
or (not self.cats)
|
||||
or (self.cat and self.cat.lower() in self.cats)
|
||||
or (not self.cat and "None" in self.cats)
|
||||
):
|
||||
if not self.guess:
|
||||
self.guess = guess_what(self.original_job_name, sort_type="episode")
|
||||
if self.guess.get("type") == "episode" and not "date" in self.guess:
|
||||
logging.debug("Using tv sorter for %s", self.original_job_name)
|
||||
self.matched = True
|
||||
self.type = "tv"
|
||||
if self.force or (cfg.enable_tv_sorting() and cfg.tv_sort_string() and self.cat.lower() in self.cats):
|
||||
self.guess = guess_what(self.original_job_name, sort_type="episode")
|
||||
if self.guess.get("type") == "episode" and "date" not in self.guess:
|
||||
logging.debug("Using tv sorter for %s", self.original_job_name)
|
||||
self.matched = True
|
||||
self.type = "tv"
|
||||
|
||||
def get_values(self) -> None:
|
||||
def get_values(self):
|
||||
"""Collect all values needed for path replacement"""
|
||||
self.get_year()
|
||||
self.get_names()
|
||||
@@ -375,7 +372,7 @@ class SeriesSorter(BaseSorter):
|
||||
self.get_showdescriptions()
|
||||
self.get_resolution()
|
||||
|
||||
def format_series_numbers(self, numbers: Union[int, List[int]], info_name: str) -> None:
|
||||
def format_series_numbers(self, numbers: Union[int, List[int]], info_name: str):
|
||||
"""Format the numbers in both plain and alternative (zero-padded) format and set as showinfo"""
|
||||
# Guessit returns multiple episodes or seasons as a list of integers, single values as int
|
||||
if isinstance(numbers, int):
|
||||
@@ -385,11 +382,11 @@ class SeriesSorter(BaseSorter):
|
||||
self.info[info_name] = "-".join([str(num) for num in numbers]) # 1-2-3
|
||||
self.info[info_name + "_alt"] = "-".join([str(num).rjust(2, "0") for num in numbers]) # 01-02-03
|
||||
|
||||
def get_seasons(self) -> None:
|
||||
def get_seasons(self):
|
||||
"""Fetch the guessed season number(s)"""
|
||||
self.format_series_numbers(self.guess.get("season", ""), "season_num")
|
||||
|
||||
def get_episodes(self) -> None:
|
||||
def get_episodes(self):
|
||||
"""Fetch the guessed episode number(s)"""
|
||||
self.format_series_numbers(self.guess.get("episode", ""), "episode_num")
|
||||
|
||||
@@ -415,23 +412,21 @@ class MovieSorter(BaseSorter):
|
||||
cat: str,
|
||||
guess: Optional[MatchesDict] = None,
|
||||
force: Optional[bool] = False,
|
||||
) -> None:
|
||||
):
|
||||
self.extra = cfg.movie_sort_extra()
|
||||
|
||||
super().__init__(nzo, job_name, path, cat, cfg.movie_sort_string(), cfg.movie_categories(), guess, force)
|
||||
|
||||
def match(self) -> None:
|
||||
def match(self):
|
||||
"""Try to guess movie info if config and category sort out or force is set"""
|
||||
if self.force or (cfg.enable_movie_sorting() and self.sort_string):
|
||||
if self.force or (self.cat and self.cat.lower() in self.cats) or (not self.cat and "None" in self.cats):
|
||||
if not self.guess:
|
||||
self.guess = guess_what(self.original_job_name, sort_type="movie")
|
||||
if self.guess.get("type") == "movie":
|
||||
logging.debug("Using movie sorter for %s", self.original_job_name)
|
||||
self.matched = True
|
||||
self.type = "movie"
|
||||
if self.force or (cfg.enable_movie_sorting() and self.sort_string and self.cat.lower() in self.cats):
|
||||
self.guess = guess_what(self.original_job_name, sort_type="movie")
|
||||
if self.guess.get("type") == "movie":
|
||||
logging.debug("Using movie sorter for %s", self.original_job_name)
|
||||
self.matched = True
|
||||
self.type = "movie"
|
||||
|
||||
def get_values(self) -> None:
|
||||
def get_values(self):
|
||||
"""Collect all values needed for path replacement"""
|
||||
self.get_year()
|
||||
self.get_resolution()
|
||||
@@ -498,22 +493,20 @@ class DateSorter(BaseSorter):
|
||||
cat: str,
|
||||
guess: Optional[MatchesDict] = None,
|
||||
force: Optional[bool] = False,
|
||||
) -> None:
|
||||
):
|
||||
|
||||
super().__init__(nzo, job_name, path, cat, cfg.date_sort_string(), cfg.date_categories(), guess, force)
|
||||
|
||||
def match(self) -> None:
|
||||
def match(self):
|
||||
"""Checks the category for a match, if so set self.matched to true"""
|
||||
if self.force or (cfg.enable_date_sorting() and self.sort_string):
|
||||
if self.force or (self.cat and self.cat.lower() in self.cats) or (not self.cat and "None" in self.cats):
|
||||
if not self.guess:
|
||||
self.guess = guess_what(self.original_job_name, sort_type="episode")
|
||||
if self.guess.get("type") == "episode" and "date" in self.guess:
|
||||
logging.debug("Using date sorter for %s", self.original_job_name)
|
||||
self.matched = True
|
||||
self.type = "date"
|
||||
if self.force or (cfg.enable_date_sorting() and self.sort_string and self.cat.lower() in self.cats):
|
||||
self.guess = guess_what(self.original_job_name, sort_type="episode")
|
||||
if self.guess.get("type") == "episode" and "date" in self.guess:
|
||||
logging.debug("Using date sorter for %s", self.original_job_name)
|
||||
self.matched = True
|
||||
self.type = "date"
|
||||
|
||||
def get_date(self) -> None:
|
||||
def get_date(self):
|
||||
"""Get month and day"""
|
||||
self.info["month"] = str(self.guess.get("date").month)
|
||||
self.info["day"] = str(self.guess.get("date").day)
|
||||
@@ -521,7 +514,7 @@ class DateSorter(BaseSorter):
|
||||
self.info["month_two"] = self.info["month"].rjust(2, "0")
|
||||
self.info["day_two"] = self.info["day"].rjust(2, "0")
|
||||
|
||||
def get_values(self) -> None:
|
||||
def get_values(self):
|
||||
"""Collect all values needed for path replacement"""
|
||||
self.get_year()
|
||||
self.get_date()
|
||||
@@ -602,7 +595,7 @@ def guess_what(name: str, sort_type: Optional[str] = None) -> MatchesDict:
|
||||
guess["title"] = guess.get("title", "")[len(digit_fix) :]
|
||||
|
||||
# Force season to 1 for seasonless episodes with no date
|
||||
if guess.get("type") == "episode" and not "date" in guess:
|
||||
if guess.get("type") == "episode" and "date" not in guess:
|
||||
guess.setdefault("season", 1)
|
||||
|
||||
# Try to avoid setting the type to movie on arbitrary jobs (e.g. 'Setup.exe') just because guessit defaults to that
|
||||
@@ -801,7 +794,7 @@ def strip_path_elements(path: str) -> str:
|
||||
return "\\\\" + path if is_unc else path
|
||||
|
||||
|
||||
def rename_similar(folder: str, skip_ext: str, name: str, skipped_files: List[str]) -> None:
|
||||
def rename_similar(folder: str, skip_ext: str, name: str, skipped_files: List[str]):
|
||||
"""Rename all other files in the 'folder' hierarchy after 'name'
|
||||
and move them to the root of 'folder'.
|
||||
Files having extension 'skip_ext' will be moved, but not renamed.
|
||||
|
||||
@@ -28,6 +28,7 @@ import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
from http.client import IncompleteRead, HTTPResponse
|
||||
from mailbox import Message
|
||||
from threading import Thread
|
||||
import base64
|
||||
from typing import Tuple, Optional
|
||||
@@ -190,8 +191,8 @@ class URLGrabber(Thread):
|
||||
nzo_info[item] = value
|
||||
|
||||
# Get filename from Content-Disposition header
|
||||
if not filename and "filename=" in value:
|
||||
filename = value[value.index("filename=") + 9 :].strip(";").strip('"')
|
||||
if not filename and "filename" in value:
|
||||
filename = filename_from_content_disposition(value)
|
||||
|
||||
if wait:
|
||||
# For sites that have a rate-limiting attribute
|
||||
@@ -369,3 +370,23 @@ def _analyse(fetch_request: HTTPResponse, future_nzo: NzbObject):
|
||||
return None, msg, True, when, data
|
||||
|
||||
return fetch_request, fetch_request.msg, False, 0, data
|
||||
|
||||
|
||||
def filename_from_content_disposition(content_disposition: str) -> Optional[str]:
|
||||
"""
|
||||
Extract and validate filename from a Content-Disposition header.
|
||||
|
||||
Origin: https://github.com/httpie/httpie/blob/4c8633c6e51f388523ab4fa649040934402a4fc9/httpie/downloads.py#L98
|
||||
:param content_disposition: Content-Disposition value
|
||||
:type content_disposition: str
|
||||
:return: the filename if present and valid, otherwise `None`
|
||||
:example:
|
||||
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:
|
||||
# Basic sanitation
|
||||
filename = os.path.basename(filename).lstrip(".").strip()
|
||||
if filename:
|
||||
return filename
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
|
||||
# You MUST use double quotes (so " and not ')
|
||||
|
||||
__version__ = "develop"
|
||||
__baseline__ = "unknown"
|
||||
__version__ = "3.4.0"
|
||||
__baseline__ = "7d5207aa67c7d558d2490595ed03aa7809fe9f70"
|
||||
|
||||
@@ -316,7 +316,7 @@ class TestDeobfuscateFinalResult:
|
||||
for (dirpath, dirnames, filenames) in os.walk(test_dir):
|
||||
list_of_files += [os.path.join(dirpath, file) for file in filenames]
|
||||
# Run deobfuscate
|
||||
deobfuscate_list(list_of_files, "doesnt_matter")
|
||||
recover_par2_names(list_of_files)
|
||||
|
||||
# Should now be renamed to the filename in the par2 file
|
||||
assert not os.path.exists(test_input)
|
||||
@@ -344,9 +344,11 @@ class TestDeobfuscateFinalResult:
|
||||
# deobfuscate will do:
|
||||
# first par2 based renaming aaaaaaaaaaa to twentymb.bin,
|
||||
# then deobfuscate twentymb.bin to the job name (with same extension)
|
||||
deobfuscate_list(list_of_files, "My Great Download")
|
||||
list_of_files = recover_par2_names(list_of_files)
|
||||
assert os.path.isfile(os.path.join(work_dir, "twentymb.bin")) # should exist
|
||||
|
||||
deobfuscate_list(list_of_files, "My Great Download")
|
||||
assert os.path.isfile(os.path.join(work_dir, "My Great Download.bin")) # the twentymb.bin should be renamed
|
||||
assert not os.path.isfile(os.path.join(work_dir, "twentymb.bin")) # should be gone
|
||||
assert not os.path.isfile(os.path.join(work_dir, "twentymb.bin")) # should now be gone
|
||||
|
||||
shutil.rmtree(work_dir)
|
||||
|
||||
@@ -72,7 +72,7 @@ class TestBasicPages(SABnzbdBaseTest):
|
||||
self.no_page_crash()
|
||||
else:
|
||||
# For others if all is fine, button will be back to normal in 1 second
|
||||
time.sleep(1.0)
|
||||
time.sleep(1.5)
|
||||
assert submit_btn.text == "Save Changes"
|
||||
|
||||
|
||||
|
||||
@@ -681,15 +681,32 @@ class TestSortingSorters:
|
||||
)
|
||||
def test_sorter_generic(self, job_name, result_sort_file, result_class):
|
||||
"""Check if the generic sorter makes the right choices"""
|
||||
generic = sorting.Sorter(None, None)
|
||||
generic.detect(job_name, SAB_CACHE_DIR)
|
||||
|
||||
assert generic.sort_file is result_sort_file
|
||||
if result_sort_file:
|
||||
assert generic.sorter
|
||||
assert generic.sorter.__class__ is result_class
|
||||
else:
|
||||
assert not generic.sorter
|
||||
@set_config(
|
||||
{
|
||||
"tv_sort_string": "test", # TV
|
||||
"tv_categories": "test_cat",
|
||||
"enable_tv_sorting": 1,
|
||||
"movie_sort_string": "test", # Movie
|
||||
"movie_categories": "test_cat",
|
||||
"enable_movie_sorting": 1,
|
||||
"date_sort_string": "test", # Date
|
||||
"date_categories": "test_cat",
|
||||
"enable_date_sorting": 1,
|
||||
}
|
||||
)
|
||||
def _func():
|
||||
generic = sorting.Sorter(None, "test_cat")
|
||||
generic.detect(job_name, SAB_CACHE_DIR)
|
||||
|
||||
assert generic.sort_file is result_sort_file
|
||||
if result_sort_file:
|
||||
assert generic.sorter
|
||||
assert generic.sorter.__class__ is result_class
|
||||
else:
|
||||
assert not generic.sorter
|
||||
|
||||
_func()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name, result",
|
||||
|
||||
@@ -158,3 +158,43 @@ class TestBuildRequest:
|
||||
self._runner(self.httpbin.url + "/status/404", 404)
|
||||
with pytest.raises(urllib.error.HTTPError):
|
||||
self._runner(self.httpbin.url + "/no/such/file", 404)
|
||||
|
||||
|
||||
class TestFilenameFromDispositionHeader:
|
||||
@pytest.mark.parametrize(
|
||||
"header, result",
|
||||
[
|
||||
(
|
||||
# In this case the first filename (not the UTF-8 encoded) is parsed.
|
||||
"attachment; filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz; filename*=UTF-8''jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
"jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
),
|
||||
(
|
||||
"filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz;",
|
||||
"jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
),
|
||||
(
|
||||
"filename*=UTF-8''jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
"jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
),
|
||||
(
|
||||
"attachment; filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
"jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
),
|
||||
(
|
||||
'attachment; filename="jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz"',
|
||||
"jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz",
|
||||
),
|
||||
(
|
||||
"attachment; filename=/what/ever/filename.tar.gz",
|
||||
"filename.tar.gz",
|
||||
),
|
||||
(
|
||||
"attachment; filename=",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_filename_from_disposition_header(self, header, result):
|
||||
"""Test the parsing of different disposition-headers."""
|
||||
assert urlgrabber.filename_from_content_disposition(header) == result
|
||||
|
||||
@@ -193,6 +193,7 @@ class FakeHistoryDB(db.HistoryDB):
|
||||
distro_choice = choice(self.distro_names)
|
||||
distro_random = random_name()
|
||||
nzo.password = choice(["secret", ""])
|
||||
nzo.correct_password = "secret"
|
||||
nzo.final_name = "%s.%s.Linux.ISO-Usenet" % (distro_choice, distro_random)
|
||||
nzo.filename = "%s.%s.Linux-Usenet%s.nzb" % (
|
||||
(distro_choice, distro_random, "{{" + nzo.password + "}}")
|
||||
|
||||
Reference in New Issue
Block a user