mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-01-29 09:52:11 -05:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54e03fb40a | ||
|
|
904bb9f85a | ||
|
|
b011e1a518 | ||
|
|
f83f71a950 | ||
|
|
4dbf5266ef | ||
|
|
05aac4e01e | ||
|
|
267c48f9a7 | ||
|
|
5168915a65 | ||
|
|
71017d0d55 | ||
|
|
a5db51a2c5 | ||
|
|
0bf2968e6a | ||
|
|
2ec5918f5e | ||
|
|
04f5a63cd7 | ||
|
|
43d8283f5b | ||
|
|
f8111121c4 | ||
|
|
b53b73c135 | ||
|
|
bd7b8a975b | ||
|
|
7ca765f276 | ||
|
|
b918a53af5 | ||
|
|
525809afc9 | ||
|
|
a7048cdc8e | ||
|
|
02888568bd | ||
|
|
203409f02f | ||
|
|
ecc8e6ac0e | ||
|
|
852636acda | ||
|
|
bc18369552 | ||
|
|
8f248a2219 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,8 +16,9 @@ SABnzbd*.exe
|
||||
SABnzbd*.gz
|
||||
SABnzbd*.dmg
|
||||
|
||||
# WingIDE project files
|
||||
# WingIDE/PyCharm project files
|
||||
*.wp[ru]
|
||||
.idea
|
||||
|
||||
# Testing folders
|
||||
.cache
|
||||
|
||||
@@ -66,3 +66,7 @@
|
||||
Config->Special->wait_for_dfolder to 1.
|
||||
SABnzbd will appear to hang until the drive is mounted.
|
||||
|
||||
- If you experience speed-drops to KB/s when using a VPN, try setting the number of connections
|
||||
to your servers to a total of 7. There is a CPU-usage reduction feature in SABnzbd that
|
||||
gets confused by the way some VPN's handle the state of a connection. Below 8 connections
|
||||
this feature is not active.
|
||||
|
||||
4
PKG-INFO
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 2.3.4
|
||||
Summary: SABnzbd-2.3.4
|
||||
Version: 2.3.5RC1
|
||||
Summary: SABnzbd-2.3.5RC1
|
||||
Home-page: https://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
30
README.mkd
30
README.mkd
@@ -1,18 +1,22 @@
|
||||
Release Notes - SABnzbd 2.3.4
|
||||
Release Notes - SABnzbd 2.3.5 RC 1
|
||||
=========================================================
|
||||
|
||||
## Changes since 2.3.3
|
||||
- Device hostname in hostname-verification always lowercased
|
||||
- Hostnames ending in ".local" are always accepted
|
||||
- URLGrabber would not always detect correct filename
|
||||
- URLGrabber would ignore some successful downloads
|
||||
- Always send NNTP QUIT after server-test
|
||||
- Added option "--disable-file-log" to disable file-based logging
|
||||
- Added CORS-header to API
|
||||
- Windows: Service compatibility with Windows 10 April update
|
||||
- Windows: Update Python to 2.7.15
|
||||
- Windows: Update 7zip to 18.05
|
||||
- macOS: Restore compatibility with El Capitan (10.11)
|
||||
## Bug fixes since 2.3.4
|
||||
- Reworked Deobfuscate.py script for much faster renaming
|
||||
- All scripts can now receive input through environment variables
|
||||
- Unable to set only one Indexer Category per category
|
||||
- Could falsely report not enough blocks are available for repair
|
||||
- Direct Unpack could abort unnecessarily
|
||||
- Rare crash during file assembly
|
||||
- Server hostname is now used in warnings and logs
|
||||
- Improved disk performance measurement
|
||||
- Windows: Tray icon also shows remaining size when paused
|
||||
- Windows: Wizard would not default to installer language
|
||||
- Windows: Update MultiPar to 1.3.0.1
|
||||
- Windows and macOS: Update UnRar to 5.60
|
||||
|
||||
Looking for help with SABnzbd development:
|
||||
https://www.reddit.com/r/usenet/918nxv/
|
||||
|
||||
## Upgrading from 2.2.x and older
|
||||
- Finish queue
|
||||
|
||||
@@ -20,7 +20,6 @@ if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
|
||||
print "Sorry, requires Python 2.6 or 2.7."
|
||||
sys.exit(1)
|
||||
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<p><strong>$T('opt-complete_dir')</strong></p>
|
||||
<div class="quoteBlock">
|
||||
$complete_dir
|
||||
<a href="${access_url}config/folders" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
|
||||
<a href="${access_url}/config/folders#complete_dir" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
|
||||
</div>
|
||||
|
||||
<p><strong>$T('opt-download_dir')</strong></p>
|
||||
<div class="quoteBlock">
|
||||
$download_dir
|
||||
<a href="${access_url}config/folders" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
|
||||
<a href="${access_url}/config/folders#complete_dir" class="indented"><span class="glyphicon glyphicon-cog"></span></a>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
BIN
osx/unrar/unrar
BIN
osx/unrar/unrar
Binary file not shown.
@@ -8,14 +8,14 @@ msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2018-03-15 13:08+0000\n"
|
||||
"PO-Revision-Date: 2018-04-15 21:22+0000\n"
|
||||
"Last-Translator: ciho <Unknown>\n"
|
||||
"PO-Revision-Date: 2018-05-31 06:22+0000\n"
|
||||
"Last-Translator: scope <Unknown>\n"
|
||||
"Language-Team: German <de@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2018-04-16 05:40+0000\n"
|
||||
"X-Generator: Launchpad (build 18610)\n"
|
||||
"X-Launchpad-Export-Date: 2018-06-01 05:38+0000\n"
|
||||
"X-Generator: Launchpad (build 18667)\n"
|
||||
|
||||
#: SABnzbd.py [Error message]
|
||||
msgid "MultiPar binary... NOT found!"
|
||||
@@ -63,7 +63,7 @@ msgstr ""
|
||||
|
||||
#: SABnzbd.py [Error message]
|
||||
msgid "Downloads will not unpacked."
|
||||
msgstr "Downloads werden nicht enpackt."
|
||||
msgstr "Downloads werden nicht entpackt."
|
||||
|
||||
#: SABnzbd.py [Error message]
|
||||
msgid "unrar binary... NOT found"
|
||||
|
||||
@@ -8,14 +8,14 @@ msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2018-03-15 13:08+0000\n"
|
||||
"PO-Revision-Date: 2018-03-15 21:38+0000\n"
|
||||
"PO-Revision-Date: 2018-06-17 21:21+0000\n"
|
||||
"Last-Translator: ION IL <Unknown>\n"
|
||||
"Language-Team: Hebrew <he@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2018-03-16 05:37+0000\n"
|
||||
"X-Generator: Launchpad (build 18571)\n"
|
||||
"X-Launchpad-Export-Date: 2018-06-18 05:54+0000\n"
|
||||
"X-Generator: Launchpad (build 18688)\n"
|
||||
|
||||
#: SABnzbd.py [Error message]
|
||||
msgid "Failed to start web-interface"
|
||||
@@ -198,7 +198,7 @@ msgstr "%s לא ניתן ליצור קובץ זמני עבור"
|
||||
|
||||
#: sabnzbd/__init__.py [Warning message] # sabnzbd/__init__.py [Warning message]
|
||||
msgid "Trying to set status of non-existing server %s"
|
||||
msgstr "%s מנסה לקבוע מצב של שרת בלתי-קיים"
|
||||
msgstr "%s מנסה לקבוע מעמד של שרת בלתי קיים"
|
||||
|
||||
#: sabnzbd/__init__.py [Error message]
|
||||
msgid "Failure in tempfile.mkstemp"
|
||||
@@ -224,7 +224,7 @@ msgstr " פותר כתובת"
|
||||
|
||||
#: sabnzbd/api.py # sabnzbd/skintext.py [No value, used in dropdown menus] # sabnzbd/skintext.py [Job details page, select no files]
|
||||
msgid "None"
|
||||
msgstr "ללא"
|
||||
msgstr "אין"
|
||||
|
||||
#: sabnzbd/api.py # sabnzbd/interface.py # sabnzbd/skintext.py [Default value, used in dropdown menus]
|
||||
msgid "Default"
|
||||
@@ -646,21 +646,21 @@ msgid ""
|
||||
"API Key missing, please enter the api key from Config->General into your 3rd "
|
||||
"party program:"
|
||||
msgstr ""
|
||||
"מתצורה->כללי לתוך תכנית הצד השלישי שלך api-חסר, אנא הכנס את מפתח ה API מפתח:"
|
||||
"מתצורה->כללי לתוך תוכנית הצד השלישי שלך api-חסר, אנא הכנס את מפתח ה API מפתח:"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"API Key incorrect, Use the api key from Config->General in your 3rd party "
|
||||
"program:"
|
||||
msgstr ""
|
||||
"מתצורה->כללי בתכנית הצד השלישי שלך api-אינו נכון, השתמש במפתח ה API מפתח:"
|
||||
"מתצורה->כללי בתוכנית הצד השלישי שלך api-אינו נכון, השתמש במפתח ה API מפתח:"
|
||||
|
||||
#: sabnzbd/interface.py
|
||||
msgid ""
|
||||
"Authentication missing, please enter username/password from Config->General "
|
||||
"into your 3rd party program:"
|
||||
msgstr ""
|
||||
":אימות חסר, אנא הכנס שם משתמש/סיסמה מתוך תצורה->כללי לתוך תכנית הצד השלישי "
|
||||
":אימות חסר, אנא הכנס שם משתמש/סיסמה מתוך תצורה->כללי לתוך תוכנית הצד השלישי "
|
||||
"שלך"
|
||||
|
||||
#: sabnzbd/interface.py [Warning message]
|
||||
@@ -998,7 +998,7 @@ msgstr "%s על ערכת par2_repair בזמן הרצת \"%s\" שגיאה"
|
||||
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
|
||||
msgid ""
|
||||
"[%s] PAR2 received incorrect options, check your Config->Switches settings"
|
||||
msgstr "קיבל אפשרויות שגויות, בדוק את קביעות תצורה->מתגים שלך PAR2 [%s]"
|
||||
msgstr "קיבל אפשרויות שגויות, בדוק את הגדרות תצורה->מתגים שלך PAR2 [%s]"
|
||||
|
||||
#: sabnzbd/newsunpack.py # sabnzbd/newsunpack.py
|
||||
msgid "[%s] Verified in %s, all files correct"
|
||||
@@ -1090,7 +1090,7 @@ msgstr "וויקי"
|
||||
|
||||
#: sabnzbd/notifier.py [Notification]
|
||||
msgid "Startup/Shutdown"
|
||||
msgstr "אתחול / כיבוי"
|
||||
msgstr "הזנק/כיבוי"
|
||||
|
||||
#: sabnzbd/notifier.py [Notification] # sabnzbd/skintext.py [Config->RSS after adding to queue]
|
||||
msgid "Added NZB"
|
||||
@@ -1154,7 +1154,7 @@ msgstr "Windows נכשל בשליחת התראת"
|
||||
|
||||
#: sabnzbd/nzbqueue.py [Warning message] # sabnzbd/postproc.py [Warning message]
|
||||
msgid "Old queue detected, use Status->Repair to convert the queue"
|
||||
msgstr "תור ישן התגלה, השתמש במצב->תיקון כדי להמיר את התור"
|
||||
msgstr "תור ישן התגלה, השתמש במעמד->תיקון כדי להמיר את התור"
|
||||
|
||||
#: sabnzbd/nzbqueue.py [Error message]
|
||||
msgid "Incompatible queuefile found, cannot proceed"
|
||||
@@ -1442,9 +1442,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"\n"
|
||||
" אחרת SABnzbd גילה נתונים שמורים מגרסת SABnzbd<br>\n"
|
||||
" .אבל אינו יכול להשתמש מחדש בנתונים של התכנית האחרת<br><br>\n"
|
||||
" .אתה אולי תרצה לסיים את התור שלך תחילה עם התכנית האחרת<br><br>\n"
|
||||
" .\"--clean\" לאחר מכן, התחל תכנית זו עם האפשרות<br>\n"
|
||||
" .אבל אינו יכול להשתמש מחדש בנתונים של התוכנית האחרת<br><br>\n"
|
||||
" .אתה אולי תרצה לסיים את התור שלך תחילה עם התוכנית האחרת<br><br>\n"
|
||||
" .\"--clean\" לאחר מכן, התחל תוכנית זו עם האפשרות<br>\n"
|
||||
" !זה ימחק את התור וההיסטוריה הנוכחיים<br>\n"
|
||||
" .\"%s\" קרא את הקובץ SABnzbd"
|
||||
|
||||
@@ -1457,7 +1457,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"\n"
|
||||
" .%s-אינו יכול למצוא את קבצי ממשק הרשת שלו ב SABnzbd<br>\n"
|
||||
" .אנא התקן את התכנית שוב<br>\n"
|
||||
" .אנא התקן את התוכנית שוב<br>\n"
|
||||
" <br>\n"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
@@ -1490,7 +1490,7 @@ msgstr "פתח חלון מסוף והקלד את הקו (דוגמה):"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid "Program did not start!"
|
||||
msgstr "!התכנית לא התחילה"
|
||||
msgstr "!התוכנית לא התחילה"
|
||||
|
||||
#: sabnzbd/panic.py
|
||||
msgid ""
|
||||
@@ -2147,7 +2147,7 @@ msgstr "הגדר"
|
||||
|
||||
#: sabnzbd/skintext.py [Main menu item] # sabnzbd/skintext.py [History table header]
|
||||
msgid "Status"
|
||||
msgstr "מצב"
|
||||
msgstr "מעמד"
|
||||
|
||||
#: sabnzbd/skintext.py [Main menu item]
|
||||
msgid "Help"
|
||||
@@ -2235,7 +2235,7 @@ msgstr "שחרור חדש %s זמין ב"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Are you sure you want to shutdown SABnzbd?"
|
||||
msgstr "?SABnzbd האם אתה בטוח שברצונך לכבות את"
|
||||
msgstr "?SABnzbd האם אתה בטוח שאתה רוצה לכבות את"
|
||||
|
||||
#: sabnzbd/skintext.py [Add NZB to queue (button)] # sabnzbd/skintext.py [Add NZB to queue (header)]
|
||||
msgid "Add"
|
||||
@@ -2611,7 +2611,7 @@ msgid ""
|
||||
"stability problem.<br />Downloading will be paused before the restart and "
|
||||
"resume afterwards."
|
||||
msgstr ""
|
||||
".SABnzbd זה יפעיל מחדש את<br />השתמש בזה כשאתה חושב שלתכנית יש בעית "
|
||||
".SABnzbd זה יפעיל מחדש את<br />השתמש בזה כשאתה חושב שלתוכנית יש בעית "
|
||||
"יציבות.<br />הורדה תושהה לפני ההפעלה מחדש ותומשך לאחר מכן."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
@@ -2920,7 +2920,7 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "This key will give 3rd party programs full access to SABnzbd."
|
||||
msgstr ".SABnzbd מפתח זה יתן לתכניות צד שלישי גישה מלאה אל"
|
||||
msgstr ".SABnzbd מפתח זה יתן לתוכניות צד שלישי גישה מלאה אל"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "NZB Key"
|
||||
@@ -2928,7 +2928,7 @@ msgstr "NZB מפתח"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "This key will allow 3rd party programs to add NZBs to SABnzbd."
|
||||
msgstr ".SABnzbd אל NZB מפתח זה יתיר לתכניות צד שלישי להוסיף קבצי"
|
||||
msgstr ".SABnzbd אל NZB מפתח זה יתיר לתוכניות צד שלישי להוסיף קבצי"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Generate New Key"
|
||||
@@ -3353,7 +3353,7 @@ msgid ""
|
||||
"Posts will be paused untill they are at least this age. Setting job priority "
|
||||
"to Force will skip the delay."
|
||||
msgstr ""
|
||||
".רשומות יושהו עד שהן לפחות בגיל זה. קביעת עדיפות עבודה אל אילוץ תדלג על "
|
||||
".רשומות יושהו עד שהן לפחות בגיל זה. הגדרת עדיפות עבודה אל אילוץ תדלג על "
|
||||
"העיכוב"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
@@ -3394,7 +3394,7 @@ msgstr ".Windows עבור שרתים: וודא שהשמות תואמים עם"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Launch Browser on Startup"
|
||||
msgstr "הפעל דפדפן באתחול"
|
||||
msgstr "הפעל דפדפן בהזנק"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Launch the default web browser when starting SABnzbd."
|
||||
@@ -4391,7 +4391,7 @@ msgstr "SABnzbd הפעל מחדש את"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Status and interface options"
|
||||
msgstr "אפשרויות מצב וממשק"
|
||||
msgstr "אפשרויות של מעמד וממשק"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Or drag and drop files in the window!"
|
||||
@@ -4419,7 +4419,7 @@ msgstr "קצב רענון"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Use global interface settings"
|
||||
msgstr "השתמש בקביעות ממשק עולמיות"
|
||||
msgstr "השתמש בהגדרות ממשק עולמיות"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Queue item limit"
|
||||
@@ -4534,7 +4534,7 @@ msgid ""
|
||||
"LocalStorage (cookies) are disabled in your browser, interface settings will "
|
||||
"be lost after you close the browser!"
|
||||
msgstr ""
|
||||
"!אחסון מקומי (עוגיות) מושבת בדפדפן שלך, קביעות ממשק יאבדו לאחר שתסגור את "
|
||||
"!אחסון מקומי (עוגיות) מושבת בדפדפן שלך, הגדרות ממשק יאבדו לאחר שתסגור את "
|
||||
"הדפדפן"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
@@ -4833,7 +4833,7 @@ msgstr "תצוגה כפולה 2"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Are you sure you want to restart SABnzbd?"
|
||||
msgstr "?SABnzbd האם אתה בטוח שברצונך להפעיל מחדש את"
|
||||
msgstr "?SABnzbd האם אתה בטוח שאתה רוצה להפעיל מחדש את"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Hide Edit Options"
|
||||
|
||||
@@ -28,9 +28,9 @@ from time import sleep
|
||||
import hashlib
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.misc import get_filepath, sanitize_filename, get_unique_filename, renamer, \
|
||||
set_permissions, long_path, clip_path, has_win_device, get_all_passwords, diskspace, \
|
||||
get_filename, get_ext
|
||||
from sabnzbd.misc import get_filepath, sanitize_filename, set_permissions, \
|
||||
long_path, clip_path, has_win_device, get_all_passwords, diskspace, \
|
||||
get_filename, get_ext, is_rarfile
|
||||
from sabnzbd.constants import Status, GIGI
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
@@ -117,7 +117,7 @@ class Assembler(Thread):
|
||||
nzf.remove_admin()
|
||||
|
||||
# Do rar-related processing
|
||||
if rarfile.is_rarfile(filepath):
|
||||
if is_rarfile(filepath):
|
||||
# Encryption and unwanted extension detection
|
||||
rar_encrypted, unwanted_file = check_encrypted_and_unwanted_files(nzo, filepath)
|
||||
if rar_encrypted:
|
||||
@@ -246,7 +246,7 @@ def check_encrypted_and_unwanted_files(nzo, filepath):
|
||||
return encrypted, unwanted
|
||||
|
||||
# Is it even a rarfile?
|
||||
if rarfile.is_rarfile(filepath):
|
||||
if is_rarfile(filepath):
|
||||
# Open the rar
|
||||
rarfile.UNRAR_TOOL = sabnzbd.newsunpack.RAR_COMMAND
|
||||
zf = rarfile.RarFile(filepath, all_names=True)
|
||||
|
||||
@@ -463,7 +463,7 @@ class ConfigCat(object):
|
||||
self.pp = OptionStr(name, 'pp', '', add=False)
|
||||
self.script = OptionStr(name, 'script', 'Default', add=False)
|
||||
self.dir = OptionDir(name, 'dir', add=False, create=False)
|
||||
self.newzbin = OptionList(name, 'newzbin', add=False)
|
||||
self.newzbin = OptionList(name, 'newzbin', add=False, validation=validate_single_tag)
|
||||
self.priority = OptionNumber(name, 'priority', DEFAULT_PRIORITY, add=False)
|
||||
|
||||
self.set_dict(values)
|
||||
@@ -1102,6 +1102,16 @@ def validate_notempty(root, value, default):
|
||||
return None, default
|
||||
|
||||
|
||||
def validate_single_tag(value):
|
||||
""" Don't split single indexer tags like "TV > HD"
|
||||
into ['TV', '>', 'HD']
|
||||
"""
|
||||
if len(value) == 3:
|
||||
if value[1] == '>':
|
||||
return None, ' '.join(value)
|
||||
return None, value
|
||||
|
||||
|
||||
def create_api_key():
|
||||
""" Return a new randomized API_KEY """
|
||||
# Create some values to seed md5
|
||||
|
||||
@@ -40,7 +40,7 @@ from sabnzbd.constants import DB_HISTORY_NAME, STAGES
|
||||
from sabnzbd.encoding import unicoder
|
||||
from sabnzbd.bpsmeter import this_week, this_month
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.misc import get_all_passwords, int_conv, remove_file, caller_name
|
||||
from sabnzbd.misc import int_conv, remove_file, caller_name
|
||||
|
||||
DB_LOCK = threading.RLock()
|
||||
|
||||
|
||||
@@ -28,9 +28,10 @@ import logging
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.misc import int_conv, clip_path, long_path, remove_all, globber, \
|
||||
format_time_string, has_win_device, real_path, remove_file
|
||||
from sabnzbd.misc import int_conv, clip_path, long_path, remove_all, \
|
||||
format_time_string, real_path, remove_file
|
||||
from sabnzbd.encoding import TRANS, unicoder
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.newsunpack import build_command, EXTRACTFROM_RE, EXTRACTED_RE, rar_volumelist
|
||||
from sabnzbd.postproc import prepare_extraction_path
|
||||
from sabnzbd.utils.rarfile import RarFile
|
||||
@@ -46,6 +47,10 @@ if sabnzbd.WIN32:
|
||||
# Load the regular POpen (which is now patched on Windows)
|
||||
from subprocess import Popen
|
||||
|
||||
# Need a lock to make sure start and stop is handled correctlty
|
||||
# Otherwise we could stop while the thread was still starting
|
||||
START_STOP_LOCK = threading.RLock()
|
||||
|
||||
MAX_ACTIVE_UNPACKERS = 10
|
||||
ACTIVE_UNPACKERS = []
|
||||
|
||||
@@ -110,6 +115,7 @@ class DirectUnpacker(threading.Thread):
|
||||
if none_counter > found_counter:
|
||||
self.total_volumes = {}
|
||||
|
||||
@synchronized(START_STOP_LOCK)
|
||||
def add(self, nzf):
|
||||
""" Add jobs and start instance of DirectUnpack """
|
||||
if not cfg.direct_unpack_tested():
|
||||
@@ -309,6 +315,7 @@ class DirectUnpacker(threading.Thread):
|
||||
with self.next_file_lock:
|
||||
self.next_file_lock.wait()
|
||||
|
||||
@synchronized(START_STOP_LOCK)
|
||||
def create_unrar_instance(self):
|
||||
""" Start the unrar instance using the user's options """
|
||||
# Generate extraction path and save for post-proc
|
||||
@@ -366,9 +373,10 @@ class DirectUnpacker(threading.Thread):
|
||||
# Doing the first
|
||||
logging.info('DirectUnpacked volume %s for %s', self.cur_volume, self.cur_setname)
|
||||
|
||||
@synchronized(START_STOP_LOCK)
|
||||
def abort(self):
|
||||
""" Abort running instance and delete generated files """
|
||||
if not self.killed:
|
||||
if not self.killed and self.cur_setname:
|
||||
logging.info('Aborting DirectUnpack for %s', self.cur_setname)
|
||||
self.killed = True
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ def is_archive(path):
|
||||
except:
|
||||
logging.info(T('Cannot read %s'), path, exc_info=True)
|
||||
return -1, None, ''
|
||||
elif rarfile.is_rarfile(path):
|
||||
elif misc.is_rarfile(path):
|
||||
try:
|
||||
# Set path to tool to open it
|
||||
rarfile.UNRAR_TOOL = sabnzbd.newsunpack.RAR_COMMAND
|
||||
|
||||
@@ -369,24 +369,24 @@ class Downloader(Thread):
|
||||
# Was it resolving problem?
|
||||
if server.info is False:
|
||||
# Warn about resolving issues
|
||||
errormsg = T('Cannot connect to server %s [%s]') % (server.id, T('Server name does not resolve'))
|
||||
errormsg = T('Cannot connect to server %s [%s]') % (server.host, T('Server name does not resolve'))
|
||||
if server.errormsg != errormsg:
|
||||
server.errormsg = errormsg
|
||||
logging.warning(errormsg)
|
||||
logging.warning(T('Server %s will be ignored for %s minutes'), server.id, _PENALTY_TIMEOUT)
|
||||
logging.warning(T('Server %s will be ignored for %s minutes'), server.host, _PENALTY_TIMEOUT)
|
||||
|
||||
# Not fully the same as the code below for optional servers
|
||||
server.bad_cons = 0
|
||||
server.active = False
|
||||
self.plan_server(server.id, _PENALTY_TIMEOUT)
|
||||
self.plan_server(server, _PENALTY_TIMEOUT)
|
||||
|
||||
# Optional and active server had too many problems.
|
||||
# Disable it now and send a re-enable plan to the scheduler
|
||||
if server.optional and server.active and (server.bad_cons / server.threads) > 3:
|
||||
server.bad_cons = 0
|
||||
server.active = False
|
||||
logging.warning(T('Server %s will be ignored for %s minutes'), server.id, _PENALTY_TIMEOUT)
|
||||
self.plan_server(server.id, _PENALTY_TIMEOUT)
|
||||
logging.warning(T('Server %s will be ignored for %s minutes'), server.host, _PENALTY_TIMEOUT)
|
||||
self.plan_server(server, _PENALTY_TIMEOUT)
|
||||
|
||||
# Remove all connections to server
|
||||
for nw in server.idle_threads + server.busy_threads:
|
||||
@@ -472,7 +472,7 @@ class Downloader(Thread):
|
||||
|
||||
if server.retention and article.nzf.nzo.avg_stamp < time.time() - server.retention:
|
||||
# Let's get rid of all the articles for this server at once
|
||||
logging.info('Job %s too old for %s, moving on', article.nzf.nzo.work_name, server.id)
|
||||
logging.info('Job %s too old for %s, moving on', article.nzf.nzo.work_name, server.host)
|
||||
while article:
|
||||
self.decode(article, None, None)
|
||||
article = article.nzf.nzo.get_article(server, self.servers)
|
||||
@@ -487,10 +487,10 @@ class Downloader(Thread):
|
||||
self.__request_article(nw)
|
||||
else:
|
||||
try:
|
||||
logging.info("%s@%s: Initiating connection", nw.thrdnum, server.id)
|
||||
logging.info("%s@%s: Initiating connection", nw.thrdnum, server.host)
|
||||
nw.init_connect(self.write_fds)
|
||||
except:
|
||||
logging.error(T('Failed to initialize %s@%s with reason: %s'), nw.thrdnum, server.id, sys.exc_info()[1])
|
||||
logging.error(T('Failed to initialize %s@%s with reason: %s'), nw.thrdnum, server.host, sys.exc_info()[1])
|
||||
self.__reset_nw(nw, "failed to initialize")
|
||||
|
||||
# Exit-point
|
||||
@@ -619,7 +619,7 @@ class Downloader(Thread):
|
||||
try:
|
||||
nw.finish_connect(nw.status_code)
|
||||
if sabnzbd.LOG_ALL:
|
||||
logging.debug("%s@%s last message -> %s", nw.thrdnum, nw.server.id, nntp_to_msg(nw.data))
|
||||
logging.debug("%s@%s last message -> %s", nw.thrdnum, nw.server.host, nntp_to_msg(nw.data))
|
||||
nw.clear_data()
|
||||
except NNTPPermanentError, error:
|
||||
# Handle login problems
|
||||
@@ -636,9 +636,9 @@ class Downloader(Thread):
|
||||
errormsg = T('Too many connections to server %s') % display_msg
|
||||
if server.errormsg != errormsg:
|
||||
server.errormsg = errormsg
|
||||
logging.warning(T('Too many connections to server %s'), server.id)
|
||||
logging.warning(T('Too many connections to server %s'), server.host)
|
||||
self.__reset_nw(nw, None, warn=False, destroy=True, quit=True)
|
||||
self.plan_server(server.id, _PENALTY_TOOMANY)
|
||||
self.plan_server(server, _PENALTY_TOOMANY)
|
||||
server.threads -= 1
|
||||
elif ecode in ('502', '481', '482') and clues_too_many_ip(msg):
|
||||
# Account sharing?
|
||||
@@ -646,7 +646,7 @@ class Downloader(Thread):
|
||||
errormsg = T('Probable account sharing') + display_msg
|
||||
if server.errormsg != errormsg:
|
||||
server.errormsg = errormsg
|
||||
name = ' (%s)' % server.id
|
||||
name = ' (%s)' % server.host
|
||||
logging.warning(T('Probable account sharing') + name)
|
||||
penalty = _PENALTY_SHARE
|
||||
block = True
|
||||
@@ -656,7 +656,7 @@ class Downloader(Thread):
|
||||
errormsg = T('Failed login for server %s') % display_msg
|
||||
if server.errormsg != errormsg:
|
||||
server.errormsg = errormsg
|
||||
logging.error(T('Failed login for server %s'), server.id)
|
||||
logging.error(T('Failed login for server %s'), server.host)
|
||||
penalty = _PENALTY_PERM
|
||||
block = True
|
||||
elif ecode in ('502', '482'):
|
||||
@@ -665,7 +665,7 @@ class Downloader(Thread):
|
||||
errormsg = T('Cannot connect to server %s [%s]') % ('', display_msg)
|
||||
if server.errormsg != errormsg:
|
||||
server.errormsg = errormsg
|
||||
logging.warning(T('Cannot connect to server %s [%s]'), server.id, msg)
|
||||
logging.warning(T('Cannot connect to server %s [%s]'), server.host, msg)
|
||||
if clues_pay(msg):
|
||||
penalty = _PENALTY_PERM
|
||||
else:
|
||||
@@ -674,7 +674,7 @@ class Downloader(Thread):
|
||||
elif ecode == '400':
|
||||
# Temp connection problem?
|
||||
if server.active:
|
||||
logging.debug('Unspecified error 400 from server %s', server.id)
|
||||
logging.debug('Unspecified error 400 from server %s', server.host)
|
||||
penalty = _PENALTY_VERYSHORT
|
||||
block = True
|
||||
else:
|
||||
@@ -683,25 +683,25 @@ class Downloader(Thread):
|
||||
errormsg = T('Cannot connect to server %s [%s]') % ('', display_msg)
|
||||
if server.errormsg != errormsg:
|
||||
server.errormsg = errormsg
|
||||
logging.warning(T('Cannot connect to server %s [%s]'), server.id, msg)
|
||||
logging.warning(T('Cannot connect to server %s [%s]'), server.host, msg)
|
||||
penalty = _PENALTY_UNKNOWN
|
||||
block = True
|
||||
if block or (penalty and server.optional):
|
||||
if server.active:
|
||||
server.active = False
|
||||
if penalty and (block or server.optional):
|
||||
self.plan_server(server.id, penalty)
|
||||
self.plan_server(server, penalty)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.reset_all_try_lists()
|
||||
self.__reset_nw(nw, None, warn=False, quit=True)
|
||||
continue
|
||||
except:
|
||||
logging.error(T('Connecting %s@%s failed, message=%s'),
|
||||
nw.thrdnum, nw.server.id, nntp_to_msg(nw.data))
|
||||
nw.thrdnum, nw.server.host, nntp_to_msg(nw.data))
|
||||
# No reset-warning needed, above logging is sufficient
|
||||
self.__reset_nw(nw, None, warn=False)
|
||||
|
||||
if nw.connected:
|
||||
logging.info("Connecting %s@%s finished", nw.thrdnum, nw.server.id)
|
||||
logging.info("Connecting %s@%s finished", nw.thrdnum, nw.server.host)
|
||||
self.__request_article(nw)
|
||||
|
||||
elif nw.status_code == '223':
|
||||
@@ -718,27 +718,27 @@ class Downloader(Thread):
|
||||
elif nw.status_code in ('411', '423', '430'):
|
||||
done = True
|
||||
logging.debug('Thread %s@%s: Article %s missing (error=%s)',
|
||||
nw.thrdnum, nw.server.id, article.article, nw.status_code)
|
||||
nw.thrdnum, nw.server.host, article.article, nw.status_code)
|
||||
nw.clear_data()
|
||||
|
||||
elif nw.status_code == '480':
|
||||
if server.active:
|
||||
server.active = False
|
||||
server.errormsg = T('Server %s requires user/password') % ''
|
||||
self.plan_server(server.id, 0)
|
||||
self.plan_server(server, 0)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.reset_all_try_lists()
|
||||
msg = T('Server %s requires user/password') % nw.server.id
|
||||
msg = T('Server %s requires user/password') % nw.server.host
|
||||
self.__reset_nw(nw, msg, quit=True)
|
||||
|
||||
elif nw.status_code == '500':
|
||||
if nzo.precheck:
|
||||
# Assume "STAT" command is not supported
|
||||
server.have_stat = False
|
||||
logging.debug('Server %s does not support STAT', server.id)
|
||||
logging.debug('Server %s does not support STAT', server.host)
|
||||
else:
|
||||
# Assume "BODY" command is not supported
|
||||
server.have_body = False
|
||||
logging.debug('Server %s does not support BODY', server.id)
|
||||
logging.debug('Server %s does not support BODY', server.host)
|
||||
nw.clear_data()
|
||||
self.__request_article(nw)
|
||||
|
||||
@@ -746,7 +746,7 @@ class Downloader(Thread):
|
||||
server.bad_cons = 0 # Succesful data, clear "bad" counter
|
||||
server.errormsg = server.warning = ''
|
||||
if sabnzbd.LOG_ALL:
|
||||
logging.debug('Thread %s@%s: %s done', nw.thrdnum, server.id, article.article)
|
||||
logging.debug('Thread %s@%s: %s done', nw.thrdnum, server.host, article.article)
|
||||
self.decode(article, nw.lines, nw.data)
|
||||
|
||||
nw.soft_reset()
|
||||
@@ -778,9 +778,9 @@ class Downloader(Thread):
|
||||
|
||||
if warn and errormsg:
|
||||
server.warning = errormsg
|
||||
logging.info('Thread %s@%s: ' + errormsg, nw.thrdnum, server.id)
|
||||
logging.info('Thread %s@%s: ' + errormsg, nw.thrdnum, server.host)
|
||||
elif errormsg:
|
||||
logging.info('Thread %s@%s: ' + errormsg, nw.thrdnum, server.id)
|
||||
logging.info('Thread %s@%s: ' + errormsg, nw.thrdnum, server.host)
|
||||
|
||||
if nw in server.busy_threads:
|
||||
server.busy_threads.remove(nw)
|
||||
@@ -814,11 +814,11 @@ class Downloader(Thread):
|
||||
if nw.server.send_group and nzo.group != nw.group:
|
||||
group = nzo.group
|
||||
if sabnzbd.LOG_ALL:
|
||||
logging.debug('Thread %s@%s: GROUP <%s>', nw.thrdnum, nw.server.id, group)
|
||||
logging.debug('Thread %s@%s: GROUP <%s>', nw.thrdnum, nw.server.host, group)
|
||||
nw.send_group(group)
|
||||
else:
|
||||
if sabnzbd.LOG_ALL:
|
||||
logging.debug('Thread %s@%s: BODY %s', nw.thrdnum, nw.server.id, nw.article.article)
|
||||
logging.debug('Thread %s@%s: BODY %s', nw.thrdnum, nw.server.host, nw.article.article)
|
||||
nw.body(nzo.precheck)
|
||||
|
||||
fileno = nw.nntp.sock.fileno()
|
||||
@@ -840,24 +840,24 @@ class Downloader(Thread):
|
||||
# Each server has a dictionary entry, consisting of a list of timestamps.
|
||||
|
||||
@synchronized(TIMER_LOCK)
|
||||
def plan_server(self, server_id, interval):
|
||||
def plan_server(self, server, interval):
|
||||
""" Plan the restart of a server in 'interval' minutes """
|
||||
if cfg.no_penalties() and interval > _PENALTY_SHORT:
|
||||
# Overwrite in case of no_penalties
|
||||
interval = _PENALTY_SHORT
|
||||
|
||||
logging.debug('Set planned server resume %s in %s mins', server_id, interval)
|
||||
if server_id not in self._timers:
|
||||
self._timers[server_id] = []
|
||||
logging.debug('Set planned server resume %s in %s mins', server.host, interval)
|
||||
if server.id not in self._timers:
|
||||
self._timers[server.id] = []
|
||||
stamp = time.time() + 60.0 * interval
|
||||
self._timers[server_id].append(stamp)
|
||||
self._timers[server.id].append(stamp)
|
||||
if interval:
|
||||
sabnzbd.scheduler.plan_server(self.trigger_server, [server_id, stamp], interval)
|
||||
sabnzbd.scheduler.plan_server(self.trigger_server, [server.id, stamp], interval)
|
||||
|
||||
@synchronized(TIMER_LOCK)
|
||||
def trigger_server(self, server_id, timestamp):
|
||||
""" Called by scheduler, start server if timer still valid """
|
||||
logging.debug('Trigger planned server resume %s', server_id)
|
||||
logging.debug('Trigger planned server resume for server-id %s', server_id)
|
||||
if server_id in self._timers:
|
||||
if timestamp in self._timers[server_id]:
|
||||
del self._timers[server_id]
|
||||
@@ -874,7 +874,7 @@ class Downloader(Thread):
|
||||
# Activate server if it was inactive
|
||||
for server in self.servers:
|
||||
if server.id == server_id and not server.active:
|
||||
logging.debug('Unblock server %s', server_id)
|
||||
logging.debug('Unblock server %s', server.host)
|
||||
self.init_server(server_id, server_id)
|
||||
break
|
||||
|
||||
@@ -891,7 +891,7 @@ class Downloader(Thread):
|
||||
kicked = []
|
||||
for server_id in self._timers.keys():
|
||||
if not [stamp for stamp in self._timers[server_id] if stamp >= now]:
|
||||
logging.debug('Forcing re-evaluation of server %s', server_id)
|
||||
logging.debug('Forcing re-evaluation of server-id %s', server_id)
|
||||
del self._timers[server_id]
|
||||
self.init_server(server_id, server_id)
|
||||
kicked.append(server_id)
|
||||
@@ -899,7 +899,7 @@ class Downloader(Thread):
|
||||
for server in self.servers:
|
||||
if server.id not in self._timers:
|
||||
if server.id not in kicked and not server.active:
|
||||
logging.debug('Forcing activation of server %s', server.id)
|
||||
logging.debug('Forcing activation of server %s', server.host)
|
||||
self.init_server(server.id, server.id)
|
||||
|
||||
def update_server(self, oldserver, newserver):
|
||||
|
||||
@@ -44,7 +44,6 @@ from sabnzbd.misc import real_path, to_units, from_units, time_format, \
|
||||
long_path, calc_age, same_file, probablyipv4, probablyipv6, \
|
||||
int_conv, globber, globber_full, remove_all, get_base_url
|
||||
from sabnzbd.newswrapper import GetServerParms
|
||||
from sabnzbd.rating import Rating
|
||||
from sabnzbd.bpsmeter import BPSMeter
|
||||
from sabnzbd.encoding import TRANS, xml_name, LatinFilter, unicoder, special_fixer, \
|
||||
platform_encode
|
||||
@@ -59,13 +58,13 @@ from sabnzbd.decoder import HAVE_YENC, SABYENC_ENABLED
|
||||
from sabnzbd.utils.diskspeed import diskspeedmeasure
|
||||
from sabnzbd.utils.getperformance import getpystone
|
||||
|
||||
from sabnzbd.constants import NORMAL_PRIORITY, MEBI, DEF_SKIN_COLORS, DEF_STDINTF, \
|
||||
from sabnzbd.constants import NORMAL_PRIORITY, MEBI, DEF_SKIN_COLORS, \
|
||||
DEF_STDCONFIG, DEF_MAIN_TMPL, DEFAULT_PRIORITY
|
||||
|
||||
from sabnzbd.lang import list_languages
|
||||
|
||||
from sabnzbd.api import list_scripts, list_cats, del_from_section, \
|
||||
api_handler, build_queue, remove_callable, rss_qstatus, build_status, \
|
||||
api_handler, build_queue, remove_callable, build_status, \
|
||||
retry_job, retry_all_jobs, build_header, build_history, del_job_files, \
|
||||
format_bytes, std_time, report, del_hist_job, Ttemplate, build_queue_header, \
|
||||
_api_test_email, _api_test_notif
|
||||
|
||||
@@ -44,6 +44,7 @@ from sabnzbd.constants import DEFAULT_PRIORITY, FUTURE_Q_FOLDER, JOB_ADMIN, \
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.encoding import unicoder, special_fixer, gUTF
|
||||
import sabnzbd.utils.rarfile as rarfile
|
||||
|
||||
TAB_UNITS = ('', 'K', 'M', 'G', 'T', 'P')
|
||||
RE_UNITS = re.compile(r'(\d+\.*\d*)\s*([KMGTP]{0,1})', re.I)
|
||||
@@ -1241,6 +1242,14 @@ def get_admin_path(name, future):
|
||||
return os.path.join(os.path.join(cfg.download_dir.get_path(), name), JOB_ADMIN)
|
||||
|
||||
|
||||
def is_rarfile(rarfile_path):
|
||||
""" Wrapper in case it crashes due to missing file or long-path problems """
|
||||
try:
|
||||
return rarfile.is_rarfile(rarfile_path)
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def on_cleanup_list(filename, skip_nzb=False):
|
||||
""" Return True if a filename matches the clean-up list """
|
||||
lst = cfg.cleanup_list()
|
||||
|
||||
@@ -32,8 +32,8 @@ import sabnzbd
|
||||
from sabnzbd.encoding import TRANS, unicoder, platform_encode, deunicode
|
||||
import sabnzbd.utils.rarfile as rarfile
|
||||
from sabnzbd.misc import format_time_string, find_on_path, make_script_path, int_conv, \
|
||||
real_path, globber, globber_full, get_all_passwords, renamer, clip_path, \
|
||||
has_win_device, calc_age, long_path, remove_file, recursive_listdir
|
||||
real_path, globber, globber_full, get_all_passwords, renamer, clip_path, calc_age, \
|
||||
has_win_device, long_path, remove_file, recursive_listdir, is_rarfile
|
||||
from sabnzbd.sorting import SeriesSorter
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.constants import Status
|
||||
@@ -159,14 +159,7 @@ def external_processing(extern_proc, nzo, complete_dir, nicename, status):
|
||||
'download_time': nzo.nzo_info.get('download_time', ''),
|
||||
'avg_bps': int(nzo.avg_bps_total / nzo.avg_bps_freq) if nzo.avg_bps_freq else 0,
|
||||
'age': calc_age(nzo.avg_date),
|
||||
'orig_nzb_gz': clip_path(nzb_paths[0]) if nzb_paths else '',
|
||||
'program_dir': sabnzbd.DIR_PROG,
|
||||
'par2_command': sabnzbd.newsunpack.PAR2_COMMAND,
|
||||
'multipar_command': sabnzbd.newsunpack.MULTIPAR_COMMAND,
|
||||
'rar_command': sabnzbd.newsunpack.RAR_COMMAND,
|
||||
'zip_command': sabnzbd.newsunpack.ZIP_COMMAND,
|
||||
'7zip_command': sabnzbd.newsunpack.SEVEN_COMMAND,
|
||||
'version': sabnzbd.__version__}
|
||||
'orig_nzb_gz': clip_path(nzb_paths[0]) if nzb_paths else ''}
|
||||
|
||||
try:
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
@@ -510,6 +503,8 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
|
||||
if wait_count > 60:
|
||||
# We abort after 2 minutes of no changes
|
||||
nzo.direct_unpacker.abort()
|
||||
else:
|
||||
wait_count = 0
|
||||
last_stats = nzo.direct_unpacker.get_formatted_stats()
|
||||
|
||||
# Did we already direct-unpack it? Not when recursive-unpacking
|
||||
@@ -656,7 +651,7 @@ def rar_extract_core(rarfile_path, numrars, one_folder, nzo, setname, extraction
|
||||
stup, need_shell, command, creationflags = build_command(command, flatten_command=True)
|
||||
|
||||
# Get list of all the volumes part of this set
|
||||
logging.debug("Analyzing rar file ... %s found", rarfile.is_rarfile(rarfile_path))
|
||||
logging.debug("Analyzing rar file ... %s found", is_rarfile(rarfile_path))
|
||||
logging.debug("Running unrar %s", command)
|
||||
p = Popen(command, shell=need_shell, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
@@ -1337,7 +1332,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False):
|
||||
block_table = {}
|
||||
for nzf in nzo.extrapars[setname]:
|
||||
if not nzf.completed:
|
||||
block_table[int_conv(nzf.blocks)] = nzf
|
||||
block_table[nzf.blocks] = nzf
|
||||
|
||||
if block_table:
|
||||
nzf = block_table[min(block_table.keys())]
|
||||
@@ -1650,7 +1645,7 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
|
||||
block_table = {}
|
||||
for nzf in nzo.extrapars[setname]:
|
||||
if not nzf.completed:
|
||||
block_table[int_conv(nzf.blocks)] = nzf
|
||||
block_table[nzf.blocks] = nzf
|
||||
|
||||
if block_table:
|
||||
nzf = block_table[min(block_table.keys())]
|
||||
@@ -1921,7 +1916,7 @@ def MultiPar_Verify(parfile, parfile_nzf, nzo, setname, joinables, single=False)
|
||||
|
||||
return finished, readd, datafiles, used_joinables, used_for_repair
|
||||
|
||||
def create_env(nzo=None, extra_env_fields=None):
|
||||
def create_env(nzo=None, extra_env_fields={}):
|
||||
""" Modify the environment for pp-scripts with extra information
|
||||
OSX: Return copy of environment without PYTHONPATH and PYTHONHOME
|
||||
other: return None
|
||||
@@ -1945,16 +1940,25 @@ def create_env(nzo=None, extra_env_fields=None):
|
||||
# Catch key/unicode errors
|
||||
pass
|
||||
|
||||
# Add extra fields
|
||||
for field in extra_env_fields:
|
||||
try:
|
||||
if extra_env_fields[field] is not None:
|
||||
env['SAB_' + field.upper()] = extra_env_fields[field]
|
||||
else:
|
||||
env['SAB_' + field.upper()] = ''
|
||||
except:
|
||||
# Catch key/unicode errors
|
||||
pass
|
||||
# Always supply basic info
|
||||
extra_env_fields.update({'program_dir': sabnzbd.DIR_PROG,
|
||||
'par2_command': sabnzbd.newsunpack.PAR2_COMMAND,
|
||||
'multipar_command': sabnzbd.newsunpack.MULTIPAR_COMMAND,
|
||||
'rar_command': sabnzbd.newsunpack.RAR_COMMAND,
|
||||
'zip_command': sabnzbd.newsunpack.ZIP_COMMAND,
|
||||
'7zip_command': sabnzbd.newsunpack.SEVEN_COMMAND,
|
||||
'version': sabnzbd.__version__})
|
||||
|
||||
# Add extra fields
|
||||
for field in extra_env_fields:
|
||||
try:
|
||||
if extra_env_fields[field] is not None:
|
||||
env['SAB_' + field.upper()] = extra_env_fields[field]
|
||||
else:
|
||||
env['SAB_' + field.upper()] = ''
|
||||
except:
|
||||
# Catch key/unicode errors
|
||||
pass
|
||||
|
||||
if sabnzbd.DARWIN:
|
||||
if 'PYTHONPATH' in env:
|
||||
@@ -2099,11 +2103,7 @@ def build_filelists(workdir, workdir_complete=None, check_both=False, check_rar=
|
||||
# Extra check for rar (takes CPU/disk)
|
||||
file_is_rar = False
|
||||
if check_rar:
|
||||
try:
|
||||
# Can fail on Windows due to long-path after recursive-unpack
|
||||
file_is_rar = rarfile.is_rarfile(file)
|
||||
except:
|
||||
pass
|
||||
file_is_rar = is_rarfile(file)
|
||||
|
||||
# Run through all the checks
|
||||
if SEVENZIP_RE.search(file) or SEVENMULTI_RE.search(file):
|
||||
@@ -2295,23 +2295,33 @@ def analyse_show(name):
|
||||
info.get('ep_name', '')
|
||||
|
||||
|
||||
def pre_queue(name, pp, cat, script, priority, size, groups):
|
||||
""" Run pre-queue script (if any) and process results """
|
||||
def pre_queue(nzo, pp, cat):
|
||||
""" Run pre-queue script (if any) and process results.
|
||||
pp and cat are supplied seperate since they can change.
|
||||
"""
|
||||
def fix(p):
|
||||
if not p or str(p).lower() == 'none':
|
||||
return ''
|
||||
return unicoder(p)
|
||||
|
||||
values = [1, name, pp, cat, script, priority, None]
|
||||
values = [1, nzo.final_name_pw_clean, pp, cat, nzo.script, nzo.priority, None]
|
||||
script_path = make_script_path(cfg.pre_script())
|
||||
if script_path:
|
||||
command = [script_path, name, pp, cat, script, priority, str(size), ' '.join(groups)]
|
||||
command.extend(analyse_show(name))
|
||||
# Basic command-line parameters
|
||||
command = [script_path, nzo.final_name_pw_clean, pp, cat, nzo.script, nzo.priority, str(nzo.bytes), ' '.join(nzo.groups)]
|
||||
command.extend(analyse_show(nzo.final_name_pw_clean))
|
||||
command = [fix(arg) for arg in command]
|
||||
|
||||
# Fields not in the NZO directly
|
||||
extra_env_fields = {'groups': ' '.join(nzo.groups),
|
||||
'show_name': command[8],
|
||||
'show_season': command[9],
|
||||
'show_episode': command[10],
|
||||
'show_episode_name': command[11]}
|
||||
|
||||
try:
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
env = create_env()
|
||||
env = create_env(nzo, extra_env_fields)
|
||||
logging.info('Running pre-queue script %s', command)
|
||||
p = Popen(command, shell=need_shell, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
@@ -2332,11 +2342,11 @@ def pre_queue(name, pp, cat, script, priority, size, groups):
|
||||
n += 1
|
||||
accept = int_conv(values[0])
|
||||
if accept < 1:
|
||||
logging.info('Pre-Q refuses %s', name)
|
||||
logging.info('Pre-Q refuses %s', nzo.final_name_pw_clean)
|
||||
elif accept == 2:
|
||||
logging.info('Pre-Q accepts&fails %s', name)
|
||||
logging.info('Pre-Q accepts&fails %s', nzo.final_name_pw_clean)
|
||||
else:
|
||||
logging.info('Pre-Q accepts %s', name)
|
||||
logging.info('Pre-Q accepts %s', nzo.final_name_pw_clean)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from threading import Thread
|
||||
from nntplib import NNTPPermanentError
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import ssl
|
||||
|
||||
import sabnzbd
|
||||
|
||||
@@ -23,7 +23,6 @@ sabnzbd.notifier - Send notifications to any notification services
|
||||
from __future__ import with_statement
|
||||
import os.path
|
||||
import logging
|
||||
import socket
|
||||
import urllib2
|
||||
import httplib
|
||||
import urllib
|
||||
|
||||
@@ -324,7 +324,7 @@ class NzbFile(TryList):
|
||||
self.is_par2 = True
|
||||
self.setname = setname
|
||||
self.vol = vol
|
||||
self.blocks = int(blocks)
|
||||
self.blocks = int_conv(blocks)
|
||||
|
||||
def get_article(self, server, servers):
|
||||
""" Get next article to be downloaded """
|
||||
@@ -827,9 +827,9 @@ class NzbObject(TryList):
|
||||
|
||||
# Run user pre-queue script if needed
|
||||
if not reuse and cfg.pre_script():
|
||||
accept, name, pp, cat_pp, script_pp, priority, group = \
|
||||
sabnzbd.newsunpack.pre_queue(self.final_name_pw_clean, pp, cat, script,
|
||||
priority, self.bytes, self.groups)
|
||||
# Call the script
|
||||
accept, name, pp, cat_pp, script_pp, priority, group = sabnzbd.newsunpack.pre_queue(self, pp, cat)
|
||||
|
||||
# Accept or reject
|
||||
accept = int_conv(accept)
|
||||
if accept < 1:
|
||||
@@ -1098,38 +1098,37 @@ class NzbObject(TryList):
|
||||
def get_extra_blocks(self, setname, needed_blocks):
|
||||
""" We want par2-files of all sets that are similar to this one
|
||||
So that we also can handle multi-sets with duplicate filenames
|
||||
Block-table has as keys the nr-blocks
|
||||
Returns number of added blocks in case they are available
|
||||
In case of duplicate files for the same set, we might add too
|
||||
little par2 on the first add-run, but that's a risk we need to take.
|
||||
"""
|
||||
logging.info('Need %s more blocks, checking blocks', needed_blocks)
|
||||
|
||||
avail_blocks = 0
|
||||
block_table = {}
|
||||
block_list = []
|
||||
for setname_search in self.extrapars:
|
||||
# Do it for our set, or highlight matching one
|
||||
# We might catch to many par2's, but that's okay
|
||||
# We might catch too many par2's, but that's okay
|
||||
if setname_search == setname or difflib.SequenceMatcher(None, setname, setname_search).ratio() > 0.85:
|
||||
for nzf in self.extrapars[setname_search]:
|
||||
# Don't count extrapars that are completed already
|
||||
if nzf.completed:
|
||||
continue
|
||||
blocks = int_conv(nzf.blocks)
|
||||
if blocks not in block_table:
|
||||
block_table[blocks] = []
|
||||
# We assume same block-vol-naming for each set
|
||||
avail_blocks += blocks
|
||||
block_table[blocks].append(nzf)
|
||||
block_list.append(nzf)
|
||||
avail_blocks += nzf.blocks
|
||||
|
||||
# Sort by smallest blocks first
|
||||
block_list.sort(key=lambda x: x.blocks)
|
||||
logging.info('%s blocks available', avail_blocks)
|
||||
|
||||
# Enough?
|
||||
if avail_blocks >= needed_blocks:
|
||||
added_blocks = 0
|
||||
while added_blocks < needed_blocks:
|
||||
block_size = min(block_table.keys())
|
||||
for new_nzf in block_table[block_size]:
|
||||
self.add_parfile(new_nzf)
|
||||
added_blocks += block_size
|
||||
block_table.pop(block_size)
|
||||
new_nzf = block_list.pop()
|
||||
self.add_parfile(new_nzf)
|
||||
added_blocks += new_nzf.blocks
|
||||
|
||||
logging.info('Added %s blocks to %s', added_blocks, self.final_name)
|
||||
return added_blocks
|
||||
else:
|
||||
@@ -1407,7 +1406,7 @@ class NzbObject(TryList):
|
||||
if (parset in nzf.filename or parset in original_filename) and self.extrapars[parset]:
|
||||
for new_nzf in self.extrapars[parset]:
|
||||
self.add_parfile(new_nzf)
|
||||
blocks_new += int_conv(new_nzf.blocks)
|
||||
blocks_new += new_nzf.blocks
|
||||
# Enough now?
|
||||
if blocks_new >= self.bad_articles:
|
||||
logging.info('Prospectively added %s repair blocks to %s', blocks_new, self.final_name)
|
||||
@@ -1502,11 +1501,11 @@ class NzbObject(TryList):
|
||||
self.set_unpack_info('Servers', ', '.join(msgs), unique=True)
|
||||
|
||||
@synchronized(NZO_LOCK)
|
||||
def increase_bad_articles_counter(self, type):
|
||||
def increase_bad_articles_counter(self, article_type):
|
||||
""" Record information about bad articles """
|
||||
if type not in self.nzo_info:
|
||||
self.nzo_info[type] = 0
|
||||
self.nzo_info[type] += 1
|
||||
if article_type not in self.nzo_info:
|
||||
self.nzo_info[article_type] = 0
|
||||
self.nzo_info[article_type] += 1
|
||||
self.bad_articles += 1
|
||||
|
||||
def get_article(self, server, servers):
|
||||
|
||||
@@ -26,7 +26,9 @@ import struct
|
||||
|
||||
|
||||
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)[\+\-](\d*)\.par2', re.I)
|
||||
PAR_ID = "PAR2\x00PKT"
|
||||
PAR_PKT_ID = "PAR2\x00PKT"
|
||||
PAR_FILE_ID = "PAR 2.0\x00FileDesc"
|
||||
PAR_CREATOR_ID = "PAR 2.0\x00Creator"
|
||||
PAR_RECOVERY_ID = "RecvSlic"
|
||||
|
||||
|
||||
@@ -35,7 +37,7 @@ def is_parfile(filename):
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
buf = f.read(8)
|
||||
return buf.startswith(PAR_ID)
|
||||
return buf.startswith(PAR_PKT_ID)
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
@@ -129,7 +131,8 @@ def parse_par2_file_packet(f, header):
|
||||
|
||||
nothing = None, None, None
|
||||
|
||||
if header != PAR_ID:
|
||||
if header != PAR_PKT_ID:
|
||||
print header
|
||||
return nothing
|
||||
|
||||
# Length must be multiple of 4 and at least 20
|
||||
@@ -157,10 +160,14 @@ def parse_par2_file_packet(f, header):
|
||||
|
||||
# See if it's the right packet and get name + hash
|
||||
for offset in range(0, len, 8):
|
||||
if data[offset:offset + 16] == "PAR 2.0\0FileDesc":
|
||||
if data[offset:offset + 16] == PAR_FILE_ID:
|
||||
hash = data[offset + 32:offset + 48]
|
||||
hash16k = data[offset + 48:offset + 64]
|
||||
filename = data[offset + 72:].strip('\0')
|
||||
return filename, hash, hash16k
|
||||
elif data[offset:offset + 15] == PAR_CREATOR_ID:
|
||||
# Here untill the end is the creator-text
|
||||
# Usefull in case of bugs in the par2-creating software
|
||||
logging.debug('Par2-creator of %s is: %s', os.path.basename(f.name), data[offset+16:])
|
||||
|
||||
return nothing
|
||||
|
||||
@@ -25,7 +25,6 @@ import urlparse
|
||||
import time
|
||||
import logging
|
||||
import copy
|
||||
import socket
|
||||
import Queue
|
||||
import collections
|
||||
from threading import RLock, Thread
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
sabtray.py - Systray icon for SABnzbd on Windows, contributed by Jan Schejbal
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
@@ -29,8 +30,6 @@ import sabnzbd.scheduler as scheduler
|
||||
from sabnzbd.downloader import Downloader
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.misc import to_units
|
||||
import os
|
||||
import cherrypy
|
||||
|
||||
# contains the tray icon, which demands its own thread
|
||||
from sabnzbd.utils.systrayiconthread import SysTrayIconThread
|
||||
@@ -98,10 +97,13 @@ class SABTrayThread(SysTrayIconThread):
|
||||
speed = to_units(bpsnow)
|
||||
|
||||
if self.sabpaused:
|
||||
self.hover_text = self.txt_paused
|
||||
if bytes_left > 0:
|
||||
self.hover_text = "%s - %s: %sB" % (self.txt_paused, self.txt_remaining, mb_left)
|
||||
else:
|
||||
self.hover_text = self.txt_paused
|
||||
self.icon = self.sabicons['pause']
|
||||
elif bytes_left > 0:
|
||||
self.hover_text = "%sB/s %s: %sB (%s)" % (speed, self.txt_remaining, mb_left, time_left)
|
||||
self.hover_text = "%sB/s - %s: %sB (%s)" % (speed, self.txt_remaining, mb_left, time_left)
|
||||
self.icon = self.sabicons['green']
|
||||
else:
|
||||
self.hover_text = self.txt_idle
|
||||
|
||||
@@ -21,7 +21,6 @@ sabnzbd.sabtraylinux - System tray icon for Linux, inspired from the Windows one
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
import cherrypy
|
||||
from time import sleep
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
|
||||
@@ -6,7 +6,6 @@ Functions to check if the path filesystem uses FAT
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
debug = False
|
||||
|
||||
|
||||
@@ -3,61 +3,41 @@
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
_DUMP_DATA = '*' * 10000
|
||||
|
||||
def writetofile(filename, mysizeMB):
|
||||
# writes string to specified file repeat delay, until mysizeMB is reached.
|
||||
writeloops = int(1024 * 1024 * mysizeMB / len(_DUMP_DATA))
|
||||
try:
|
||||
f = open(filename, 'w')
|
||||
except:
|
||||
logging.debug('Cannot create file %s', filename)
|
||||
logging.debug("Traceback: ", exc_info=True)
|
||||
return False
|
||||
|
||||
try:
|
||||
for x in xrange(writeloops):
|
||||
f.write(_DUMP_DATA)
|
||||
except:
|
||||
logging.debug('Cannot write to file %s', filename)
|
||||
logging.debug("Traceback: ", exc_info=True)
|
||||
return False
|
||||
f.close()
|
||||
return True
|
||||
_DUMP_DATA_SIZE = 10 * 1024 * 1024
|
||||
_DUMP_DATA = os.urandom(_DUMP_DATA_SIZE)
|
||||
|
||||
|
||||
def diskspeedmeasure(dirname):
|
||||
# returns writing speed to dirname in MB/s
|
||||
# method: keep writing a file, until 0.5 seconds is passed. Then divide bytes written by time passed
|
||||
filesize = 10 # MB
|
||||
maxtime = 0.5 # sec
|
||||
""" Returns writing speed to dirname in MB/s
|
||||
method: keep writing a file, until 1 second is passed.
|
||||
Then divide bytes written by time passed
|
||||
"""
|
||||
maxtime = 1.0 # sec
|
||||
total_written = 0
|
||||
filename = os.path.join(dirname, 'outputTESTING.txt')
|
||||
|
||||
if os.name == 'nt':
|
||||
# On Windows, this crazy action is needed to
|
||||
# avoid a "permission denied" error
|
||||
try:
|
||||
os.popen('echo Hi >%s' % filename)
|
||||
except:
|
||||
pass
|
||||
# Use low-level I/O
|
||||
fp = os.open(filename, os.O_CREAT | os.O_WRONLY, 0o777)
|
||||
|
||||
start = time.time()
|
||||
loopcounter = 0
|
||||
while True:
|
||||
if not writetofile(filename, filesize):
|
||||
return 0
|
||||
loopcounter += 1
|
||||
diff = time.time() - start
|
||||
if diff > maxtime:
|
||||
break
|
||||
# Start looping
|
||||
total_time = 0.0
|
||||
while total_time < maxtime:
|
||||
start = time.time()
|
||||
os.write(fp, _DUMP_DATA)
|
||||
os.fsync(fp)
|
||||
total_time += time.time() - start
|
||||
total_written += _DUMP_DATA_SIZE
|
||||
|
||||
# Remove the file
|
||||
try:
|
||||
# Have to use low-level close
|
||||
os.close(fp)
|
||||
os.remove(filename)
|
||||
except:
|
||||
pass
|
||||
return (loopcounter * filesize) / diff
|
||||
|
||||
return total_written / total_time / 1024 / 1024
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import platform, subprocess
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
|
||||
def getcpu():
|
||||
|
||||
@@ -78,7 +78,6 @@ import os
|
||||
import sys
|
||||
import sched
|
||||
import time
|
||||
import traceback
|
||||
import weakref
|
||||
import logging
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import os
|
||||
from sabnzbd.encoding import unicoder
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.misc import get_ext, get_filename, get_from_url
|
||||
import sabnzbd.newsunpack
|
||||
from sabnzbd.constants import VALID_ARCHIVES, VALID_NZB_FILES
|
||||
|
||||
from sabnzbd.dirscanner import ProcessArchiveFile, ProcessSingleFile
|
||||
|
||||
@@ -21,7 +21,6 @@ sabnzbd.zconfig - bonjour/zeroconfig support
|
||||
|
||||
import os
|
||||
import logging
|
||||
import cherrypy
|
||||
|
||||
_HOST_PORT = (None, None)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ NOTES:
|
||||
1) To use this script you need Python installed on your system and
|
||||
select "Add to path" during its installation. Select this folder in
|
||||
Config > Folders > Scripts Folder and select this script for each job
|
||||
you want it sued for, or link it to a category in Config > Categories.
|
||||
you want it used for, or link it to a category in Config > Categories.
|
||||
2) Beware that files on the 'Cleanup List' are removed before
|
||||
scripts are called and if any of them happen to be required by
|
||||
the found par2 file, it will fail.
|
||||
@@ -39,37 +39,116 @@ NOTES:
|
||||
5) Feedback or bugs in this script can be reported in on our forum:
|
||||
https://forums.sabnzbd.org/viewforum.php?f=9
|
||||
|
||||
|
||||
Improved by P1nGu1n
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import fnmatch
|
||||
import subprocess
|
||||
import struct
|
||||
import hashlib
|
||||
from os import path
|
||||
|
||||
# Files to exclude and minimal file size for renaming
|
||||
EXCLUDED_FILE_EXTS = ('.vob', '.bin')
|
||||
MIN_FILE_SIZE = 40*1024*1024
|
||||
|
||||
# Are we being called from SABnzbd?
|
||||
if not os.environ.get('SAB_VERSION'):
|
||||
print "This script needs to be called from SABnzbd as post-processing script."
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Files to exclude and minimal file size for renaming
|
||||
EXCLUDED_FILE_EXTS = ('.vob', '.bin')
|
||||
MIN_FILE_SIZE = 40*1024*1024
|
||||
|
||||
# see: http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
|
||||
STRUCT_PACKET_HEADER = struct.Struct("<"
|
||||
"8s" # Magic sequence
|
||||
"Q" # Length of the entire packet (including header), must be multiple of 4
|
||||
"16s" # MD5 Hash of packet
|
||||
"16s" # Recovery Set ID
|
||||
"16s" # Packet type
|
||||
)
|
||||
|
||||
PACKET_TYPE_FILE_DESC = 'PAR 2.0\x00FileDesc'
|
||||
STRUCT_FILE_DESC_PACKET = struct.Struct("<"
|
||||
"16s" # File ID
|
||||
"16s" # MD5 hash of the entire file
|
||||
"16s" # MD5 hash of the first 16KiB of the file
|
||||
"Q" # Length of the file
|
||||
)
|
||||
|
||||
|
||||
# Supporting functions
|
||||
def print_splitter():
|
||||
""" Simple helper function """
|
||||
print '\n------------------------\n'
|
||||
|
||||
# Windows or others?
|
||||
par2_command = os.environ['SAB_PAR2_COMMAND']
|
||||
if os.environ['SAB_MULTIPAR_COMMAND']:
|
||||
par2_command = os.environ['SAB_MULTIPAR_COMMAND']
|
||||
|
||||
# Diagnostic info
|
||||
def decodePar(parfile):
|
||||
result = False
|
||||
dir = os.path.dirname(parfile)
|
||||
with open(parfile, 'rb') as parfileToDecode:
|
||||
while (True):
|
||||
header = parfileToDecode.read(STRUCT_PACKET_HEADER.size)
|
||||
if not header: break # file fully read
|
||||
|
||||
(_, packetLength, _, _, packetType) = STRUCT_PACKET_HEADER.unpack(header)
|
||||
bodyLength = packetLength - STRUCT_PACKET_HEADER.size
|
||||
|
||||
# only process File Description packets
|
||||
if (packetType != PACKET_TYPE_FILE_DESC):
|
||||
# skip this packet
|
||||
parfileToDecode.seek(bodyLength, os.SEEK_CUR)
|
||||
continue
|
||||
|
||||
chunck = parfileToDecode.read(STRUCT_FILE_DESC_PACKET.size)
|
||||
(_, _, hash16k, filelength) = STRUCT_FILE_DESC_PACKET.unpack(chunck)
|
||||
|
||||
# filename makes up for the rest of the packet, padded with null characters
|
||||
targetName = parfileToDecode.read(bodyLength - STRUCT_FILE_DESC_PACKET.size).rstrip('\0')
|
||||
targetPath = path.join(dir, targetName)
|
||||
|
||||
# file already exists, skip it
|
||||
if (path.exists(targetPath)):
|
||||
print "File already exists: " + targetName
|
||||
continue
|
||||
|
||||
# find and rename file
|
||||
srcPath = findFile(dir, filelength, hash16k)
|
||||
if (srcPath is not None):
|
||||
os.rename(srcPath, targetPath)
|
||||
print "Renamed file from " + path.basename(srcPath) + " to " + targetName
|
||||
result = True
|
||||
else:
|
||||
print "No match found for: " + targetName
|
||||
return result
|
||||
|
||||
|
||||
def findFile(dir, filelength, hash16k):
|
||||
for filename in os.listdir(dir):
|
||||
filepath = path.join(dir, filename)
|
||||
|
||||
# check if the size matches as an indication
|
||||
if (path.getsize(filepath) != filelength): continue
|
||||
|
||||
with open(filepath, 'rb') as fileToMatch:
|
||||
data = fileToMatch.read(16 * 1024)
|
||||
m = hashlib.md5()
|
||||
m.update(data)
|
||||
|
||||
# compare hash to confirm the match
|
||||
if (m.digest() == hash16k):
|
||||
return filepath
|
||||
return None
|
||||
|
||||
|
||||
# Run main program
|
||||
print_splitter()
|
||||
print 'SABnzbd version: ', os.environ['SAB_VERSION']
|
||||
print 'Job location: ', os.environ['SAB_COMPLETE_DIR']
|
||||
print 'Par2-command: ', par2_command
|
||||
print_splitter()
|
||||
|
||||
# Search for par2 files
|
||||
@@ -86,34 +165,14 @@ if not matches:
|
||||
|
||||
# Run par2 from SABnzbd on them
|
||||
for par2_file in matches:
|
||||
# Build command, make it check the whole directory
|
||||
wildcard = os.path.join(os.environ['SAB_COMPLETE_DIR'], '*')
|
||||
command = [str(par2_command), 'r', par2_file, wildcard]
|
||||
|
||||
# Start command
|
||||
# Analyse data and analyse result
|
||||
print_splitter()
|
||||
print 'Starting command: ', repr(command)
|
||||
try:
|
||||
result = subprocess.check_output(command)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Multipar also gives non-zero in case of succes
|
||||
result = e.output
|
||||
|
||||
# Show output
|
||||
print_splitter()
|
||||
print result
|
||||
print_splitter()
|
||||
|
||||
# Last status-line for the History
|
||||
# Check if the magic words are there
|
||||
if 'Repaired successfully' in result or 'All files are correct' in result or \
|
||||
'Repair complete' in result or 'All Files Complete' in result or 'PAR File(s) Incomplete' in result:
|
||||
if decodePar(par2_file):
|
||||
print 'Recursive repair/verify finished.'
|
||||
run_renamer = False
|
||||
else:
|
||||
print 'Recursive repair/verify did not complete!'
|
||||
|
||||
|
||||
# No matches? Then we try to rename the largest file to the job-name
|
||||
if run_renamer:
|
||||
print_splitter()
|
||||
|
||||
@@ -105,7 +105,7 @@ def get_install_lng():
|
||||
""" Return language-code used by the installer """
|
||||
lng = 0
|
||||
try:
|
||||
hive = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
hive = _winreg.ConnectRegistry(None, _winreg.HKEY_CURRENT_USER)
|
||||
key = _winreg.OpenKey(hive, r"Software\SABnzbd")
|
||||
for i in range(0, _winreg.QueryInfoKey(key)[1]):
|
||||
name, value, val_type = _winreg.EnumValue(key, i)
|
||||
@@ -116,7 +116,31 @@ def get_install_lng():
|
||||
pass
|
||||
finally:
|
||||
_winreg.CloseKey(hive)
|
||||
return lng
|
||||
|
||||
if lng in LanguageMap:
|
||||
return LanguageMap[lng]
|
||||
return 'en'
|
||||
|
||||
|
||||
# Map from NSIS-codepage to our language-strings
|
||||
LanguageMap = {
|
||||
'1033': 'en',
|
||||
'1036': 'fr',
|
||||
'1031': 'de',
|
||||
'1043': 'nl',
|
||||
'1035': 'fi',
|
||||
'1045': 'pl',
|
||||
'1053': 'sv',
|
||||
'1030': 'da',
|
||||
'2068': 'nb',
|
||||
'1048': 'ro',
|
||||
'1034': 'es',
|
||||
'1046': 'pr_BR',
|
||||
'3098': 'sr',
|
||||
'1037': 'he',
|
||||
'1049': 'ru',
|
||||
'2052': 'zh_CN'
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user