mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-01-06 14:39:41 -05:00
Compare commits
235 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a82df9bf2e | ||
|
|
986604f27c | ||
|
|
59324c7453 | ||
|
|
91613a5b37 | ||
|
|
5ca05fd2c0 | ||
|
|
4d4045cff4 | ||
|
|
1f209a42d8 | ||
|
|
bffbb362db | ||
|
|
435eed8818 | ||
|
|
f86656543a | ||
|
|
9c510c6dd1 | ||
|
|
f81ab3d1c0 | ||
|
|
d1585c28a9 | ||
|
|
9c314532c0 | ||
|
|
853bda5d86 | ||
|
|
d05e31f7f0 | ||
|
|
383354871d | ||
|
|
2086a217e0 | ||
|
|
34f3574746 | ||
|
|
1dfe0b957e | ||
|
|
17d14bc3b4 | ||
|
|
885032e436 | ||
|
|
ceee95aaf7 | ||
|
|
bc6b3091eb | ||
|
|
4be1a13316 | ||
|
|
a77327ee7f | ||
|
|
aa706012af | ||
|
|
f5b6203194 | ||
|
|
1ced9a54e4 | ||
|
|
06c7089a77 | ||
|
|
ee1d864eea | ||
|
|
d703338935 | ||
|
|
e87b24c460 | ||
|
|
3404ef6516 | ||
|
|
181897e92b | ||
|
|
26a504e3e2 | ||
|
|
b72ed09011 | ||
|
|
bb99c0d58e | ||
|
|
4516027fdb | ||
|
|
e35f2ea3cd | ||
|
|
6b79fad626 | ||
|
|
ac311be430 | ||
|
|
4fb32bff5f | ||
|
|
5fda342a55 | ||
|
|
e23aab4710 | ||
|
|
3837d5dace | ||
|
|
f61e7cb1ed | ||
|
|
3de0c0e4ac | ||
|
|
63796d3feb | ||
|
|
6b07529300 | ||
|
|
e10676710c | ||
|
|
77f67c6666 | ||
|
|
bdbcdd61e1 | ||
|
|
4ab7ec754d | ||
|
|
20f98f48bc | ||
|
|
84e0502e50 | ||
|
|
2aa1b00dbb | ||
|
|
972078a514 | ||
|
|
be8382d25b | ||
|
|
8d46e88cd8 | ||
|
|
6b6b1b79ad | ||
|
|
e1fd40b34d | ||
|
|
bc1f8f97a8 | ||
|
|
b51705f458 | ||
|
|
aaed5f4797 | ||
|
|
a8eedef1d2 | ||
|
|
9407e21e1e | ||
|
|
ba6dcfd467 | ||
|
|
e2c1de5008 | ||
|
|
10b7403748 | ||
|
|
1ba924cc12 | ||
|
|
11eb034bd3 | ||
|
|
c3250e15cb | ||
|
|
8ff8a59b4c | ||
|
|
0c646d88b2 | ||
|
|
05670ea599 | ||
|
|
e25eb32885 | ||
|
|
250f75f084 | ||
|
|
cdd39e6777 | ||
|
|
281ed6766c | ||
|
|
cd78c89de1 | ||
|
|
c6c983e8f2 | ||
|
|
ef4d1ce54f | ||
|
|
b1177f4265 | ||
|
|
02d373e4a6 | ||
|
|
58c8608667 | ||
|
|
848110ac3e | ||
|
|
74d677cf09 | ||
|
|
f029c4eb4f | ||
|
|
838811f085 | ||
|
|
e212ec7ca3 | ||
|
|
6314a536af | ||
|
|
df33765ce0 | ||
|
|
2524333e79 | ||
|
|
cf81e815ee | ||
|
|
7bd7f1826c | ||
|
|
9ce7b528e3 | ||
|
|
db7b3cf0b5 | ||
|
|
201b30b6c4 | ||
|
|
36a6bcd1f5 | ||
|
|
20cfca5bb6 | ||
|
|
63fe4b15c8 | ||
|
|
aabc57a1f8 | ||
|
|
9a8eca0993 | ||
|
|
f8723d7e52 | ||
|
|
59bb5528ed | ||
|
|
247c10692a | ||
|
|
63bed3c127 | ||
|
|
ca96743bad | ||
|
|
525fb4de61 | ||
|
|
f294084dbc | ||
|
|
ca1327a9ae | ||
|
|
dcb1b0b3dc | ||
|
|
ec4b613498 | ||
|
|
c3f4eccfbc | ||
|
|
1aafe25a83 | ||
|
|
cc25ef0af0 | ||
|
|
5c221f4a14 | ||
|
|
03221fc645 | ||
|
|
febf81e597 | ||
|
|
447ec55822 | ||
|
|
eee1f49c4a | ||
|
|
140b903783 | ||
|
|
8361bc9f3a | ||
|
|
744290c228 | ||
|
|
20768df430 | ||
|
|
7ec7e8d432 | ||
|
|
7b657a85ba | ||
|
|
258699f1db | ||
|
|
0412f45323 | ||
|
|
997eb93cd9 | ||
|
|
b94192486b | ||
|
|
73bdd2c5bf | ||
|
|
56e9b54cd9 | ||
|
|
3308074f81 | ||
|
|
c2305034a1 | ||
|
|
d77b22be37 | ||
|
|
9717912ff7 | ||
|
|
cfa79c08b2 | ||
|
|
4f65f87ad6 | ||
|
|
aea0d21fd2 | ||
|
|
ba77b43364 | ||
|
|
7727eb58a8 | ||
|
|
dbd2e3f54b | ||
|
|
7ac6e07576 | ||
|
|
24ffd90fb4 | ||
|
|
011b680337 | ||
|
|
5328e07a93 | ||
|
|
2d07af7cc8 | ||
|
|
a40d2da2ab | ||
|
|
25ac101751 | ||
|
|
a666165a5e | ||
|
|
e24aedc6ac | ||
|
|
d10d69e44b | ||
|
|
af5c01ee3a | ||
|
|
bf350cddc8 | ||
|
|
9bc4d909b5 | ||
|
|
42236de5bd | ||
|
|
02e2fe2cc8 | ||
|
|
b75bcb90f4 | ||
|
|
56b88eb406 | ||
|
|
93741ea9ab | ||
|
|
f7509132fc | ||
|
|
2d0d62ec00 | ||
|
|
4e07a84102 | ||
|
|
32a048a879 | ||
|
|
caac28fcbc | ||
|
|
5b0bbf57c6 | ||
|
|
072af938c2 | ||
|
|
9e8202371e | ||
|
|
27dd253c5d | ||
|
|
8d651af2f8 | ||
|
|
6358312272 | ||
|
|
42c8367e13 | ||
|
|
c1e38b5e81 | ||
|
|
1b4ce24037 | ||
|
|
cf440750b6 | ||
|
|
30c480df36 | ||
|
|
9c3dbd39ef | ||
|
|
1af2f92828 | ||
|
|
45277bb00f | ||
|
|
10e21a3af9 | ||
|
|
19cbadd420 | ||
|
|
a28cbe52b9 | ||
|
|
37f1d64e46 | ||
|
|
7170325df5 | ||
|
|
c44d98da66 | ||
|
|
018410afb0 | ||
|
|
fc47238a7a | ||
|
|
9561b8a64e | ||
|
|
7b0e56b55f | ||
|
|
c6d5a79776 | ||
|
|
faa4cacd3e | ||
|
|
56e417eea1 | ||
|
|
5f02ec00f9 | ||
|
|
5ea35db922 | ||
|
|
5dcf26a56c | ||
|
|
35b598d10e | ||
|
|
5e7b27c4ef | ||
|
|
9ed408d35b | ||
|
|
6c782fe255 | ||
|
|
1689323dc3 | ||
|
|
a3c50a907a | ||
|
|
36a3792846 | ||
|
|
4cd0c0691a | ||
|
|
6ac98dcacd | ||
|
|
0a0d00930a | ||
|
|
28a0d041f9 | ||
|
|
85bb91a7ea | ||
|
|
6561e0abfa | ||
|
|
6715e61a68 | ||
|
|
a886b284b6 | ||
|
|
a349c82b6f | ||
|
|
1f4df0ebf4 | ||
|
|
0221e7bf93 | ||
|
|
f9cf14e7d8 | ||
|
|
7258e56a20 | ||
|
|
90bd495d44 | ||
|
|
6c216d6dfe | ||
|
|
e1f3fae6c7 | ||
|
|
29f126ca47 | ||
|
|
8b4b742466 | ||
|
|
57a9d362bc | ||
|
|
b7d54c2bea | ||
|
|
59f9833076 | ||
|
|
8e360fe53e | ||
|
|
afc5005382 | ||
|
|
3a531c6d2b | ||
|
|
f056ad6347 | ||
|
|
5c1342a663 | ||
|
|
dfe8a47a2a | ||
|
|
e293a439dd | ||
|
|
7e0027922a | ||
|
|
00b5302ba9 | ||
|
|
79488c4785 |
@@ -1,5 +1,5 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd 0.7.1 ***
|
||||
*** This is SABnzbd 0.7.7 ***
|
||||
*******************************************
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
|
||||
189
CHANGELOG.txt
189
CHANGELOG.txt
@@ -1,3 +1,192 @@
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.7Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Windows/OSX: Update unrar to 4.20
|
||||
- Fix some issues with orphaned items
|
||||
- Generic sort didn't always rename media files in multi-part jobs properly
|
||||
- Optional web-ui watchdog
|
||||
- Always show RSS items in the same order as the original RSS feed
|
||||
- Remove unusable folders from folder selector (Plush skin)
|
||||
- Remove newzbin support
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.6Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Recursive scanning when re-queuing downloaded NZB files
|
||||
- Log "User-Agent" header of API calls
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.6Beta2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- A damaged smallest par2 can block fetching of more par2 files
|
||||
- Fix evaluation of schedules at startup
|
||||
- Make check for running SABnzbd instance more robust
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.6Beta1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Handle par2 sets that were renamed after creation
|
||||
- Prevent blocking assembly of completed files, ( this resulted in
|
||||
excessive CPU and memory usage)
|
||||
- Fix speed issues with some Usenet servers due to unreachable IPv6 addresses
|
||||
- Fix issues with SFV-base checks
|
||||
- Prevent crash on Unix-Pythons that don't have the os.getloadavg() function
|
||||
- Successfully pre-checked job lost its attributes when those were changed during check
|
||||
- Remove version check when looking for a running instance of SABnzbd
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.5Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Add missing %dn formula to Generic Sort
|
||||
- Improve RSS logging
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.5RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Prevent stuck jobs at end of pre-check.
|
||||
- Fix issues with accented and special characters in names of downloaded files.
|
||||
- Adjust nzbmatrix category table.
|
||||
- Add 'prio_sort_list' special
|
||||
- Add special option 'empty_postproc'.
|
||||
- Prevent CherryPy crash when reading a cookie from another app which has a non-standard name.
|
||||
- Prevent crash when trying to open non-existing "complete" folder from Windows System-tray icon.
|
||||
- Fix problem with "Read" button when RSS feed name contains "&".
|
||||
- Prevent unusual SFV files from crashing post-processing.
|
||||
- OSX: Retina compatible menu-bar icons.
|
||||
- Don't show speed and ETA when download is paused during post-processing
|
||||
- Prevent soft-crash when api-function "addfile" is called without parameters.
|
||||
- Add news channel frame
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.4Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Pre-queue script no longer got the show/season/episode information.
|
||||
- Prevent crash on startup when a fully downloaded job is still in download queue.
|
||||
- New RSS feed should no longer be considered new after first, but empty readout.
|
||||
- Make "auth" call backward-compatible with 0.6.x releases.
|
||||
- Config->Notifications: email and growl server addresses should not be marked as "url" type.
|
||||
- OSX: fix top menu queue info so that it shows total queue size
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.4RC2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Pre-check failed to consider extra par2 files
|
||||
- Fixed unjustified warning that can occur with OSX Growl 2.0
|
||||
- Show memory usage on Linux systems
|
||||
- Fix incorrect end-of-month quota reset
|
||||
- Fix UI refresh issue when using Safari on iOS6
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.4RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Remove potential queue stalling when downloading extra par2 files
|
||||
- Make Windows version less eager to use par2-classic
|
||||
- Fixed DMG images
|
||||
- Add missing encoding directive to Plush and Classic skins
|
||||
- Prevent oversized data in API-call "history"
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.4Beta3 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- All three OSX build in one DMG again
|
||||
- Minor bugfixes
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.4Beta2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fix failure to fetch more par2-files for posts with badly formatted subject lines
|
||||
- After successful pre-check, preserve a job's position in the queue
|
||||
- Restore SABnzbd icon for Growl
|
||||
- Fix "check new releases" option in Config skin
|
||||
- Separate DMG files for OSX Leopard/SL, Lion and MLion
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.4Beta1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- OSX Mountain Lion Notification Center support
|
||||
- OSX Mountain Lion improved "keep awake" support
|
||||
- OSX: separate builds: one for Mountain Lion and one for all others
|
||||
- OSX removed 64bit code
|
||||
- Scheduler: action can now run on multiple weekdays
|
||||
- Scheduler: add "remove failed jobs" action
|
||||
- Special option: rss_odd_titles (see Wiki)
|
||||
- Support for HTTPS chain files (needed when you buy your own certificate)
|
||||
- Prevent jobs from showing up in queue and history simultaneously
|
||||
- Add parameter 'pp_active' to history elements in qstatus API call
|
||||
- Fix some minor par2 handling bugs
|
||||
- Prevent potential crash when an actively downloading job is deleted from the queue
|
||||
- Special option: 'overwrite_files' (See Wiki)
|
||||
- Don't try an SFV check when a retried job was already successfully verified by par2
|
||||
- Enable compression of API call results
|
||||
- Log failed attempts to log in to the Web UI
|
||||
- A job with "forced" priority should keep that when fetching more par2 files
|
||||
- Updated translations
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.3Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Rename Special "random_server_ip" to "randomize_server_ip" so that we
|
||||
can force the default to "Off". "On" kills speed on some servers.
|
||||
- Ignore pseudo NZB files that start with a period in the name
|
||||
- SFV failure now listed in History instead of issuing warnings
|
||||
- Translation updates
|
||||
- "502" errors about payments/credits will now block a server
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.3Beta2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Try to keep OSX Mountain Lion awake as long as downloading/postprocessing runs
|
||||
- Prevent queue deadlock in case of fatally damaged par2 files
|
||||
- Add RSS filter-enable checkboxes to Plush, Smpl and Classic skins
|
||||
- Fix problem with saving modified paramters of an already enabled server
|
||||
- Extend "check new release" option with test releases
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.3Beta1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Correct several errors in Sort function
|
||||
- Improve organization of Config->Servers
|
||||
- Support for nzbxxx.com
|
||||
- Make detection of samples less aggressive
|
||||
- Some minor corrections
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.2Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fix for NZB-icon issue when 0.7.0 was previously installed
|
||||
- Check validity of totals9.sab file
|
||||
- Fix startup problem when localhost has unexpected order of IP addresses
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.2RC2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Improve support for nzbsrus.com
|
||||
- Don't try to show NZB age when not known yet
|
||||
- Prevent systems with unresolvable hostnames from always using 0.0.0.0
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.2RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fix fatal error in nzbsrus.com support
|
||||
- Initial "quota left" was not set correctly when enabling quota
|
||||
- Report incorrect RSS filter expressions (instead of aborting analysis)
|
||||
- Improve detection of invalid articles (so that backup server will be tried)
|
||||
- Windows installer: improve NZB association so that a reboot isn't needed
|
||||
- Windows installer: don't remove settimngs by default when uninstalling
|
||||
- Fix sorting of rar files in job so that .rar preceeds .r00
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.1Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Disable VC90 check in Windows Installer as long as we're still on Python 2.5
|
||||
- Windows: make sure \\server\share notation is never seen as a relative path
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.1RC5 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fix signing of OSX DMG
|
||||
- Fix endless par2-fetch loop after retrying failed job
|
||||
- Don't send "bad fetch" email when emailing is off
|
||||
- Add some support for nzbrus.com's non-VIP limiting
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.1RC4 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fix failure to grab NZBs from indexers that send compressed files.
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.1RC3 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fixed stalling par2 fetches (after first verification run)
|
||||
- Fixed retry behaviour of NZB fetching from URL
|
||||
and add handling of nzbsrus.com error codes
|
||||
- Make sure that all malformed articles are retried on another server
|
||||
- Add no_ipv6 option that suppresses listing on ::1
|
||||
(to be used if your system cannot handle that)
|
||||
- Prevent crash in QuickCheck when expected par2 file wasn't downloaded
|
||||
- Verification/repair would not be executed properly when one more RAR files
|
||||
missed their first article.
|
||||
- API calls "addurl" and "addid" (newzbin) can be used interchangeably
|
||||
(Fixes a problem in Qouch)
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.1RC2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
SABnzbd 0.7.1
|
||||
SABnzbd 0.7.7
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
@@ -60,7 +60,8 @@ Unix/Linux/OSX
|
||||
OSX Leopard/SnowLeopard
|
||||
Python 2.6 http://www.activestate.com
|
||||
|
||||
OSX Lion Apple Python 2.7 (included in OSX)
|
||||
OSX Lion/MountainLion
|
||||
Apple Python 2.7 Included in OSX (default)
|
||||
|
||||
Windows
|
||||
Python-2.7.latest http://www.activestate.com
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
!include "MUI2.nsh"
|
||||
!include "registerExtension.nsh"
|
||||
!include "FileFunc.nsh"
|
||||
!include "LogicLib.nsh"
|
||||
!include "WinVer.nsh"
|
||||
!include "WinSxSQuery.nsh"
|
||||
@@ -245,6 +246,7 @@ Function .onInit
|
||||
;--------------------------------
|
||||
;make sure that the requires MS Runtimes are installed
|
||||
;
|
||||
goto nodownload ; Not needed while still using Python25
|
||||
runtime_loop:
|
||||
push 'msvcr90.dll'
|
||||
push 'Microsoft.VC90.CRT,version="9.0.21022.8",type="win32",processorArchitecture="x86",publicKeyToken="1fc8b3b9a1e18e3b"'
|
||||
@@ -297,7 +299,7 @@ SetOutPath "$INSTDIR"
|
||||
;------------------------------------------------------------------
|
||||
; Make sure old versions are gone
|
||||
IfFileExists $INSTDIR\sabnzbd.exe 0 endWarnExist
|
||||
IfFileExists $INSTDIR\python25.dll 0 endWarnExist
|
||||
IfFileExists $INSTDIR\python27.dll 0 endWarnExist
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(MsgRemoveOld)$\n$\n$(MsgRemoveOld2)" IDOK uninst
|
||||
Abort
|
||||
uninst:
|
||||
@@ -318,7 +320,7 @@ WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninst
|
||||
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "URLUpdateInfo" 'http://sabnzbd.org/'
|
||||
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "Comments" 'The automated Usenet download tool'
|
||||
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "DisplayIcon" '$INSTDIR\interfaces\Classic\templates\static\images\favicon.ico'
|
||||
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 18400
|
||||
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 25674
|
||||
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoRepair" -1
|
||||
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoModify" -1
|
||||
; write out uninstaller
|
||||
@@ -350,7 +352,7 @@ SectionEnd ; end of desktop icon section
|
||||
|
||||
Section /o $(MsgAssoc) assoc
|
||||
${registerExtension} "$INSTDIR\icons\nzb.ico" "$INSTDIR\SABnzbd.exe" ".nzb" "NZB File"
|
||||
;${registerExtension} "$INSTDIR\SABnzbd.exe" ".nzb" "NZB File"
|
||||
${RefreshShellIcons}
|
||||
SectionEnd ; end of file association section
|
||||
|
||||
; begin uninstall settings/section
|
||||
@@ -406,11 +408,11 @@ Section "un.$(MsgDelProgram)" Uninstall
|
||||
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
|
||||
|
||||
${unregisterExtension} ".nzb" "NZB File"
|
||||
|
||||
${RefreshShellIcons}
|
||||
|
||||
SectionEnd ; end of uninstall section
|
||||
|
||||
Section "un.$(MsgDelSettings)" DelSettings
|
||||
Section /o "un.$(MsgDelSettings)" DelSettings
|
||||
DetailPrint "Uninstall settings $LOCALAPPDATA"
|
||||
Delete "$LOCALAPPDATA\sabnzbd\sabnzbd.ini"
|
||||
RMDir /r "$LOCALAPPDATA\sabnzbd"
|
||||
|
||||
6
PKG-INFO
6
PKG-INFO
@@ -1,8 +1,8 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 0.7.1RC2
|
||||
Summary: SABnzbd-0.7.1RC2
|
||||
Home-page: http://sourceforge.net/projects/sabnzbdplus
|
||||
Version: 0.7.7
|
||||
Summary: SABnzbd-0.7.7
|
||||
Home-page: http://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
License: GNU General Public License 2 (GPL2 or later)
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
Release Notes - SABnzbd 0.7.1RC2
|
||||
==================================
|
||||
Release Notes - SABnzbd 0.7.7
|
||||
===============================
|
||||
|
||||
## Fixes in 0.7.1
|
||||
### RC2
|
||||
- Improved backup of sabnzbd.ini file, now uses backup when original is gone or corrupt
|
||||
- Swedish translation extended
|
||||
### RC1
|
||||
- Plush skin: fix problems with pull-down menus in Mobile Safari
|
||||
- On some Linux and OSX systems using localhost would still make SABnzbd
|
||||
give access to other computers
|
||||
- Windows: the installer did not set an icon when associating NZB files with SABnzbd
|
||||
- Fix problem that the Opera browser had with Config->Servers
|
||||
- Retry a few times when accessing a mounted drive to create the
|
||||
final destination folder
|
||||
- Minor fixes in Window Tray icon and OSX top menu
|
||||
## Features
|
||||
- Updated unrar to 4.20 (OSX/Windows)
|
||||
- Optional web-ui watchdog (diagnostic tool)
|
||||
- Removed account support for defunct indexers
|
||||
|
||||
## Bug fixes
|
||||
- Fix some issues with orphaned items
|
||||
- Generic sort didn't always rename media files in multi-part jobs properly
|
||||
- Always show RSS items in the same order as the original RSS feed
|
||||
|
||||
## What's new in 0.7.0
|
||||
|
||||
@@ -43,4 +39,30 @@ Release Notes - SABnzbd 0.7.1RC2
|
||||
built-in post-processing options that automatically verify, repair,
|
||||
extract and clean up posts downloaded from Usenet.
|
||||
|
||||
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||
(c) Copyright 2007-2012 by "The SABnzbd-team" \<team@sabnzbd.org\>
|
||||
|
||||
|
||||
### IMPORTANT INFORMATION about release 0.7.x
|
||||
<http://wiki.sabnzbd.org/introducing-0-7-0>
|
||||
|
||||
### Known problems and solutions
|
||||
- Read the file "ISSUES.txt"
|
||||
|
||||
### Upgrading from 0.6.x
|
||||
- Stop SABnzbd
|
||||
- Install new version
|
||||
- Start SABnzbd
|
||||
|
||||
### Upgrading from 0.5.x
|
||||
- Stop SABnzbd
|
||||
- Install new version
|
||||
- Start SABnzbd.
|
||||
|
||||
The organization of the download queue is different from 0.5.x.
|
||||
0.7.x will finish downloading an existing queue, but you
|
||||
cannot go back to an older version without losing your queue.
|
||||
Also, your sabnzbd.ini file will be upgraded, making it
|
||||
incompatible with release 0.5.x
|
||||
|
||||
### Upgrading from 0.4.x
|
||||
Download your current queue before upgrading.
|
||||
111
README.rtf
111
README.rtf
@@ -1,111 +0,0 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\paperw11900\paperh16840\vieww16360\viewh15680\viewkind0
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
|
||||
|
||||
\f0\b\fs48 \cf0 SABnzbd 0.7.1RC2\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
|
||||
|
||||
\b0\fs26 \cf0 \
|
||||
|
||||
\b Fixes in 0.7.1
|
||||
\b0 \
|
||||
RC2\
|
||||
- Improved backup of sabnzbd.ini file, now uses backup when original is gone or corrupt\
|
||||
- Swedish translation is extended\
|
||||
RC1\
|
||||
- Plush skin: fix problems with pull-down menus in Mobile Safari\
|
||||
- On some Linux and OSX systems using localhost would still make SABnzbd give access to other computers\
|
||||
- Windows: the installer did not set an icon when associating NZB files with SABnzbd\
|
||||
- Fix problem that the Opera browser had with Config->Servers\
|
||||
- Retry a few times when accessing a mounted drive to create the final destination folder\
|
||||
- Minor fixes in Window Tray icon and OSX top menu\
|
||||
\
|
||||
|
||||
\b What's new
|
||||
\b0 \
|
||||
- Download quota management\
|
||||
- Windows: simple system tray menu\
|
||||
- Multi-platform Growl support\
|
||||
- NotifyOSD support for Linux distros that have it\
|
||||
- Option to set maximum number of retries for servers (prevents deadlock)\
|
||||
- Pre-download check to estimate completeness (reliability is limited)\
|
||||
- Prevent partial downloading of par2 files that are not needed yet\
|
||||
- Config->Special for settings previously only available in the sabnzbd.ini file\
|
||||
- For Usenet servers with multiple IP addresses, pick a random one per connection\
|
||||
- Add pseudo-priority "Stop" that will send the job immediately to the post-processing queue\
|
||||
- Allow jobs still waiting for post-processing to be deleted too\
|
||||
- More persistent retries for unreliable indexers\
|
||||
- Single Configuration skin for all others skins (there is an option for the old style)\
|
||||
- Config->Special for settings that were previously only changeable in the sabnzbd.ini file\
|
||||
- Add Spanish, Portuguese (Brazil) and Polish translations\
|
||||
- Individual RSS filter toggle\
|
||||
- Unified OSX DMG\
|
||||
\
|
||||
|
||||
\b About
|
||||
\b0 \
|
||||
SABnzbd is an open-source cross-platform binary newsreader.\
|
||||
It simplifies the process of downloading from Usenet dramatically,\
|
||||
thanks to its web-based user interface and advanced\
|
||||
built-in post-processing options that automatically verify, repair,\
|
||||
extract and clean up posts downloaded from Usenet.\
|
||||
SABnzbd also has a fully customizable user interface,\
|
||||
and offers a complete API for third-party applications to hook into.\
|
||||
\
|
||||
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>\
|
||||
\
|
||||
There is an extensive Wiki on the use of SABnzbd.\
|
||||
{\field{\*\fldinst{HYPERLINK "http://wiki.sabnzbd.org/"}}{\fldrslt http://wiki.sabnzbd.org/}}\
|
||||
\
|
||||
|
||||
\b IMPORTANT INFORMATION
|
||||
\b0 about release 0.7.1:\
|
||||
{\field{\*\fldinst{HYPERLINK "http://wiki.sabnzbd.org/introducing-0-7-0"}}{\fldrslt http://wiki.sabnzbd.org/introducing-0-7-0}}\
|
||||
\
|
||||
|
||||
\b Known problems and solutions\
|
||||
|
||||
\b0 Read the file "ISSUES.txt"
|
||||
\b \
|
||||
|
||||
\b0 \
|
||||
\
|
||||
|
||||
\b\fs40 Upgrading from 0.6.x
|
||||
\b0\fs26 \
|
||||
Stop SABnzbd.\
|
||||
Install new version\
|
||||
Start SABnzbd.\
|
||||
\
|
||||
|
||||
\b\fs40 Upgrading from 0.5.x
|
||||
\b0\fs26 \
|
||||
Stop SABnzbd.\
|
||||
Uninstall current version, keeping the data.\
|
||||
Install new version\
|
||||
Start SABnzbd.\
|
||||
\
|
||||
The organization of the download queue is different from 0.5.x.\
|
||||
0.6.x will finish downloading an existing queue, but you\
|
||||
cannot go back to an older version without losing your queue.\
|
||||
Also, your sabnzbd.ini file will be upgraded, making it\
|
||||
incompatible with release 0.5.x\
|
||||
\
|
||||
|
||||
\b\fs40 \
|
||||
Upgrading from 0.4.x
|
||||
\b0\fs26 \
|
||||
\
|
||||
|
||||
\b PLEASE DOWNLOAD YOUR CURRENT QUEUE BEFORE UPGRADING
|
||||
\b0 \
|
||||
\
|
||||
When upgrading from a 0.4.x release such as 0.4.12 your old settings will be kept.\
|
||||
You will however be given a fresh queue and history. If you have items in your queue\
|
||||
from the older version of SABnzbd, you can either re-import the nzb files if you kept\
|
||||
an nzb backup folder, or temporarily go back to 0.4.x until your queue is complete.\
|
||||
The history is now stored in a better format meaning future upgrades should be backwards\
|
||||
compatible.\
|
||||
}
|
||||
124
SABnzbd.py
124
SABnzbd.py
@@ -16,8 +16,8 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
if sys.version_info < (2,5):
|
||||
print "Sorry, requires Python 2.5 or higher."
|
||||
if sys.version_info < (2, 5):
|
||||
print "Sorry, requires Python 2.5, 2.6 or 2.7."
|
||||
sys.exit(1)
|
||||
|
||||
import logging
|
||||
@@ -28,6 +28,7 @@ import signal
|
||||
import socket
|
||||
import platform
|
||||
import time
|
||||
import re
|
||||
|
||||
try:
|
||||
import Cheetah
|
||||
@@ -253,6 +254,7 @@ def print_help():
|
||||
print " --log-all Log all article handling (for developers)"
|
||||
print " --console Force console logging for OSX app"
|
||||
print " --new Run a new instance of SABnzbd"
|
||||
print " --no_ipv6 Do not listen on IPv6 address [::1]"
|
||||
|
||||
def print_version():
|
||||
print """
|
||||
@@ -524,30 +526,24 @@ def all_localhosts():
|
||||
return ips
|
||||
|
||||
|
||||
def ipv_localhost(v):
|
||||
""" Return True if localhost resolves to some IPV4 ('4') or IPV6 ('6') address
|
||||
def check_resolve(host):
|
||||
""" Return True if 'host' resolves
|
||||
"""
|
||||
try:
|
||||
info = socket.getaddrinfo('localhost', None)
|
||||
info = socket.getaddrinfo(host, None)
|
||||
except:
|
||||
# localhost does not resolve
|
||||
# Does not resolve
|
||||
return False
|
||||
for item in info:
|
||||
item = item[4][0]
|
||||
if v == '4' and ':' not in item:
|
||||
return True
|
||||
elif v == '6' and ':' in item:
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def get_webhost(cherryhost, cherryport, https_port):
|
||||
""" Determine the webhost address and port,
|
||||
return (host, port, browserhost)
|
||||
"""
|
||||
if cherryhost == '0.0.0.0' and not ipv_localhost('4'):
|
||||
if cherryhost == '0.0.0.0' and not check_resolve('127.0.0.1'):
|
||||
cherryhost = ''
|
||||
elif cherryhost == '::' and not ipv_localhost('6'):
|
||||
elif cherryhost == '::' and not check_resolve('::1'):
|
||||
cherryhost = ''
|
||||
|
||||
if cherryhost is None:
|
||||
@@ -562,14 +558,18 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
try:
|
||||
info = socket.getaddrinfo(socket.gethostname(), None)
|
||||
except:
|
||||
# Hostname does not resolve, use 0.0.0.0
|
||||
if cherryhost not in ('localhost', '127.0.0.1', '::1'):
|
||||
cherryhost = '0.0.0.0'
|
||||
# Hostname does not resolve
|
||||
try:
|
||||
info = socket.getaddrinfo(localhost, None)
|
||||
# Valid user defined name?
|
||||
info = socket.getaddrinfo(cherryhost, None)
|
||||
except:
|
||||
info = socket.getaddrinfo('127.0.0.1', None)
|
||||
localhost = '127.0.0.1'
|
||||
if cherryhost not in ('localhost', '127.0.0.1', '::1'):
|
||||
cherryhost = '0.0.0.0'
|
||||
try:
|
||||
info = socket.getaddrinfo(localhost, None)
|
||||
except:
|
||||
info = socket.getaddrinfo('127.0.0.1', None)
|
||||
localhost = '127.0.0.1'
|
||||
for item in info:
|
||||
ip = str(item[4][0])
|
||||
if ip.startswith('169.254.'):
|
||||
@@ -672,28 +672,27 @@ def get_webhost(cherryhost, cherryport, https_port):
|
||||
return cherryhost, cherryport, browserhost, https_port
|
||||
|
||||
|
||||
def attach_server(host, port, cert=None, key=None):
|
||||
def attach_server(host, port, cert=None, key=None, chain=None):
|
||||
""" Define and attach server, optionally HTTPS
|
||||
"""
|
||||
http_server = _cpwsgi_server.CPWSGIServer()
|
||||
http_server.bind_addr = (host, port)
|
||||
if cert and key:
|
||||
http_server.ssl_certificate = cert
|
||||
http_server.ssl_private_key = key
|
||||
adapter = _cpserver.ServerAdapter(cherrypy.engine, http_server, http_server.bind_addr)
|
||||
adapter.subscribe()
|
||||
if not (sabnzbd.cfg.no_ipv6() and '::1' in host):
|
||||
http_server = _cpwsgi_server.CPWSGIServer()
|
||||
http_server.bind_addr = (host, port)
|
||||
if cert and key:
|
||||
http_server.ssl_certificate = cert
|
||||
http_server.ssl_private_key = key
|
||||
http_server.ssl_certificate_chain = chain
|
||||
adapter = _cpserver.ServerAdapter(cherrypy.engine, http_server, http_server.bind_addr)
|
||||
adapter.subscribe()
|
||||
|
||||
|
||||
def is_sabnzbd_running(url):
|
||||
def is_sabnzbd_running(url, timeout=None):
|
||||
""" Return True when there's already a SABnzbd instance running.
|
||||
"""
|
||||
try:
|
||||
url = '%s&mode=version' % (url)
|
||||
ver = sabnzbd.newsunpack.get_from_url(url)
|
||||
if ver and ver.strip(' \n\r\t') == sabnzbd.__version__:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
ver = sabnzbd.newsunpack.get_from_url(url, timeout=timeout)
|
||||
return bool(ver and re.search(r'\d+\.\d+\.', ver))
|
||||
except:
|
||||
return False
|
||||
|
||||
@@ -713,7 +712,7 @@ def find_free_port(host, currentport):
|
||||
|
||||
|
||||
def check_for_sabnzbd(url, upload_nzbs, allow_browser=True):
|
||||
""" Check for a running instance of sabnzbd(same version) on this port
|
||||
""" Check for a running instance of sabnzbd on this port
|
||||
allow_browser==True|None will launch the browser, False will not.
|
||||
"""
|
||||
if allow_browser is None:
|
||||
@@ -841,7 +840,7 @@ def commandline_handler(frozen=True):
|
||||
try:
|
||||
opts, args = getopt.getopt(info, "phdvncw:l:s:f:t:b:2:",
|
||||
['pause', 'help', 'daemon', 'nobrowser', 'clean', 'logging=',
|
||||
'weblogging=', 'server=', 'templates',
|
||||
'weblogging=', 'server=', 'templates', 'no_ipv6',
|
||||
'template2', 'browser=', 'config-file=', 'force',
|
||||
'version', 'https=', 'autorestarted', 'repair', 'repair-all',
|
||||
'log-all', 'no-login', 'pid=', 'new', 'sessions', 'console',
|
||||
@@ -919,6 +918,7 @@ def main():
|
||||
new_instance = False
|
||||
force_sessions = False
|
||||
osx_console = False
|
||||
no_ipv6 = False
|
||||
|
||||
service, sab_opts, serv_opts, upload_nzbs = commandline_handler()
|
||||
|
||||
@@ -1005,6 +1005,8 @@ def main():
|
||||
elif opt in ('--console',):
|
||||
re_argv.append(opt)
|
||||
osx_console = True
|
||||
elif opt in ('--no_ipv6',):
|
||||
no_ipv6 = True
|
||||
|
||||
sabnzbd.MY_FULLNAME = os.path.normpath(os.path.abspath(sabnzbd.MY_FULLNAME))
|
||||
sabnzbd.MY_NAME = os.path.basename(sabnzbd.MY_FULLNAME)
|
||||
@@ -1087,6 +1089,9 @@ def main():
|
||||
# Set root folders for HTTPS server file paths
|
||||
sabnzbd.cfg.set_root_folders2()
|
||||
|
||||
if no_ipv6:
|
||||
sabnzbd.cfg.no_ipv6.set(True)
|
||||
|
||||
# Determine web host address
|
||||
cherryhost, cherryport, browserhost, https_port = get_webhost(cherryhost, cherryport, https_port)
|
||||
enable_https = sabnzbd.cfg.enable_https()
|
||||
@@ -1205,8 +1210,6 @@ def main():
|
||||
sabnzbd.cfg.log_backups())
|
||||
|
||||
format = '%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s'
|
||||
if sabnzbd.WIN32:
|
||||
format += '\r'
|
||||
rollover_log.setFormatter(logging.Formatter(format))
|
||||
rollover_log.addFilter(FilterCP3())
|
||||
sabnzbd.LOGHANDLER = rollover_log
|
||||
@@ -1346,6 +1349,10 @@ def main():
|
||||
|
||||
https_cert = sabnzbd.cfg.https_cert.get_path()
|
||||
https_key = sabnzbd.cfg.https_key.get_path()
|
||||
https_chain = sabnzbd.cfg.https_chain.get_path()
|
||||
if not (sabnzbd.cfg.https_chain() and os.path.exists(https_chain)):
|
||||
https_chain = None
|
||||
|
||||
if enable_https:
|
||||
# If either the HTTPS certificate or key do not exist, make some self-signed ones.
|
||||
if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)):
|
||||
@@ -1364,8 +1371,8 @@ def main():
|
||||
hosts[1] = '::1'
|
||||
|
||||
# The Windows binary requires numeric localhost as primary address
|
||||
if multilocal and cherryhost == 'localhost' and hosts[1] == '127.0.0.1':
|
||||
cherryhost = '::1'
|
||||
if multilocal and cherryhost == 'localhost':
|
||||
cherryhost = hosts[0]
|
||||
|
||||
if enable_https:
|
||||
if https_port:
|
||||
@@ -1375,14 +1382,15 @@ def main():
|
||||
# Extra HTTP port for secondary localhost
|
||||
attach_server(hosts[1], cherryport)
|
||||
# Extra HTTPS port for secondary localhost
|
||||
attach_server(hosts[1], https_port, https_cert, https_key)
|
||||
attach_server(hosts[1], https_port, https_cert, https_key, https_chain)
|
||||
cherryport = https_port
|
||||
elif multilocal:
|
||||
# Extra HTTPS port for secondary localhost
|
||||
attach_server(hosts[1], cherryport, https_cert, https_key)
|
||||
|
||||
cherrypy.config.update({'server.ssl_certificate' : https_cert,
|
||||
'server.ssl_private_key' : https_key })
|
||||
'server.ssl_private_key' : https_key,
|
||||
'server.ssl_certificate_chain' : https_chain})
|
||||
elif multilocal:
|
||||
# Extra HTTP port for secondary localhost
|
||||
attach_server(hosts[1], cherryport)
|
||||
@@ -1400,6 +1408,17 @@ def main():
|
||||
else:
|
||||
sessions = None
|
||||
|
||||
mime_gzip = ('text/html',
|
||||
'text/plain',
|
||||
'text/css',
|
||||
'text/xml',
|
||||
'text/javascript',
|
||||
'application/javascript',
|
||||
'text/x-javascript',
|
||||
'application/x-javascript',
|
||||
'text/x-json',
|
||||
'application/json'
|
||||
)
|
||||
cherrypy.config.update({'server.environment': 'production',
|
||||
'server.socket_host': cherryhost,
|
||||
'server.socket_port': cherryport,
|
||||
@@ -1409,7 +1428,7 @@ def main():
|
||||
'engine.reexec_retry' : 100,
|
||||
'tools.encode.on' : True,
|
||||
'tools.gzip.on' : True,
|
||||
'tools.gzip.mime_types' : ['text/html', 'text/plain', 'text/javascript', 'text/css', 'application/x-javascript'],
|
||||
'tools.gzip.mime_types' : mime_gzip,
|
||||
'tools.sessions.on' : bool(sessions),
|
||||
'tools.sessions.storage_type' : 'file',
|
||||
'tools.sessions.storage_path' : sessions,
|
||||
@@ -1500,8 +1519,8 @@ def main():
|
||||
if sabnzbd.FOUNDATION:
|
||||
import sabnzbd.osxmenu
|
||||
sabnzbd.osxmenu.notify("SAB_Launched", None)
|
||||
growler.send_notification('SABnzbd %s' % (sabnzbd.__version__),
|
||||
"http://%s:%s/sabnzbd" % (browserhost, cherryport), 'startup')
|
||||
growler.send_notification('SABnzbd%s' % growler.hostname(),
|
||||
T('SABnzbd %s started') % sabnzbd.__version__, 'startup')
|
||||
# Now's the time to check for a new version
|
||||
check_latest_version()
|
||||
autorestarted = False
|
||||
@@ -1544,7 +1563,7 @@ def main():
|
||||
add_local(f)
|
||||
|
||||
# Have to keep this running, otherwise logging will terminate
|
||||
timer = 0
|
||||
timer = timer5 = 0
|
||||
while not sabnzbd.SABSTOP:
|
||||
if sabnzbd.WIN_SERVICE:
|
||||
rc = win32event.WaitForMultipleObjects((sabnzbd.WIN_SERVICE.hWaitStop,
|
||||
@@ -1570,7 +1589,7 @@ def main():
|
||||
### 30 sec polling tasks
|
||||
if timer > 9:
|
||||
timer = 0
|
||||
# Keep Windows awake (if needed)
|
||||
# Keep OS awake (if needed)
|
||||
sabnzbd.keep_awake()
|
||||
# Restart scheduler (if needed)
|
||||
scheduler.restart()
|
||||
@@ -1584,6 +1603,15 @@ def main():
|
||||
if sabnzbd.WIN_SERVICE and mail:
|
||||
mail.send('active')
|
||||
|
||||
if timer5 > 9:
|
||||
### 5 minute polling tasks
|
||||
timer5 = 0
|
||||
if sabnzbd.cfg.web_watchdog() and not is_sabnzbd_running('%s/api?tickleme=1' % sabnzbd.BROWSER_URL, 120):
|
||||
autorestarted = True
|
||||
cherrypy.engine.execv = True
|
||||
else:
|
||||
timer5 += 1
|
||||
|
||||
else:
|
||||
timer += 1
|
||||
|
||||
|
||||
@@ -658,7 +658,10 @@ class Request(object):
|
||||
# Handle cookies differently because on Konqueror, multiple
|
||||
# cookies come on different lines with the same key
|
||||
if name == 'Cookie':
|
||||
self.cookie.load(value)
|
||||
try:
|
||||
self.cookie.load(value)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not dict.__contains__(headers, 'Host'):
|
||||
# All Internet-based HTTP/1.1 servers MUST respond with a 400
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import cherrypy
|
||||
from cherrypy.lib import httpauth
|
||||
|
||||
@@ -9,10 +10,10 @@ def check_auth(users, encrypt=None, realm=None):
|
||||
ah = httpauth.parseAuthorization(cherrypy.request.headers['authorization'])
|
||||
if ah is None:
|
||||
raise cherrypy.HTTPError(400, 'Bad Request')
|
||||
|
||||
|
||||
if not encrypt:
|
||||
encrypt = httpauth.DIGEST_AUTH_ENCODERS[httpauth.MD5]
|
||||
|
||||
|
||||
if callable(users):
|
||||
try:
|
||||
# backward compatibility
|
||||
@@ -20,7 +21,7 @@ def check_auth(users, encrypt=None, realm=None):
|
||||
|
||||
if not isinstance(users, dict):
|
||||
raise ValueError, "Authentication users must be a dictionary"
|
||||
|
||||
|
||||
# fetch the user password
|
||||
password = users.get(ah["username"], None)
|
||||
except TypeError:
|
||||
@@ -29,23 +30,26 @@ def check_auth(users, encrypt=None, realm=None):
|
||||
else:
|
||||
if not isinstance(users, dict):
|
||||
raise ValueError, "Authentication users must be a dictionary"
|
||||
|
||||
|
||||
# fetch the user password
|
||||
password = users.get(ah["username"], None)
|
||||
|
||||
|
||||
# validate the authorization by re-computing it here
|
||||
# and compare it with what the user-agent provided
|
||||
if httpauth.checkResponse(ah, password, method=cherrypy.request.method,
|
||||
encrypt=encrypt, realm=realm):
|
||||
cherrypy.request.login = ah["username"]
|
||||
return True
|
||||
|
||||
|
||||
if ah.get('username') or ah.get('password'):
|
||||
logging.info('Attempt to login with wrong credentials from %s',
|
||||
cherrypy.request.headers['Remote-Addr'])
|
||||
cherrypy.request.login = False
|
||||
return False
|
||||
|
||||
def basic_auth(realm, users, encrypt=None):
|
||||
"""If auth fails, raise 401 with a basic authentication header.
|
||||
|
||||
|
||||
realm: a string containing the authentication realm.
|
||||
users: a dict of the form: {username: password} or a callable returning a dict.
|
||||
encrypt: callable used to encrypt the password returned from the user-agent.
|
||||
@@ -53,23 +57,23 @@ def basic_auth(realm, users, encrypt=None):
|
||||
"""
|
||||
if check_auth(users, encrypt):
|
||||
return
|
||||
|
||||
|
||||
# inform the user-agent this path is protected
|
||||
cherrypy.response.headers['www-authenticate'] = httpauth.basicAuth(realm)
|
||||
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
def digest_auth(realm, users):
|
||||
"""If auth fails, raise 401 with a digest authentication header.
|
||||
|
||||
|
||||
realm: a string containing the authentication realm.
|
||||
users: a dict of the form: {username: password} or a callable returning a dict.
|
||||
"""
|
||||
if check_auth(users, realm=realm):
|
||||
return
|
||||
|
||||
|
||||
# inform the user-agent this path is protected
|
||||
cherrypy.response.headers['www-authenticate'] = httpauth.digestAuth(realm)
|
||||
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
|
||||
raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
|
||||
|
||||
|
||||
594
gntp/__init__.py
594
gntp/__init__.py
@@ -1,332 +1,402 @@
|
||||
import re
|
||||
import hashlib
|
||||
import time
|
||||
import platform
|
||||
import StringIO
|
||||
|
||||
__version__ = '0.8'
|
||||
|
||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||
GNTP_INFO_LINE = re.compile(
|
||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
|
||||
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
|
||||
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
GNTP_INFO_LINE_SHORT = re.compile(
|
||||
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
||||
|
||||
GNTP_EOL = '\r\n'
|
||||
|
||||
__version__ = '0.4'
|
||||
|
||||
class BaseError(Exception):
|
||||
pass
|
||||
def gntp_error(self):
|
||||
error = GNTPError(self.errorcode, self.errordesc)
|
||||
return error.encode()
|
||||
|
||||
|
||||
class ParseError(BaseError):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(errorcode=500,errordesc='Error parsing the message')
|
||||
return error.encode()
|
||||
errorcode = 500
|
||||
errordesc = 'Error parsing the message'
|
||||
|
||||
|
||||
class AuthError(BaseError):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(errorcode=400,errordesc='Error with authorization')
|
||||
return error.encode()
|
||||
errorcode = 400
|
||||
errordesc = 'Error with authorization'
|
||||
|
||||
|
||||
class UnsupportedError(BaseError):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(errorcode=500,errordesc='Currently unsupported by gntp.py')
|
||||
return error.encode()
|
||||
errorcode = 500
|
||||
errordesc = 'Currently unsupported by gntp.py'
|
||||
|
||||
|
||||
class _GNTPBuffer(StringIO.StringIO):
|
||||
"""GNTP Buffer class"""
|
||||
def writefmt(self, message="", *args):
|
||||
"""Shortcut function for writing GNTP Headers"""
|
||||
self.write((message % args).encode('utf8', 'replace'))
|
||||
self.write(GNTP_EOL)
|
||||
|
||||
|
||||
class _GNTPBase(object):
|
||||
info = {
|
||||
'version':'1.0',
|
||||
'messagetype':None,
|
||||
'encryptionAlgorithmID':None
|
||||
}
|
||||
_requiredHeaders = []
|
||||
headers = {}
|
||||
resources = {}
|
||||
def add_origin_info(self):
|
||||
self.add_header('Origin-Machine-Name',platform.node())
|
||||
self.add_header('Origin-Software-Name','gntp.py')
|
||||
self.add_header('Origin-Software-Version',__version__)
|
||||
self.add_header('Origin-Platform-Name',platform.system())
|
||||
self.add_header('Origin-Platform-Version',platform.platform())
|
||||
"""Base initilization
|
||||
|
||||
:param string messagetype: GNTP Message type
|
||||
:param string version: GNTP Protocol version
|
||||
:param string encription: Encryption protocol
|
||||
"""
|
||||
def __init__(self, messagetype=None, version='1.0', encryption=None):
|
||||
self.info = {
|
||||
'version': version,
|
||||
'messagetype': messagetype,
|
||||
'encryptionAlgorithmID': encryption
|
||||
}
|
||||
self.headers = {}
|
||||
self.resources = {}
|
||||
|
||||
def __str__(self):
|
||||
return self.encode()
|
||||
def _parse_info(self,data):
|
||||
'''
|
||||
Parse the first line of a GNTP message to get security and other info values
|
||||
@param data: GNTP Message
|
||||
@return: GNTP Message information in a dictionary
|
||||
'''
|
||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||
match = re.match('GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)'+
|
||||
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?'+
|
||||
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n', data,re.IGNORECASE)
|
||||
|
||||
|
||||
def _parse_info(self, data):
|
||||
"""Parse the first line of a GNTP message to get security and other info values
|
||||
|
||||
:param string data: GNTP Message
|
||||
:return dict: Parsed GNTP Info line
|
||||
"""
|
||||
|
||||
match = GNTP_INFO_LINE.match(data)
|
||||
|
||||
if not match:
|
||||
raise ParseError('ERROR_PARSING_INFO_LINE')
|
||||
|
||||
|
||||
info = match.groupdict()
|
||||
if info['encryptionAlgorithmID'] == 'NONE':
|
||||
info['encryptionAlgorithmID'] = None
|
||||
|
||||
|
||||
return info
|
||||
def set_password(self,password,encryptAlgo='MD5'):
|
||||
'''
|
||||
Set a password for a GNTP Message
|
||||
@param password: Null to clear password
|
||||
@param encryptAlgo: Supports MD5,SHA1,SHA256,SHA512
|
||||
@todo: Support other hash functions
|
||||
'''
|
||||
|
||||
def set_password(self, password, encryptAlgo='MD5'):
|
||||
"""Set a password for a GNTP Message
|
||||
|
||||
:param string password: Null to clear password
|
||||
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
|
||||
"""
|
||||
hash = {
|
||||
'MD5': hashlib.md5,
|
||||
'SHA1': hashlib.sha1,
|
||||
'SHA256': hashlib.sha256,
|
||||
'SHA512': hashlib.sha512,
|
||||
}
|
||||
|
||||
|
||||
self.password = password
|
||||
self.encryptAlgo = encryptAlgo.upper()
|
||||
if not password:
|
||||
self.info['encryptionAlgorithmID'] = None
|
||||
self.info['keyHashAlgorithm'] = None;
|
||||
self.info['keyHashAlgorithm'] = None
|
||||
return
|
||||
if not self.encryptAlgo in hash.keys():
|
||||
raise UnsupportedError('INVALID HASH "%s"'%self.encryptAlgo)
|
||||
|
||||
raise UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
|
||||
|
||||
hashfunction = hash.get(self.encryptAlgo)
|
||||
|
||||
|
||||
password = password.encode('utf8')
|
||||
seed = time.ctime()
|
||||
salt = hashfunction(seed).hexdigest()
|
||||
saltHash = hashfunction(seed).digest()
|
||||
keyBasis = password+saltHash
|
||||
keyBasis = password + saltHash
|
||||
key = hashfunction(keyBasis).digest()
|
||||
keyHash = hashfunction(key).hexdigest()
|
||||
|
||||
|
||||
self.info['keyHashAlgorithmID'] = self.encryptAlgo
|
||||
self.info['keyHash'] = keyHash.upper()
|
||||
self.info['salt'] = salt.upper()
|
||||
def _decode_hex(self,value):
|
||||
'''
|
||||
Helper function to decode hex string to `proper` hex string
|
||||
@param value: Value to decode
|
||||
@return: Hex string
|
||||
'''
|
||||
|
||||
def _decode_hex(self, value):
|
||||
"""Helper function to decode hex string to `proper` hex string
|
||||
|
||||
:param string value: Human readable hex string
|
||||
:return string: Hex string
|
||||
"""
|
||||
result = ''
|
||||
for i in range(0,len(value),2):
|
||||
tmp = int(value[i:i+2],16)
|
||||
for i in range(0, len(value), 2):
|
||||
tmp = int(value[i:i + 2], 16)
|
||||
result += chr(tmp)
|
||||
return result
|
||||
def _decode_binary(self,rawIdentifier,identifier):
|
||||
|
||||
def _decode_binary(self, rawIdentifier, identifier):
|
||||
rawIdentifier += '\r\n\r\n'
|
||||
dataLength = int(identifier['Length'])
|
||||
pointerStart = self.raw.find(rawIdentifier)+len(rawIdentifier)
|
||||
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
|
||||
pointerEnd = pointerStart + dataLength
|
||||
data = self.raw[pointerStart:pointerEnd]
|
||||
if not len(data) == dataLength:
|
||||
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s'%(dataLength,len(data)))
|
||||
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
|
||||
return data
|
||||
def _validate_password(self,password):
|
||||
'''
|
||||
Validate GNTP Message against stored password
|
||||
'''
|
||||
|
||||
def _validate_password(self, password):
|
||||
"""Validate GNTP Message against stored password"""
|
||||
self.password = password
|
||||
if password == None: raise Exception()
|
||||
keyHash = self.info.get('keyHash',None)
|
||||
if password == None:
|
||||
raise AuthError('Missing password')
|
||||
keyHash = self.info.get('keyHash', None)
|
||||
if keyHash is None and self.password is None:
|
||||
return True
|
||||
if keyHash is None:
|
||||
raise AuthError('Invalid keyHash')
|
||||
if self.password is None:
|
||||
raise AuthError('Missing password')
|
||||
|
||||
|
||||
password = self.password.encode('utf8')
|
||||
saltHash = self._decode_hex(self.info['salt'])
|
||||
|
||||
keyBasis = password+saltHash
|
||||
|
||||
keyBasis = password + saltHash
|
||||
key = hashlib.md5(keyBasis).digest()
|
||||
keyHash = hashlib.md5(key).hexdigest()
|
||||
|
||||
|
||||
if not keyHash.upper() == self.info['keyHash'].upper():
|
||||
raise AuthError('Invalid Hash')
|
||||
return True
|
||||
|
||||
def validate(self):
|
||||
'''
|
||||
Verify required headers
|
||||
'''
|
||||
"""Verify required headers"""
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header,False):
|
||||
raise ParseError('Missing Notification Header: '+header)
|
||||
|
||||
if not self.headers.get(header, False):
|
||||
raise ParseError('Missing Notification Header: ' + header)
|
||||
|
||||
def _format_info(self):
|
||||
'''
|
||||
Generate info line for GNTP Message
|
||||
@return: Info line string
|
||||
'''
|
||||
info = u'GNTP/%s %s'%(
|
||||
"""Generate info line for GNTP Message
|
||||
|
||||
:return string:
|
||||
"""
|
||||
info = u'GNTP/%s %s' % (
|
||||
self.info.get('version'),
|
||||
self.info.get('messagetype'),
|
||||
)
|
||||
if self.info.get('encryptionAlgorithmID',None):
|
||||
info += ' %s:%s'%(
|
||||
if self.info.get('encryptionAlgorithmID', None):
|
||||
info += ' %s:%s' % (
|
||||
self.info.get('encryptionAlgorithmID'),
|
||||
self.info.get('ivValue'),
|
||||
)
|
||||
else:
|
||||
info+=' NONE'
|
||||
|
||||
if self.info.get('keyHashAlgorithmID',None):
|
||||
info += ' %s:%s.%s'%(
|
||||
info += ' NONE'
|
||||
|
||||
if self.info.get('keyHashAlgorithmID', None):
|
||||
info += ' %s:%s.%s' % (
|
||||
self.info.get('keyHashAlgorithmID'),
|
||||
self.info.get('keyHash'),
|
||||
self.info.get('salt')
|
||||
)
|
||||
|
||||
return info
|
||||
def _parse_dict(self,data):
|
||||
'''
|
||||
Helper function to parse blocks of GNTP headers into a dictionary
|
||||
@param data:
|
||||
@return: Dictionary of headers
|
||||
'''
|
||||
)
|
||||
|
||||
return info
|
||||
|
||||
def _parse_dict(self, data):
|
||||
"""Helper function to parse blocks of GNTP headers into a dictionary
|
||||
|
||||
:param string data:
|
||||
:return dict:
|
||||
"""
|
||||
dict = {}
|
||||
for line in data.split('\r\n'):
|
||||
match = re.match('([\w-]+):(.+)', line)
|
||||
if not match: continue
|
||||
|
||||
key = match.group(1).strip()
|
||||
val = match.group(2).strip()
|
||||
match = GNTP_HEADER.match(line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
key = unicode(match.group(1).strip(), 'utf8', 'replace')
|
||||
val = unicode(match.group(2).strip(), 'utf8', 'replace')
|
||||
dict[key] = val
|
||||
return dict
|
||||
def add_header(self,key,value):
|
||||
|
||||
def add_header(self, key, value):
|
||||
if isinstance(value, unicode):
|
||||
self.headers[key] = value
|
||||
else:
|
||||
self.headers[key] = unicode('%s'%value,'utf8','replace')
|
||||
def decode(self,data,password=None):
|
||||
'''
|
||||
Decode GNTP Message
|
||||
@param data:
|
||||
'''
|
||||
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
|
||||
|
||||
def add_resource(self, data):
|
||||
"""Add binary resource
|
||||
|
||||
:param string data: Binary Data
|
||||
"""
|
||||
identifier = hashlib.md5(data).hexdigest()
|
||||
self.resources[identifier] = data
|
||||
return 'x-growl-resource://%s' % identifier
|
||||
|
||||
def decode(self, data, password=None):
|
||||
"""Decode GNTP Message
|
||||
|
||||
:param string data:
|
||||
"""
|
||||
self.password = password
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
def encode(self):
|
||||
'''
|
||||
Encode a GNTP Message
|
||||
@return: GNTP Message ready to be sent
|
||||
'''
|
||||
self.validate()
|
||||
EOL = u'\r\n'
|
||||
|
||||
message = self._format_info() + EOL
|
||||
"""Encode a generic GNTP Message
|
||||
|
||||
:return string: GNTP Message ready to be sent
|
||||
"""
|
||||
|
||||
buffer = _GNTPBuffer()
|
||||
|
||||
buffer.writefmt(self._format_info())
|
||||
|
||||
#Headers
|
||||
for k,v in self.headers.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
message += EOL
|
||||
return message
|
||||
for k, v in self.headers.iteritems():
|
||||
buffer.writefmt('%s: %s', k, v)
|
||||
buffer.writefmt()
|
||||
|
||||
#Resources
|
||||
for resource, data in self.resources.iteritems():
|
||||
buffer.writefmt('Identifier: %s', resource)
|
||||
buffer.writefmt('Length: %d', len(data))
|
||||
buffer.writefmt()
|
||||
buffer.write(data)
|
||||
buffer.writefmt()
|
||||
buffer.writefmt()
|
||||
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
class GNTPRegister(_GNTPBase):
|
||||
"""Represents a GNTP Registration Command"""
|
||||
notifications = []
|
||||
"""Represents a GNTP Registration Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notifications-Count'
|
||||
]
|
||||
_requiredNotificationHeaders = ['Notification-Name']
|
||||
def __init__(self,data=None,password=None):
|
||||
'''
|
||||
@param data: (Optional) See decode()
|
||||
@param password: (Optional) Password to use while encoding/decoding messages
|
||||
'''
|
||||
self.info['messagetype'] = 'REGISTER'
|
||||
|
||||
|
||||
def __init__(self, data=None, password=None):
|
||||
_GNTPBase.__init__(self, 'REGISTER')
|
||||
self.notifications = []
|
||||
|
||||
if data:
|
||||
self.decode(data,password)
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
self.add_header('Application-Name', 'pygntp')
|
||||
self.add_header('Notifications-Count', 0)
|
||||
self.add_origin_info()
|
||||
|
||||
def validate(self):
|
||||
'''
|
||||
Validate required headers and validate notification headers
|
||||
'''
|
||||
'''Validate required headers and validate notification headers'''
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header,False):
|
||||
raise ParseError('Missing Registration Header: '+header)
|
||||
if not self.headers.get(header, False):
|
||||
raise ParseError('Missing Registration Header: ' + header)
|
||||
for notice in self.notifications:
|
||||
for header in self._requiredNotificationHeaders:
|
||||
if not notice.get(header,False):
|
||||
raise ParseError('Missing Notification Header: '+header)
|
||||
def decode(self,data,password):
|
||||
'''
|
||||
Decode existing GNTP Registration message
|
||||
@param data: Message to decode.
|
||||
'''
|
||||
if not notice.get(header, False):
|
||||
raise ParseError('Missing Notification Header: ' + header)
|
||||
|
||||
def decode(self, data, password):
|
||||
"""Decode existing GNTP Registration message
|
||||
|
||||
:param string data: Message to decode
|
||||
"""
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i,part in enumerate(parts):
|
||||
if i==0: continue #Skip Header
|
||||
if part.strip()=='': continue
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Notification-Name',False):
|
||||
if notice.get('Notification-Name', False):
|
||||
self.notifications.append(notice)
|
||||
elif notice.get('Identifier',False):
|
||||
notice['Data'] = self._decode_binary(part,notice)
|
||||
elif notice.get('Identifier', False):
|
||||
notice['Data'] = self._decode_binary(part, notice)
|
||||
#open('register.png','wblol').write(notice['Data'])
|
||||
self.resources[ notice.get('Identifier') ] = notice
|
||||
|
||||
def add_notification(self,name,enabled=True):
|
||||
'''
|
||||
Add new Notification to Registration message
|
||||
@param name: Notification Name
|
||||
@param enabled: Default Notification to Enabled
|
||||
'''
|
||||
self.resources[notice.get('Identifier')] = notice
|
||||
|
||||
def add_notification(self, name, enabled=True):
|
||||
"""Add new Notification to Registration message
|
||||
|
||||
:param string name: Notification Name
|
||||
:param boolean enabled: Enable this notification by default
|
||||
"""
|
||||
notice = {}
|
||||
notice['Notification-Name'] = u'%s'%name
|
||||
notice['Notification-Enabled'] = u'%s'%enabled
|
||||
|
||||
notice['Notification-Name'] = u'%s' % name
|
||||
notice['Notification-Enabled'] = u'%s' % enabled
|
||||
|
||||
self.notifications.append(notice)
|
||||
self.add_header('Notifications-Count', len(self.notifications))
|
||||
|
||||
def encode(self):
|
||||
'''
|
||||
Encode a GNTP Registration Message
|
||||
@return: GNTP Registration Message ready to be sent
|
||||
'''
|
||||
self.validate()
|
||||
EOL = u'\r\n'
|
||||
|
||||
message = self._format_info() + EOL
|
||||
"""Encode a GNTP Registration Message
|
||||
|
||||
:return string: Encoded GNTP Registration message
|
||||
"""
|
||||
|
||||
buffer = _GNTPBuffer()
|
||||
|
||||
buffer.writefmt(self._format_info())
|
||||
|
||||
#Headers
|
||||
for k,v in self.headers.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
for k, v in self.headers.iteritems():
|
||||
buffer.writefmt('%s: %s', k, v)
|
||||
buffer.writefmt()
|
||||
|
||||
#Notifications
|
||||
if len(self.notifications)>0:
|
||||
if len(self.notifications) > 0:
|
||||
for notice in self.notifications:
|
||||
message += EOL
|
||||
for k,v in notice.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
message += EOL
|
||||
return message
|
||||
for k, v in notice.iteritems():
|
||||
buffer.writefmt('%s: %s', k, v)
|
||||
buffer.writefmt()
|
||||
|
||||
#Resources
|
||||
for resource, data in self.resources.iteritems():
|
||||
buffer.writefmt('Identifier: %s', resource)
|
||||
buffer.writefmt('Length: %d', len(data))
|
||||
buffer.writefmt()
|
||||
buffer.write(data)
|
||||
buffer.writefmt()
|
||||
buffer.writefmt()
|
||||
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
class GNTPNotice(_GNTPBase):
|
||||
"""Represents a GNTP Notification Command"""
|
||||
"""Represents a GNTP Notification Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string app: (Optional) Set Application-Name
|
||||
:param string name: (Optional) Set Notification-Name
|
||||
:param string title: (Optional) Set Notification Title
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notification-Name',
|
||||
'Notification-Title'
|
||||
]
|
||||
def __init__(self,data=None,app=None,name=None,title=None,password=None):
|
||||
'''
|
||||
|
||||
@param data: (Optional) See decode()
|
||||
@param app: (Optional) Set Application-Name
|
||||
@param name: (Optional) Set Notification-Name
|
||||
@param title: (Optional) Set Notification Title
|
||||
@param password: (Optional) Password to use while encoding/decoding messages
|
||||
'''
|
||||
self.info['messagetype'] = 'NOTIFY'
|
||||
|
||||
|
||||
def __init__(self, data=None, app=None, name=None, title=None, password=None):
|
||||
_GNTPBase.__init__(self, 'NOTIFY')
|
||||
|
||||
if data:
|
||||
self.decode(data,password)
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
if app:
|
||||
@@ -335,105 +405,103 @@ class GNTPNotice(_GNTPBase):
|
||||
self.add_header('Notification-Name', name)
|
||||
if title:
|
||||
self.add_header('Notification-Title', title)
|
||||
self.add_origin_info()
|
||||
def decode(self,data,password):
|
||||
'''
|
||||
Decode existing GNTP Notification message
|
||||
@param data: Message to decode.
|
||||
'''
|
||||
|
||||
def decode(self, data, password):
|
||||
"""Decode existing GNTP Notification message
|
||||
|
||||
:param string data: Message to decode.
|
||||
"""
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i,part in enumerate(parts):
|
||||
if i==0: continue #Skip Header
|
||||
if part.strip()=='': continue
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Identifier',False):
|
||||
notice['Data'] = self._decode_binary(part,notice)
|
||||
if notice.get('Identifier', False):
|
||||
notice['Data'] = self._decode_binary(part, notice)
|
||||
#open('notice.png','wblol').write(notice['Data'])
|
||||
self.resources[ notice.get('Identifier') ] = notice
|
||||
def encode(self):
|
||||
'''
|
||||
Encode a GNTP Notification Message
|
||||
@return: GNTP Notification Message ready to be sent
|
||||
'''
|
||||
self.validate()
|
||||
EOL = u'\r\n'
|
||||
|
||||
message = self._format_info() + EOL
|
||||
#Headers
|
||||
for k,v in self.headers.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
message += EOL
|
||||
return message
|
||||
self.resources[notice.get('Identifier')] = notice
|
||||
|
||||
|
||||
class GNTPSubscribe(_GNTPBase):
|
||||
"""Represents a GNTP Subscribe Command"""
|
||||
def __init__(self,data=None,password=None):
|
||||
self.info['messagetype'] = 'SUBSCRIBE'
|
||||
self._requiredHeaders = [
|
||||
'Subscriber-ID',
|
||||
'Subscriber-Name',
|
||||
]
|
||||
"""Represents a GNTP Subscribe Command
|
||||
|
||||
:param string data: (Optional) See decode()
|
||||
:param string password: (Optional) Password to use while encoding/decoding messages
|
||||
"""
|
||||
_requiredHeaders = [
|
||||
'Subscriber-ID',
|
||||
'Subscriber-Name',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, password=None):
|
||||
_GNTPBase.__init__(self, 'SUBSCRIBE')
|
||||
if data:
|
||||
self.decode(data,password)
|
||||
self.decode(data, password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
self.add_origin_info()
|
||||
|
||||
|
||||
class GNTPOK(_GNTPBase):
|
||||
"""Represents a GNTP OK Response"""
|
||||
"""Represents a GNTP OK Response
|
||||
|
||||
:param string data: (Optional) See _GNTPResponse.decode()
|
||||
:param string action: (Optional) Set type of action the OK Response is for
|
||||
"""
|
||||
_requiredHeaders = ['Response-Action']
|
||||
def __init__(self,data=None,action=None):
|
||||
'''
|
||||
@param data: (Optional) See _GNTPResponse.decode()
|
||||
@param action: (Optional) Set type of action the OK Response is for
|
||||
'''
|
||||
self.info['messagetype'] = '-OK'
|
||||
|
||||
def __init__(self, data=None, action=None):
|
||||
_GNTPBase.__init__(self, '-OK')
|
||||
if data:
|
||||
self.decode(data)
|
||||
if action:
|
||||
self.add_header('Response-Action', action)
|
||||
self.add_origin_info()
|
||||
|
||||
|
||||
class GNTPError(_GNTPBase):
|
||||
_requiredHeaders = ['Error-Code','Error-Description']
|
||||
def __init__(self,data=None,errorcode=None,errordesc=None):
|
||||
'''
|
||||
@param data: (Optional) See _GNTPResponse.decode()
|
||||
@param errorcode: (Optional) Error code
|
||||
@param errordesc: (Optional) Error Description
|
||||
'''
|
||||
self.info['messagetype'] = '-ERROR'
|
||||
"""Represents a GNTP Error response
|
||||
|
||||
:param string data: (Optional) See _GNTPResponse.decode()
|
||||
:param string errorcode: (Optional) Error code
|
||||
:param string errordesc: (Optional) Error Description
|
||||
"""
|
||||
_requiredHeaders = ['Error-Code', 'Error-Description']
|
||||
|
||||
def __init__(self, data=None, errorcode=None, errordesc=None):
|
||||
_GNTPBase.__init__(self, '-ERROR')
|
||||
if data:
|
||||
self.decode(data)
|
||||
if errorcode:
|
||||
self.add_header('Error-Code', errorcode)
|
||||
self.add_header('Error-Description', errordesc)
|
||||
self.add_origin_info()
|
||||
def error(self):
|
||||
return self.headers['Error-Code'],self.headers['Error-Description']
|
||||
|
||||
def parse_gntp(data,password=None):
|
||||
'''
|
||||
Attempt to parse a message as a GNTP message
|
||||
@param data: Message to be parsed
|
||||
@param password: Optional password to be used to verify the message
|
||||
'''
|
||||
match = re.match('GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',data,re.IGNORECASE)
|
||||
def error(self):
|
||||
return (self.headers.get('Error-Code', None),
|
||||
self.headers.get('Error-Description', None))
|
||||
|
||||
|
||||
def parse_gntp(data, password=None):
|
||||
"""Attempt to parse a message as a GNTP message
|
||||
|
||||
:param string data: Message to be parsed
|
||||
:param string password: Optional password to be used to verify the message
|
||||
"""
|
||||
match = GNTP_INFO_LINE_SHORT.match(data)
|
||||
if not match:
|
||||
raise ParseError('INVALID_GNTP_INFO')
|
||||
info = match.groupdict()
|
||||
if info['messagetype'] == 'REGISTER':
|
||||
return GNTPRegister(data,password=password)
|
||||
return GNTPRegister(data, password=password)
|
||||
elif info['messagetype'] == 'NOTIFY':
|
||||
return GNTPNotice(data,password=password)
|
||||
return GNTPNotice(data, password=password)
|
||||
elif info['messagetype'] == 'SUBSCRIBE':
|
||||
return GNTPSubscribe(data,password=password)
|
||||
return GNTPSubscribe(data, password=password)
|
||||
elif info['messagetype'] == '-OK':
|
||||
return GNTPOK(data)
|
||||
elif info['messagetype'] == '-ERROR':
|
||||
|
||||
217
gntp/notifier.py
217
gntp/notifier.py
@@ -12,10 +12,55 @@ using GNTP
|
||||
import gntp
|
||||
import socket
|
||||
import logging
|
||||
import platform
|
||||
|
||||
__all__ = [
|
||||
'mini',
|
||||
'GrowlNotifier',
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def mini(description, applicationName='PythonMini', noteType="Message",
|
||||
title="Mini Message", applicationIcon=None, hostname='localhost',
|
||||
password=None, port=23053, sticky=False, priority=None,
|
||||
callback=None, notificationIcon=None, identifier=None):
|
||||
"""Single notification function
|
||||
|
||||
Simple notification function in one line. Has only one required parameter
|
||||
and attempts to use reasonable defaults for everything else
|
||||
:param string description: Notification message
|
||||
|
||||
.. warning::
|
||||
For now, only URL callbacks are supported. In the future, the
|
||||
callback argument will also support a function
|
||||
"""
|
||||
growl = GrowlNotifier(
|
||||
applicationName=applicationName,
|
||||
notifications=[noteType],
|
||||
defaultNotifications=[noteType],
|
||||
applicationIcon=applicationIcon,
|
||||
hostname=hostname,
|
||||
password=password,
|
||||
port=port,
|
||||
)
|
||||
result = growl.register()
|
||||
if result is not True:
|
||||
return result
|
||||
|
||||
return growl.notify(
|
||||
noteType=noteType,
|
||||
title=title,
|
||||
description=description,
|
||||
icon=notificationIcon,
|
||||
sticky=sticky,
|
||||
priority=priority,
|
||||
callback=callback,
|
||||
identifier=identifier,
|
||||
)
|
||||
|
||||
|
||||
class GrowlNotifier(object):
|
||||
"""Helper class to simplfy sending Growl messages
|
||||
|
||||
@@ -27,55 +72,35 @@ class GrowlNotifier(object):
|
||||
:param string hostname: Remote host
|
||||
:param integer port: Remote port
|
||||
"""
|
||||
applicationName = 'Python GNTP'
|
||||
notifications = []
|
||||
defaultNotifications = []
|
||||
applicationIcon = None
|
||||
|
||||
passwordHash = 'MD5'
|
||||
socketTimeout = 3
|
||||
|
||||
#GNTP Specific
|
||||
password = None
|
||||
hostname = 'localhost'
|
||||
port = 23053
|
||||
def __init__(self, applicationName='Python GNTP', notifications=[],
|
||||
defaultNotifications=None, applicationIcon=None, hostname='localhost',
|
||||
password=None, port=23053):
|
||||
|
||||
def __init__(self, applicationName=None, notifications=None, defaultNotifications=None, applicationIcon=None, hostname=None, password=None, port=None):
|
||||
if applicationName:
|
||||
self.applicationName = applicationName
|
||||
assert self.applicationName, 'An application name is required.'
|
||||
|
||||
if notifications:
|
||||
self.notifications = list(notifications)
|
||||
assert self.notifications, 'A sequence of one or more notification names is required.'
|
||||
|
||||
if defaultNotifications is not None:
|
||||
self.applicationName = applicationName
|
||||
self.notifications = list(notifications)
|
||||
if defaultNotifications:
|
||||
self.defaultNotifications = list(defaultNotifications)
|
||||
elif not self.defaultNotifications:
|
||||
self.defaultNotifications = list(self.notifications)
|
||||
else:
|
||||
self.defaultNotifications = self.notifications
|
||||
self.applicationIcon = applicationIcon
|
||||
|
||||
if applicationIcon is not None:
|
||||
self.applicationIcon = self._checkIcon(applicationIcon)
|
||||
elif self.applicationIcon is not None:
|
||||
self.applicationIcon = self._checkIcon(self.applicationIcon)
|
||||
|
||||
#GNTP Specific
|
||||
if password:
|
||||
self.password = password
|
||||
|
||||
if hostname:
|
||||
self.hostname = hostname
|
||||
assert self.hostname, 'Requires valid hostname'
|
||||
|
||||
if port:
|
||||
self.port = int(port)
|
||||
assert isinstance(self.port, int), 'Requires valid port'
|
||||
self.password = password
|
||||
self.hostname = hostname
|
||||
self.port = int(port)
|
||||
|
||||
def _checkIcon(self, data):
|
||||
'''
|
||||
Check the icon to see if it's valid
|
||||
@param data:
|
||||
@todo Consider checking for a valid URL
|
||||
|
||||
If it's a simple URL icon, then we return True. If it's a data icon
|
||||
then we return False
|
||||
'''
|
||||
return data
|
||||
logger.debug('Checking icon')
|
||||
return data.startswith('http')
|
||||
|
||||
def register(self):
|
||||
"""Send GNTP Registration
|
||||
@@ -84,23 +109,26 @@ class GrowlNotifier(object):
|
||||
Before sending notifications to Growl, you need to have
|
||||
sent a registration message at least once
|
||||
"""
|
||||
logger.info('Sending registration to %s:%s', self.hostname, self.port)
|
||||
logger.debug('Sending registration to %s:%s', self.hostname, self.port)
|
||||
register = gntp.GNTPRegister()
|
||||
register.add_header('Application-Name', self.applicationName)
|
||||
for notification in self.notifications:
|
||||
enabled = notification in self.defaultNotifications
|
||||
register.add_notification(notification, enabled)
|
||||
if self.applicationIcon:
|
||||
register.add_header('Application-Icon', self.applicationIcon)
|
||||
if self._checkIcon(self.applicationIcon):
|
||||
register.add_header('Application-Icon', self.applicationIcon)
|
||||
else:
|
||||
id = register.add_resource(self.applicationIcon)
|
||||
register.add_header('Application-Icon', id)
|
||||
if self.password:
|
||||
register.set_password(self.password, self.passwordHash)
|
||||
response = self._send('register', register.encode())
|
||||
if isinstance(response, gntp.GNTPOK):
|
||||
return True
|
||||
logger.error('Invalid response %s', response.error())
|
||||
return response.error()
|
||||
self.add_origin_info(register)
|
||||
self.register_hook(register)
|
||||
return self._send('register', register)
|
||||
|
||||
def notify(self, noteType, title, description, icon=None, sticky=False, priority=None):
|
||||
def notify(self, noteType, title, description, icon=None, sticky=False,
|
||||
priority=None, callback=None, identifier=None):
|
||||
"""Send a GNTP notifications
|
||||
|
||||
.. warning::
|
||||
@@ -112,8 +140,13 @@ class GrowlNotifier(object):
|
||||
:param string icon: Icon URL path
|
||||
:param boolean sticky: Sticky notification
|
||||
:param integer priority: Message priority level from -2 to 2
|
||||
:param string callback: URL callback
|
||||
|
||||
.. warning::
|
||||
For now, only URL callbacks are supported. In the future, the
|
||||
callback argument will also support a function
|
||||
"""
|
||||
logger.info('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
|
||||
logger.debug('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
|
||||
assert noteType in self.notifications
|
||||
notice = gntp.GNTPNotice()
|
||||
notice.add_header('Application-Name', self.applicationName)
|
||||
@@ -126,14 +159,23 @@ class GrowlNotifier(object):
|
||||
if priority:
|
||||
notice.add_header('Notification-Priority', priority)
|
||||
if icon:
|
||||
notice.add_header('Notification-Icon', self._checkIcon(icon))
|
||||
if self._checkIcon(icon):
|
||||
notice.add_header('Notification-Icon', icon)
|
||||
else:
|
||||
id = notice.add_resource(icon)
|
||||
notice.add_header('Notification-Icon', id)
|
||||
|
||||
if description:
|
||||
notice.add_header('Notification-Text', description)
|
||||
response = self._send('notify', notice.encode())
|
||||
if isinstance(response, gntp.GNTPOK):
|
||||
return True
|
||||
logger.error('Invalid response %s', response.error())
|
||||
return response.error()
|
||||
if callback:
|
||||
notice.add_header('Notification-Callback-Target', callback)
|
||||
if identifier:
|
||||
notice.add_header('Notification-Coalescing-ID', identifier)
|
||||
|
||||
self.add_origin_info(notice)
|
||||
self.notify_hook(notice)
|
||||
|
||||
return self._send('notify', notice)
|
||||
|
||||
def subscribe(self, id, name, port):
|
||||
"""Send a Subscribe request to a remote machine"""
|
||||
@@ -143,30 +185,63 @@ class GrowlNotifier(object):
|
||||
sub.add_header('Subscriber-Port', port)
|
||||
if self.password:
|
||||
sub.set_password(self.password, self.passwordHash)
|
||||
response = self._send('subscribe', sub.encode())
|
||||
if isinstance(response, gntp.GNTPOK):
|
||||
return True
|
||||
logger.error('Invalid response %s', response.error())
|
||||
return response.error()
|
||||
|
||||
def _send(self, type, data):
|
||||
self.add_origin_info(sub)
|
||||
self.subscribe_hook(sub)
|
||||
|
||||
return self._send('subscribe', sub)
|
||||
|
||||
def add_origin_info(self, packet):
|
||||
"""Add optional Origin headers to message"""
|
||||
packet.add_header('Origin-Machine-Name', platform.node())
|
||||
packet.add_header('Origin-Software-Name', 'gntp.py')
|
||||
packet.add_header('Origin-Software-Version', gntp.__version__)
|
||||
packet.add_header('Origin-Platform-Name', platform.system())
|
||||
packet.add_header('Origin-Platform-Version', platform.platform())
|
||||
|
||||
def register_hook(self, packet):
|
||||
pass
|
||||
|
||||
def notify_hook(self, packet):
|
||||
pass
|
||||
|
||||
def subscribe_hook(self, packet):
|
||||
pass
|
||||
|
||||
def _send(self, messagetype, packet):
|
||||
"""Send the GNTP Packet"""
|
||||
#logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, type, data)
|
||||
#Less verbose please
|
||||
logger.debug('To : %s:%s <%s>', self.hostname, self.port, type)
|
||||
|
||||
packet.validate()
|
||||
data = packet.encode()
|
||||
|
||||
#logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
||||
#Less verbose
|
||||
logger.debug('To : %s:%s <%s>', self.hostname, self.port, packet.__class__)
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(self.socketTimeout)
|
||||
s.connect((self.hostname, self.port))
|
||||
s.send(data.encode('utf8', 'replace'))
|
||||
try:
|
||||
s.settimeout(10)
|
||||
except:
|
||||
pass
|
||||
response = gntp.parse_gntp(s.recv(1024))
|
||||
s.send(data)
|
||||
recv_data = s.recv(1024)
|
||||
while not recv_data.endswith("\r\n\r\n"):
|
||||
recv_data += s.recv(1024)
|
||||
response = gntp.parse_gntp(recv_data)
|
||||
s.close()
|
||||
|
||||
#logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
||||
#Less verbose please
|
||||
#Less verbose
|
||||
logger.debug('From : %s:%s <%s>', self.hostname, self.port, response.__class__)
|
||||
|
||||
return response
|
||||
if type(response) == gntp.GNTPOK:
|
||||
return True
|
||||
if response.error()[0] == '404' and 'disabled' in response.error()[1]:
|
||||
# Ignore message saying that user has disabled this class
|
||||
return True
|
||||
logger.error('Invalid response: %s', response.error())
|
||||
return response.error()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If we're running this module directly we're likely running it as a test
|
||||
# so extra debugging is useful
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
mini('Testing mini notification')
|
||||
|
||||
@@ -6,59 +6,6 @@
|
||||
<!--#set global $submenu="newzbin"#-->
|
||||
<!--#include $webdir + "/inc_cmenu.tmpl"#-->
|
||||
|
||||
<h2>Newzbin</h2>
|
||||
$T('explain-newzbin')<br/><br/>
|
||||
<form action="saveNewzbin" method="post" autocomplete="off">
|
||||
<div class="EntryBlock">
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('accountInfo')</legend>
|
||||
<strong>$T('opt-username_newzbin'):</strong><br>
|
||||
$T('explain-username_newzbin')<br>
|
||||
<input type="text" name="username_newzbin" value="$username_newzbin">
|
||||
<br>
|
||||
<br>
|
||||
<strong>$T('opt-password_newzbin'):</strong><br>
|
||||
$T('explain-password_newzbin')<br>
|
||||
<input type="password" name="password_newzbin" value="$password_newzbin">
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="EntryBlock">
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('newzbinBookmarks')</legend>
|
||||
<label><input type="checkbox" name="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks > 0 then "checked=1" else ""#--> <strong>$T('opt-newzbin_bookmarks'):</strong></label><br>
|
||||
$T('explain-newzbin_bookmarks')<br>
|
||||
<a href="getBookmarks?session=$session">$T('link-getBookmarks')</a>
|
||||
<br>
|
||||
<!--#if $bookmarks_list#-->
|
||||
<a href="hideBookmarks?session=$session">$T('link-HideBM')</a>
|
||||
<!--#else#-->
|
||||
<a href="showBookmarks?session=$session">$T('link-ShowBM')</a>
|
||||
<!--#end if#-->
|
||||
<br/>
|
||||
<br/>
|
||||
<label><input type="checkbox" name="newzbin_unbookmark" value="1" <!--#if $newzbin_unbookmark > 0 then "checked=1" else ""#--> /> <strong>$T('opt-newzbin_unbookmark'):</strong></label><br>
|
||||
$T('explain-newzbin_unbookmark')<br>
|
||||
<br/>
|
||||
<strong>$T('opt-bookmark_rate'):</strong><br>
|
||||
$T('explain-bookmark_rate')<br>
|
||||
<input type="text" name="bookmark_rate" value="$bookmark_rate">
|
||||
</fieldset>
|
||||
</div>
|
||||
<!--#if $bookmarks_list#-->
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('processedBM')</legend>
|
||||
<!--#for $msgid in $bookmarks_list#-->
|
||||
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a>
|
||||
<!--#end for#-->
|
||||
</fieldset>
|
||||
<!--#end if#-->
|
||||
<input type="hidden" name="session" value="$session">
|
||||
<p><input type="submit" value="$T('button-saveChanges')"></p>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>NzbMatrix</h2>
|
||||
$T('explain-nzbmatrix')<br/><br/>
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th>$T('rss-order')</th>
|
||||
<th>$T('rss-type')</th>
|
||||
<th>$T('rss-filter')</th>
|
||||
@@ -86,6 +87,7 @@
|
||||
<form action="upd_rss_filter" method="get">
|
||||
<tr class="odd">
|
||||
<td></td>
|
||||
<td><input type="checkbox" name="enabled" value="1" checked="checked" /></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<select name="filter_type">
|
||||
@@ -152,6 +154,10 @@
|
||||
</td>
|
||||
|
||||
<form action="upd_rss_filter" method="get">
|
||||
<td>
|
||||
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6] == '1' then 'checked="checked"' else ""#--> />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" size="3" name="new_index" value=$fnum>
|
||||
</td>
|
||||
@@ -208,6 +214,7 @@
|
||||
<input type="hidden" name="feed" value="$feed"/>
|
||||
<input type="hidden" name="session" value="$session">
|
||||
<input type="submit" value="$T('button-save')"/>
|
||||
<!--#if not $rss[$feed].filter_states[$fnum]#--> $T('Incorrect filter')<!--#end if#-->
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
|
||||
@@ -31,16 +31,14 @@ $T('hour'):<br>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<br>$T('sch-frequency'): <br>
|
||||
<select name="dayofweek">
|
||||
<option value="*" selected>$T('daily')
|
||||
<option value="1">$T('monday')
|
||||
<option value="2">$T('tuesday')
|
||||
<option value="3">$T('wednesday')
|
||||
<option value="4">$T('thursday')
|
||||
<option value="5">$T('friday')
|
||||
<option value="6">$T('saturday')
|
||||
<option value="7">$T('sunday')
|
||||
</select>
|
||||
<input type="checkbox" name="daysofweek" value="1">$T('monday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="2">$T('tuesday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="3">$T('wednesday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="4">$T('thursday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="5">$T('friday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="6">$T('saturday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="7">$T('sunday')<br/>
|
||||
|
||||
<br>$T('sch-action'):<br>
|
||||
<select name="action">
|
||||
<!--#for $action in $actions#-->
|
||||
|
||||
@@ -47,11 +47,13 @@
|
||||
<a href="$cpath/notify/">$T('cmenu-notif')</a> |
|
||||
<!--#end if#-->
|
||||
|
||||
<!--#if 0#-->
|
||||
<!--#if $submenu=="indexers"#-->
|
||||
<a class="current" href="./">$T('cmenu-newzbin')</a> |
|
||||
<!--#else#-->
|
||||
<a href="$cpath/indexers/">$T('cmenu-newzbin')</a> |
|
||||
<!--#end if#-->
|
||||
<!--#end if#-->
|
||||
|
||||
<!--#if $submenu=="categories"#-->
|
||||
<a class="current" href="./">$T('cmenu-cat')</a> |
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>$mbleft MB $T('queued') - SABnzbd $version</title>
|
||||
<link rel="stylesheet" type="text/css" href="$statpath/static/stylesheets/default.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="$statpath/static/stylesheets/defaultcolors.css"/>
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
<div class="EntryBlock">
|
||||
<form action="addID" method="get">
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('add')
|
||||
<!--#if $varExists('newzbinDetails')#--> $T('reportId') / <!--#end if#-->URL</legend>
|
||||
<legend>$T('add') URL</legend>
|
||||
<input type="text" name="id">
|
||||
<!--#if $cat_list#-->
|
||||
<select name="cat" >
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
|
||||
\$(document).ready(function () {
|
||||
|
||||
\$('.col2 H3').click(function () { \$(this).parent().next().toggle() });
|
||||
#if $pane != "Servers"#
|
||||
\$('.col2 H3').click(function () { \$(this).parent().next().toggle() });
|
||||
#end if#
|
||||
|
||||
\$('.sabnzbd_restart').click(function () {
|
||||
\$('.sabnzbd_restart').each(function () {
|
||||
@@ -97,7 +99,7 @@
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
@@ -126,9 +128,11 @@
|
||||
<a href="${root}config/notify/">
|
||||
<div #if $pane == "Email" then 'class="active"' else ""#>$T('cmenu-notif')</div>
|
||||
</a>
|
||||
<!--#if 0#-->
|
||||
<a href="${root}config/indexers/">
|
||||
<div #if $pane == "Index Sites" then 'class="active"' else ""#>$T('cmenu-newzbin')</div>
|
||||
</a>
|
||||
<!--#end if#-->
|
||||
<a href="${root}config/categories/">
|
||||
<div #if $pane == "Categories" then 'class="active"' else ""#>$T('cmenu-cat')</div>
|
||||
</a>
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
<h5 class="copyright">Copyright © 2008-2012 The SABnzbd Team <<span style="color: #0000ff;">team@sabnzbd.org</span>></h5>
|
||||
<p class="copyright"><small>$T('yourRights')</small></p>
|
||||
</div>
|
||||
<!--#if $news_items#-->
|
||||
<div class="padding">
|
||||
<iframe frameborder=0 width=100% src="http://sabnzbdplus.sourceforge.net/version/news.html"></iframe>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||
|
||||
@@ -127,6 +127,11 @@
|
||||
<input type="text" name="https_key" id="https_key" value="$https_key" size="50" />
|
||||
<span class="desc">$T('explain-https_key')</span>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="https_chain">$T('opt-https_chain')</label>
|
||||
<input type="text" name="https_chain" id="https_chain" value="$https_chain" size="50" />
|
||||
<span class="desc">$T('explain-https_chain')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
|
||||
<input type="button" value="$T('button-restart') SABnzbd" class="sabnzbd_restart" />
|
||||
@@ -152,7 +157,7 @@
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="cleanup_list">$T('opt-cleanup_list')</label>
|
||||
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list" size="50" placeholder=".nfo, .sfv" />
|
||||
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list" size="50"/>
|
||||
<span class="desc">$T('explain-cleanup_list')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
|
||||
@@ -33,84 +33,6 @@
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<div class="section">
|
||||
<div class="col2">
|
||||
<h3>Newzbin $T('accountInfo')</h3>
|
||||
<p>$T('explain-newzbin')</p>
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="username_newzbin">$T('opt-username_newzbin')</label>
|
||||
<input type="text" name="username_newzbin" id="username_newzbin" value="$username_newzbin" size="30" />
|
||||
<span class="desc">$T('explain-username_newzbin')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="password_newzbin">$T('opt-password_newzbin')</label>
|
||||
<input type="password" name="password_newzbin" id="password_newzbin" value="$password_newzbin" size="30" />
|
||||
<span class="desc">$T('explain-password_newzbin')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<div class="section">
|
||||
<div class="col2">
|
||||
<h3>Newzbin $T('newzbinBookmarks')</h3>
|
||||
<p>
|
||||
<input type="button" id="getBookmarks" value="$T('link-getBookmarks')" />
|
||||
<span id="getBookmarks-result" class="icon"> </span>
|
||||
<br/><br/>
|
||||
<!--#if $bookmarks_list#-->
|
||||
<input type="button" id="hideBookmarks" value="$T('link-HideBM')" />
|
||||
<!--#else#-->
|
||||
<input type="button" id="showBookmarks" value="$T('link-ShowBM')" />
|
||||
<!--#end if#-->
|
||||
</p>
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="newzbin_bookmarks">$T('opt-newzbin_bookmarks')</label>
|
||||
<input type="checkbox" name="newzbin_bookmarks" id="newzbin_bookmarks" value="1" <!--#if int($newzbin_bookmarks) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-newzbin_bookmarks')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="newzbin_unbookmark">$T('opt-newzbin_unbookmark')</label>
|
||||
<input type="checkbox" name="newzbin_unbookmark" id="newzbin_unbookmark" value="1" <!--#if int($newzbin_unbookmark) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-newzbin_unbookmark')</span>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="bookmark_rate">$T('opt-bookmark_rate')</label>
|
||||
<input type="number" name="bookmark_rate" id="bookmark_rate" value="$bookmark_rate" size="8" min="15" max="1440" />
|
||||
<span class="desc">$T('explain-bookmark_rate')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<!--#if $bookmarks_list#-->
|
||||
<div class="section">
|
||||
<div class="col2">
|
||||
<h3>Newzbin Bookmarks</h3>
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<!--#set $odd = False#-->
|
||||
<!--#for $msgid in $bookmarks_list#-->
|
||||
<!--#set $odd = not $odd#-->
|
||||
<div class="field-pair <!--#if $odd then "alt" else ""#-->">
|
||||
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a><br/>
|
||||
</div>
|
||||
<!--#end for#-->
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<!--#end if#-->
|
||||
<div class="padding alt">
|
||||
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
|
||||
<input type="button" value="$T('button-restart') SABnzbd" class="sabnzbd_restart" />
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<fieldset>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="email_server">$T('opt-email_server')</label>
|
||||
<input type="url" name="email_server" id="email_server" value="$email_server" size="40" />
|
||||
<input type="text" name="email_server" id="email_server" value="$email_server" size="40" />
|
||||
<span class="desc">$T('explain-email_server')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -85,19 +85,33 @@
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1">
|
||||
<fieldset>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="notify_classes">$T('opt-notify_classes')</label>
|
||||
<select name="notify_classes" multiple="multiple" class="multiple_cats">
|
||||
<!--#for $item in $notify_list#-->
|
||||
<option value="$item" <!--#if $item in $notify_classes then 'selected="selected"' else ""#--> >$T($notify_texts[$item])</option>
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<span class="desc">$T('explain-notify_classes')</span>
|
||||
</div>
|
||||
<div class="field-pair <!--#if not $have_ncenter then "disabled" else "" #-->">
|
||||
<label class="config" for="ncenter_enable">$T('opt-ncenter_enable')</label>
|
||||
<input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if int($ncenter_enable) > 0 then 'checked="checked"' else ""#--> <!--#if not $have_ncenter then 'readonly="readonly" disabled="disabled"' else "" #--> />
|
||||
<span class="desc">$T('explain-ncenter_enable')</span>
|
||||
</div>
|
||||
<div class="field-pair alt <!--#if not $have_ntfosd then "disabled" else "" #-->">
|
||||
<label class="config" for="ntfosd_enable">$T('opt-ntfosd_enable')</label>
|
||||
<input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if int($ntfosd_enable) > 0 then 'checked="checked"' else ""#--> <!--#if not $have_ntfosd then 'readonly="readonly" disabled="disabled"' else "" #--> />
|
||||
<span class="desc">$T('explain-ntfosd_enable')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<div class="field-pair" >
|
||||
<label class="config" for="growl_enable">$T('opt-growl_enable')</label>
|
||||
<input type="checkbox" name="growl_enable" id="growl_enable" value="1" <!--#if int($growl_enable) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-growl_enable')</span>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="growl_server">$T('opt-growl_server')</label>
|
||||
<input type="url" name="growl_server" id="growl_server" value="$growl_server" size="40" />
|
||||
<input type="text" name="growl_server" id="growl_server" value="$growl_server" size="40" />
|
||||
<span class="desc">$T('explain-growl_server')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -163,7 +177,7 @@
|
||||
success: function (data) {
|
||||
if (data.status == true) {
|
||||
\$('#testnotice-result').addClass("success").html('$T('smpl-notesent')');
|
||||
} else {
|
||||
} else {
|
||||
\$('#testnotice-result').addClass("failure").html(data.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--#end if#-->
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
<td>
|
||||
<input type="submit" class="Save" value="$T('button-save')" />
|
||||
<input type="button" class="delFilter" value="$T('button-x')" />
|
||||
<!--#if not $rss[$feed].filter_states[$fnum]#--> $T('Incorrect filter')<!--#end if#-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -497,6 +498,10 @@
|
||||
</div><!-- /colmask -->
|
||||
|
||||
<script>
|
||||
function urlencode(str) {
|
||||
return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
|
||||
}
|
||||
|
||||
\$(document).ready(function(){
|
||||
\$('.editFeed').click(function(){
|
||||
var oldURI = \$(this).prev().val();
|
||||
@@ -510,7 +515,7 @@
|
||||
}).done(function( msg ) {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -536,7 +541,7 @@
|
||||
url: "test_rss_feed",
|
||||
data: {feed: whichFeed, session: "$session" }
|
||||
}).done(function( msg ) {
|
||||
location = '?feed=' + whichFeed;
|
||||
location = '?feed=' + urlencode(whichFeed);
|
||||
// location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,17 +34,16 @@ else:
|
||||
</select>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="dayofweek">$T('sch-frequency')</label>
|
||||
<select name="dayofweek" id="dayofweek">
|
||||
<option value="*" selected="selected">$T('daily')</option>
|
||||
<option value="1">$T('monday')</option>
|
||||
<option value="2">$T('tuesday')</option>
|
||||
<option value="3">$T('wednesday')</option>
|
||||
<option value="4">$T('thursday')</option>
|
||||
<option value="5">$T('friday')</option>
|
||||
<option value="6">$T('saturday')</option>
|
||||
<option value="7">$T('sunday')</option>
|
||||
</select>
|
||||
<label class="config" for="daysofweek">$T('sch-frequency')</label>
|
||||
<div class="checkbox-days">
|
||||
<p><input type="checkbox" name="daysofweek" value="1"><label>$T('monday')</label></p>
|
||||
<p><input type="checkbox" name="daysofweek" value="2"><label>$T('tuesday')</label></p>
|
||||
<p><input type="checkbox" name="daysofweek" value="3"><label>$T('wednesday')</label></p>
|
||||
<p><input type="checkbox" name="daysofweek" value="4"><label>$T('thursday')</label></p>
|
||||
<p><input type="checkbox" name="daysofweek" value="5"><label>$T('friday')</label></p>
|
||||
<p><input type="checkbox" name="daysofweek" value="6"><label>$T('saturday')</label></p>
|
||||
<p><input type="checkbox" name="daysofweek" value="7"><label>$T('sunday')</label></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="action">$T('sch-action')</label>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="password">$T('srv-password')</label>
|
||||
<input type="text" name="password" id="password" size="30" />
|
||||
<input type="password" name="password" id="password" size="30" />
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="connections">$T('srv-connections')</label>
|
||||
@@ -86,18 +86,16 @@
|
||||
<div class="section">
|
||||
<div class="col2">
|
||||
<h3>$servers[$server]['name']</h3>
|
||||
<!--#if 'amounts' in $servers[$server]#-->
|
||||
<p>
|
||||
<b>$T('srv-bandwidth'):</b><br/>
|
||||
$T('total'): $servers[$server]['amounts'][0]<br/>
|
||||
$T('today'): $servers[$server]['amounts'][3]<br/>
|
||||
$T('thisWeek'): $servers[$server]['amounts'][2]<br/>
|
||||
$T('thisMonth'): $servers[$server]['amounts'][1]
|
||||
<table><tr>
|
||||
<td><input type="checkbox" class="toggleServerCheckbox" name="q_enable" value="1" <!--#if int($servers[$server]['enable']) != 0 then 'checked="checked"' else ""#--> rel="$server" /></td>
|
||||
<td> $T('enabled')</td>
|
||||
</tr></table>
|
||||
</p>
|
||||
<input type="button" value="$T('button-clrServer')" class="clrServer" />
|
||||
<!--#end if#-->
|
||||
<input type="button" value="$T('showDetails')" class="showserver" />
|
||||
</div><!-- /col2 -->
|
||||
<div class="col1">
|
||||
<div class="col1" style="display:none;">
|
||||
<fieldset>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="enable$cur">$T('srv-enable')</label>
|
||||
@@ -154,6 +152,15 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
<div class="col2" style="display:block;">
|
||||
<!--#if 'amounts' in $servers[$server]#-->
|
||||
<b>$T('srv-bandwidth'):</b><br/>
|
||||
$T('total'): $(servers[$server]['amounts'][0])B<br/>
|
||||
$T('today'): $(servers[$server]['amounts'][3])B<br/>
|
||||
$T('thisWeek'): $(servers[$server]['amounts'][2])B<br/>
|
||||
$T('thisMonth'): $(servers[$server]['amounts'][1])B
|
||||
<!--#end if#-->
|
||||
</div>
|
||||
</div><!-- /section -->
|
||||
</form>
|
||||
<!--#end for#-->
|
||||
@@ -162,6 +169,15 @@
|
||||
|
||||
<script>
|
||||
\$(document).ready(function(){
|
||||
\$('.showserver').click(function () {
|
||||
\$(this).parent().next().toggle();
|
||||
\$(this).parent().next().next().toggle();
|
||||
if (\$(this).attr("value") == "$T('showDetails')") {
|
||||
\$(this).attr("value", "$T('hideDetails')");
|
||||
} else {
|
||||
\$(this).attr("value", "$T('showDetails')");
|
||||
}
|
||||
});
|
||||
\$('#addServerButton').click(function(){
|
||||
\$('#addServer').hide();
|
||||
\$('#addServerContent').show();
|
||||
@@ -188,6 +204,16 @@
|
||||
\$(this).parents('form:first').attr('action','clrServer').submit();
|
||||
return false;
|
||||
});
|
||||
\$('.toggleServerCheckbox').click(function(){
|
||||
var whichServer = \$(this).attr("rel");
|
||||
\$.ajax({
|
||||
type: "POST",
|
||||
url: "toggleServer",
|
||||
data: {server: whichServer, session: "$session" }
|
||||
}).done(function() {
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -265,6 +265,11 @@
|
||||
<td>$T('sort-File')</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="align-right"><b>$T('orgDirname'):</b></td>
|
||||
<td>%dn</td>
|
||||
<td>$T("sort-Folder")</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td class="align-right"><b>$T('lowercase'):</b></td>
|
||||
<td>{$T('TEXT')}</td>
|
||||
<td>$T('text')</td>
|
||||
@@ -432,7 +437,7 @@
|
||||
return function(callback, ms){
|
||||
clearTimeout (timer);
|
||||
timer = setTimeout(callback, ms);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function tvSet(val) {
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
<fieldset>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="check_new_rel">$T('opt-check_new_rel')</label>
|
||||
<input type="checkbox" name="check_new_rel" id="check_new_rel" value="1" <!--#if int($check_new_rel) > 0 then 'checked="checked"' else ""#--> />
|
||||
<select name="check_new_rel" id="check_new_rel">
|
||||
<option value="0" <!--#if $check_new_rel == 0 then 'selected="selected" class="selected"' else ""#--> >$T('off')</option>
|
||||
<option value="1" <!--#if $check_new_rel == 1 then 'selected="selected" class="selected"' else ""#--> >$T('on')</option>
|
||||
<option value="2" <!--#if $check_new_rel == 2 then 'selected="selected" class="selected"' else ""#--> >$T('also-test')</option>
|
||||
</select>
|
||||
<span class="desc">$T('explain-check_new_rel')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -9,7 +9,7 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>SABnzbd $version - $T('queued'): $mbleft $T('MB')</title>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="${path}rss?mode=history"/>
|
||||
|
||||
@@ -126,8 +126,10 @@
|
||||
<div class="config_sprite_container sprite_config_nav_scheduling">$T('Plush-cmenu-scheduling')</div></a></li>
|
||||
<li><a class="#if $pane=="Email"#nav_active#end if#" id="config_nav_email" href="${path}config/notify/">
|
||||
<div class="config_sprite_container sprite_config_nav_email">$T('cmenu-notif')</div></a></li>
|
||||
<!--#if 0#-->
|
||||
<li><a class="#if $pane=="Index Sites"#nav_active#end if#" id="config_nav_index_sites" href="${path}config/indexers/">
|
||||
<div class="config_sprite_container sprite_config_nav_indexsites">$T('cmenu-newzbin')</div></a></li>
|
||||
<!--#end if#-->
|
||||
<li><a class="#if $pane=="Categories"#nav_active#end if#" id="config_nav_categories" href="${path}config/categories/">
|
||||
<div class="config_sprite_container sprite_config_nav_categories">$T('cmenu-cat')</div></a></li>
|
||||
<li><a class="#if $pane=="Sorting"#nav_active#end if#" id="config_nav_sorting" href="${path}config/sorting/">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</table>
|
||||
<div class="sabnzbd_logo main_sprite_container sprite_sabnzbdplus_logo"></div>
|
||||
<p><strong>SABnzbd $T('version'):</strong> $version</p>
|
||||
<p><small>Copyright (C) 2008-2012, The SABnzbd Team <<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>></small></p>
|
||||
<p><small>Copyright (C) 2008-2012, The SABnzbd Team <team@sabnzbd.org></small></p>
|
||||
<p><small>$T('yourRights')</small></p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -43,90 +43,6 @@
|
||||
</fieldset>
|
||||
</div><!-- /component-group1 -->
|
||||
|
||||
<div id="core-component-group2" class="component-group clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3>Newzbin $T('accountInfo')</h3>
|
||||
<p>$T('explain-newzbin')</p>
|
||||
</div>
|
||||
<fieldset class="component-group-list">
|
||||
<div class="field-pair">
|
||||
<label class="nocheck clearfix" for="username_newzbin">
|
||||
<span class="component-title">$T('opt-username_newzbin')</span>
|
||||
<input type="text" name="username_newzbin" id="username_newzbin" value="$username_newzbin"/>
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">$T('explain-username_newzbin')</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="nocheck clearfix" for="password_newzbin">
|
||||
<span class="component-title">$T('opt-password_newzbin')</span>
|
||||
<input type="password" name="password_newzbin" id="password_newzbin" value="$password_newzbin"/>
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">$T('explain-password_newzbin')</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /component-group2 -->
|
||||
|
||||
<div id="core-component-group3" class="component-group clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3>Newzbin $T('newzbinBookmarks')</h3>
|
||||
<p>
|
||||
<input type="button" class="juiButton" id="getBookmarks" value="$T('link-getBookmarks')" />
|
||||
<br/><br/>
|
||||
<!--#if $bookmarks_list#-->
|
||||
<input type="button" class="juiButton" id="hideBookmarks" value="$T('link-HideBM')" />
|
||||
<!--#else#-->
|
||||
<input type="button" class="juiButton" id="showBookmarks" value="$T('link-ShowBM')" />
|
||||
<!--#end if#-->
|
||||
</p>
|
||||
</div>
|
||||
<fieldset class="component-group-list">
|
||||
<div class="field-pair">
|
||||
<input type="checkbox" name="newzbin_bookmarks" id="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks > 0 then "checked=1" else ""#--> />
|
||||
<label class="clearfix" for="newzbin_bookmarks">
|
||||
<span class="component-title">$T('opt-newzbin_bookmarks')</span>
|
||||
<span class="component-desc">$T('explain-newzbin_bookmarks')</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<input type="checkbox" name="newzbin_unbookmark" id="newzbin_unbookmark" value="1" <!--#if $newzbin_unbookmark > 0 then "checked=1" else ""#--> />
|
||||
<label class="clearfix" for="newzbin_unbookmark">
|
||||
<span class="component-title">$T('opt-newzbin_unbookmark')</span>
|
||||
<span class="component-desc">$T('explain-newzbin_unbookmark')</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="nocheck clearfix" for="bookmark_rate">
|
||||
<span class="component-title">$T('opt-bookmark_rate')</span>
|
||||
<input type="text" name="bookmark_rate" id="bookmark_rate" size="6" value="$bookmark_rate"/>
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">$T('explain-bookmark_rate')</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /component-group3 -->
|
||||
|
||||
<!--#if $bookmarks_list#-->
|
||||
<div id="core-component-group4" class="component-group clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3>Newzbin $T('accountInfo')</h3>
|
||||
<p>$T('explain-newzbin')</p>
|
||||
</div>
|
||||
<fieldset class="component-group-list">
|
||||
<!--#for $msgid in $bookmarks_list#-->
|
||||
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a><br/>
|
||||
<!--#end for#-->
|
||||
</fieldset>
|
||||
</div><!-- /component-group4 -->
|
||||
<!--#end if#-->
|
||||
|
||||
<div class="component-group-last clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3> </h3>
|
||||
|
||||
@@ -174,6 +174,7 @@ $T('explain-RSS')
|
||||
<table class="rssTable">
|
||||
<tr>
|
||||
<th>$T('Plush-rss-delete')</th>
|
||||
<th> </th>
|
||||
<th>$T('rss-order')</th>
|
||||
<th>$T('rss-type')</th>
|
||||
<th>$T('rss-filter')</th>
|
||||
@@ -187,6 +188,7 @@ $T('explain-RSS')
|
||||
<form action="upd_rss_filter" method="get">
|
||||
<tr class="odd">
|
||||
<td></td>
|
||||
<td><input type="checkbox" name="enabled" value="1" checked="checked" /></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<select name="filter_type">
|
||||
@@ -255,7 +257,10 @@ $T('explain-RSS')
|
||||
</td>
|
||||
|
||||
<form action="upd_rss_filter" method="get">
|
||||
<td>
|
||||
<td>
|
||||
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6] == '1' then 'checked="checked"' else ""#--> />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="3" name="new_index" value=$fnum>
|
||||
</td>
|
||||
<td>
|
||||
@@ -310,6 +315,7 @@ $T('explain-RSS')
|
||||
<input type="hidden" name="feed" value="$feed"/>
|
||||
<input type="hidden" name="session" value="$session">
|
||||
<input type="submit" class="juiButton" value="$T('button-save')"/>
|
||||
<!--#if not $rss[$feed].filter_states[$fnum]#--> $T('Incorrect filter')<!--#end if#-->
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
|
||||
@@ -36,18 +36,15 @@ else:
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="nocheck clearfix" for="dayofweek">
|
||||
<label class="nocheck clearfix" for="daysofweek">
|
||||
<span class="component-title">$T('sch-frequency')</span>
|
||||
<select name="dayofweek" id="dayofweek">
|
||||
<option value="*" selected>$T('daily')</option>
|
||||
<option value="1">$T('monday')</option>
|
||||
<option value="2">$T('tuesday')</option>
|
||||
<option value="3">$T('wednesday')</option>
|
||||
<option value="4">$T('thursday')</option>
|
||||
<option value="5">$T('friday')</option>
|
||||
<option value="6">$T('saturday')</option>
|
||||
<option value="7">$T('sunday')</option>
|
||||
</select>
|
||||
<input type="checkbox" name="daysofweek" value="1">$T('monday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="2">$T('tuesday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="3">$T('wednesday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="4">$T('thursday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="5">$T('friday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="6">$T('saturday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="7">$T('sunday')<br/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="field-pair alt">
|
||||
<label class="nocheck clearfix" for="password">
|
||||
<span class="component-title">$T('srv-password')</span>
|
||||
<input type="text" size="25" name="password"/>
|
||||
<input type="password" size="25" name="password"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
@@ -156,7 +156,7 @@
|
||||
<div class="field-pair alt">
|
||||
<label class="nocheck clearfix" for="password">
|
||||
<span class="component-title">$T('srv-password')</span>
|
||||
<input type="text" size="25" name="password" value="$servers[$server]['password']" />
|
||||
<input type="password" size="25" name="password" value="$servers[$server]['password']" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<a class="sf-with-ul">$T('menu-queue')</a>
|
||||
<ul>
|
||||
<!--#if $have_quota#--><li><a id="reset_quota_now" class="pointer">$T('link-resetQuota')</a></li><!--#end if#-->
|
||||
<!--#if $varExists('newzbinDetails')#--><li><a id="get_bookmarks_now" class="pointer">$T('link-getBookmarks')</a></li><!--#end if#-->
|
||||
<!--#if $have_rss_defined#--><li><a id="get_rss_now" class="pointer">$T('button-rssNow')</a></li><!--#end if#-->
|
||||
<!--#if $have_watched_dir#--><li><a id="get_watched_now" class="pointer">$T('sch-scan_folder')</a></li><!--#end if#-->
|
||||
<li><a id="topmenu_toggle" class="pointer">$T('Plush-topMenu')</a></li>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</td>
|
||||
|
||||
<td class="download-title">
|
||||
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.missing#-->$T('missingArt'): $slot.missing<!--#end if#-->">$slot.filename</a>
|
||||
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.missing#-->$T('missingArt'): $slot.missing<!--#end if#-->">$slot.filename.replace('.', '.​').replace('_', '_​')</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
@@ -40,6 +40,7 @@ jQuery(function($){
|
||||
$('#addID').click(function(){ // also works when hitting enter because of <form>
|
||||
if ($('#addID_input').val()!='URL') {
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {
|
||||
@@ -158,6 +159,7 @@ jQuery(function($){
|
||||
else
|
||||
$('#speed-wrapper .sprite_q_menu_pausefor').removeClass('sprite_q_menu_pausefor_on');
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'config', name:'set_speedlimit', value: str, apikey: $.plush.apikey}
|
||||
@@ -213,6 +215,7 @@ jQuery(function($){
|
||||
else
|
||||
$('.sprite_q_queue').removeClass('sprite_q_queue_on');
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'change_complete_action', value: $(this).val(), apikey: $.plush.apikey}
|
||||
@@ -234,6 +237,7 @@ jQuery(function($){
|
||||
value="all";
|
||||
}
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'delete', value:value, del_files:del_files, apikey: $.plush.apikey},
|
||||
@@ -258,6 +262,7 @@ jQuery(function($){
|
||||
case 'sortSizeDesc': sort='size'; dir='desc'; break;
|
||||
}
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'sort', sort: sort, dir: dir, apikey: $.plush.apikey},
|
||||
@@ -272,6 +277,7 @@ jQuery(function($){
|
||||
minutes = prompt($(event.target).attr('title'));
|
||||
$.plush.SetQueuePauseInfo(true,minutes+':00');
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'config', name:'set_pause', value: minutes, apikey: $.plush.apikey},
|
||||
@@ -282,6 +288,7 @@ jQuery(function($){
|
||||
// Get Bookmarks
|
||||
$('#get_bookmarks_now').click(function() {
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'newzbin', name:'get_bookmarks', apikey: $.plush.apikey},
|
||||
@@ -292,6 +299,7 @@ jQuery(function($){
|
||||
// Reset Quota
|
||||
$('#reset_quota_now').click(function() {
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'reset_quota', apikey: $.plush.apikey},
|
||||
@@ -302,6 +310,7 @@ jQuery(function($){
|
||||
// Get RSS
|
||||
$('#get_rss_now').click(function() {
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'rss_now', apikey: $.plush.apikey},
|
||||
@@ -312,6 +321,7 @@ jQuery(function($){
|
||||
// Get Watched folder
|
||||
$('#get_watched_now').click(function() {
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'watched_now', apikey: $.plush.apikey},
|
||||
@@ -475,6 +485,7 @@ jQuery(function($){
|
||||
$('#pause_resume').removeClass('sprite_q_pause_on').addClass('sprite_q_pause');
|
||||
$('#pause_int').html("");
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'resume', apikey: $.plush.apikey}
|
||||
@@ -483,6 +494,7 @@ jQuery(function($){
|
||||
$('#pause_resume').removeClass('sprite_q_pause').addClass('sprite_q_pause_on');
|
||||
$('#pause_int').html("");
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'pause', apikey: $.plush.apikey}
|
||||
@@ -526,6 +538,7 @@ jQuery(function($){
|
||||
if ($(this).hasClass('sprite_ql_grip_resume_on')) {
|
||||
$(this).toggleClass('sprite_ql_grip_resume_on').toggleClass('sprite_ql_grip_pause_on');
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'pause', value: pid, apikey: $.plush.apikey}
|
||||
@@ -533,6 +546,7 @@ jQuery(function($){
|
||||
} else {
|
||||
$(this).toggleClass('sprite_ql_grip_resume_on').toggleClass('sprite_ql_grip_pause_on');
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'resume', value: pid, apikey: $.plush.apikey}
|
||||
@@ -577,6 +591,7 @@ jQuery(function($){
|
||||
var nzbid = $(this).parent().parent().attr('id');
|
||||
var oldPos = $('#'+nzbid)[0].rowIndex + $.plush.queuecurpage * $.plush.queuePerPage;
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'priority', value: nzbid, value2: $(this).val(), apikey: $.plush.apikey},
|
||||
@@ -599,6 +614,7 @@ jQuery(function($){
|
||||
var val = $(this).parent().parent().attr('id');
|
||||
var cval = $(this).attr('class').split(" ")[0]; // ignore added "hovering" class
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode: cval, value: val, value2: $(this).val(), apikey: $.plush.apikey},
|
||||
@@ -687,6 +703,7 @@ $.plush.queueprevslots = $.plush.queuenoofslots; // for the next refresh
|
||||
if (table.tBodies[0].rows[i].id == row.id) {
|
||||
val2 = (i + $.plush.queuecurpage * $.plush.queuePerPage);
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'switch', value: row.id, value2: val2, apikey: $.plush.apikey},
|
||||
@@ -772,6 +789,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
|
||||
if ($('#multi_status').val())
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:$('#multi_status').val(), value: nzo_ids, apikey: $.plush.apikey}
|
||||
@@ -779,6 +797,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
|
||||
if ($('#multi_cat').val())
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode: 'change_cat', value: nzo_ids, value2: $('#multi_cat').val(), apikey: $.plush.apikey}
|
||||
@@ -786,6 +805,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
|
||||
if ($('#multi_priority').val())
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'queue', name:'priority', value: nzo_ids, value2: $('#multi_priority').val(), apikey: $.plush.apikey}
|
||||
@@ -793,6 +813,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
|
||||
if ($('#multi_pp').val())
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode: 'change_opts', value: nzo_ids, value2: $('#multi_pp').val(), apikey: $.plush.apikey}
|
||||
@@ -800,6 +821,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
|
||||
if ($('#multi_script').val())
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode: 'change_script', value: nzo_ids, value2: $('#multi_script').val(), apikey: $.plush.apikey}
|
||||
@@ -870,9 +892,10 @@ $("a","#multiops_inputs").click(function(e){
|
||||
value="failed";
|
||||
}
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:'history', name:'delete', value:value, del_files:del_files, apikey: $.plush.apikey},
|
||||
data: {mode:'history', name:'delete', value:value, del_files:del_files, search: $('#historySearchBox').val(), apikey: $.plush.apikey},
|
||||
success: function(){
|
||||
$.colorbox.close();
|
||||
$.plush.modalOpen=false;
|
||||
@@ -959,6 +982,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
$.plush.pendingHistoryRefresh = true;
|
||||
$.colorbox.close();
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "tapi",
|
||||
data: {mode:mode, name:'delete', value: delid, del_files: del_files, apikey: $.plush.apikey},
|
||||
@@ -1085,6 +1109,7 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
|
||||
|
||||
// Fetch updated content from queue.tmpl
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "queue/",
|
||||
data: {start: ( page * $.plush.queuePerPage ), limit: $.plush.queuePerPage},
|
||||
@@ -1140,6 +1165,7 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
|
||||
|
||||
|
||||
$.ajax({
|
||||
headers: {"Cache-Control": "no-cache"},
|
||||
type: "POST",
|
||||
url: "history/",
|
||||
data: data,
|
||||
@@ -1216,12 +1242,16 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
|
||||
SetQueueETAStats : function(speed,kbpersec,timeleft,eta) {
|
||||
|
||||
// ETA/speed stats at top of queue
|
||||
if (kbpersec < 1 && $.plush.paused)
|
||||
if (kbpersec < 1 || $.plush.paused) {
|
||||
$('#stats_eta').html('—');
|
||||
else
|
||||
$('#stats_speed').html('—');
|
||||
$('#time-left').attr('title','—'); // Tooltip on "time left"
|
||||
}
|
||||
else {
|
||||
$('#stats_eta').html(timeleft);
|
||||
$('#stats_speed').html(speed+"B/s");
|
||||
$('#time-left').attr('title',eta); // Tooltip on "time left"
|
||||
$('#stats_speed').html(speed+"B/s");
|
||||
$('#time-left').attr('title',eta); // Tooltip on "time left"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!--#for $warn in $warnings#-->
|
||||
<!--#set $odd = not $odd#-->
|
||||
<tr class="<!--#if $odd then "odd" else "even"#-->">
|
||||
<td>$warn.replace("\n","</td><td>")</td></tr>
|
||||
<td>$warn.replace("\n","</td><td>", 2)</td></tr>
|
||||
<!--#end for#-->
|
||||
</table>
|
||||
<!--#else#-->
|
||||
|
||||
@@ -1,69 +1,4 @@
|
||||
<a href="${helpuri}Configure+Indexers-0-7" id="help" target="_blank">$T('menu-help')</a><h3>Newzbin</h3>
|
||||
<form id="configNewzbin" class="cmxform" autocomplete="off">
|
||||
|
||||
$T('explain-newzbin')<br/>
|
||||
<br/>
|
||||
<div class="EntryBlock">
|
||||
|
||||
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('accountInfo')</legend>
|
||||
<hr />
|
||||
<label class="label">$T('opt-username_newzbin'):</label>
|
||||
<input type="text" name="username_newzbin" value="$username_newzbin">
|
||||
<span class="tips">$T('explain-username_newzbin')</span>
|
||||
<br class="clear" />
|
||||
|
||||
|
||||
<label class="label">$T('opt-password_newzbin'):</label>
|
||||
<input type="password" name="password_newzbin" value="$password_newzbin">
|
||||
<span class="tips">$T('explain-password_newzbin')</span>
|
||||
<br class="clear" />
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('newzbinBookmarks')</legend>
|
||||
<hr />
|
||||
|
||||
<label><span class="label">$T('newzbinBookmarks'):</span>
|
||||
<input class="radio" type="checkbox" name="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks > 0 then "checked=1" else ""#--> />
|
||||
<span class="tips">$T('explain-newzbin_bookmarks')</span></label>
|
||||
<br class="clear" />
|
||||
|
||||
<label><span class="label">$T('opt-newzbin_unbookmark'):</span>
|
||||
<input class="radio" type="checkbox" name="newzbin_unbookmark" value="1" <!--#if $newzbin_unbookmark > 0 then "checked=1" else ""#--> />
|
||||
<span class="tips">$T('explain-newzbin_unbookmark')</span></label>
|
||||
<br class="clear" />
|
||||
|
||||
<label class="label">$T('opt-bookmark_rate'):</label>
|
||||
<input type="text" name="bookmark_rate" value="$bookmark_rate">
|
||||
<span class="tips">$T('explain-bookmark_rate')</span>
|
||||
<br class="clear" />
|
||||
</fieldset>
|
||||
|
||||
<a class="config" onClick="getBookmarks();">$T('link-getBookmarks')</a>
|
||||
<!--#if $bookmarks_list#-->
|
||||
<a class="config" onClick="lr('config/indexers/hideBookmarks');">$T('link-HideBM')</a>
|
||||
<!--#else#-->
|
||||
<a class="config" onClick="lr('config/indexers/showBookmarks');">$T('link-ShowBM')</a>
|
||||
<!--#end if#-->
|
||||
|
||||
<!--#if $bookmarks_list#-->
|
||||
<fieldset class="EntryFieldSet">
|
||||
<legend>$T('processedBM')</legend>
|
||||
<hr />
|
||||
<!--#for $msgid in $bookmarks_list#-->
|
||||
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a>
|
||||
<!--#end for#-->
|
||||
<br class="clear" />
|
||||
</fieldset>
|
||||
<!--#end if#-->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<br/><hr/>
|
||||
<a href="${helpuri}Configure+Indexers-0-7" id="help" target="_blank">$T('menu-help')</a>
|
||||
<h3>NzbMatrix</h3><br/>
|
||||
|
||||
$T('explain-nzbmatrix')<br/>
|
||||
|
||||
@@ -79,6 +79,7 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th>$T('rss-order')</th>
|
||||
<th>$T('rss-type')</th>
|
||||
<th>$T('rss-filter')</th>
|
||||
@@ -92,6 +93,7 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
|
||||
<tbody>
|
||||
<tr id="$feed+new_filter">
|
||||
<td></td>
|
||||
<td><input type="checkbox" name="enabled" value="1" checked="checked" /></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<select name="filter_type">
|
||||
@@ -157,7 +159,9 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
|
||||
<td>
|
||||
<input type="submit" value="$T('rss-delFilter')" onclick="javascript:lr('config/rss/del_rss_filter','index=$fnum&feed=$feed')" />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6] == '1' then 'checked="checked"' else ""#--> />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" size="3" name="new_index" value=$fnum>
|
||||
</td>
|
||||
@@ -213,6 +217,7 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
|
||||
<input type="hidden" name="index" value="$fnum"/>
|
||||
<input type="hidden" name="feed" value="$feed"/>
|
||||
<input type="submit" value="$T('button-save')" onclick="javascript:submitconfig('config/rss/upd_rss_filter', this,'$feed+$fnum','1' )"/>
|
||||
<!--#if not $rss[$feed].filter_states[$fnum]#--> $T('Incorrect filter')<!--#end if#-->
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
@@ -26,17 +26,14 @@ else:
|
||||
<!--#end for#-->
|
||||
</select>
|
||||
<br class="clear" />
|
||||
<label class="label">$T('sch-frequency'):</label>
|
||||
<select name="dayofweek">
|
||||
<option value="*" selected>$T('daily')
|
||||
<option value="1">$T('monday')
|
||||
<option value="2">$T('tuesday')
|
||||
<option value="3">$T('wednesday')
|
||||
<option value="4">$T('thursday')
|
||||
<option value="5">$T('friday')
|
||||
<option value="6">$T('saturday')
|
||||
<option value="7">$T('sunday')
|
||||
</select>
|
||||
<label class="label" for="daysofweek">$T('sch-frequency'):</label>
|
||||
<input type="checkbox" name="daysofweek" value="1">$T('monday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="2">$T('tuesday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="3">$T('wednesday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="4">$T('thursday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="5">$T('friday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="6">$T('saturday')<br/>
|
||||
<input type="checkbox" name="daysofweek" value="7">$T('sunday')<br/>
|
||||
<br class="clear" />
|
||||
<label class="label">$T('sch-action'):</label>
|
||||
<select name="action">
|
||||
|
||||
@@ -808,7 +808,7 @@ function lrb(url, extra, refresh)
|
||||
{
|
||||
method:'POST',
|
||||
sendContent:values,
|
||||
headers: {"Content-Type":"application/x-www-form-urlencoded"}
|
||||
headers: {"Content-Type":"application/x-www-form-urlencoded", "Cache-Control": "no-cache"}
|
||||
|
||||
});
|
||||
d.addCallback(function(d)
|
||||
@@ -840,7 +840,7 @@ function lrb(url, extra, refresh)
|
||||
{
|
||||
method:'POST',
|
||||
sendContent:values,
|
||||
headers: {"Content-Type":"application/x-www-form-urlencoded"}
|
||||
headers: {"Content-Type":"application/x-www-form-urlencoded", "Cache-Control": "no-cache"}
|
||||
|
||||
});
|
||||
if (saveelement) {
|
||||
@@ -1133,7 +1133,9 @@ function loadingJSON(){
|
||||
<li><a class="config" href="$prefix/config/scheduling/" onclick="lr('config/scheduling/','', 0, 0);">$T('cmenu-scheduling')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/rss/" onclick="lr('config/rss/','', 0, 0);">$T('cmenu-rss')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/notify/" onclick="lr('config/notify/','', 0, 0);">$T('cmenu-notif')</a></li>
|
||||
<!--#if 0#-->
|
||||
<li><a class="config" href="$prefix/config/indexers/" onclick="lr('config/indexers/', '', 0, 0);">$T('cmenu-newzbin')</a></li>
|
||||
<!--#end if#-->
|
||||
<li><a class="config" href="$prefix/config/categories/" onclick="lr('config/categories/', '', 0, 0);">$T('cmenu-cat')</a></li>
|
||||
<li><a class="config" href="$prefix/config/sorting/" onclick="lr('config/sorting/', '', 0, 0);">$T('cmenu-sorting')</a></li>
|
||||
</ul>
|
||||
@@ -1152,9 +1154,6 @@ function loadingJSON(){
|
||||
<li><a class="config" onclick="javascript:timedPause()">$T("smpl-custom")</a></li>
|
||||
|
||||
</ul>
|
||||
<!--#if $varExists('newzbinDetails')#-->
|
||||
<li><a onclick="getBookmarks()">$T('smpl-getbookmarks')</a></li>
|
||||
<!--#end if#-->
|
||||
<!--#if $have_quota#-->
|
||||
<li><a onclick="resetQuota()">$T('link-resetQuota')</a></li>
|
||||
<!--#end if#-->
|
||||
@@ -1183,7 +1182,7 @@ function loadingJSON(){
|
||||
<div id="RightContainer" class="left-border">
|
||||
<div id="addNew" class="centerLinks" style="overflow: hidden; display: none;">
|
||||
<form action="addID" method="get">
|
||||
<input type="text" style="width:218px;" name="id" value="$T('enterURL')<!--#if $varExists('newzbinDetails') then $T('enterID') else '' #-->" onfocus="clearForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')" onblur="setForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')">
|
||||
<input type="text" style="width:218px;" name="id" value="$T('enterURL')" onfocus="clearForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')" onblur="setForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')">
|
||||
<!--#if $cat_list#-->
|
||||
<select name="cat" >
|
||||
<optgroup label="$T('category')">
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<!--#include $webdir + "/inc_top.tmpl"#-->
|
||||
<script type="text/javascript" src="static/javascript/jquery.js"></script>
|
||||
<script type="text/javascript" src="static/javascript/restart.js"></script>
|
||||
<br/><br/>
|
||||
<h4 id="restarting" class="align-center">$T('wizard-restarting')</h4>
|
||||
<h4 id="complete" class="align-center success hidden">$T('wizard-complete')</h4>
|
||||
<br />
|
||||
<br/>
|
||||
<div id="tips" class="hidden">
|
||||
$T('wizard-tip1') <span class="bold">$T('wizard-tip2')</span><br/>
|
||||
<!--#if len($urls) > 1#--><!--#set $s = 's'#--><!--#else#--><!--#set $s = ''#--><!--#end if#-->
|
||||
<!--#set $tip3 = $T('wizard-tip3') % $s#-->
|
||||
$tip3:<br/><br/>
|
||||
<div class="quoteBlock">
|
||||
<!--#set $i = 0#-->
|
||||
<!--#for $url in $urls#-->
|
||||
<!--#set $i = $i+1#-->
|
||||
<a href="$url">$url</a><!--#if $i != len($urls)#--><br /><!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</div><br/>
|
||||
$T('wizard-tip4')
|
||||
<br/><br/>
|
||||
$T('wizard-tip-wiki') <a href="$helpuri">wiki</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr /><br/>
|
||||
<div class="full-width">
|
||||
<table class="full-width">
|
||||
<tr class="align-center">
|
||||
<td><input type="hidden" name="session" id="apikey" value="$session"><input class="bigbutton disabled" type="button" onclick="document.location ='$access_url'" value="$T('wizard-goto')" disabled="disabled"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/inc_bottom.tmpl"#-->
|
||||
@@ -31,7 +31,7 @@ $T('wizard-explain-server')
|
||||
<div id="connections-tip" class="tips">$T('wizard-server-con-explain') $T('wizard-server-con-eg')</div>
|
||||
<div id="connections-error" class="error-text hidden">$T('wizard-server-number')</div>
|
||||
<br class="clear" />
|
||||
<label><span class="label">$T('srv-ssl')</span>
|
||||
<label for="srv-ssl"><!--#if $have_ssl then $T('srv-ssl') else '<span class="disabled-text">'+$T('srv-ssl')+'</span> (pyopenssl (python-ssl) '+$T('opt-notInstalled')+')'#-->
|
||||
<input class="validate-text" class="radio" type="checkbox" name="ssl" value="1" <!--#if $have_ssl then '' else 'disabled'#--><!--#if $ssl == 1 then 'checked' else ''#-->></label>
|
||||
<div class="tips">$T('wizard-server-ssl-explain')</div>
|
||||
<br class="clear" />
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
<!--#include $webdir + "/inc_top.tmpl"#-->
|
||||
|
||||
<form action="./four" method="post" autocomplete="off">
|
||||
<p>$T('wizard-index-explain')</p>
|
||||
<div id="serverDetails">
|
||||
<h3><a href="http://$newzbin_url" target="_blank">Newzbin2.es</a> ($T('wizard-optional'))</h3>
|
||||
<label class="label">$T('srv-username'):</label><input type="text" size="20" value="$newzbin_user" name="newzbin_user">
|
||||
<br class="clear" />
|
||||
<label class="label">$T('srv-password'):</label><input type="password" size="20" value="$newzbin_pass" name="newzbin_pass">
|
||||
<br class="clear" />
|
||||
<input type="checkbox" name="newzbin_bookmarks" id="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks == 1 then 'checked="checked"' else ''#-->> <label for="newzbin_bookmarks">$T('wizard-index-bookmark')</label><br />
|
||||
|
||||
|
||||
<h3><a href="http://nzbmatrix.com" target="_blank">NZBMatrix.com</a> ($T('wizard-optional'))</h3>
|
||||
<label class="label">$T('srv-username'):</label><input type="text" size="20" value="$matrix_user" name="matrix_user">
|
||||
<br class="clear" />
|
||||
<label class="label">$T('opt-apikey'):</label><input type="text" size="20" value="$matrix_apikey" name="matrix_apikey">
|
||||
</div></div>
|
||||
|
||||
<script type="text/javascript" src="static/javascript/jquery.js"></script>
|
||||
<script type="text/javascript" src="static/javascript/restart.js"></script>
|
||||
<br/><br/>
|
||||
<h4 id="restarting" class="align-center">$T('wizard-restarting')</h4>
|
||||
<h4 id="complete" class="align-center success hidden">$T('wizard-complete')</h4>
|
||||
<br />
|
||||
<br/>
|
||||
<div id="tips" class="hidden">
|
||||
$T('wizard-tip1') <span class="bold">$T('wizard-tip2')</span><br/>
|
||||
<!--#set $tip3 = $T('wizard-tip3') % ''#-->
|
||||
$tip3<br/><br/>
|
||||
<div class="quoteBlock">
|
||||
<!--#set $i = 0#-->
|
||||
<!--#for $url in $urls#-->
|
||||
<!--#set $i = $i+1#-->
|
||||
<a href="$url">$url</a><!--#if $i != len($urls)#--><br /><!--#end if#-->
|
||||
<!--#end for#-->
|
||||
</div><br/>
|
||||
$T('wizard-tip4')
|
||||
<br/><br/>
|
||||
$T('wizard-tip-wiki') <a href="$helpuri">wiki</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr /><br/>
|
||||
<div class="full-width">
|
||||
<table class="full-width">
|
||||
<tr>
|
||||
<td><input class="bigbutton" type="button" onclick="document.location ='./two'" value="‹ $T('wizard-previous')" /></td>
|
||||
<td>
|
||||
<div class="align-center">
|
||||
<!--#for $step in xrange($steps)#-->
|
||||
<!--#set $step = $step + 1#-->
|
||||
<span class="<!--#if $step == $number then 'selected' else 'unselected'#-->">$step</span>
|
||||
<!--#end for#-->
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-right"><input class="bigbutton" type="submit" value="$T('wizard-next') »" /></td>
|
||||
<tr class="align-center">
|
||||
<td><input type="hidden" name="session" id="apikey" value="$session"><input class="bigbutton disabled" type="button" onclick="document.location ='$access_url'" value="$T('wizard-goto')" disabled="disabled"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!--#include $webdir + "/inc_bottom.tmpl"#-->
|
||||
<!--#include $webdir + "/inc_bottom.tmpl"#-->
|
||||
103
make_dmg.py
Normal file
103
make_dmg.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python -OO
|
||||
#
|
||||
# Copyright 2008-2012 The SABnzbd-Team <team@sabnzbd.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print 'Usage: %s <release>' % os.path.split(sys.argv[0])[1]
|
||||
exit(1)
|
||||
|
||||
# Setup file names
|
||||
release = sys.argv[1]
|
||||
prod = 'SABnzbd-' + release
|
||||
fileDmg = prod + '-osx.dmg'
|
||||
fileOSr = prod + '-osx-src.tar.gz'
|
||||
fileImg = prod + '.sparseimage'
|
||||
builds = ('sl', 'lion', 'ml')
|
||||
build_folders = (
|
||||
'OS X 10.5 and 10.6 (Leopards)',
|
||||
'OS X 10.7 (Lion)',
|
||||
'OS X 10.8 (Mountain Lion)'
|
||||
)
|
||||
|
||||
# Check presense of all builds
|
||||
build_paths = []
|
||||
for build in builds:
|
||||
path = os.path.join(os.environ['HOME'], 'project/osx/%s-%s.cpio' % (prod, build))
|
||||
if os.path.exists(path):
|
||||
build_paths.append(path)
|
||||
else:
|
||||
print 'Missing build %s' % path
|
||||
exit(1)
|
||||
|
||||
# Create sparseimage from template
|
||||
os.system("unzip -o osx/image/template.sparseimage.zip")
|
||||
os.rename('template.sparseimage', fileImg)
|
||||
|
||||
# mount sparseimage and modify volume label
|
||||
os.system("hdiutil mount %s | grep /Volumes/SABnzbd >mount.log" % fileImg)
|
||||
|
||||
# Rename the volume
|
||||
fp = open('mount.log', 'r')
|
||||
data = fp.read()
|
||||
fp.close()
|
||||
os.remove('mount.log')
|
||||
m = re.search(r'/dev/(\w+)\s+', data)
|
||||
volume = 'SABnzbd-' + str(release)
|
||||
os.system('diskutil rename %s %s' % (m.group(1), volume))
|
||||
|
||||
authority = os.environ.get('SIGNING_AUTH')
|
||||
|
||||
# Unpack build into image and sign if possible
|
||||
for build in xrange(len(builds)):
|
||||
vol_path = '/Volumes/%s/%s/' % (volume, build_folders[build])
|
||||
os.system('ditto -x -z "%s" "%s"' % (build_paths[build], vol_path))
|
||||
if authority:
|
||||
app_name = '%s-%s' % (volume, builds[build])
|
||||
os.system('codesign -f -i "%s" -s "%s" "%s/SABnzbd.app"' % (app_name, authority, vol_path))
|
||||
|
||||
|
||||
# Put README.rtf in root
|
||||
from_path = '/Volumes/%s/%s/SABnzbd.app/Contents/Resources/Credits.rtf' % (volume, build_folders[0])
|
||||
to_path = '/Volumes/%s/README.rtf' % volume
|
||||
os.system('cp "%s" "%s"' % (from_path, to_path))
|
||||
|
||||
# Unmount sparseimage
|
||||
print 'Eject volume'
|
||||
os.system("hdiutil eject /Volumes/%s/>/dev/null" % volume)
|
||||
|
||||
print 'Wait 1 second'
|
||||
os.system("sleep 1")
|
||||
|
||||
# Convert sparseimage to read-only compressed dmg
|
||||
print 'Create DMG file'
|
||||
if os.path.exists(fileDmg):
|
||||
os.remove(fileDmg)
|
||||
os.system("hdiutil convert %s -format UDBZ -o %s>/dev/null" % (fileImg, fileDmg))
|
||||
|
||||
# Remove sparseimage
|
||||
os.system("rm %s>/dev/null" % fileImg)
|
||||
|
||||
print 'Make image internet-enabled'
|
||||
os.system("hdiutil internet-enable %s" % fileDmg)
|
||||
|
||||
print 'Copy GZ file'
|
||||
os.system('cp ~/project/osx/%s .' % fileOSr)
|
||||
BIN
osx/image/sabnzbd.png
Normal file
BIN
osx/image/sabnzbd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 388 B |
BIN
osx/resources/sab_clicked.tiff
Normal file
BIN
osx/resources/sab_clicked.tiff
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 902 B |
BIN
osx/resources/sab_idle.tiff
Normal file
BIN
osx/resources/sab_idle.tiff
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
BIN
osx/resources/sab_pause.tiff
Normal file
BIN
osx/resources/sab_pause.tiff
Normal file
Binary file not shown.
BIN
osx/unrar/unrar
BIN
osx/unrar/unrar
Binary file not shown.
193
package.py
193
package.py
@@ -34,12 +34,13 @@ except ImportError:
|
||||
try:
|
||||
import py2app
|
||||
from setuptools import setup
|
||||
OSX_LION = [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 7, 0]
|
||||
OSX_SL = not OSX_LION and [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 6, 0]
|
||||
OSX_LEOPARD = not (OSX_LION or OSX_SL)
|
||||
OSX_ML = [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 8, 0]
|
||||
OSX_LION = not OSX_ML and [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 7, 0]
|
||||
OSX_SL = not OSX_LION and not OSX_ML
|
||||
class WindowsError (): pass
|
||||
except ImportError:
|
||||
py2app = None
|
||||
|
||||
OSX_ML = OSX_LION = OSX_SL = False
|
||||
|
||||
VERSION_FILE = 'sabnzbd/version.py'
|
||||
VERSION_FILEAPP = 'osx/resources/InfoPlist.strings'
|
||||
@@ -47,8 +48,8 @@ VERSION_FILEAPP = 'osx/resources/InfoPlist.strings'
|
||||
my_version = 'unknown'
|
||||
my_baseline = 'unknown'
|
||||
|
||||
def DeleteFiles(name):
|
||||
''' Delete one file or set of files from wild-card spec '''
|
||||
def delete_files(name):
|
||||
""" Delete one file or set of files from wild-card spec """
|
||||
for f in glob.glob(name):
|
||||
try:
|
||||
if os.path.exists(f):
|
||||
@@ -95,7 +96,7 @@ def PatchVersion(name):
|
||||
try:
|
||||
pipe = subprocess.Popen(GitStatus, shell=True, stdout=subprocess.PIPE).stdout
|
||||
for line in pipe.read().split('\n'):
|
||||
if 'nothing to commit' in line:
|
||||
if 'nothing to commit' in line or 'nothing added to commit' in line:
|
||||
state = ''
|
||||
break
|
||||
pipe.close()
|
||||
@@ -151,8 +152,7 @@ def PairList(src):
|
||||
lst.append((path, flist))
|
||||
else:
|
||||
path, name = os.path.split(item)
|
||||
items = []
|
||||
items.append(name)
|
||||
items = [name]
|
||||
lst.append((path, items))
|
||||
return lst
|
||||
|
||||
@@ -207,7 +207,7 @@ def Dos2Unix(name):
|
||||
def Unix2Dos(name):
|
||||
""" Read file, remove \r, replace \n by \r\n and write back """
|
||||
base, ext = os.path.splitext(name)
|
||||
if ext.lower() not in ('.py', '.txt', '.css', '.js', '.tmpl', '.sh', '.cmd'):
|
||||
if ext.lower() not in ('.py', '.txt', '.css', '.js', '.tmpl', '.sh', '.cmd', '.mkd'):
|
||||
return
|
||||
|
||||
print name
|
||||
@@ -230,9 +230,9 @@ def Unix2Dos(name):
|
||||
|
||||
|
||||
def rename_file(folder, old, new):
|
||||
oldpath = "%s/%s" % (folder, old)
|
||||
newpath = "%s/%s" % (folder, new)
|
||||
try:
|
||||
oldpath = "%s/%s" % (folder, old)
|
||||
newpath = "%s/%s" % (folder, new)
|
||||
if os.path.exists(newpath):
|
||||
os.remove(newpath)
|
||||
os.rename(oldpath, newpath)
|
||||
@@ -246,6 +246,11 @@ print sys.argv[0]
|
||||
Git = CheckPath('git')
|
||||
ZipCmd = CheckPath('zip')
|
||||
UnZipCmd = CheckPath('unzip')
|
||||
if os.name != 'nt':
|
||||
PanDoc = CheckPath('pandoc')
|
||||
else:
|
||||
PanDoc = None
|
||||
|
||||
if os.name == 'nt':
|
||||
msg = 'Requires the standard version of NSIS'
|
||||
NSIS = CheckPath('makensis')
|
||||
@@ -254,7 +259,7 @@ if os.name == 'nt':
|
||||
os.system('%s >%s' % (NSIS, log))
|
||||
if 'Unicode' not in open(log).read():
|
||||
msg = ''
|
||||
DeleteFiles(log)
|
||||
delete_files(log)
|
||||
if msg:
|
||||
print msg
|
||||
exit(1)
|
||||
@@ -293,11 +298,14 @@ Win32TempName = 'SABnzbd-windows.exe'
|
||||
fileIns = prod + '-win32-setup.exe'
|
||||
fileBin = prod + '-win32-bin.zip'
|
||||
fileSrc = prod + '-src.tar.gz'
|
||||
fileDmg = prod + '-osx.dmg'
|
||||
fileDmgLp = prod + '-osx-leopard.dmg'
|
||||
fileDmg_ml = prod + '-osx-mountainlion.dmg'
|
||||
fileDmg_lion = prod + '-osx-lion.dmg'
|
||||
fileDmg_sl = prod + '-osx-snowleopard.dmg'
|
||||
fileOSr = prod + '-osx-src.tar.gz'
|
||||
fileImg = prod + '.sparseimage'
|
||||
|
||||
if OSX_SL: postfix = 'sl'
|
||||
if OSX_LION: postfix = 'lion'
|
||||
if OSX_ML: postfix = 'ml'
|
||||
|
||||
PatchVersion(release)
|
||||
|
||||
@@ -305,7 +313,7 @@ PatchVersion(release)
|
||||
# List of data elements, directories end with a '/'
|
||||
data_files = [
|
||||
'ABOUT.txt',
|
||||
'README.txt',
|
||||
'README.mkd',
|
||||
'INSTALL.txt',
|
||||
'GPL2.txt',
|
||||
'GPL3.txt',
|
||||
@@ -351,34 +359,19 @@ if target == 'app':
|
||||
os.system(GitRevertVersion)
|
||||
exit(1)
|
||||
|
||||
if not PanDoc:
|
||||
print "Sorry, requires pandoc in the $PATH"
|
||||
os.system(GitRevertVersion)
|
||||
exit(1)
|
||||
|
||||
# Check which Python flavour
|
||||
apple_py = 'ActiveState' not in sys.copyright
|
||||
|
||||
if OSX_LION:
|
||||
# Check if Leopard build is present
|
||||
leopard_build = '/project/leopard/%s' % str(my_version)
|
||||
if not os.path.isdir(leopard_build):
|
||||
print 'Leopard build not found at %s' % leopard_build
|
||||
exit(1)
|
||||
|
||||
# Create sparseimage from template
|
||||
os.system("unzip -o osx/image/template.sparseimage.zip")
|
||||
os.rename('template.sparseimage', fileImg)
|
||||
|
||||
# mount sparseimage and modify volume label
|
||||
os.system("hdiutil mount %s | grep /Volumes/SABnzbd >mount.log" % (fileImg))
|
||||
|
||||
# Rename the volume
|
||||
fp = open('mount.log', 'r')
|
||||
data = fp.read()
|
||||
fp.close()
|
||||
os.remove('mount.log')
|
||||
m = re.search(r'/dev/(\w+)\s+', data)
|
||||
volume = 'SABnzbd-' + str(my_version)
|
||||
os.system('disktool -n %s %s' % (m.group(1), volume))
|
||||
|
||||
options['description'] = 'SABnzbd ' + str(my_version)
|
||||
|
||||
# Remove previous build result
|
||||
os.system('rm -rf dist/ build/')
|
||||
|
||||
# Create MO files
|
||||
os.system('python ./tools/make_mo.py')
|
||||
|
||||
@@ -389,7 +382,7 @@ if target == 'app':
|
||||
DATA_FILES = ['interfaces', 'locale', 'email', ('', glob.glob("osx/resources/*"))]
|
||||
|
||||
NZBFILE = dict(
|
||||
CFBundleTypeExtensions = [ "nzb","zip","rar" ],
|
||||
CFBundleTypeExtensions = [ "nzb" ],
|
||||
CFBundleTypeIconFile = 'nzbfile.icns',
|
||||
CFBundleTypeMIMETypes = [ "text/nzb" ],
|
||||
CFBundleTypeName = 'NZB File',
|
||||
@@ -418,67 +411,63 @@ if target == 'app':
|
||||
setup_requires=['py2app'],
|
||||
)
|
||||
|
||||
# copy unrar & par2 binary to avoid striping
|
||||
# Remove 64bit code
|
||||
if not OSX_SL:
|
||||
os.system("mv dist/SABnzbd.app dist/SABnzbd.app.temp")
|
||||
os.system("ditto --arch i386 --arch ppc dist/SABnzbd.app.temp dist/SABnzbd.app/")
|
||||
os.system("rm -rf dist/SABnzbd.app.temp")
|
||||
|
||||
# copy unrar & par2 binary
|
||||
os.system("mkdir dist/SABnzbd.app/Contents/Resources/osx>/dev/null")
|
||||
os.system("mkdir dist/SABnzbd.app/Contents/Resources/osx/par2>/dev/null")
|
||||
os.system("cp -pR osx/par2/ dist/SABnzbd.app/Contents/Resources/osx/par2>/dev/null")
|
||||
os.system("mkdir dist/SABnzbd.app/Contents/Resources/osx/unrar>/dev/null")
|
||||
os.system("cp -pR osx/unrar/license.txt dist/SABnzbd.app/Contents/Resources/osx/unrar/ >/dev/null")
|
||||
if OSX_LION:
|
||||
os.system("cp -pR osx/unrar/unrar dist/SABnzbd.app/Contents/Resources/osx/unrar/ >/dev/null")
|
||||
else:
|
||||
if OSX_SL:
|
||||
os.system("cp -pR osx/unrar/unrar-leopard dist/SABnzbd.app/Contents/Resources/osx/unrar/unrar >/dev/null")
|
||||
else:
|
||||
os.system("cp -pR osx/unrar/unrar dist/SABnzbd.app/Contents/Resources/osx/unrar/ >/dev/null")
|
||||
os.system("cp icons/sabnzbd.ico dist/SABnzbd.app/Contents/Resources >/dev/null")
|
||||
os.system("cp README.rtf dist/SABnzbd.app/Contents/Resources/Credits.rtf >/dev/null")
|
||||
os.system("pandoc -f markdown -t rtf -s -o dist/SABnzbd.app/Contents/Resources/Credits.rtf README.mkd >/dev/null")
|
||||
os.system("find dist/SABnzbd.app -name .git | xargs rm -rf")
|
||||
|
||||
if OSX_LION:
|
||||
# Sign the App if possible
|
||||
authority = os.environ.get('SIGNING_AUTH')
|
||||
if authority:
|
||||
os.system('codesign -f -i "%s-lion" -s "%s" dist/SABnzbd.app' % (volume, authority))
|
||||
os.system('codesign -f -i "%s-leopard" -s "%s" %s/dist/SABnzbd.app' % (volume, authority, leopard_build))
|
||||
# Remove source files to prevent re-compilation, which would invalidate signing
|
||||
py_ver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
|
||||
os.system("find dist/SABnzbd.app/Contents/Resources/lib/python%s/Cheetah -name '*.py' | xargs rm" % py_ver)
|
||||
os.system("find dist/SABnzbd.app/Contents/Resources/lib/python%s/xml -name '*.py' | xargs rm" % py_ver)
|
||||
os.system('rm dist/SABnzbd.app/Contents/Resources/site.py')
|
||||
|
||||
# Add the SabNotifier app
|
||||
if OSX_ML and os.path.exists('/project/sabnotifier/SABnzbd.app'):
|
||||
os.system("cp -pR /project/sabnotifier/SABnzbd.app dist/SABnzbd.app/Contents/Resources/")
|
||||
|
||||
# copy app to mounted sparseimage
|
||||
os.system('cp -r dist/SABnzbd.app "/Volumes/%s/OS X 10.6 and Above/" >/dev/null' % volume)
|
||||
# Add License files
|
||||
os.mkdir("dist/SABnzbd.app/Contents/Resources/licenses/")
|
||||
os.system("cp -p licenses/*.txt dist/SABnzbd.app/Contents/Resources/licenses/")
|
||||
os.system("cp -p *.txt dist/SABnzbd.app/Contents/Resources/licenses/")
|
||||
|
||||
# Copy the Leopard build
|
||||
os.system('cp -r %s/dist/SABnzbd.app "/Volumes/%s/OS X 10.5 and Below/" >/dev/null' % (leopard_build, volume))
|
||||
os.system("sleep 5")
|
||||
|
||||
# Archive result to share
|
||||
dest_path = '/Volumes/VMware Shared Folders/osx'
|
||||
if not os.path.exists(dest_path):
|
||||
dest_path = '$HOME/project/osx'
|
||||
cpio_path = os.path.join(dest_path, prod) + '-' + postfix + '.cpio'
|
||||
print 'Create CPIO file %s' % cpio_path
|
||||
delete_files(cpio_path)
|
||||
os.system('ditto -c -z dist/ "%s"' % cpio_path)
|
||||
|
||||
if OSX_ML:
|
||||
print 'Create src %s' % fileOSr
|
||||
delete_files(fileOSr)
|
||||
os.system('tar -czf %s --exclude ".git*" --exclude "sab*.zip" --exclude "SAB*.tar.gz" --exclude "*.cmd" --exclude "*.pyc" '
|
||||
'--exclude "*.sparseimage" --exclude "dist" --exclude "build" --exclude "*.nsi" --exclude "win" --exclude "*.dmg" '
|
||||
'./ >/dev/null' % (fileOSr) )
|
||||
|
||||
# Copy README.txt
|
||||
os.system("cp README.rtf /Volumes/%s/" % volume)
|
||||
|
||||
# Remove site.py to prevent re-compilation (otherwise the OSX Firewall may complain)
|
||||
os.remove('/Volumes/%s/OS X 10.6 and Above/SABnzbd.app/Contents/Resources/site.py' % volume)
|
||||
os.remove('/Volumes/%s/OS X 10.5 and Below/SABnzbd.app/Contents/Resources/site.py' % volume)
|
||||
|
||||
#Unmount sparseimage
|
||||
os.system("hdiutil eject /Volumes/%s/>/dev/null" % volume)
|
||||
|
||||
os.system("sleep 5")
|
||||
# Convert sparseimage to read only compressed dmg
|
||||
if os.path.exists(fileDmg):
|
||||
os.remove(fileDmg)
|
||||
os.system("hdiutil convert %s -format UDBZ -o %s>/dev/null" % (fileImg, fileDmg))
|
||||
# Remove sparseimage
|
||||
os.system("rm %s>/dev/null" % (fileImg))
|
||||
|
||||
# Make image internet-enabled
|
||||
os.system("hdiutil internet-enable %s" % fileDmg)
|
||||
else:
|
||||
dest = '/Volumes/VMware Shared Folders/leopard/%s' % str(my_version)
|
||||
os.system('rm -rf "%s"' % dest)
|
||||
os.makedirs('%s/dist' % dest)
|
||||
os.system('cp -r dist/SABnzbd.app "%s/dist/" >/dev/null' % dest)
|
||||
'--exclude "*.sparseimage*" --exclude "dist" --exclude "build" --exclude "*.nsi" --exclude "win" --exclude "*.dmg" '
|
||||
'./ >/dev/null' % os.path.join(dest_path, fileOSr) )
|
||||
|
||||
os.system(GitRevertApp + VERSION_FILEAPP)
|
||||
os.system(GitRevertApp + VERSION_FILE)
|
||||
|
||||
|
||||
elif target in ('binary', 'installer'):
|
||||
if not py2exe:
|
||||
print "Sorry, only works on Windows!"
|
||||
@@ -518,12 +507,12 @@ elif target in ('binary', 'installer'):
|
||||
for tup in options['data_files']:
|
||||
for file in tup[1]:
|
||||
name, ext = os.path.splitext(file)
|
||||
if ext.lower() in ('.txt', '.cmd'):
|
||||
if ext.lower() in ('.txt', '.cmd', '.mkd'):
|
||||
Unix2Dos("dist/%s" % file)
|
||||
DeleteFiles('dist/Sample-PostProc.sh')
|
||||
DeleteFiles('dist/PKG-INFO')
|
||||
delete_files('dist/Sample-PostProc.sh')
|
||||
delete_files('dist/PKG-INFO')
|
||||
|
||||
DeleteFiles('*.ini')
|
||||
delete_files('*.ini')
|
||||
|
||||
############################
|
||||
# Generate the windowed-app
|
||||
@@ -560,14 +549,14 @@ elif target in ('binary', 'installer'):
|
||||
|
||||
############################
|
||||
# Remove unwanted system DLL files that Py2Exe copies when running on Win7
|
||||
DeleteFiles(r'dist\lib\API-MS-Win-*.dll')
|
||||
DeleteFiles(r'dist\lib\MSWSOCK.DLL')
|
||||
DeleteFiles(r'dist\lib\POWRPROF.DLL')
|
||||
DeleteFiles(r'dist\lib\KERNELBASE.dll')
|
||||
delete_files(r'dist\lib\API-MS-Win-*.dll')
|
||||
delete_files(r'dist\lib\MSWSOCK.DLL')
|
||||
delete_files(r'dist\lib\POWRPROF.DLL')
|
||||
delete_files(r'dist\lib\KERNELBASE.dll')
|
||||
|
||||
############################
|
||||
# Remove .git residue
|
||||
DeleteFiles(r'dist\interfaces\Config\.git')
|
||||
delete_files(r'dist\interfaces\Config\.git')
|
||||
|
||||
############################
|
||||
# Copy Curl if needed
|
||||
@@ -575,18 +564,25 @@ elif target in ('binary', 'installer'):
|
||||
# Curl for Python 2.5
|
||||
os.system(r'unzip -o win\curl\curl.zip -d dist\lib')
|
||||
|
||||
############################
|
||||
# Fix icon issue with NZB association
|
||||
os.system(r'copy dist\icons\nzb.ico dist')
|
||||
|
||||
############################
|
||||
# Rename MKD file
|
||||
rename_file('dist', 'README.mkd', 'README.txt')
|
||||
|
||||
############################
|
||||
if target == 'installer':
|
||||
DeleteFiles(fileIns)
|
||||
delete_files(fileIns)
|
||||
os.system('makensis.exe /v3 /DSAB_PRODUCT=%s /DSAB_VERSION=%s /DSAB_FILE=%s NSIS_Installer.nsi.tmp' % \
|
||||
(prod, release, fileIns))
|
||||
DeleteFiles('NSIS_Installer.nsi.tmp')
|
||||
delete_files('NSIS_Installer.nsi.tmp')
|
||||
if not os.path.exists(fileIns):
|
||||
print 'Fatal error creating %s' % fileIns
|
||||
exit(1)
|
||||
|
||||
DeleteFiles(fileBin)
|
||||
delete_files(fileBin)
|
||||
os.rename('dist', prod)
|
||||
os.system('zip -9 -r -X %s %s' % (fileBin, prod))
|
||||
time.sleep(1.0)
|
||||
@@ -653,6 +649,10 @@ else:
|
||||
shutil.copy2(file, dest)
|
||||
Dos2Unix(fullname)
|
||||
|
||||
############################
|
||||
# Rename MKD file
|
||||
rename_file(root, 'README.mkd', 'README.txt')
|
||||
|
||||
os.chdir(root)
|
||||
os.chdir('..')
|
||||
|
||||
@@ -660,4 +660,3 @@ else:
|
||||
CreateTar('srcdist', fileSrc, prod)
|
||||
|
||||
os.system(GitRevertVersion)
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-04-28 12:01+0000\n"
|
||||
"PO-Revision-Date: 2012-03-14 04:51+0000\n"
|
||||
"Last-Translator: Rene <Unknown>\n"
|
||||
"PO-Revision-Date: 2012-08-03 17:24+0000\n"
|
||||
"Last-Translator: shypike <Unknown>\n"
|
||||
"Language-Team: Danish <da@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: 2012-04-29 05:17+0000\n"
|
||||
"X-Generator: Launchpad (build 15149)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-04 05:38+0000\n"
|
||||
"X-Generator: Launchpad (build 15742)\n"
|
||||
|
||||
#: email/email.tmpl:1
|
||||
msgid ""
|
||||
@@ -65,42 +65,42 @@ msgid ""
|
||||
"<!--#end if#-->\n"
|
||||
msgstr ""
|
||||
"##\n"
|
||||
"## Default Email template for SABnzbd\n"
|
||||
"## This a Cheetah template\n"
|
||||
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"## Standard Email skabelon til SABnzbd\n"
|
||||
"## Dette er en Cheetah skabelon\n"
|
||||
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Newlines and whitespace are significant!\n"
|
||||
"## Linjeskift og blanktegn er betydelig!\n"
|
||||
"##\n"
|
||||
"## These are the email headers\n"
|
||||
"## Disse er e-mail-headerne \n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd has <!--#if $status then \"completed\" else \"failed\" #--> "
|
||||
"Subject: SABnzbd har <!--#if $status then \"hentet\" else \"fejlet\" #--> "
|
||||
"job $name\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"## Efter dette kommer body, den tomme linje kræves!\n"
|
||||
"\n"
|
||||
"Hej,\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"SABnzbd har downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else "
|
||||
"\"(newzbin #\" + $msgid + \")\"#-->\n"
|
||||
"SABnzbd har hentet \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin "
|
||||
"#\" + $msgid + \")\"#-->\n"
|
||||
"<!--#else#-->\n"
|
||||
"SABnzbd har ikke downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else "
|
||||
"SABnzbd kunne ikke hente \"$name\" <!--#if $msgid==\"\" then \"\" else "
|
||||
"\"(newzbin #\" + $msgid + \")\"#-->\n"
|
||||
"<!--#end if#-->\n"
|
||||
"Færdig kl. $end_time\n"
|
||||
"Downloaded $size\n"
|
||||
"Hentet $size\n"
|
||||
"\n"
|
||||
"Resultat af job:\n"
|
||||
"<!--#for $stage in $stages #-->\n"
|
||||
"Stage $stage <!--#slurp#-->\n"
|
||||
"Etape $stage <!--#slurp#-->\n"
|
||||
"<!--#for $result in $stages[$stage]#-->\n"
|
||||
" $result <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#if $script!=\"\" #-->\n"
|
||||
"Output from user script \"$script\" (Exit code = $script_ret):\n"
|
||||
"Output fra bruger script \"$script\" (Exit code = $script_ret):\n"
|
||||
"$script_output\n"
|
||||
"<!--#end if#-->\n"
|
||||
"<!--#if $status #-->\n"
|
||||
@@ -138,20 +138,20 @@ msgid ""
|
||||
"Bye\n"
|
||||
msgstr ""
|
||||
"##\n"
|
||||
"## RSS Email template for SABnzbd\n"
|
||||
"## This a Cheetah template\n"
|
||||
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"## RSS Email skabelon til SABnzbd\n"
|
||||
"## Dette er Cheetah skabelon\n"
|
||||
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Newlines and whitespace are significant!\n"
|
||||
"## Linjeskift og blanktegn er betydelig!\n"
|
||||
"##\n"
|
||||
"## These are the email headers\n"
|
||||
"## Dette er email headers\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd har tilføjet $antal jobs til køen\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"## Efter dette kommer body, den tomme linje kræves!\n"
|
||||
"\n"
|
||||
"Hej,\n"
|
||||
"\n"
|
||||
@@ -189,24 +189,24 @@ msgid ""
|
||||
"Bye\n"
|
||||
msgstr ""
|
||||
"##\n"
|
||||
"## Bad URL Fetch Email template for SABnzbd\n"
|
||||
"## This a Cheetah template\n"
|
||||
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"## Dårlig URL Fetch E-mail skabelon for SABnzbd\n"
|
||||
"## Dette er en Cheetah skabelon\n"
|
||||
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Newlines and whitespace are significant!\n"
|
||||
"## Linjeskift og blanktegn er betydelig!\n"
|
||||
"##\n"
|
||||
"## These are the email headers\n"
|
||||
"## Dette er email headers\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd failed to fetch an NZB\n"
|
||||
"Subject: SABnzbd kunne ikke hente en NZB\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"## Efter dette kommer body, den tomme linje kræves!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"Hej,\n"
|
||||
"\n"
|
||||
"SABnzbd has failed to retrieve the NZB from $url.\n"
|
||||
"The error message was: $msg\n"
|
||||
"SABnzbd kunne ikke hente NZB fra $url.\n"
|
||||
"Fejl meddelelsen er: $msg\n"
|
||||
"\n"
|
||||
"Bye\n"
|
||||
"Farvel\n"
|
||||
|
||||
2101
po/main/SABnzbd.pot
2101
po/main/SABnzbd.pot
File diff suppressed because it is too large
Load Diff
2002
po/main/da.po
2002
po/main/da.po
File diff suppressed because it is too large
Load Diff
2013
po/main/de.po
2013
po/main/de.po
File diff suppressed because it is too large
Load Diff
2010
po/main/es.po
2010
po/main/es.po
File diff suppressed because it is too large
Load Diff
2019
po/main/fr.po
2019
po/main/fr.po
File diff suppressed because it is too large
Load Diff
2015
po/main/nb.po
2015
po/main/nb.po
File diff suppressed because it is too large
Load Diff
2023
po/main/nl.po
2023
po/main/nl.po
File diff suppressed because it is too large
Load Diff
2013
po/main/pl.px
2013
po/main/pl.px
File diff suppressed because it is too large
Load Diff
2006
po/main/pt_BR.po
2006
po/main/pt_BR.po
File diff suppressed because it is too large
Load Diff
2008
po/main/ro.px
2008
po/main/ro.px
File diff suppressed because it is too large
Load Diff
2076
po/main/sv.po
2076
po/main/sv.po
File diff suppressed because it is too large
Load Diff
@@ -13,71 +13,71 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=ASCII\n"
|
||||
"Content-Transfer-Encoding: 7bit\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid " >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid "This system requires the Microsoft runtime library VC90 to be installed first. Do you want to do that now?"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid "You cannot overwrite an existing installation. \\n\\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-03 03:22+0000\n"
|
||||
"Last-Translator: Rene <Unknown>\n"
|
||||
"Language-Team: Danish <da@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: 2012-05-04 05:14+0000\n"
|
||||
"X-Generator: Launchpad (build 15195)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Gå til SABnzbd Wiki"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Vis udgivelsesbemærkninger"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Støtte projektet, donere!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Luk 'SABnzbd.exe' først"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -42,31 +42,31 @@ msgstr ""
|
||||
"produktbemærkningerne eller gå til http://wiki.sabnzbd.org/introducing-0-7-0 "
|
||||
"!"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Dette vil afinstallere SABnzbd fra dit system"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Kør ved opstart"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Skrivebords ikon"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "NZB filtilknytning"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Slet program"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Slet instillinger"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -74,19 +74,19 @@ msgstr ""
|
||||
"Dette system kræver, at Microsoft runtime biblioteket VC90, der skal "
|
||||
"installeres først. Ønsker du at gøre det nu?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Downloading Microsoft runtime installer..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Download fejl, prøv igen?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Kan ikke installere uden runtime bibliotek, prøv igen?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -94,7 +94,7 @@ msgstr ""
|
||||
"Du kan ikke overskrive en eksisterende installation. Klik `OK` for at fjerne "
|
||||
"den tidligere version eller `Annuller` for at annullere denne opgradering."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Dine indstillinger og data vil blive opbevaret."
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-04 14:06+0000\n"
|
||||
"Last-Translator: shypike <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: 2012-05-05 05:41+0000\n"
|
||||
"X-Generator: Launchpad (build 15195)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Gehen Sie auf die SABnzbd Wiki-Seite"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Versionshinweise anzeigen"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Bitte unterstützen Sie das Projekt durch eine Spende!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Schliessen Sie bitte zuerst \"SABnzbd.exe\"."
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -42,31 +42,31 @@ msgstr ""
|
||||
"Versionshinweise oder gehen Sie zu http://wiki.sabnzbd.org/introducing-0-7-0 "
|
||||
"!"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Dies entfernt SABnzbd von Ihrem System"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Beim Systemstart ausführen"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Desktop-Symbol"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "Mit NZB-Dateien verknüpfen"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Programm löschen"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Einstellungen löschen"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -74,22 +74,22 @@ msgstr ""
|
||||
"Dieses System erfordert die Installation der Laufzeitbibliothek VC90 von "
|
||||
"Microsoft. Möchten Sie die Installation jetzt durchführen?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr ""
|
||||
"Installationsprogramm für Microsoft-Laufzeitbibliothek wird "
|
||||
"heruntergeladen..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Download-Fehler. Erneut versuchen?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr ""
|
||||
"Installation ohne Laufzeitbibliothek nicht möglich. Erneut versuchen?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -98,7 +98,7 @@ msgstr ""
|
||||
"Sie 'OK', um die vorherige Version zu entfernen oder 'Abbrechen' um die "
|
||||
"Aktualisierung abzubrechen."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Ihre Einstellungen und Daten bleiben erhalten."
|
||||
|
||||
|
||||
@@ -7,63 +7,65 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"PO-Revision-Date: 2012-06-07 03:07+0000\n"
|
||||
"Last-Translator: Carlos Hernandez <imaginingdreamer@yahoo.com>\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-07-30 09:38+0000\n"
|
||||
"Last-Translator: Victor Herrero <victorhera@gmail.com>\n"
|
||||
"Language-Team: Spanish <es@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: 2012-06-08 05:13+0000\n"
|
||||
"X-Generator: Launchpad (build 15376)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Ir al wiki de SABnzbd"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Mostrar notas de la versión"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "¡Apoye el proyecto, haga una donación!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Por favor cierre primero \"SABnzbd.exe\""
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
msgstr ""
|
||||
" >>>> ATENCION <<<<\\r\\n\\r\\nPor favor, compruebe las "
|
||||
"notas de version o visite http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Esto desinstalará SABnzbd de su sistema"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Ejecutar al inicio"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Icono del escritorio"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "Asociación de archivos NZB"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Eliminar programa"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Eliminar Ajustes"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -71,20 +73,20 @@ msgstr ""
|
||||
"Este sistema requiere la ejecución de la biblioteca Microsoft runtime VC90 "
|
||||
"que debe ser instalada. ¿Quieres hacerlo ahora?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Descargando el instalador de Microsoft runtime..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Error en la descarga, ¿probamos de nuevo?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr ""
|
||||
"No se puede instalar sin la biblioteca runtime, ¿Lo volvemos a intentar?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -92,7 +94,7 @@ msgstr ""
|
||||
"No es posible sobrescribir una instalación existente. \\n\\nPresione `OK' "
|
||||
"para quitar la versión anterior o 'Cancelar' para cancelar la actualización."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Tus ajustes y datos se mantendrán intactos."
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-02 15:56+0000\n"
|
||||
"Last-Translator: Fox Ace <Unknown>\n"
|
||||
"Language-Team: French <fr@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: 2012-05-03 05:55+0000\n"
|
||||
"X-Generator: Launchpad (build 15185)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Aller sur le Wiki de SABnzbd"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Afficher les notes de version"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Supportez le projet, faites un don !"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Quittez \"SABnzbd.exe\" avant l'installation, SVP"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -42,31 +42,31 @@ msgstr ""
|
||||
"vérifiez d'abord les notes de version ou consultez "
|
||||
"http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Ceci désinstallera SABnzbd de votre système"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Lancer au démarrage"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Icône sur le Bureau"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "Association des fichiers NZB"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Supprimer le programme"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Supprimer les Paramètres"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -74,19 +74,19 @@ msgstr ""
|
||||
"Ce système nécessite que la bibliothèque d'exécution Microsoft vc90 soit "
|
||||
"installée en premier. Voulez-vous le faire maintenant?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Téléchargement de Microsoft runtime installateur ..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Erreur téléchargement, réessayer?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Impossible d'installer sans bibliothèque d'exécution, réessayer?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -95,7 +95,7 @@ msgstr ""
|
||||
"pour supprimer la version précédente ou `Annuler` pour annuler cette mise à "
|
||||
"niveau."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Vos paramètres et les données seront conservées."
|
||||
|
||||
|
||||
@@ -7,86 +7,86 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2011-06-26 10:50+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Norwegian Bokmal <nb@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: 2012-05-02 05:52+0000\n"
|
||||
"X-Generator: Launchpad (build 15177)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
msgstr ""
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr ""
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-01 18:56+0000\n"
|
||||
"Last-Translator: shypike <Unknown>\n"
|
||||
"Language-Team: Dutch <nl@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: 2012-05-02 05:52+0000\n"
|
||||
"X-Generator: Launchpad (build 15177)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Ga naar de SABnzbd Wiki"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Toon vrijgave bericht"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Steun het project, Doneer!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Sluit \"SABnzbd.exe\" eerst af"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -41,31 +41,31 @@ msgstr ""
|
||||
" >>>> WAARSCHUWING <<<<\\\\r\\\\n\\\\r\\\\nLees eerst het "
|
||||
"vrijgave bericht of ga naar http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Dit verwijdert SABnzbd van je systeem"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Opstarten bij systeem start"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Pictogram op bureaublad"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "NZB bestanden koppelen aan SABnzbd"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Verwijder programma"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Verwijder instellingen"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -73,19 +73,19 @@ msgstr ""
|
||||
"Op dit systeem moeten eerst de Microsoft runtime bibliotheek VC90 "
|
||||
"geïnstalleerd worden. Wilt u dat nu doen?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Downloaden van de Microsoft bibliotheek"
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Download mislukt, opnieuw?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Installeren heeft geen zin zonder de bibliotheek, opnieuw?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -93,7 +93,7 @@ msgstr ""
|
||||
"U kunt geen bestaande installatie overschrijven.\\n\\nKlik op `OK` om de "
|
||||
"vorige versie te verwijderen of op `Annuleren` om te stoppen."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Je instellingen en bestanden blijven behouden."
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-02 10:07+0000\n"
|
||||
"Last-Translator: Tomasz 'Zen' Napierala <tomasz@napierala.org>\n"
|
||||
"Language-Team: Polish <pl@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: 2012-05-03 05:55+0000\n"
|
||||
"X-Generator: Launchpad (build 15185)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Idź do wiki SABnzbd"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Pokaż informacje o wydaniu"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Wspomóż projekt!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Najpierw zamknij SABnzbd.exe"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -41,31 +41,31 @@ msgstr ""
|
||||
" >>>> UWAGA <<<<\\r\\n\\r\\nNajpierw przeczytaj informacje "
|
||||
"o wydaniu lub odwiedź http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "To odinstaluje SABnzbd z systemu"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Uruchom wraz z systemem"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Ikona pulpitu"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "powiązanie pliku NZB"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Usuń program"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Skasuj obecne ustawienia"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -73,19 +73,19 @@ msgstr ""
|
||||
"Ten system wymaga najpierw zainstalowania bibliotek Microsoft VC90. Czy "
|
||||
"chcesz wykonać teraz instalację?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Pobieranie instalatora bibliotek Microsoft..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Problem z pobieraniem, spróbować ponownie?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Nie można wykonać instalacji bez bibliotek, spróbować ponownie?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -93,7 +93,7 @@ msgstr ""
|
||||
"Nie można nadpisać istniejącej instalacji. \\n\\n Naciśnij `OK`, aby usunąć "
|
||||
"poprzednia wersję lub `Anuluj` aby anulować aktualizację."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Twoje ustawienia i dane zostaną zachowane."
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-06-17 03:09+0000\n"
|
||||
"Last-Translator: lrrosa <Unknown>\n"
|
||||
"Language-Team: Brazilian Portuguese <pt_BR@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: 2012-06-18 05:39+0000\n"
|
||||
"X-Generator: Launchpad (build 15419)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Vá para a Wiki do SABnzbd"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Mostrar Notas de Lançamento"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Apoie o projeto. Faça uma doação!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Por favor, feche \"SABnzbd.exe\" primeiro"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -41,31 +41,31 @@ msgstr ""
|
||||
" >>>> ATENÇÃO <<<<\\r\\n\\r\\nPor favor, verifique primeiro "
|
||||
"as notas de lançamento ou vá até http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Isso irá desinstalar SABnzbd de seu sistema"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Executar na inicialização"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Ícone na Área de Trabalho"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "Associação com Arquivos NZB"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Excluir o Programa"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Apagar Configurações"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -73,19 +73,19 @@ msgstr ""
|
||||
"Este sistema precisa que a biblioteca runtime Microsoft VC90 seja instalada "
|
||||
"antes. Você quer fazer isso agora?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Baixando o instalador runtime da Microsoft ..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Houve um erro de download. Quer tentar novamente?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Não é possível instalar sem a biblioteca runtime. Quer repetir?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -93,7 +93,7 @@ msgstr ""
|
||||
"Você não pode substituir uma instalação existente. \\n\\nClique `OK` para "
|
||||
"remover a versão anterior ou `Cancelar` para cancelar esta atualização."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Suas configurações e os dados serão preservados."
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-04 12:45+0000\n"
|
||||
"Last-Translator: nicusor <Unknown>\n"
|
||||
"Language-Team: Romanian <ro@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: 2012-05-05 05:41+0000\n"
|
||||
"X-Generator: Launchpad (build 15195)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Dute la Wiki SABnzbd"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Arată Notele de Publicare"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Susţine proiectul, Donează!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Închideţi mai întâi \"SABnzbd.exe\""
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -42,31 +42,31 @@ msgstr ""
|
||||
"întâi notele de publicare sau să vizitaţi "
|
||||
"http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Acest lucru va dezinstala SABnzbd din sistem"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Executare la pornire"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Icoană Desktop"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "Asociere cu Fişierele NZB"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Şterge Program"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Ştergeţi Setări"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -74,19 +74,19 @@ msgstr ""
|
||||
"Acest sistem necesită librăria Microsoft VC90 instalată. Dortiți să faceți "
|
||||
"asta acum ?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Descărcare rutină instalare Microsoft..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Eroare descărcare, încerc din nou?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Nu pot instala fără rutină librărie, încerc din nou?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -94,7 +94,7 @@ msgstr ""
|
||||
"Nu puteți suprascrie instalarea existentă. \\n\\nClick `OK` pentru a elimina "
|
||||
"versiunea anterioară sau `Anulare` pentru a anula actualizarea."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Setările şi informaţiile vor fi salvate."
|
||||
|
||||
|
||||
@@ -7,33 +7,33 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
|
||||
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
|
||||
"PO-Revision-Date: 2012-05-15 19:42+0000\n"
|
||||
"Last-Translator: Andreas Lindberg <andypandyswe@gmail.com>\n"
|
||||
"Language-Team: Swedish <sv@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: 2012-05-16 05:28+0000\n"
|
||||
"X-Generator: Launchpad (build 15247)\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 15801)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:417
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Gå till SABnzbd Wiki"
|
||||
|
||||
#: NSIS_Installer.nsi:419
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Visa releasenoteringar"
|
||||
|
||||
#: NSIS_Installer.nsi:421
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Donera och stöd detta projekt!"
|
||||
|
||||
#: NSIS_Installer.nsi:423
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Var vänlig stäng \"SABnzbd.exe\" först"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
@@ -41,31 +41,31 @@ msgstr ""
|
||||
" >>>> VARNING <<<<\\r\\n\\r\\nVänligen läs först "
|
||||
"releasenoteringarna eller gå till http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Detta kommer att avinstallera SABnzbd från systemet"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Kör vid uppstart"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Skrivbordsikon"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "NZB Filassosication"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Radera programmet"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Radera inställningar"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
#: NSIS_Installer.nsi:447
|
||||
msgid ""
|
||||
"This system requires the Microsoft runtime library VC90 to be installed "
|
||||
"first. Do you want to do that now?"
|
||||
@@ -73,19 +73,19 @@ msgstr ""
|
||||
"Detta system kräver att Microsofts runtimebibliotek VC90 är installerat. "
|
||||
"Vill du göra detta nu?"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Laddar ned Microsofts runtimeinstaller..."
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Misslyckat nedladdningsförsök, försök igen?"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Kan inte installera utan runtimebibliotek, försök igen?"
|
||||
|
||||
#: NSIS_Installer.nsi:447
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
@@ -94,7 +94,7 @@ msgstr ""
|
||||
"avinstallera tidigare version eller 'Avbryt' för att avbryta denna "
|
||||
"uppgradering."
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Dina inställningar och ditt data kommer att bevaras."
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ import subprocess
|
||||
import time
|
||||
import cherrypy
|
||||
from threading import RLock, Lock, Condition, Thread
|
||||
try:
|
||||
import sleepless
|
||||
except ImportError:
|
||||
sleepless = None
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# Determine platform flags
|
||||
@@ -59,6 +63,10 @@ elif os.name == 'posix':
|
||||
pass
|
||||
if platform.machine() == 'i386':
|
||||
DARWIN_INTEL = True
|
||||
if DARWIN:
|
||||
DARWIN_ML = [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 8]
|
||||
else:
|
||||
DARWIN_ML = False
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
|
||||
@@ -294,10 +302,11 @@ def initialize(pause_downloader = False, clean_up = False, evalSched=False, repa
|
||||
PostProcessor()
|
||||
|
||||
NzbQueue()
|
||||
NzbQueue.do.read_queue(repair)
|
||||
|
||||
Assembler()
|
||||
|
||||
NzbQueue.do.read_queue(repair)
|
||||
|
||||
Downloader(pause_downloader or paused)
|
||||
|
||||
DirScanner()
|
||||
@@ -752,13 +761,21 @@ def empty_queues():
|
||||
|
||||
|
||||
def keep_awake():
|
||||
""" If we still have work to do, keep Windows system awake
|
||||
""" If we still have work to do, keep Windows/OSX system awake
|
||||
"""
|
||||
global KERNEL32
|
||||
if KERNEL32 and not sabnzbd.downloader.Downloader.do.paused:
|
||||
if (not PostProcessor.do.empty()) or not NzbQueue.do.is_empty():
|
||||
# set ES_SYSTEM_REQUIRED
|
||||
KERNEL32.SetThreadExecutionState(ctypes.c_int(0x00000001))
|
||||
if KERNEL32 or sleepless:
|
||||
if sabnzbd.cfg.keep_awake():
|
||||
awake = False
|
||||
if not sabnzbd.downloader.Downloader.do.paused:
|
||||
if (not PostProcessor.do.empty()) or not NzbQueue.do.is_empty():
|
||||
awake = True
|
||||
if KERNEL32:
|
||||
# set ES_SYSTEM_REQUIRED
|
||||
KERNEL32.SetThreadExecutionState(ctypes.c_int(0x00000001))
|
||||
else:
|
||||
sleepless.keep_awake(u'SABnzbd is busy downloading and/or post-processing')
|
||||
if not awake and sleepless:
|
||||
sleepless.allow_sleep()
|
||||
|
||||
|
||||
def CheckFreeSpace():
|
||||
@@ -1021,6 +1038,9 @@ def check_all_tasks():
|
||||
# Check one-shot pause
|
||||
sabnzbd.scheduler.pause_check()
|
||||
|
||||
# Check (and terminate) idle jobs
|
||||
sabnzbd.nzbqueue.NzbQueue.do.stop_idle_jobs()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -1060,16 +1080,9 @@ def active_primaries():
|
||||
return sabnzbd.downloader.Downloader.do.active_primaries()
|
||||
|
||||
|
||||
def proxy_postproc(nzo):
|
||||
sabnzbd.postproc.PostProcessor.do.process(nzo)
|
||||
|
||||
def proxy_pre_queue(name, pp, cat, script, priority, size, groups):
|
||||
return sabnzbd.newsunpack.pre_queue(name, pp, cat, script, priority, size, groups)
|
||||
|
||||
def proxy_get_history_size():
|
||||
history_db = sabnzbd.database.get_history_handle()
|
||||
return history_db.get_history_size()
|
||||
|
||||
def proxy_build_history():
|
||||
""" Proxy to let nzbqueue call api """
|
||||
return sabnzbd.api.build_history()
|
||||
|
||||
@@ -293,9 +293,11 @@ def _api_addfile(name, output, kwargs):
|
||||
#Side effect of next line is that attribute .value is created
|
||||
#which is needed to make add_nzbfile() work
|
||||
size = name.length
|
||||
else:
|
||||
elif hasattr(name, 'value'):
|
||||
size = len(name.value)
|
||||
if name is not None and name.filename and size:
|
||||
else:
|
||||
size = 0
|
||||
if name is not None and size and name.filename:
|
||||
cat = kwargs.get('cat')
|
||||
xcat = kwargs.get('xcat')
|
||||
if not cat and xcat:
|
||||
@@ -422,6 +424,8 @@ def _api_history(name, output, kwargs):
|
||||
limit = kwargs.get('limit')
|
||||
search = kwargs.get('search')
|
||||
failed_only = kwargs.get('failed_only')
|
||||
if not limit:
|
||||
limit = cfg.history_limit()
|
||||
|
||||
if name == 'delete':
|
||||
special = value.lower()
|
||||
@@ -430,10 +434,10 @@ def _api_history(name, output, kwargs):
|
||||
history_db = cherrypy.thread_data.history_db
|
||||
if special in ('all', 'failed'):
|
||||
if del_files:
|
||||
del_job_files(history_db.get_failed_paths())
|
||||
history_db.remove_failed()
|
||||
del_job_files(history_db.get_failed_paths(search))
|
||||
history_db.remove_failed(search)
|
||||
if special in ('all', 'completed'):
|
||||
history_db.remove_completed()
|
||||
history_db.remove_completed(search)
|
||||
return report(output)
|
||||
elif value:
|
||||
jobs = value.split(',')
|
||||
@@ -461,33 +465,6 @@ def _api_get_files(name, output, kwargs):
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE)
|
||||
|
||||
def _api_addurl(names, output, kwargs):
|
||||
""" API: accepts name, output, pp, script, cat, priority, nzbname """
|
||||
pp = kwargs.get('pp')
|
||||
script = kwargs.get('script')
|
||||
cat = kwargs.get('cat')
|
||||
priority = kwargs.get('priority')
|
||||
nzbnames = kwargs.get('nzbname')
|
||||
if not isinstance(names, list):
|
||||
names = [names]
|
||||
if not isinstance(nzbnames, list):
|
||||
nzbnames = [nzbnames]
|
||||
|
||||
for n in xrange(len(names)):
|
||||
name = names[n]
|
||||
if n < len(nzbnames):
|
||||
nzbname = nzbnames[n]
|
||||
else:
|
||||
nzbname = ''
|
||||
if name:
|
||||
name = name.strip()
|
||||
sabnzbd.add_url(name, pp, script, cat, priority, nzbname)
|
||||
|
||||
if len(names) > 0:
|
||||
return report(output)
|
||||
else:
|
||||
return report(output, _MSG_NO_VALUE)
|
||||
|
||||
|
||||
_RE_NEWZBIN_URL = re.compile(r'/browse/post/(\d+)')
|
||||
def _api_addid(names, output, kwargs):
|
||||
@@ -581,10 +558,13 @@ def _api_auth(name, output, kwargs):
|
||||
if not cfg.disable_key():
|
||||
auth = 'badkey'
|
||||
key = kwargs.get('key', '')
|
||||
if key == cfg.nzb_key():
|
||||
auth = 'nzbkey'
|
||||
if key == cfg.api_key():
|
||||
if not key:
|
||||
auth = 'apikey'
|
||||
else:
|
||||
if key == cfg.nzb_key():
|
||||
auth = 'nzbkey'
|
||||
if key == cfg.api_key():
|
||||
auth = 'apikey'
|
||||
elif cfg.username() and cfg.password():
|
||||
auth = 'login'
|
||||
return report(output, keyword='auth', data=auth)
|
||||
@@ -796,7 +776,7 @@ _api_table = {
|
||||
'fullstatus' : _api_fullstatus,
|
||||
'history' : _api_history,
|
||||
'get_files' : _api_get_files,
|
||||
'addurl' : _api_addurl,
|
||||
'addurl' : _api_addid,
|
||||
'addid' : _api_addid,
|
||||
'pause' : _api_pause,
|
||||
'resume' : _api_resume,
|
||||
@@ -1127,7 +1107,7 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
|
||||
slot['mbdone_fmt'] = locale.format('%d', int(mb-mbleft), True)
|
||||
slot['size'] = format_bytes(bytes)
|
||||
slot['sizeleft'] = format_bytes(bytesleft)
|
||||
if not Downloader.do.paused and status != 'Paused' and status != 'Fetching' and not found_active:
|
||||
if not Downloader.do.paused and status not in (Status.PAUSED, Status.FETCHING) and not found_active:
|
||||
if status == Status.CHECKING:
|
||||
slot['status'] = Status.CHECKING
|
||||
else:
|
||||
@@ -1151,7 +1131,7 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
|
||||
slot['percentage'] = "%s" % (int(((mb-mbleft) / mb) * 100))
|
||||
slot['missing'] = missing
|
||||
|
||||
if status in (Status.PAUSED, Status.CHECKING):
|
||||
if Downloader.do.paused or Downloader.do.postproc or status not in (Status.DOWNLOADING, Status.QUEUED):
|
||||
slot['timeleft'] = '0:00:00'
|
||||
slot['eta'] = 'unknown'
|
||||
else:
|
||||
@@ -1165,7 +1145,11 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
|
||||
datestart = datetime.datetime.now()
|
||||
slot['eta'] = 'unknown'
|
||||
|
||||
slot['avg_age'] = calc_age(average_date, bool(trans))
|
||||
if status == Status.GRABBING:
|
||||
slot['avg_age'] = '---'
|
||||
else:
|
||||
slot['avg_age'] = calc_age(average_date, bool(trans))
|
||||
|
||||
slot['verbosity'] = ""
|
||||
if web_dir:
|
||||
finished = []
|
||||
@@ -1289,12 +1273,13 @@ def qstatus_data():
|
||||
|
||||
state = "IDLE"
|
||||
if Downloader.do.paused:
|
||||
state = "PAUSED"
|
||||
state = Status.PAUSED
|
||||
elif qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI > 0:
|
||||
state = "DOWNLOADING"
|
||||
state = Status.DOWNLOADING
|
||||
|
||||
status = {
|
||||
"state" : state,
|
||||
"pp_active" : not PostProcessor.do.empty(),
|
||||
"paused" : Downloader.do.paused,
|
||||
"pause_int" : scheduler.pause_int(),
|
||||
"kbpersec" : bpsnow / KIBI,
|
||||
@@ -1559,7 +1544,8 @@ def build_header(prim, webdir=''):
|
||||
if not color:
|
||||
color = ''
|
||||
|
||||
header = { 'T': Ttemplate, 'Tspec': Tspec, 'Tx' : Ttemplate, 'version':sabnzbd.__version__, 'paused': Downloader.do.paused,
|
||||
header = { 'T': Ttemplate, 'Tspec': Tspec, 'Tx' : Ttemplate, 'version':sabnzbd.__version__,
|
||||
'paused': Downloader.do.paused or Downloader.do.postproc,
|
||||
'pause_int': scheduler.pause_int(), 'paused_all': sabnzbd.PAUSED_ALL,
|
||||
'uptime':uptime, 'color_scheme':color }
|
||||
speed_limit = Downloader.do.get_limit()
|
||||
@@ -1610,13 +1596,13 @@ def build_header(prim, webdir=''):
|
||||
header['left_quota'] = to_units(BPSMeter.do.left)
|
||||
|
||||
status = ''
|
||||
if Downloader.do.paused:
|
||||
if Downloader.do.paused or Downloader.do.postproc:
|
||||
status = Status.PAUSED
|
||||
elif bytespersec > 0:
|
||||
status = Status.DOWNLOADING
|
||||
else:
|
||||
status = 'Idle'
|
||||
header['status'] = "%s" % status
|
||||
header['status'] = status
|
||||
|
||||
anfo = ArticleCache.do.cache_info()
|
||||
|
||||
@@ -1696,9 +1682,11 @@ def build_history(start=None, limit=None, verbose=False, verbose_list=None, sear
|
||||
# Aquire the db instance
|
||||
try:
|
||||
history_db = cherrypy.thread_data.history_db
|
||||
close_db = False
|
||||
except:
|
||||
# Required for repairs at startup because Cherrypy isn't active yet
|
||||
history_db = get_history_handle()
|
||||
close_db = True
|
||||
|
||||
# Fetch history items
|
||||
if not h_limit:
|
||||
@@ -1764,6 +1752,9 @@ def build_history(start=None, limit=None, verbose=False, verbose_list=None, sear
|
||||
total_items += full_queue_size
|
||||
fetched_items = len(items)
|
||||
|
||||
if close_db:
|
||||
history_db.close()
|
||||
|
||||
return (items, fetched_items, total_items)
|
||||
|
||||
|
||||
@@ -1955,3 +1946,13 @@ def del_from_section(kwargs):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def history_remove_failed():
|
||||
""" Remove all failed jobs from history, including files """
|
||||
logging.info('Scheduled removal of all failed jobs')
|
||||
history_db = get_history_handle()
|
||||
del_job_files(history_db.get_failed_paths())
|
||||
history_db.remove_failed()
|
||||
history_db.close()
|
||||
del history_db
|
||||
|
||||
@@ -35,7 +35,8 @@ except:
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.misc import get_filepath, sanitize_filename, get_unique_path, renamer, \
|
||||
set_permissions
|
||||
set_permissions, flag_file
|
||||
from sabnzbd.constants import QCHECK_FILE
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
from sabnzbd.postproc import PostProcessor
|
||||
@@ -60,18 +61,18 @@ class Assembler(Thread):
|
||||
def stop(self):
|
||||
self.process(None)
|
||||
|
||||
def process(self, nzf):
|
||||
self.queue.put(nzf)
|
||||
def process(self, job):
|
||||
self.queue.put(job)
|
||||
|
||||
def run(self):
|
||||
import sabnzbd.nzbqueue
|
||||
while 1:
|
||||
nzo_nzf_tuple = self.queue.get()
|
||||
if not nzo_nzf_tuple:
|
||||
job = self.queue.get()
|
||||
if not job:
|
||||
logging.info("Shutting down")
|
||||
break
|
||||
|
||||
nzo, nzf = nzo_nzf_tuple
|
||||
nzo, nzf = job
|
||||
|
||||
if nzf:
|
||||
sabnzbd.CheckFreeSpace()
|
||||
@@ -87,13 +88,17 @@ class Assembler(Thread):
|
||||
try:
|
||||
filepath = _assemble(nzf, filepath, dupe)
|
||||
except IOError, (errno, strerror):
|
||||
# 28 == disk full => pause downloader
|
||||
if errno == 28:
|
||||
logging.error(Ta('Disk full! Forcing Pause'))
|
||||
if nzo.deleted:
|
||||
# Job was deleted, ignore error
|
||||
pass
|
||||
else:
|
||||
logging.error(Ta('Disk error on creating file %s'), latin1(filepath))
|
||||
# Pause without saving
|
||||
sabnzbd.downloader.Downloader.do.pause(save=False)
|
||||
# 28 == disk full => pause downloader
|
||||
if errno == 28:
|
||||
logging.error(Ta('Disk full! Forcing Pause'))
|
||||
else:
|
||||
logging.error(Ta('Disk error on creating file %s'), latin1(filepath))
|
||||
# Pause without saving
|
||||
sabnzbd.downloader.Downloader.do.pause(save=False)
|
||||
except:
|
||||
logging.error('Fatal error in Assembler', exc_info = True)
|
||||
break
|
||||
@@ -109,6 +114,7 @@ class Assembler(Thread):
|
||||
if check_encrypted_rar(nzo, filepath):
|
||||
logging.warning(Ta('WARNING: Paused job "%s" because of encrypted RAR file'), latin1(nzo.final_name))
|
||||
nzo.pause()
|
||||
nzf.completed = True
|
||||
else:
|
||||
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo.nzo_id, add_to_history=False, cleanup=False)
|
||||
PostProcessor.do.process(nzo)
|
||||
@@ -133,7 +139,7 @@ def _assemble(nzf, path, dupe):
|
||||
decodetable = nzf.decodetable
|
||||
|
||||
for articlenum in decodetable:
|
||||
sleep(0.01)
|
||||
sleep(0.001)
|
||||
article = decodetable[articlenum]
|
||||
|
||||
data = ArticleCache.do.load_article(article)
|
||||
@@ -174,7 +180,7 @@ def _assemble(nzf, path, dupe):
|
||||
|
||||
fout.flush()
|
||||
fout.close()
|
||||
set_permissions(path)
|
||||
set_permissions(path)
|
||||
if md5:
|
||||
nzf.md5sum = md5.digest()
|
||||
del md5
|
||||
@@ -200,35 +206,37 @@ def file_has_articles(nzf):
|
||||
# For a full description of the par2 specification, visit:
|
||||
# http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
|
||||
|
||||
def GetMD5Hashes(fname):
|
||||
def GetMD5Hashes(fname, force=False):
|
||||
""" Get the hash table from a PAR2 file
|
||||
Return as dictionary, indexed on names and True for utf8-encoded names
|
||||
"""
|
||||
new_encoding = False
|
||||
new_encoding = True
|
||||
table = {}
|
||||
try:
|
||||
f = open(fname, 'rb')
|
||||
except:
|
||||
return table
|
||||
if force or not flag_file(os.path.split(fname)[0], QCHECK_FILE):
|
||||
try:
|
||||
f = open(fname, 'rb')
|
||||
except:
|
||||
return table, new_encoding
|
||||
|
||||
try:
|
||||
header = f.read(8)
|
||||
while header:
|
||||
name, hash = ParseFilePacket(f, header)
|
||||
new_encoding |= is_utf8(name)
|
||||
if name:
|
||||
table[name] = hash
|
||||
new_encoding = False
|
||||
try:
|
||||
header = f.read(8)
|
||||
while header:
|
||||
name, hash = ParseFilePacket(f, header)
|
||||
new_encoding |= is_utf8(name)
|
||||
if name:
|
||||
table[name] = hash
|
||||
header = f.read(8)
|
||||
|
||||
except (struct.error, IndexError):
|
||||
logging.info('Cannot use corrupt par2 file for QuickCheck, "%s"', fname)
|
||||
table = {}
|
||||
except:
|
||||
logging.debug('QuickCheck parser crashed in file %s', fname)
|
||||
logging.info('Traceback: ', exc_info = True)
|
||||
table = {}
|
||||
except (struct.error, IndexError):
|
||||
logging.info('Cannot use corrupt par2 file for QuickCheck, "%s"', fname)
|
||||
table = {}
|
||||
except:
|
||||
logging.debug('QuickCheck parser crashed in file %s', fname)
|
||||
logging.info('Traceback: ', exc_info = True)
|
||||
table = {}
|
||||
|
||||
f.close()
|
||||
f.close()
|
||||
return table, new_encoding
|
||||
|
||||
|
||||
|
||||
@@ -63,12 +63,12 @@ def this_month(t):
|
||||
|
||||
|
||||
_DAYS = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||
def last_month_day(t=None):
|
||||
def last_month_day(tm):
|
||||
""" Return last day of this month """
|
||||
t = t or time.localtime(t)
|
||||
year, month = time.localtime(t)[:2]
|
||||
year, month = tm[:2]
|
||||
day = _DAYS[month]
|
||||
if day == 28 and (year % 4) == 0 and (year % 400) == 0:
|
||||
# This simple formula for leap years is good enough
|
||||
if day == 28 and (year % 4) == 0:
|
||||
day = 29
|
||||
return day
|
||||
|
||||
@@ -136,8 +136,28 @@ class BPSMeter(object):
|
||||
sabnzbd.save_admin(data, BYTES_FILE_NAME)
|
||||
|
||||
|
||||
def defaults(self):
|
||||
""" Get the latest data from the database and assign to a fake server
|
||||
"""
|
||||
logging.debug('Setting default BPS meter values')
|
||||
import sabnzbd.database
|
||||
history_db = sabnzbd.database.get_history_handle()
|
||||
grand, month, week = history_db.get_history_size()
|
||||
history_db.close()
|
||||
self.grand_total = {}
|
||||
self.month_total = {}
|
||||
self.week_total = {}
|
||||
self.day_total = {}
|
||||
if grand: self.grand_total['x'] = grand
|
||||
if month: self.month_total['x'] = month
|
||||
if week: self.week_total['x'] = week
|
||||
self.quota = self.left = cfg.quota_size.get_float()
|
||||
|
||||
|
||||
def read(self):
|
||||
""" Read admin from disk """
|
||||
""" Read admin from disk, return True when pause is needed
|
||||
"""
|
||||
res = False
|
||||
quota = self.left = cfg.quota_size.get_float() # Quota for this period
|
||||
self.have_quota = bool(cfg.quota_size())
|
||||
data = sabnzbd.load_admin(BYTES_FILE_NAME)
|
||||
@@ -155,16 +175,14 @@ class BPSMeter(object):
|
||||
self.quota = self.left = cfg.quota_size.get_float()
|
||||
res = self.reset_quota()
|
||||
except:
|
||||
# Get the latest data from the database and assign to a fake server
|
||||
logging.debug('Setting default BPS meter values')
|
||||
grand, month, week = sabnzbd.proxy_get_history_size()
|
||||
if grand: self.grand_total['x'] = grand
|
||||
if month: self.month_total['x'] = month
|
||||
if week: self.week_total['x'] = week
|
||||
self.quota = self.left = cfg.quota_size.get_float()
|
||||
res = False
|
||||
# Force update of counters
|
||||
self.update()
|
||||
self.defaults()
|
||||
# Force update of counters and validate data
|
||||
try:
|
||||
for server in self.grand_total:
|
||||
self.update(server)
|
||||
except TypeError:
|
||||
self.defaults()
|
||||
self.update()
|
||||
return res
|
||||
|
||||
|
||||
@@ -292,20 +310,21 @@ class BPSMeter(object):
|
||||
tm = time.localtime(t)
|
||||
if self.q_period == 'd':
|
||||
nx = (tm[0], tm[1], tm[2], self.q_hour, self.q_minute, 0, 0, 0, tm[8])
|
||||
if (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60):
|
||||
if (tm.tm_hour * 60 + tm.tm_min) >= (self.q_hour * 60 + self.q_minute):
|
||||
# If today's moment has passed, it will happen tomorrow
|
||||
t = time.mktime(nx) + 24 * 3600
|
||||
tm = time.localtime(t)
|
||||
elif self.q_period == 'w':
|
||||
if self.q_day < tm.tm_wday+1 or (self.q_day == tm.tm_wday+1 and (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60)):
|
||||
if self.q_day < tm.tm_wday+1 or (self.q_day == tm.tm_wday+1 and (tm.tm_hour * 60 + tm.tm_min) >= (self.q_hour * 60 + self.q_minute)):
|
||||
tm = time.localtime(next_week(t))
|
||||
dif = abs(self.q_day - tm.tm_wday - 1)
|
||||
t = time.mktime(tm) + dif * 24 * 3600
|
||||
tm = time.localtime(t)
|
||||
elif self.q_period == 'm':
|
||||
if self.q_day < tm.tm_mday or (self.q_day == tm.tm_mday and (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60)):
|
||||
if self.q_day < tm.tm_mday or (self.q_day == tm.tm_mday and (tm.tm_hour * 60 + tm.tm_min) >= (self.q_hour * 60 + self.q_minute)):
|
||||
tm = time.localtime(next_month(t))
|
||||
tm = (tm[0], tm[1], self.q_day, self.q_hour, self.q_minute, 0, 0, 0, tm[8])
|
||||
day = min(last_month_day(tm), self.q_day)
|
||||
tm = (tm[0], tm[1], day, self.q_hour, self.q_minute, 0, 0, 0, tm[8])
|
||||
else:
|
||||
return
|
||||
tm = (tm[0], tm[1], tm[2], self.q_hour, self.q_minute, 0, 0, 0, tm[8])
|
||||
@@ -330,7 +349,12 @@ class BPSMeter(object):
|
||||
self.have_quota = bool(cfg.quota_size())
|
||||
if self.have_quota:
|
||||
quota = cfg.quota_size.get_float()
|
||||
self.left = quota - (self.quota - self.left)
|
||||
if self.quota:
|
||||
# Quota change, recalculate amount left
|
||||
self.left = quota - (self.quota - self.left)
|
||||
else:
|
||||
# If previously no quota, self.left holds this period's usage
|
||||
self.left = quota - self.left
|
||||
self.quota = quota
|
||||
else:
|
||||
self.quota = self.left = 0L
|
||||
@@ -360,8 +384,14 @@ class BPSMeter(object):
|
||||
if m:
|
||||
self.q_hour = int(m.group(1))
|
||||
self.q_minute = int(m.group(2))
|
||||
self.q_day = max(1, self.q_day)
|
||||
self.q_day = min(7, self.q_day)
|
||||
if self.q_period == 'w':
|
||||
self.q_day = max(1, self.q_day)
|
||||
self.q_day = min(7, self.q_day)
|
||||
elif self.q_period == 'm':
|
||||
self.q_day = max(1, self.q_day)
|
||||
self.q_day = min(31, self.q_day)
|
||||
else:
|
||||
self.q_day = 1
|
||||
self.change_quota(allow_resume=False)
|
||||
return quota_handler, self.q_hour, self.q_minute
|
||||
else:
|
||||
|
||||
@@ -24,7 +24,7 @@ import sabnzbd
|
||||
from sabnzbd.constants import DEF_HOST, DEF_PORT_WIN_SSL, DEF_PORT_WIN, DEF_STDINTF, \
|
||||
DEF_DOWNLOAD_DIR, DEF_NZBBACK_DIR, DEF_PORT_UNIX_SSL, \
|
||||
NORMAL_PRIORITY, DEF_SCANRATE, DEF_PORT_UNIX, DEF_COMPLETE_DIR, \
|
||||
DEF_ADMIN_DIR
|
||||
DEF_ADMIN_DIR, NOTIFY_KEYS
|
||||
from sabnzbd.config import OptionBool, OptionNumber, OptionPassword, \
|
||||
OptionDir, OptionStr, OptionList, no_nonsense, \
|
||||
validate_octal, validate_safedir, validate_dir_exists, \
|
||||
@@ -65,7 +65,7 @@ else:
|
||||
# Configuration instances
|
||||
#
|
||||
quick_check = OptionBool('misc', 'quick_check', True)
|
||||
fail_on_crc = OptionBool('misc', 'fail_on_crc', False)
|
||||
fail_on_crc = OptionBool('misc', 'fail_on_crc', True)
|
||||
send_group = OptionBool('misc', 'send_group', False)
|
||||
sfv_check = OptionBool('misc', 'sfv_check', True)
|
||||
|
||||
@@ -79,7 +79,8 @@ email_full = OptionBool('misc', 'email_full', False)
|
||||
email_dir = OptionDir('misc', 'email_dir', create=True)
|
||||
email_rss = OptionBool('misc', 'email_rss', False)
|
||||
|
||||
version_check = OptionBool('misc', 'check_new_rel', True)
|
||||
version_check = OptionNumber('misc', 'check_new_rel', 1)
|
||||
news_items = OptionBool('misc', 'news_items', True)
|
||||
autobrowser = OptionBool('misc', 'auto_browser', True)
|
||||
replace_illegal = OptionBool('misc', 'replace_illegal', True)
|
||||
pre_script = OptionStr('misc', 'pre_script', 'None')
|
||||
@@ -92,6 +93,7 @@ enable_tsjoin = OptionBool('misc', 'enable_tsjoin', True)
|
||||
enable_par_cleanup = OptionBool('misc', 'enable_par_cleanup', True)
|
||||
never_repair = OptionBool('misc', 'never_repair', False)
|
||||
ignore_unrar_dates = OptionBool('misc', 'ignore_unrar_dates', False)
|
||||
overwrite_files = OptionBool('misc', 'overwrite_files', False)
|
||||
|
||||
par_option = OptionStr('misc', 'par_option', '', validation=no_nonsense)
|
||||
nice = OptionStr('misc', 'nice', '', validation=no_nonsense)
|
||||
@@ -124,11 +126,14 @@ auto_sort = OptionBool('misc', 'auto_sort', False)
|
||||
folder_rename = OptionBool('misc', 'folder_rename', True)
|
||||
folder_max_length = OptionNumber('misc', 'folder_max_length', DEF_FOLDER_MAX, 20, 65000)
|
||||
pause_on_pwrar = OptionBool('misc', 'pause_on_pwrar', True)
|
||||
prio_sort_list = OptionList('misc', 'prio_sort_list')
|
||||
|
||||
safe_postproc = OptionBool('misc', 'safe_postproc', True)
|
||||
empty_postproc = OptionBool('misc', 'empty_postproc', False)
|
||||
pause_on_post_processing = OptionBool('misc', 'pause_on_post_processing', False)
|
||||
ampm = OptionBool('misc', 'ampm', False)
|
||||
rss_filenames = OptionBool('misc', 'rss_filenames', False)
|
||||
rss_odd_titles = OptionList('misc', 'rss_odd_titles', ['nzbindex.nl/', 'nzbindex.com/', 'nzbclub.com/'])
|
||||
|
||||
schedules = OptionList('misc', 'schedlines')
|
||||
|
||||
@@ -151,6 +156,8 @@ date_categories = OptionStr('misc', 'date_categories', ['tv'])
|
||||
matrix_username = OptionStr('nzbmatrix', 'username')
|
||||
matrix_apikey = OptionStr('nzbmatrix', 'apikey')
|
||||
matrix_del_bookmark = OptionBool('nzbmatrix', 'del_bookmark', True)
|
||||
xxx_username = OptionStr('nzbxxx', 'username')
|
||||
xxx_apikey = OptionStr('nzbxxx', 'apikey')
|
||||
|
||||
configlock = OptionBool('misc', 'config_lock', 0)
|
||||
|
||||
@@ -203,13 +210,15 @@ log_new = OptionBool('logging', 'log_new', False)
|
||||
|
||||
https_cert = OptionDir('misc', 'https_cert', 'server.cert', create=False)
|
||||
https_key = OptionDir('misc', 'https_key', 'server.key', create=False)
|
||||
https_chain = OptionDir('misc','https_chain', create=False)
|
||||
enable_https = OptionBool('misc', 'enable_https', False)
|
||||
|
||||
language = OptionStr('misc', 'language', 'en')
|
||||
ssl_type = OptionStr('misc', 'ssl_type', 'v23')
|
||||
unpack_check = OptionBool('misc', 'unpack_check', True)
|
||||
no_penalties = OptionBool('misc', 'no_penalties', False)
|
||||
random_server_ip = OptionBool('misc', 'random_server_ip', True)
|
||||
randomize_server_ip = OptionBool('misc', 'randomize_server_ip', False)
|
||||
ipv6_servers = OptionNumber('misc', 'ipv6_servers', 1, 0, 2)
|
||||
|
||||
# Internal options, not saved in INI file
|
||||
debug_delay = OptionNumber('misc', 'debug_delay', 0, add=False)
|
||||
@@ -218,14 +227,18 @@ api_key = OptionStr('misc', 'api_key', create_api_key())
|
||||
nzb_key = OptionStr('misc', 'nzb_key', create_api_key())
|
||||
disable_key = OptionBool('misc', 'disable_api_key', False)
|
||||
api_warnings = OptionBool('misc', 'api_warnings', True)
|
||||
local_range = OptionStr('misc', 'local_range')
|
||||
max_art_tries = OptionNumber('misc', 'max_art_tries', 3, 2)
|
||||
max_art_opt = OptionBool('misc', 'max_art_opt', False)
|
||||
use_pickle = OptionBool('misc', 'use_pickle', False)
|
||||
no_ipv6 = OptionBool('misc', 'no_ipv6', False)
|
||||
|
||||
growl_server = OptionStr('growl', 'growl_server')
|
||||
growl_password = OptionPassword('growl', 'growl_password')
|
||||
growl_enable = OptionBool('growl', 'growl_enable', True)
|
||||
growl_enable = OptionBool('growl', 'growl_enable', not sabnzbd.DARWIN_ML)
|
||||
ntfosd_enable = OptionBool('growl', 'ntfosd_enable', not sabnzbd.WIN32 and not sabnzbd.DARWIN)
|
||||
ncenter_enable = OptionBool('growl', 'ncenter_enable', sabnzbd.DARWIN)
|
||||
notify_classes = OptionList('growl', 'notify_classes', NOTIFY_KEYS)
|
||||
|
||||
quota_size = OptionStr('misc', 'quota_size')
|
||||
quota_day = OptionStr('misc', 'quota_day')
|
||||
@@ -234,11 +247,15 @@ quota_period = OptionStr('misc', 'quota_period', 'm')
|
||||
|
||||
osx_menu = OptionBool('misc', 'osx_menu', True)
|
||||
osx_speed = OptionBool('misc', 'osx_speed', True)
|
||||
keep_awake = OptionBool('misc', 'keep_awake', True)
|
||||
win_menu = OptionBool('misc', 'win_menu', True)
|
||||
uniconfig = OptionBool('misc', 'uniconfig', True)
|
||||
allow_incomplete_nzb = OptionBool('misc', 'allow_incomplete_nzb', False)
|
||||
marker_file = OptionStr('misc', 'nomedia_marker', '')
|
||||
wait_ext_drive = OptionNumber('misc', 'wait_ext_drive', 5, 1, 60)
|
||||
history_limit = OptionNumber('misc', 'history_limit', 50, 0)
|
||||
show_sysload = OptionNumber('misc', 'show_sysload', 2, 0, 2)
|
||||
web_watchdog = OptionBool('misc', 'web_watchdog', False)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Set root folders for Folder config-items
|
||||
@@ -258,3 +275,4 @@ def set_root_folders(home, lcldata):
|
||||
def set_root_folders2():
|
||||
https_cert.set_root(admin_dir.get_path())
|
||||
https_key.set_root(admin_dir.get_path())
|
||||
https_chain.set_root(admin_dir.get_path())
|
||||
@@ -139,7 +139,7 @@ class OptionNumber(Option):
|
||||
else:
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
value = 0
|
||||
value = self._Option__default_val
|
||||
if self.__validation:
|
||||
error, val = self.__validation(value)
|
||||
self._Option__set(val)
|
||||
@@ -224,7 +224,10 @@ class OptionList(Option):
|
||||
error = None
|
||||
if value is not None:
|
||||
if not isinstance(value, list):
|
||||
value = listquote.simplelist(value)
|
||||
if '"' not in value and ',' not in value:
|
||||
value = value.split()
|
||||
else:
|
||||
value = listquote.simplelist(value)
|
||||
if self.__validation:
|
||||
error, value = self.__validation(value)
|
||||
if not error:
|
||||
|
||||
@@ -43,6 +43,7 @@ PNFO_MISSING_FIELD = 18
|
||||
QNFO_BYTES_FIELD = 0
|
||||
QNFO_BYTES_LEFT_FIELD = 1
|
||||
QNFO_PNFO_LIST_FIELD = 2
|
||||
QNFO_Q_SIZE_LIST_FIELD = 3
|
||||
|
||||
ANFO_ARTICLE_SUM_FIELD = 0
|
||||
ANFO_CACHE_SIZE_FIELD = 1
|
||||
@@ -63,6 +64,8 @@ TERM_FLAG_FILE = 'running.sab'
|
||||
FUTURE_Q_FOLDER = 'future'
|
||||
JOB_ADMIN = '__ADMIN__'
|
||||
VERIFIED_FILE = '__verified__'
|
||||
QCHECK_FILE = '__skip_qcheck__'
|
||||
RENAMES_FILE = '__renames__'
|
||||
ATTRIB_FILE = 'SABnzbd_attrib'
|
||||
REPAIR_REQUEST = 'repair-all.sab'
|
||||
|
||||
@@ -139,7 +142,7 @@ date_match = [r'(\d{4})\W(\d{1,2})\W(\d{1,2})', #2008-10-16
|
||||
|
||||
year_match = r'[\W]([1|2]\d{3})([^\w]|$)' # Something '(YYYY)' or '.YYYY.' or ' YYYY '
|
||||
|
||||
sample_match = r'((^|[\W_])sample\d*[\W_])|(-s\.)' # something-sample.avi something-s.avi
|
||||
sample_match = r'((^|[\W_])sample\d*[\W_])|(-s\.\w+$)' # something-sample.avi something-s.avi
|
||||
|
||||
class Status():
|
||||
COMPLETED = 'Completed'
|
||||
@@ -156,3 +159,5 @@ class Status():
|
||||
REPAIRING = 'Repairing'
|
||||
RUNNING = 'Running'
|
||||
VERIFYING = 'Verifying'
|
||||
|
||||
NOTIFY_KEYS = ('startup', 'download', 'pp', 'complete', 'other')
|
||||
|
||||
@@ -51,6 +51,27 @@ def get_history_handle():
|
||||
return HistoryDB(_HISTORY_DB)
|
||||
|
||||
|
||||
def convert_search(search):
|
||||
""" Convert classic wildcard to SQL wildcard """
|
||||
if not search:
|
||||
# Default value
|
||||
search = ''
|
||||
else:
|
||||
# Allow * for wildcard matching and space
|
||||
search = search.replace('*','%').replace(' ', '%')
|
||||
|
||||
# Allow ^ for start of string and $ for end of string
|
||||
if search and search.startswith('^'):
|
||||
search = search.replace('^','')
|
||||
search += '%'
|
||||
elif search and search.endswith('$'):
|
||||
search = search.replace('$','')
|
||||
search = '%' + search
|
||||
else:
|
||||
search = '%' + search + '%'
|
||||
return search
|
||||
|
||||
|
||||
# Note: Add support for execute return values
|
||||
|
||||
class HistoryDB(object):
|
||||
@@ -141,19 +162,22 @@ class HistoryDB(object):
|
||||
logging.error(Ta('Failed to close database, see log'))
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
|
||||
def remove_completed(self):
|
||||
return self.execute("""DELETE FROM history WHERE status = 'Completed'""", save=True)
|
||||
def remove_completed(self, search=None):
|
||||
search = convert_search(search)
|
||||
return self.execute("""DELETE FROM history WHERE name LIKE ? AND status = 'Completed'""", (search,), save=True)
|
||||
|
||||
def get_failed_paths(self):
|
||||
def get_failed_paths(self, search=None):
|
||||
""" Return list of all storage paths of failed jobs (may contain non-existing or empty paths) """
|
||||
fetch_ok = self.execute("""SELECT path FROM history WHERE status = 'Failed'""")
|
||||
search = convert_search(search)
|
||||
fetch_ok = self.execute("""SELECT path FROM history WHERE name LIKE ? AND status = 'Failed'""", (search,))
|
||||
if fetch_ok:
|
||||
return [item.get('path') for item in self.c.fetchall()]
|
||||
else:
|
||||
return []
|
||||
|
||||
def remove_failed(self):
|
||||
return self.execute("""DELETE FROM history WHERE status = 'Failed'""", save=True)
|
||||
def remove_failed(self, search=None):
|
||||
search = convert_search(search)
|
||||
return self.execute("""DELETE FROM history WHERE name LIKE ? AND status = 'Failed'""", (search,), save=True)
|
||||
|
||||
def remove_history(self, jobs=None):
|
||||
if jobs is None:
|
||||
@@ -180,22 +204,7 @@ class HistoryDB(object):
|
||||
|
||||
def fetch_history(self, start=None, limit=None, search=None, failed_only=0):
|
||||
|
||||
if not search:
|
||||
# Default value
|
||||
search = ''
|
||||
else:
|
||||
# Allow * for wildcard matching and space
|
||||
search = search.replace('*','%').replace(' ', '%')
|
||||
|
||||
# Allow ^ for start of string and $ for end of string
|
||||
if search and search.startswith('^'):
|
||||
search = search.replace('^','')
|
||||
search += '%'
|
||||
elif search and search.endswith('$'):
|
||||
search = search.replace('$','')
|
||||
search = '%' + search
|
||||
else:
|
||||
search = '%' + search + '%'
|
||||
search = convert_search(search)
|
||||
|
||||
# Get the number of results
|
||||
if failed_only:
|
||||
|
||||
@@ -23,6 +23,7 @@ import Queue
|
||||
import binascii
|
||||
import logging
|
||||
import re
|
||||
from time import sleep
|
||||
from threading import Thread
|
||||
try:
|
||||
import _yenc
|
||||
@@ -37,6 +38,7 @@ from sabnzbd.articlecache import ArticleCache
|
||||
import sabnzbd.downloader
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.encoding import name_fixer
|
||||
from sabnzbd.misc import match_str
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
@@ -71,6 +73,7 @@ class Decoder(Thread):
|
||||
def run(self):
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
while 1:
|
||||
sleep(0.001)
|
||||
art_tup = self.queue.get()
|
||||
if not art_tup:
|
||||
break
|
||||
@@ -85,18 +88,19 @@ class Decoder(Thread):
|
||||
data = None
|
||||
|
||||
register = True # Finish article
|
||||
found = True # Article found (only relevant for precheck)
|
||||
found = False # Proper article found
|
||||
|
||||
if lines:
|
||||
logme = None
|
||||
try:
|
||||
if nzo.precheck and '223' in lines[0]:
|
||||
raise IndexError
|
||||
if nzo.precheck:
|
||||
raise BadYenc
|
||||
register = True
|
||||
logging.debug("Decoding %s", article)
|
||||
|
||||
data = decode(article, lines)
|
||||
nzf.article_count += 1
|
||||
found = True
|
||||
except IOError, e:
|
||||
logme = Ta('Decoding %s failed') % article
|
||||
logging.info(logme)
|
||||
@@ -120,25 +124,45 @@ class Decoder(Thread):
|
||||
register = False
|
||||
logme = None
|
||||
|
||||
except BadYenc, e:
|
||||
logme = Ta('Badly formed yEnc article in %s') % article
|
||||
logging.info(logme)
|
||||
except BadYenc:
|
||||
# Handles precheck and badly formed articles
|
||||
killed = False
|
||||
found = False
|
||||
if nzo.precheck and lines and lines[0].startswith('223 '):
|
||||
# STAT was used, so we only get a status code
|
||||
found = True
|
||||
else:
|
||||
# Examine headers (for precheck) or body (for download)
|
||||
# And look for DMCA clues (while skipping "X-" headers)
|
||||
for line in lines:
|
||||
if not line.startswith('X-') and match_str(line.lower(), ('dmca', 'removed', 'cancel', 'blocked')):
|
||||
logging.info('Article removed from server (%s)', article)
|
||||
killed = True
|
||||
break
|
||||
if nzo.precheck:
|
||||
if found or not killed:
|
||||
# Pre-check, proper article found, just register
|
||||
logging.debug('Server has article %s', article)
|
||||
register = True
|
||||
elif not killed and not found:
|
||||
logme = Ta('Badly formed yEnc article in %s') % article
|
||||
logging.info(logme)
|
||||
|
||||
if cfg.fail_on_crc():
|
||||
if not found:
|
||||
new_server_found = self.__search_new_server(article)
|
||||
if new_server_found:
|
||||
register = False
|
||||
logme = None
|
||||
|
||||
except IndexError:
|
||||
# Pre-check, article found, just register
|
||||
register = True
|
||||
|
||||
except:
|
||||
logme = Ta('Unknown Error while decoding %s') % article
|
||||
logging.info(logme)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
pass
|
||||
|
||||
new_server_found = self.__search_new_server(article)
|
||||
if new_server_found:
|
||||
register = False
|
||||
logme = None
|
||||
|
||||
if logme:
|
||||
article.nzf.nzo.inc_log('bad_art_log', logme)
|
||||
@@ -211,19 +235,22 @@ def decode(article, data):
|
||||
#Deal with non-yencoded posts
|
||||
if not ybegin:
|
||||
found = False
|
||||
for i in xrange(10):
|
||||
if data[i].startswith('begin '):
|
||||
nzf.filename = name_fixer(data[i].split(None, 2)[2])
|
||||
nzf.type = 'uu'
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
for n in xrange(i+1):
|
||||
data.pop(0)
|
||||
if data[-1] == 'end':
|
||||
data.pop()
|
||||
if data[-1] == '`':
|
||||
try:
|
||||
for i in xrange(10):
|
||||
if data[i].startswith('begin '):
|
||||
nzf.filename = name_fixer(data[i].split(None, 2)[2])
|
||||
nzf.type = 'uu'
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
for n in xrange(i+1):
|
||||
data.pop(0)
|
||||
if data[-1] == 'end':
|
||||
data.pop()
|
||||
if data[-1] == '`':
|
||||
data.pop()
|
||||
except IndexError:
|
||||
raise BadYenc()
|
||||
|
||||
decoded_data = '\r\n'.join(data)
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ class DirScanner(threading.Thread):
|
||||
|
||||
for filename in files:
|
||||
path = os.path.join(folder, filename)
|
||||
if os.path.isdir(path) or path in self.ignored:
|
||||
if os.path.isdir(path) or path in self.ignored or filename[0] == '.':
|
||||
continue
|
||||
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
|
||||
@@ -86,7 +86,7 @@ class Server(object):
|
||||
def hostip(self):
|
||||
""" Return a random entry from the possible IPs
|
||||
"""
|
||||
if cfg.random_server_ip() and self.info and len(self.info) > 1:
|
||||
if cfg.randomize_server_ip() and self.info and len(self.info) > 1:
|
||||
rnd = random.randint(0, len(self.info)-1)
|
||||
ip = self.info[rnd][4][0]
|
||||
logging.debug('For server %s, using IP %s' % (self.host, ip))
|
||||
@@ -531,7 +531,10 @@ class Downloader(Thread):
|
||||
if server.active:
|
||||
server.errormsg = Ta('Cannot connect to server %s [%s]') % ('', display_msg)
|
||||
logging.warning(Ta('Cannot connect to server %s [%s]'), '%s:%s' % (server.host, server.port), msg)
|
||||
penalty = _PENALTY_502
|
||||
if clues_pay(msg):
|
||||
penalty = _PENALTY_PERM
|
||||
else:
|
||||
penalty = _PENALTY_502
|
||||
block = True
|
||||
else:
|
||||
# Unknown error, just keep trying
|
||||
@@ -818,3 +821,13 @@ def clues_too_many_ip(text):
|
||||
if clue in text:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def clues_pay(text):
|
||||
""" Check for messages about payments
|
||||
"""
|
||||
text = text.lower()
|
||||
for clue in ('credits', 'paym', 'expired'):
|
||||
if clue in text:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -24,11 +24,13 @@ import os.path
|
||||
import logging
|
||||
import socket
|
||||
import time
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
|
||||
import sabnzbd
|
||||
import sabnzbd.cfg
|
||||
from sabnzbd.encoding import unicoder, latin1
|
||||
from sabnzbd.constants import NOTIFY_KEYS
|
||||
from gntp import GNTPRegister
|
||||
from gntp.notifier import GrowlNotifier
|
||||
try:
|
||||
@@ -51,33 +53,36 @@ except:
|
||||
#------------------------------------------------------------------------------
|
||||
# Define translatable message table
|
||||
TT = lambda x:x
|
||||
_NOTIFICATION = {
|
||||
NOTIFICATION = {
|
||||
'startup' : TT('Startup/Shutdown'), #: Message class for Growl server
|
||||
'download' : TT('Added NZB'), #: Message class for Growl server
|
||||
'pp' : TT('Post-processing started'), #: Message class for Growl server
|
||||
'complete' : TT('Job finished'), #: Message class for Growl server
|
||||
'other' : TT('Other Messages') #: Message class for Growl server
|
||||
}
|
||||
_KEYS = ('startup', 'download', 'pp', 'complete', 'other')
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Setup platform dependent Growl support
|
||||
#
|
||||
_GROWL_ICON = None # Platform-dependant icon path
|
||||
_GROWL = None # Instance of the Notifier after registration
|
||||
_GROWL_REG = False # Succesful registration
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def get_icon(host):
|
||||
if host is None:
|
||||
icon = os.path.join(os.path.join(sabnzbd.DIR_PROG, 'icons'), 'sabnzbd.ico')
|
||||
if not os.path.isfile(icon):
|
||||
icon = os.path.join(sabnzbd.DIR_PROG, 'sabnzbd.ico')
|
||||
if not os.path.isfile(icon):
|
||||
icon = None
|
||||
def get_icon():
|
||||
icon = os.path.join(os.path.join(sabnzbd.DIR_PROG, 'icons'), 'sabnzbd.ico')
|
||||
if not os.path.isfile(icon):
|
||||
icon = os.path.join(sabnzbd.DIR_PROG, 'sabnzbd.ico')
|
||||
if os.path.isfile(icon):
|
||||
if sabnzbd.WIN32 or sabnzbd.DARWIN:
|
||||
fp = open(icon, 'rb')
|
||||
icon = fp.read()
|
||||
fp.close
|
||||
else:
|
||||
# Due to a bug in GNTP, need this work-around for Linux/Unix
|
||||
icon = 'http://sabnzbdplus.sourceforge.net/version/sabnzbd.ico'
|
||||
else:
|
||||
icon = 'http://sabnzbdplus.sourceforge.net/version/sabnzbd.ico'
|
||||
icon = None
|
||||
return icon
|
||||
|
||||
|
||||
@@ -101,24 +106,24 @@ def send_notification(title , msg, gtype, wait=False):
|
||||
""" Send Notification message
|
||||
Return '' when OK, otherwise an error string
|
||||
"""
|
||||
msg1 = ''
|
||||
msg2 = ''
|
||||
if sabnzbd.cfg.growl_enable():
|
||||
if _HAVE_CLASSIC_GROWL and not sabnzbd.cfg.growl_server():
|
||||
return send_local_growl(title, msg, gtype)
|
||||
else:
|
||||
if wait:
|
||||
msg1 = send_growl(title, msg, gtype)
|
||||
res = []
|
||||
if gtype in sabnzbd.cfg.notify_classes() or wait:
|
||||
if sabnzbd.DARWIN_ML and sabnzbd.cfg.ncenter_enable():
|
||||
res.append(send_notification_center(title, msg, gtype))
|
||||
if sabnzbd.cfg.growl_enable():
|
||||
if _HAVE_CLASSIC_GROWL and not sabnzbd.cfg.growl_server():
|
||||
return send_local_growl(title, msg, gtype)
|
||||
else:
|
||||
msg1 = 'ok'
|
||||
Thread(target=send_growl, args=(title, msg, gtype)).start()
|
||||
time.sleep(0.5)
|
||||
if have_ntfosd():
|
||||
msg2 = send_notify_osd(title, msg)
|
||||
if msg1 and msg2:
|
||||
return '%s / %s' % (msg1, msg2)
|
||||
return msg1 or msg2
|
||||
if wait:
|
||||
res.append(send_growl(title, msg, gtype))
|
||||
else:
|
||||
res.append('ok')
|
||||
Thread(target=send_growl, args=(title, msg, gtype)).start()
|
||||
time.sleep(0.5)
|
||||
if have_ntfosd():
|
||||
res.append(send_notify_osd(title, msg))
|
||||
|
||||
return ' / '.join([r for r in res if r])
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def reset_growl():
|
||||
@@ -136,10 +141,7 @@ def register_growl():
|
||||
error = None
|
||||
host, port = sabnzbd.misc.split_host(sabnzbd.cfg.growl_server())
|
||||
|
||||
if host:
|
||||
sys_name = '@' + sabnzbd.misc.hostname().lower()
|
||||
else:
|
||||
sys_name = ''
|
||||
sys_name = hostname(host)
|
||||
|
||||
# Clean up persistent data in GNTP to make re-registration work
|
||||
GNTPRegister.notifications = []
|
||||
@@ -147,9 +149,9 @@ def register_growl():
|
||||
|
||||
growler = GrowlNotifier(
|
||||
applicationName = 'SABnzbd%s' % sys_name,
|
||||
applicationIcon = get_icon(host or None),
|
||||
notifications = [Tx(_NOTIFICATION[key]) for key in _KEYS],
|
||||
hostname = host or None,
|
||||
applicationIcon = get_icon(),
|
||||
notifications = [Tx(NOTIFICATION[key]) for key in NOTIFY_KEYS],
|
||||
hostname = host or 'localhost',
|
||||
port = port or 23053,
|
||||
password = sabnzbd.cfg.growl_password() or None
|
||||
)
|
||||
@@ -172,6 +174,7 @@ def register_growl():
|
||||
except:
|
||||
error = 'Unknown Growl registration error'
|
||||
logging.debug(error)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
del growler
|
||||
ret = None
|
||||
|
||||
@@ -196,12 +199,9 @@ def send_growl(title , msg, gtype):
|
||||
logging.debug('Send to Growl: %s %s %s', gtype, latin1(title), latin1(msg))
|
||||
try:
|
||||
ret = _GROWL.notify(
|
||||
noteType = Tx(_NOTIFICATION.get(gtype, 'other')),
|
||||
noteType = Tx(NOTIFICATION.get(gtype, 'other')),
|
||||
title = title,
|
||||
description = unicoder(msg),
|
||||
#icon = options.icon,
|
||||
#sticky = options.sticky,
|
||||
#priority = options.priority
|
||||
)
|
||||
if ret is None or isinstance(ret, bool):
|
||||
return None
|
||||
@@ -238,7 +238,7 @@ if _HAVE_CLASSIC_GROWL:
|
||||
""" Send to local Growl server, OSX-only """
|
||||
global _local_growl
|
||||
if not _local_growl:
|
||||
notes = [Tx(_NOTIFICATION[key]) for key in _KEYS]
|
||||
notes = [Tx(NOTIFICATION[key]) for key in NOTIFY_KEYS]
|
||||
_local_growl = Growl.GrowlNotifier(
|
||||
applicationName = 'SABnzbd',
|
||||
applicationIcon = _OSX_ICON,
|
||||
@@ -246,7 +246,7 @@ if _HAVE_CLASSIC_GROWL:
|
||||
defaultNotifications = notes
|
||||
)
|
||||
_local_growl.register()
|
||||
_local_growl.notify(Tx(_NOTIFICATION.get(gtype, 'other')), title, msg)
|
||||
_local_growl.notify(Tx(NOTIFICATION.get(gtype, 'other')), title, msg)
|
||||
return None
|
||||
|
||||
|
||||
@@ -277,3 +277,47 @@ if _HAVE_NTFOSD:
|
||||
return error
|
||||
else:
|
||||
return 'Not enabled'
|
||||
|
||||
|
||||
def ncenter_path():
|
||||
""" Return path of Notification Center tool, if it exists """
|
||||
tool = os.path.normpath(os.path.join(sabnzbd.DIR_PROG, '../Resources/SABnzbd.app/Contents/MacOS/SABnzbd'))
|
||||
if os.path.exists(tool):
|
||||
return tool
|
||||
else:
|
||||
return None
|
||||
|
||||
def send_notification_center(title, msg, gtype):
|
||||
""" Send message to Mountain Lion's Notification Center """
|
||||
tool = ncenter_path()
|
||||
if tool:
|
||||
try:
|
||||
command = [tool, '-title', title, '-message', msg, '-group', Tx(NOTIFICATION.get(gtype, 'other'))]
|
||||
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
|
||||
output = proc.stdout.read()
|
||||
proc.wait()
|
||||
if 'Notification delivered' in output:
|
||||
output = ''
|
||||
except:
|
||||
logging.info('Cannot run notifier "%s"', tool)
|
||||
logging.debug("Traceback: ", exc_info = True)
|
||||
output = 'Notifier tool crashed'
|
||||
else:
|
||||
output = 'Notifier app not found'
|
||||
return output.strip('*\n ')
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def hostname(host=True):
|
||||
""" Return host's pretty name """
|
||||
if sabnzbd.WIN32:
|
||||
sys_name = os.environ.get('computername', 'unknown')
|
||||
else:
|
||||
try:
|
||||
sys_name = os.uname()[1]
|
||||
except:
|
||||
sys_name = 'unknown'
|
||||
if host:
|
||||
return '@%s' % sys_name.lower()
|
||||
else:
|
||||
return ''
|
||||
|
||||
@@ -84,6 +84,12 @@ def check_server(host, port):
|
||||
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port))
|
||||
|
||||
|
||||
def check_access():
|
||||
""" Check if external address is allowed """
|
||||
referrer = cherrypy.request.remote.ip
|
||||
return referrer in ('127.0.0.1', '::1') or referrer.startswith(cfg.local_range())
|
||||
|
||||
|
||||
def ConvertSpecials(p):
|
||||
""" Convert None to 'None' and 'Default' to ''
|
||||
"""
|
||||
@@ -158,6 +164,8 @@ def set_auth(conf):
|
||||
|
||||
def check_session(kwargs):
|
||||
""" Check session key """
|
||||
if not check_access():
|
||||
return u'No access'
|
||||
key = kwargs.get('session')
|
||||
if not key:
|
||||
key = kwargs.get('apikey')
|
||||
@@ -176,6 +184,10 @@ def check_apikey(kwargs, nokey=False):
|
||||
""" Check api key or nzbkey
|
||||
Return None when OK, otherwise an error message
|
||||
"""
|
||||
def log_warning(txt):
|
||||
txt = '%s %s' % (txt, cherrypy.request.headers.get('User-Agent', '??'))
|
||||
logging.warning('%s', txt)
|
||||
|
||||
output = kwargs.get('output')
|
||||
mode = kwargs.get('mode', '')
|
||||
callback = kwargs.get('callback')
|
||||
@@ -188,19 +200,22 @@ def check_apikey(kwargs, nokey=False):
|
||||
# For NZB upload calls, a separate key can be used
|
||||
nzbkey = kwargs.get('mode', '') in ('addid', 'addurl', 'addfile', 'addlocalfile')
|
||||
|
||||
if not nzbkey and not check_access():
|
||||
return report(output, 'No access')
|
||||
|
||||
# First check APIKEY, if OK that's sufficient
|
||||
if not (cfg.disable_key() or nokey):
|
||||
key = kwargs.get('apikey')
|
||||
if not key:
|
||||
if not special:
|
||||
logging.warning(Ta('API Key missing, please enter the api key from Config->General into your 3rd party program:'))
|
||||
log_warning(Ta('API Key missing, please enter the api key from Config->General into your 3rd party program:'))
|
||||
return report(output, 'API Key Required', callback=callback)
|
||||
elif nzbkey and key == cfg.nzb_key():
|
||||
return None
|
||||
elif key == cfg.api_key():
|
||||
return None
|
||||
else:
|
||||
logging.warning(Ta('API Key incorrect, Use the api key from Config->General in your 3rd party program:'))
|
||||
log_warning(Ta('API Key incorrect, Use the api key from Config->General in your 3rd party program:'))
|
||||
return report(output, 'API Key Incorrect', callback=callback)
|
||||
|
||||
# No active APIKEY, check web credentials instead
|
||||
@@ -209,7 +224,7 @@ def check_apikey(kwargs, nokey=False):
|
||||
pass
|
||||
else:
|
||||
if not special:
|
||||
logging.warning(Ta('Authentication missing, please enter username/password from Config->General into your 3rd party program:'))
|
||||
log_warning(Ta('Authentication missing, please enter username/password from Config->General into your 3rd party program:'))
|
||||
return report(output, 'Missing authentication', callback=callback)
|
||||
return None
|
||||
|
||||
@@ -249,6 +264,8 @@ class MainPage(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if not check_access(): return Protected()
|
||||
|
||||
if sabnzbd.OLD_QUEUE and not cfg.warned_old_queue():
|
||||
cfg.warned_old_queue.set(True)
|
||||
config.save_config()
|
||||
@@ -293,6 +310,7 @@ class MainPage(object):
|
||||
|
||||
|
||||
def add_handler(self, kwargs):
|
||||
if not check_access(): return Protected()
|
||||
id = kwargs.get('id', '')
|
||||
if not id:
|
||||
id = kwargs.get('url', '')
|
||||
@@ -397,7 +415,9 @@ class MainPage(object):
|
||||
def api(self, **kwargs):
|
||||
"""Handler for API over http, with explicit authentication parameters
|
||||
"""
|
||||
logging.debug('API-call from %s %s', cherrypy.request.remote.ip, kwargs)
|
||||
if not kwargs.get('tickleme') or not cfg.web_watchdog():
|
||||
logging.debug('API-call from %s [%s] %s', cherrypy.request.remote.ip, \
|
||||
cherrypy.request.headers.get('User-Agent', '??'), kwargs)
|
||||
if kwargs.get('mode', '') not in ('version', 'auth'):
|
||||
msg = check_apikey(kwargs)
|
||||
if msg: return msg
|
||||
@@ -407,6 +427,7 @@ class MainPage(object):
|
||||
def scriptlog(self, **kwargs):
|
||||
""" Duplicate of scriptlog of History, needed for some skins """
|
||||
# No session key check, due to fixed URLs
|
||||
if not check_access(): return Protected()
|
||||
|
||||
name = kwargs.get('name')
|
||||
if name:
|
||||
@@ -458,7 +479,7 @@ class NzoPage(object):
|
||||
# /nzb/SABnzbd_nzo_xxxxx/files
|
||||
# /nzb/SABnzbd_nzo_xxxxx/bulk_operation
|
||||
# /nzb/SABnzbd_nzo_xxxxx/save
|
||||
|
||||
if not check_access(): return Protected()
|
||||
nzo_id = None
|
||||
for a in args:
|
||||
if a.startswith('SABnzbd_nzo'):
|
||||
@@ -629,6 +650,7 @@ class QueuePage(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if not check_access(): return Protected()
|
||||
start = kwargs.get('start')
|
||||
limit = kwargs.get('limit')
|
||||
dummy2 = kwargs.get('dummy2')
|
||||
@@ -845,6 +867,7 @@ class HistoryPage(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if not check_access(): return Protected()
|
||||
start = kwargs.get('start')
|
||||
limit = kwargs.get('limit')
|
||||
search = kwargs.get('search')
|
||||
@@ -863,9 +886,11 @@ class HistoryPage(object):
|
||||
#history_items, total_bytes, bytes_beginning = sabnzbd.history_info()
|
||||
#history['bytes_beginning'] = "%.2f" % (bytes_beginning / GIGI)
|
||||
|
||||
postfix = T('B') #: Abbreviation for bytes, as in GB
|
||||
grand, month, week, day = BPSMeter.do.get_sums()
|
||||
history['total_size'], history['month_size'], history['week_size'], history['day_size'] = \
|
||||
to_units(grand), to_units(month), to_units(week), to_units(day)
|
||||
to_units(grand, postfix=postfix), to_units(month, postfix=postfix), \
|
||||
to_units(week, postfix=postfix), to_units(day, postfix=postfix)
|
||||
|
||||
history['lines'], history['fetched'], history['noofslots'] = build_history(limit=limit, start=start, verbose=self.__verbose, verbose_list=self.__verbose_list, search=search, failed_only=failed_only)
|
||||
|
||||
@@ -963,7 +988,7 @@ class HistoryPage(object):
|
||||
def scriptlog(self, **kwargs):
|
||||
""" Duplicate of scriptlog of History, needed for some skins """
|
||||
# No session key check, due to fixed URLs
|
||||
|
||||
if not check_access(): return Protected()
|
||||
name = kwargs.get('name')
|
||||
if name:
|
||||
history_db = cherrypy.thread_data.history_db
|
||||
@@ -1009,6 +1034,7 @@ class ConfigPage(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if not check_access(): return Protected()
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
|
||||
conf['configfn'] = config.get_filename()
|
||||
@@ -1018,6 +1044,7 @@ class ConfigPage(object):
|
||||
for svr in config.get_servers():
|
||||
new[svr] = {}
|
||||
conf['servers'] = new
|
||||
conf['news_items'] = cfg.news_items()
|
||||
|
||||
conf['folders'] = sabnzbd.nzbqueue.scan_jobs(all=False, action=False)
|
||||
|
||||
@@ -1065,12 +1092,14 @@ class ConfigPage(object):
|
||||
def orphan_delete(kwargs):
|
||||
path = kwargs.get('name')
|
||||
if path:
|
||||
path = platform_encode(path)
|
||||
path = os.path.join(cfg.download_dir.get_path(), path)
|
||||
remove_all(path, recursive=True)
|
||||
|
||||
def orphan_add(kwargs):
|
||||
path = kwargs.get('name')
|
||||
if path:
|
||||
path = platform_encode(path)
|
||||
path = os.path.join(cfg.download_dir.get_path(), path)
|
||||
sabnzbd.nzbqueue.repair_job(path, None)
|
||||
|
||||
@@ -1090,7 +1119,7 @@ class ConfigFolders(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -1146,7 +1175,7 @@ class ConfigSwitches(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -1186,14 +1215,18 @@ class ConfigSwitches(object):
|
||||
SPECIAL_BOOL_LIST = \
|
||||
( 'start_paused', 'no_penalties', 'ignore_wrong_unrar', 'create_group_folders',
|
||||
'queue_complete_pers', 'api_warnings', 'allow_64bit_tools', 'par2_multicore',
|
||||
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames',
|
||||
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames', 'news_items',
|
||||
'osx_menu', 'osx_speed', 'win_menu', 'uniconfig', 'use_pickle', 'allow_incomplete_nzb',
|
||||
'random_server_ip'
|
||||
'randomize_server_ip', 'no_ipv6', 'keep_awake', 'overwrite_files', 'empty_postproc',
|
||||
'web_watchdog'
|
||||
)
|
||||
SPECIAL_VALUE_LIST = \
|
||||
( 'size_limit', 'folder_max_length', 'fsys_type', 'movie_rename_limit', 'nomedia_marker',
|
||||
'req_completion_rate', 'wait_ext_drive'
|
||||
'req_completion_rate', 'wait_ext_drive', 'history_limit', 'show_sysload', 'ipv6_servers'
|
||||
)
|
||||
SPECIAL_LIST_LIST = \
|
||||
( 'rss_odd_titles', 'prio_sort_list'
|
||||
)
|
||||
|
||||
class ConfigSpecial(object):
|
||||
def __init__(self, web_dir, root, prim):
|
||||
@@ -1203,7 +1236,7 @@ class ConfigSpecial(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -1212,6 +1245,7 @@ class ConfigSpecial(object):
|
||||
|
||||
conf['switches'] = [ (kw, config.get_config('misc', kw)(), config.get_config('misc', kw).default()) for kw in SPECIAL_BOOL_LIST]
|
||||
conf['entries'] = [ (kw, config.get_config('misc', kw)(), config.get_config('misc', kw).default()) for kw in SPECIAL_VALUE_LIST]
|
||||
conf['entries'].extend( [ (kw, config.get_config('misc', kw).get_string(), '') for kw in SPECIAL_LIST_LIST] )
|
||||
|
||||
template = Template(file=os.path.join(self.__web_dir, 'config_special.tmpl'),
|
||||
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
||||
@@ -1222,7 +1256,7 @@ class ConfigSpecial(object):
|
||||
msg = check_session(kwargs)
|
||||
if msg: return msg
|
||||
|
||||
for kw in SPECIAL_BOOL_LIST + SPECIAL_VALUE_LIST:
|
||||
for kw in SPECIAL_BOOL_LIST + SPECIAL_VALUE_LIST + SPECIAL_LIST_LIST:
|
||||
item = config.get_config('misc', kw)
|
||||
value = kwargs.get(kw)
|
||||
msg = item.set(value)
|
||||
@@ -1236,8 +1270,8 @@ class ConfigSpecial(object):
|
||||
#------------------------------------------------------------------------------
|
||||
GENERAL_LIST = (
|
||||
'host', 'port', 'username', 'password', 'disable_api_key',
|
||||
'refresh_rate', 'cache_limit',
|
||||
'enable_https', 'https_port', 'https_cert', 'https_key'
|
||||
'refresh_rate', 'cache_limit', 'local_range',
|
||||
'enable_https', 'https_port', 'https_cert', 'https_key', 'https_chain'
|
||||
)
|
||||
|
||||
class ConfigGeneral(object):
|
||||
@@ -1271,7 +1305,7 @@ class ConfigGeneral(object):
|
||||
else:
|
||||
return ''
|
||||
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -1335,6 +1369,7 @@ class ConfigGeneral(object):
|
||||
conf['https_port'] = cfg.https_port()
|
||||
conf['https_cert'] = cfg.https_cert()
|
||||
conf['https_key'] = cfg.https_key()
|
||||
conf['https_chain'] = cfg.https_chain()
|
||||
conf['enable_https'] = cfg.enable_https()
|
||||
conf['username'] = cfg.username()
|
||||
conf['password'] = cfg.password.get_stars()
|
||||
@@ -1343,6 +1378,8 @@ class ConfigGeneral(object):
|
||||
conf['cache_limit'] = cfg.cache_limit()
|
||||
conf['cleanup_list'] = cfg.cleanup_list.get_string()
|
||||
conf['nzb_key'] = cfg.nzb_key()
|
||||
conf['local_range'] = cfg.local_range()
|
||||
conf['my_lcldata'] = cfg.admin_dir.get_path()
|
||||
|
||||
template = Template(file=os.path.join(self.__web_dir, 'config_general.tmpl'),
|
||||
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
||||
@@ -1450,7 +1487,7 @@ class ConfigServer(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -1506,6 +1543,18 @@ class ConfigServer(object):
|
||||
BPSMeter.do.clear_server(server)
|
||||
raise dcRaiser(self.__root, kwargs)
|
||||
|
||||
@cherrypy.expose
|
||||
def toggleServer(self, **kwargs):
|
||||
msg = check_session(kwargs)
|
||||
if msg: return msg
|
||||
server = kwargs.get('server')
|
||||
if server:
|
||||
svr = config.get_config('servers', server)
|
||||
svr.enable.set(not svr.enable())
|
||||
config.save_config()
|
||||
Downloader.do.update_server(server, server)
|
||||
raise dcRaiser(self.__root, kwargs)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def unique_svr_name(server):
|
||||
@@ -1597,7 +1646,7 @@ class ConfigRss(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -1616,6 +1665,7 @@ class ConfigRss(object):
|
||||
rss[feed] = feeds[feed].get_dict()
|
||||
filters = feeds[feed].filters()
|
||||
rss[feed]['filters'] = filters
|
||||
rss[feed]['filter_states'] = [bool(sabnzbd.rss.convert_filter(f[4])) for f in filters]
|
||||
rss[feed]['filtercount'] = len(filters)
|
||||
|
||||
rss[feed]['pick_cat'] = pick_cat
|
||||
@@ -1872,7 +1922,7 @@ class ConfigRss(object):
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
_SCHED_ACTIONS = ('resume', 'pause', 'pause_all', 'shutdown', 'restart', 'speedlimit',
|
||||
'pause_post', 'resume_post', 'scan_folder', 'rss_scan')
|
||||
'pause_post', 'resume_post', 'scan_folder', 'rss_scan', 'remove_failed')
|
||||
|
||||
class ConfigScheduling(object):
|
||||
def __init__(self, web_dir, root, prim):
|
||||
@@ -1894,22 +1944,22 @@ class ConfigScheduling(object):
|
||||
days["7"] = T('Sunday')
|
||||
return days
|
||||
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
|
||||
actions = []
|
||||
actions.extend(_SCHED_ACTIONS)
|
||||
days = get_days()
|
||||
day_names = get_days()
|
||||
conf['schedlines'] = []
|
||||
snum = 1
|
||||
conf['taskinfo'] = []
|
||||
for ev in scheduler.sort_schedules(forward=True):
|
||||
for ev in scheduler.sort_schedules(all_events=False):
|
||||
line = ev[3]
|
||||
conf['schedlines'].append(line)
|
||||
try:
|
||||
m, h, day, action = line.split(' ', 3)
|
||||
m, h, day_numbers, action = line.split(' ', 3)
|
||||
except:
|
||||
continue
|
||||
action = action.strip()
|
||||
@@ -1929,7 +1979,17 @@ class ConfigScheduling(object):
|
||||
act = ''
|
||||
if act in ('enable_server', 'disable_server'):
|
||||
action = Ttemplate("sch-" + act) + ' ' + server
|
||||
item = (snum, '%02d' % int(h), '%02d' % int(m), days.get(day, '**'), '%s %s' % (action, value))
|
||||
|
||||
if day_numbers == "1234567":
|
||||
days_of_week = "Daily"
|
||||
elif day_numbers == "12345":
|
||||
days_of_week = "Weekdays"
|
||||
elif day_numbers == "67":
|
||||
days_of_week = "Weekends"
|
||||
else:
|
||||
days_of_week = ", ".join([day_names.get(i, "**") for i in day_numbers])
|
||||
item = (snum, '%02d' % int(h), '%02d' % int(m), days_of_week, '%s %s' % (action, value))
|
||||
|
||||
conf['taskinfo'].append(item)
|
||||
snum += 1
|
||||
|
||||
@@ -1954,7 +2014,9 @@ class ConfigScheduling(object):
|
||||
|
||||
minute = kwargs.get('minute')
|
||||
hour = kwargs.get('hour')
|
||||
dayofweek = kwargs.get('dayofweek')
|
||||
days_of_week = ''.join([str(x) for x in kwargs.get('daysofweek', '')])
|
||||
if not days_of_week:
|
||||
days_of_week = '1234567'
|
||||
action = kwargs.get('action')
|
||||
arguments = kwargs.get('arguments')
|
||||
|
||||
@@ -1964,7 +2026,7 @@ class ConfigScheduling(object):
|
||||
elif arguments in ('off','disable'):
|
||||
arguments = '0'
|
||||
|
||||
if minute and hour and dayofweek and action:
|
||||
if minute and hour and days_of_week and action:
|
||||
if action == 'speedlimit':
|
||||
if not (arguments and arguments.isdigit()):
|
||||
action = '0'
|
||||
@@ -1983,7 +2045,7 @@ class ConfigScheduling(object):
|
||||
if action:
|
||||
sched = cfg.schedules()
|
||||
sched.append('%s %s %s %s %s' %
|
||||
(minute, hour, dayofweek, action, arguments))
|
||||
(minute, hour, days_of_week, action, arguments))
|
||||
cfg.schedules.set(sched)
|
||||
|
||||
config.save_config()
|
||||
@@ -2014,7 +2076,7 @@ class ConfigIndexers(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -2097,7 +2159,7 @@ class ConfigCats(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -2171,7 +2233,7 @@ class ConfigSorting(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -2224,6 +2286,7 @@ class Status(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if not check_access(): return Protected()
|
||||
header, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
|
||||
header['logfile'] = sabnzbd.LOGFILE
|
||||
@@ -2475,10 +2538,15 @@ def GetRssLog(feed):
|
||||
# Sort in the order the jobs came from the feed
|
||||
names.sort(lambda x, y: jobs[x].get('order', 0) - jobs[y].get('order', 0))
|
||||
|
||||
done = [xml_name(jobs[job]['title']) for job in names if jobs[job]['status'] == 'D']
|
||||
good = [make_item(jobs[job]) for job in names if jobs[job]['status'][0] == 'G']
|
||||
bad = [make_item(jobs[job]) for job in names if jobs[job]['status'][0] == 'B']
|
||||
|
||||
# Sort in reverse order of time stamp for 'Done'
|
||||
dnames = [job for job in jobs.keys() if jobs[job]['status'] == 'D']
|
||||
dnames.sort(lambda x, y: jobs[y].get('timestamp', 0) - jobs[x].get('timestamp', 0))
|
||||
done = [xml_name(jobs[job]['title']) for job in dnames]
|
||||
|
||||
|
||||
return done, good, bad
|
||||
|
||||
def ShowRssLog(feed, all):
|
||||
@@ -2562,7 +2630,7 @@ LIST_EMAIL = (
|
||||
'email_server', 'email_to', 'email_from',
|
||||
'email_account', 'email_pwd', 'email_dir', 'email_rss'
|
||||
)
|
||||
LIST_GROWL = ('growl_enable', 'growl_server', 'growl_password', 'ntfosd_enable')
|
||||
LIST_GROWL = ('growl_enable', 'growl_server', 'growl_password', 'ntfosd_enable', 'ncenter_enable')
|
||||
|
||||
class ConfigNotify(object):
|
||||
def __init__(self, web_dir, root, prim):
|
||||
@@ -2573,7 +2641,7 @@ class ConfigNotify(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, **kwargs):
|
||||
if cfg.configlock():
|
||||
if cfg.configlock() or not check_access():
|
||||
return Protected()
|
||||
|
||||
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
|
||||
@@ -2582,11 +2650,15 @@ class ConfigNotify(object):
|
||||
conf['lastmail'] = self.__lastmail
|
||||
conf['have_growl'] = True
|
||||
conf['have_ntfosd'] = sabnzbd.growler.have_ntfosd()
|
||||
conf['have_ncenter'] = sabnzbd.DARWIN_ML and bool(sabnzbd.growler.ncenter_path())
|
||||
|
||||
for kw in LIST_EMAIL:
|
||||
conf[kw] = config.get_config('misc', kw).get_string()
|
||||
for kw in LIST_GROWL:
|
||||
conf[kw] = config.get_config('growl', kw).get_string()
|
||||
conf['notify_list'] = NOTIFY_KEYS
|
||||
conf['notify_classes'] = cfg.notify_classes.get_string()
|
||||
conf['notify_texts'] = sabnzbd.growler.NOTIFICATION
|
||||
|
||||
template = Template(file=os.path.join(self.__web_dir, 'config_notify.tmpl'),
|
||||
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
||||
@@ -2605,6 +2677,7 @@ class ConfigNotify(object):
|
||||
msg = config.get_config('growl', kw).set(platform_encode(kwargs.get(kw)))
|
||||
if msg:
|
||||
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)))
|
||||
cfg.notify_classes.set(kwargs.get('notify_classes', ''))
|
||||
|
||||
config.save_config()
|
||||
self.__lastmail = None
|
||||
|
||||
122
sabnzbd/misc.py
122
sabnzbd/misc.py
@@ -39,7 +39,7 @@ except:
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.constants import DEFAULT_PRIORITY, FUTURE_Q_FOLDER, JOB_ADMIN, GIGI, VERIFIED_FILE, Status
|
||||
from sabnzbd.constants import DEFAULT_PRIORITY, FUTURE_Q_FOLDER, JOB_ADMIN, GIGI, Status, MEBI
|
||||
import sabnzbd.config as config
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.encoding import unicoder, latin1
|
||||
@@ -259,10 +259,10 @@ def sanitize_foldername(name):
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def verified_flag_file(path, create=False):
|
||||
def flag_file(path, flag, create=False):
|
||||
""" Create verify flag file or return True if it already exists """
|
||||
path = os.path.join(path, JOB_ADMIN)
|
||||
path = os.path.join(path, VERIFIED_FILE)
|
||||
path = os.path.join(path, flag)
|
||||
if create:
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
@@ -317,6 +317,8 @@ def real_path(loc, path):
|
||||
When 'path' is absolute, return normalized path
|
||||
A path starting with ~ will be located in the user's Home folder
|
||||
"""
|
||||
# The Windows part is a bit convoluted because
|
||||
# os.path.join() doesn't behave the same for all Python versions
|
||||
if path:
|
||||
path = path.strip()
|
||||
else:
|
||||
@@ -325,9 +327,15 @@ def real_path(loc, path):
|
||||
if not sabnzbd.WIN32 and path.startswith('~/'):
|
||||
path = path.replace('~', sabnzbd.DIR_HOME, 1)
|
||||
if sabnzbd.WIN32:
|
||||
if path[0].isalpha() and len(path) > 1 and path[1] == ':':
|
||||
if len(path) == 2 or path[2] not in '\\/':
|
||||
path = path.replace('/', '\\')
|
||||
if len(path) > 1 and path[0].isalpha() and path[1] == ':':
|
||||
if len(path) == 2 or path[2] != '\\':
|
||||
path = path.replace(':', ':\\', 1)
|
||||
elif path.startswith('\\\\'):
|
||||
pass
|
||||
elif path.startswith('\\'):
|
||||
if len(loc) > 1 and loc[0].isalpha() and loc[1] == ':':
|
||||
path = loc[:2] + path
|
||||
else:
|
||||
path = os.path.join(loc, path)
|
||||
elif path[0] != '/':
|
||||
@@ -582,11 +590,20 @@ def check_latest_version():
|
||||
|
||||
logging.debug("Checked for a new release, cur= %s, latest= %s (on %s)", current, latest, url)
|
||||
|
||||
if latest_test and cfg.version_check() > 1:
|
||||
# User always wants to see the latest test release
|
||||
latest = latest_test
|
||||
url = url_beta
|
||||
|
||||
if testver and current < latest:
|
||||
# This is a test version, but user has't seen the
|
||||
# "Final" of this one yet, so show the Final
|
||||
sabnzbd.NEW_VERSION = "%s;%s" % (latest_label, url)
|
||||
elif current < latest:
|
||||
# This one is behind, show latest final
|
||||
sabnzbd.NEW_VERSION = "%s;%s" % (latest_label, url)
|
||||
elif testver and current < latest_test:
|
||||
# This is a test version beyond the latest Final, so show latest Alpha/Beta/RC
|
||||
sabnzbd.NEW_VERSION = "%s;%s" % (latest_testlabel, url_beta)
|
||||
|
||||
|
||||
@@ -614,7 +631,7 @@ def from_units(val):
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def to_units(val, spaces=0, dec_limit=2):
|
||||
def to_units(val, spaces=0, dec_limit=2, postfix=''):
|
||||
""" Convert number to K/M/G/T/P notation
|
||||
Add "spaces" if not ending in letter
|
||||
dig_limit==1 show single decimal for M and higher
|
||||
@@ -643,8 +660,8 @@ def to_units(val, spaces=0, dec_limit=2):
|
||||
else:
|
||||
decimals = 0
|
||||
|
||||
format = '%%s%%.%sf %%s' % decimals
|
||||
return format % (sign, val, unit)
|
||||
format = '%%s%%.%sf %%s%%s' % decimals
|
||||
return format % (sign, val, unit, postfix)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def same_file(a, b):
|
||||
@@ -699,17 +716,6 @@ def split_host(srv):
|
||||
return (host, port)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def hostname():
|
||||
""" Return host's pretty name """
|
||||
if sabnzbd.WIN32:
|
||||
return os.environ.get('computername', 'unknown')
|
||||
try:
|
||||
return os.uname()[1]
|
||||
except:
|
||||
return 'unknown'
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def check_mount(path):
|
||||
""" Return False if volume isn't mounted on Linux or OSX
|
||||
@@ -780,13 +786,23 @@ def create_dirs(dirpath):
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
def move_to_path(path, new_path, unique=True):
|
||||
""" Move a file to a new path, optionally give unique filename """
|
||||
if unique:
|
||||
new_path = get_unique_path(new_path, create_dir=False)
|
||||
def move_to_path(path, new_path):
|
||||
""" Move a file to a new path, optionally give unique filename
|
||||
Return (ok, new_path)
|
||||
"""
|
||||
ok = True
|
||||
overwrite = cfg.overwrite_files()
|
||||
if overwrite and os.path.exists(new_path):
|
||||
try:
|
||||
os.remove(new_path)
|
||||
except:
|
||||
overwrite = False
|
||||
if not overwrite:
|
||||
new_path = get_unique_filename(new_path)
|
||||
|
||||
if new_path:
|
||||
logging.debug("Moving. Old path:%s new path:%s unique?:%s",
|
||||
path,new_path, unique)
|
||||
logging.debug("Moving. Old path:%s new path:%s overwrite?:%s",
|
||||
path, new_path, overwrite)
|
||||
try:
|
||||
# First try cheap rename
|
||||
renamer(path, new_path)
|
||||
@@ -801,8 +817,8 @@ def move_to_path(path, new_path, unique=True):
|
||||
if not (cfg.marker_file() and cfg.marker_file() in path):
|
||||
logging.error(Ta('Failed moving %s to %s'), path, new_path)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
new_path = None
|
||||
return new_path
|
||||
ok = False
|
||||
return ok, new_path
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
@@ -821,6 +837,10 @@ def cleanup_empty_directories(path):
|
||||
pass
|
||||
if not repeat:
|
||||
break
|
||||
try:
|
||||
remove_dir(path)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@synchronized(DIR_LOCK)
|
||||
@@ -939,8 +959,9 @@ def bad_fetch(nzo, url, msg='', retry=False, content=False):
|
||||
if isinstance(url, int) or url.isdigit():
|
||||
url = 'Newzbin #%s' % url
|
||||
growler.send_notification(T('URL Fetching failed; %s') % '', '%s\n%s' % (msg, url), 'other')
|
||||
#import sabnzbd.emailer
|
||||
sabnzbd.emailer.badfetch_mail(msg, url)
|
||||
if cfg.email_endjob() > 0:
|
||||
#import sabnzbd.emailer
|
||||
sabnzbd.emailer.badfetch_mail(msg, url)
|
||||
|
||||
from sabnzbd.nzbqueue import NzbQueue
|
||||
assert isinstance(NzbQueue.do, NzbQueue)
|
||||
@@ -978,15 +999,40 @@ def get_filename(path):
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
def memory_usage():
|
||||
try:
|
||||
# Probably only works on Linux because it uses /proc/<pid>/statm
|
||||
t = open('/proc/%d/statm' % os.getpid())
|
||||
v = t.read().split()
|
||||
t.close()
|
||||
virt = int(_PAGE_SIZE * int(v[0]) / MEBI)
|
||||
res = int(_PAGE_SIZE * int(v[1]) / MEBI)
|
||||
return "V=%sM R=%sM" % (virt, res)
|
||||
except:
|
||||
return ''
|
||||
|
||||
try:
|
||||
_PAGE_SIZE = os.sysconf("SC_PAGE_SIZE")
|
||||
except:
|
||||
_PAGE_SIZE = 0
|
||||
_HAVE_STATM = _PAGE_SIZE and memory_usage()
|
||||
|
||||
|
||||
def loadavg():
|
||||
""" Return 1, 5 and 15 minute load average of host or "" if not supported
|
||||
"""
|
||||
if sabnzbd.WIN32 or sabnzbd.DARWIN:
|
||||
return ""
|
||||
try:
|
||||
return "%.2f | %.2f | %.2f" % os.getloadavg()
|
||||
except:
|
||||
return ""
|
||||
p = ''
|
||||
if not sabnzbd.WIN32 and not sabnzbd.DARWIN:
|
||||
opt = cfg.show_sysload()
|
||||
if opt:
|
||||
try:
|
||||
p = '%.2f | %.2f | %.2f' % os.getloadavg()
|
||||
except:
|
||||
pass
|
||||
if opt > 1 and _HAVE_STATM:
|
||||
p = '%s | %s' % (p, memory_usage())
|
||||
return p
|
||||
|
||||
|
||||
def format_time_string(seconds, days=0):
|
||||
@@ -1035,7 +1081,11 @@ def int_conv(value):
|
||||
# Diskfree
|
||||
if sabnzbd.WIN32:
|
||||
# windows diskfree
|
||||
import win32api
|
||||
try:
|
||||
# Careful here, because win32api test hasn't been done yet!
|
||||
import win32api
|
||||
except:
|
||||
pass
|
||||
def diskfree(_dir):
|
||||
""" Return amount of free diskspace in GBytes
|
||||
"""
|
||||
|
||||
@@ -31,10 +31,12 @@ import sabnzbd
|
||||
from sabnzbd.encoding import TRANS, UNTRANS, unicode2local, name_fixer, \
|
||||
reliable_unpack_names, unicoder, latin1, platform_encode
|
||||
from sabnzbd.utils.rarfile import RarFile, is_rarfile
|
||||
from sabnzbd.misc import format_time_string, find_on_path, make_script_path, int_conv
|
||||
from sabnzbd.misc import format_time_string, find_on_path, make_script_path, int_conv, \
|
||||
flag_file, real_path
|
||||
from sabnzbd.tvsort import SeriesSorter
|
||||
import sabnzbd.cfg as cfg
|
||||
from constants import Status
|
||||
from sabnzbd.constants import Status, QCHECK_FILE, RENAMES_FILE
|
||||
load_data = save_data = None
|
||||
|
||||
if sabnzbd.WIN32:
|
||||
try:
|
||||
@@ -77,6 +79,7 @@ CURL_COMMAND = None
|
||||
def find_programs(curdir):
|
||||
"""Find external programs
|
||||
"""
|
||||
global load_data, save_data
|
||||
def check(path, program):
|
||||
p = os.path.abspath(os.path.join(path, program))
|
||||
if os.access(p, os.X_OK):
|
||||
@@ -84,6 +87,10 @@ def find_programs(curdir):
|
||||
else:
|
||||
return None
|
||||
|
||||
# Another crazy Python import bug work-around
|
||||
load_data = sabnzbd.load_data
|
||||
save_data = sabnzbd.save_data
|
||||
|
||||
if sabnzbd.DARWIN:
|
||||
try:
|
||||
os_version = subprocess.Popen("sw_vers -productVersion", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.read()
|
||||
@@ -124,7 +131,7 @@ def find_programs(curdir):
|
||||
def external_processing(extern_proc, complete_dir, filename, msgid, nicename, cat, group, status):
|
||||
""" Run a user postproc script, return console output and exit value
|
||||
"""
|
||||
command = [str(extern_proc), str(complete_dir), str(filename), \
|
||||
command = [str(extern_proc), str(complete_dir), str(filename),
|
||||
str(nicename), str(msgid), str(cat), str(group), str(status)]
|
||||
|
||||
if extern_proc.endswith('.py') and (sabnzbd.WIN32 or not os.access(extern_proc, os.X_OK)):
|
||||
@@ -132,7 +139,7 @@ def external_processing(extern_proc, complete_dir, filename, msgid, nicename, ca
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
env = fix_env()
|
||||
|
||||
logging.info('Running external script %s(%s, %s, %s, %s, %s, %s, %s)', \
|
||||
logging.info('Running external script %s(%s, %s, %s, %s, %s, %s, %s)',
|
||||
extern_proc, complete_dir, filename, nicename, msgid, cat, group, status)
|
||||
|
||||
try:
|
||||
@@ -257,8 +264,8 @@ def match_ts(file):
|
||||
|
||||
|
||||
def clean_up_joinables(names):
|
||||
''' Remove joinable files and their .1 backups
|
||||
'''
|
||||
""" Remove joinable files and their .1 backups
|
||||
"""
|
||||
for name in names:
|
||||
if os.path.exists(name):
|
||||
logging.debug("Deleting %s", name)
|
||||
@@ -275,8 +282,8 @@ def clean_up_joinables(names):
|
||||
pass
|
||||
|
||||
def get_seq_number(name):
|
||||
''' Return sequence number if name as an int
|
||||
'''
|
||||
""" Return sequence number if name as an int
|
||||
"""
|
||||
head, tail = os.path.splitext(name)
|
||||
if tail == '.ts':
|
||||
match, set, num = match_ts(name)
|
||||
@@ -296,7 +303,8 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
|
||||
|
||||
# Create matching sets from the list of files
|
||||
joinable_sets = {}
|
||||
set = match = num = None
|
||||
joinable_set = None
|
||||
set = num = None
|
||||
for joinable in joinables:
|
||||
head, tail = os.path.splitext(joinable)
|
||||
if tail == '.ts':
|
||||
@@ -353,7 +361,7 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
|
||||
# Finish up
|
||||
joined_file.flush()
|
||||
joined_file.close()
|
||||
newfiles.append(joinable_set)
|
||||
newfiles.append(filename)
|
||||
|
||||
if seq_error:
|
||||
msg = T('Incomplete sequence of joinable files')
|
||||
@@ -383,6 +391,7 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
|
||||
When 'one_folder' is set, all files will be in a single folder
|
||||
"""
|
||||
extracted_files = []
|
||||
success = False
|
||||
|
||||
rar_sets = {}
|
||||
for rar in rars:
|
||||
@@ -437,7 +446,7 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
|
||||
except OSError:
|
||||
logging.warning(Ta('Deleting %s failed!'), latin1(rar))
|
||||
|
||||
brokenrar = '%s.1' % (rar)
|
||||
brokenrar = '%s.1' % rar
|
||||
|
||||
if os.path.exists(brokenrar):
|
||||
logging.info("Deleting %s", brokenrar)
|
||||
@@ -456,6 +465,8 @@ def rar_extract(rarfile, numrars, one_folder, nzo, setname, extraction_path):
|
||||
"""
|
||||
|
||||
fail = 0
|
||||
new_files = None
|
||||
rars = []
|
||||
if nzo.password:
|
||||
passwords = [nzo.password]
|
||||
else:
|
||||
@@ -523,17 +534,23 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
action = 'e'
|
||||
else:
|
||||
action = 'x'
|
||||
if cfg.overwrite_files():
|
||||
overwrite = '-o+' # Enable overwrite
|
||||
rename = '-o+' # Dummy
|
||||
else:
|
||||
overwrite = '-o-' # Disable overwrite
|
||||
rename = '-or' # Auto renaming
|
||||
if sabnzbd.WIN32:
|
||||
# Use all flags
|
||||
command = ['%s' % RAR_COMMAND, action, '-idp', '-o-', '-or', '-ai', password,
|
||||
command = ['%s' % RAR_COMMAND, action, '-idp', overwrite, rename, '-ai', password,
|
||||
'%s' % rarfile, '%s/' % extraction_path]
|
||||
elif RAR_PROBLEM:
|
||||
# Use only oldest options (specifically no "-or")
|
||||
command = ['%s' % RAR_COMMAND, action, '-idp', '-o-', password,
|
||||
command = ['%s' % RAR_COMMAND, action, '-idp', overwrite, password,
|
||||
'%s' % rarfile, '%s/' % extraction_path]
|
||||
else:
|
||||
# Don't use "-ai" (not needed for non-Windows)
|
||||
command = ['%s' % RAR_COMMAND, action, '-idp', '-o-', '-or', password,
|
||||
command = ['%s' % RAR_COMMAND, action, '-idp', overwrite, rename, password,
|
||||
'%s' % rarfile, '%s/' % extraction_path]
|
||||
|
||||
if cfg.ignore_unrar_dates():
|
||||
@@ -550,7 +567,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
if p.stdin:
|
||||
p.stdin.close()
|
||||
|
||||
nzo.set_action_line(T('Unpacking'), '00/%02d' % (numrars))
|
||||
nzo.set_action_line(T('Unpacking'), '00/%02d' % numrars)
|
||||
|
||||
# Loop over the output from rar!
|
||||
curr = 0
|
||||
@@ -620,14 +637,14 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
else:
|
||||
m = re.search(r'^(Extracting|Creating|...)\s+(.*?)\s+OK\s*$', line)
|
||||
if m:
|
||||
extracted.append(TRANS(m.group(2)))
|
||||
extracted.append(real_path(extraction_path, TRANS(m.group(2))))
|
||||
|
||||
if fail:
|
||||
if proc:
|
||||
proc.close()
|
||||
p.wait()
|
||||
|
||||
return (fail, (), ())
|
||||
return fail, (), ()
|
||||
|
||||
if proc:
|
||||
proc.close()
|
||||
@@ -669,7 +686,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
nzo.set_unpack_info('Unpack', '[%s] %s' % (unicoder(setname), msg), set=setname)
|
||||
logging.info('%s', msg)
|
||||
|
||||
return (0, extracted, rarfiles)
|
||||
return 0, extracted, rarfiles
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# (Un)Zip Functions
|
||||
@@ -714,7 +731,7 @@ def unzip(nzo, workdir, workdir_complete, delete, one_folder, zips):
|
||||
except OSError:
|
||||
logging.warning(Ta('Deleting %s failed!'), latin1(_zip))
|
||||
|
||||
brokenzip = '%s.1' % (_zip)
|
||||
brokenzip = '%s.1' % _zip
|
||||
|
||||
if os.path.exists(brokenzip):
|
||||
logging.info("Deleting %s", brokenzip)
|
||||
@@ -779,6 +796,7 @@ def par2_repair(parfile_nzf, nzo, workdir, setname):
|
||||
result = True
|
||||
|
||||
if not result:
|
||||
flag_file(workdir, QCHECK_FILE, True)
|
||||
nzo.status = Status.REPAIRING
|
||||
result = False
|
||||
readd = False
|
||||
@@ -876,6 +894,8 @@ def par2_repair(parfile_nzf, nzo, workdir, setname):
|
||||
|
||||
|
||||
_RE_BLOCK_FOUND = re.compile('File: "([^"]+)" - found \d+ of \d+ data blocks from "([^"]+)"')
|
||||
_RE_IS_MATCH_FOR = re.compile('File: "([^"]+)" - is a match for "([^"]+)"')
|
||||
|
||||
def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
""" Run par2 on par-set """
|
||||
if cfg.never_repair():
|
||||
@@ -892,7 +912,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
logging.debug('Par2-classic = %s', classic)
|
||||
|
||||
import sabnzbd.assembler
|
||||
if (sabnzbd.assembler.GetMD5Hashes(parfile)[1] and not classic) or not PAR2C_COMMAND:
|
||||
if (sabnzbd.assembler.GetMD5Hashes(parfile, True)[1] and not classic) or not PAR2C_COMMAND:
|
||||
if cfg.par_option():
|
||||
command = [str(PAR2_COMMAND), cmd, str(cfg.par_option().strip()), parfile]
|
||||
else:
|
||||
@@ -906,9 +926,13 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
if setname in joinable:
|
||||
command.append(joinable)
|
||||
|
||||
# Append the wildcard for this set
|
||||
command.append('%s*' % os.path.join(os.path.split(parfile)[0], setname))
|
||||
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
logging.debug('Starting par2: %s', command)
|
||||
|
||||
lines = []
|
||||
try:
|
||||
p = subprocess.Popen(command, shell=need_shell, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
@@ -922,6 +946,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
# Set up our variables
|
||||
pars = []
|
||||
datafiles = []
|
||||
renames = {}
|
||||
|
||||
linebuf = ''
|
||||
finished = 0
|
||||
@@ -949,7 +974,9 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
if line == '':
|
||||
continue
|
||||
|
||||
# And off we go
|
||||
if 'Repairing:' not in line:
|
||||
lines.append(line)
|
||||
|
||||
if line.startswith('Invalid option specified'):
|
||||
msg = T('[%s] PAR2 received incorrect options, check your Config->Switches settings') % unicoder(setname)
|
||||
nzo.set_unpack_info('Repair', msg, set=setname)
|
||||
@@ -970,8 +997,8 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
start = time()
|
||||
verified = 1
|
||||
|
||||
elif line.startswith('Main packet not found'):
|
||||
## Initialparfile probaly didn't decode properly,
|
||||
elif line.startswith('Main packet not found') or 'The recovery file does not exist' in line:
|
||||
## Initialparfile probably didn't decode properly,
|
||||
logging.info(Ta('Main packet not found...'))
|
||||
|
||||
extrapars = parfile_nzf.extrapars
|
||||
@@ -981,14 +1008,21 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
## Look for the smallest par2file
|
||||
block_table = {}
|
||||
for nzf in extrapars:
|
||||
block_table[int_conv(nzf.blocks)] = nzf
|
||||
if not nzf.completed:
|
||||
block_table[int_conv(nzf.blocks)] = nzf
|
||||
|
||||
if block_table:
|
||||
nzf = block_table[min(block_table.keys())]
|
||||
|
||||
logging.info("Found new par2file %s", nzf.filename)
|
||||
|
||||
## Move from extrapar list to files to be downloaded
|
||||
nzo.add_parfile(nzf)
|
||||
extrapars.remove(nzf)
|
||||
## Now set new par2 file as primary par2
|
||||
nzo.partable[setname] = nzf
|
||||
nzf.extrapars= extrapars
|
||||
parfile_nzf = []
|
||||
## mark for readd
|
||||
readd = True
|
||||
else:
|
||||
@@ -1050,7 +1084,9 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
extrapar_list = block_table[block_size]
|
||||
|
||||
if extrapar_list:
|
||||
nzo.add_parfile(extrapar_list.pop())
|
||||
new_nzf = extrapar_list.pop()
|
||||
nzo.add_parfile(new_nzf)
|
||||
if new_nzf in extrapars: extrapars.remove(new_nzf)
|
||||
added_blocks += block_size
|
||||
|
||||
else:
|
||||
@@ -1079,7 +1115,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
elif line.startswith('Repairing:'):
|
||||
chunks = line.split()
|
||||
per = float(chunks[-1][:-1])
|
||||
nzo.set_action_line(T('Repairing'), '%2d%%' % (per))
|
||||
nzo.set_action_line(T('Repairing'), '%2d%%' % per)
|
||||
nzo.status = Status.REPAIRING
|
||||
|
||||
elif line.startswith('Repair complete'):
|
||||
@@ -1105,6 +1141,26 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
# Hit a bug in par2-tbb, retry with par2-classic
|
||||
retry_classic = True
|
||||
|
||||
elif ' cannot be renamed to ' in line:
|
||||
if not classic and sabnzbd.WIN32:
|
||||
# Hit a bug in par2-tbb, retry with par2-classic
|
||||
retry_classic = True
|
||||
else:
|
||||
msg = unicoder(line.strip())
|
||||
nzo.fail_msg = msg
|
||||
msg = u'[%s] %s' % (unicoder(setname), msg)
|
||||
nzo.set_unpack_info('Repair', msg, set=setname)
|
||||
nzo.status = Status.FAILED
|
||||
|
||||
# File: "oldname.rar" - is a match for "newname.rar".
|
||||
elif 'is a match for' in line:
|
||||
m = _RE_IS_MATCH_FOR.search(line)
|
||||
if m:
|
||||
old_name = m.group(1)
|
||||
new_name = m.group(2)
|
||||
logging.debug('PAR2 will rename "%s" to "%s"', old_name, new_name)
|
||||
renames[new_name] = old_name
|
||||
|
||||
elif not verified:
|
||||
if line.startswith('Verifying source files'):
|
||||
nzo.set_action_line(T('Verifying'), '01/%02d' % verifytotal)
|
||||
@@ -1143,11 +1199,20 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
else:
|
||||
raise WindowsError(err)
|
||||
|
||||
logging.debug('PAR2 output was\n%s', '\n'.join(lines))
|
||||
|
||||
# If successful, add renamed files to the collection
|
||||
if finished and renames:
|
||||
previous = load_data(RENAMES_FILE, nzo.workpath, remove=False)
|
||||
for name in previous or {}:
|
||||
renames[name] = previous[name]
|
||||
save_data(renames, RENAMES_FILE, nzo.workpath)
|
||||
|
||||
if retry_classic:
|
||||
logging.debug('Retry PAR2-joining with par2-classic')
|
||||
return PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=True)
|
||||
else:
|
||||
return (finished, readd, pars, datafiles, used_joinables)
|
||||
return finished, readd, pars, datafiles, used_joinables
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
@@ -1198,7 +1263,7 @@ def build_command(command):
|
||||
if need_shell:
|
||||
command = list2cmdline(command)
|
||||
|
||||
return (stup, need_shell, command, creationflags)
|
||||
return stup, need_shell, command, creationflags
|
||||
|
||||
# Sort the various RAR filename formats properly :\
|
||||
def rar_sort(a, b):
|
||||
@@ -1254,7 +1319,10 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
|
||||
|
||||
zips = [f for f in filelist if ZIP_RE.search(f)]
|
||||
|
||||
rars = [f for f in filelist if RAR_RE.search(f) and is_rarfile(f)]
|
||||
if check_rar:
|
||||
rars = [f for f in filelist if RAR_RE.search(f) and is_rarfile(f)]
|
||||
else:
|
||||
rars = [f for f in filelist if RAR_RE.search(f)]
|
||||
|
||||
ts = [f for f in filelist if TS_RE.search(f) and f not in joinables]
|
||||
|
||||
@@ -1263,7 +1331,7 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
|
||||
logging.debug("build_filelists(): rars: %s", rars)
|
||||
logging.debug("build_filelists(): ts: %s", ts)
|
||||
|
||||
return (joinables, zips, rars, ts)
|
||||
return joinables, zips, rars, ts
|
||||
|
||||
|
||||
def QuickCheck(set, nzo):
|
||||
@@ -1277,13 +1345,12 @@ def QuickCheck(set, nzo):
|
||||
nzf_list = nzo.finished_files
|
||||
|
||||
for file in md5pack:
|
||||
file = name_fixer(file)
|
||||
if sabnzbd.misc.on_cleanup_list(file, False):
|
||||
result = True
|
||||
continue
|
||||
found = False
|
||||
for nzf in nzf_list:
|
||||
if file == name_fixer(nzf.filename):
|
||||
if file == nzf.filename:
|
||||
found = True
|
||||
if (nzf.md5sum is not None) and nzf.md5sum == md5pack[file]:
|
||||
logging.debug('Quick-check of file %s OK', file)
|
||||
@@ -1334,32 +1401,35 @@ def unrar_check(rar):
|
||||
def sfv_check(sfv_path):
|
||||
""" Verify files using SFV file,
|
||||
input: full path of sfv, file are assumed to be relative to sfv
|
||||
returns: True when all files verified OK
|
||||
returns: List of failing files or [] when all is OK
|
||||
"""
|
||||
failed = []
|
||||
try:
|
||||
fp = open(sfv_path, 'r')
|
||||
except:
|
||||
logging.info('Cannot open SFV file %s', sfv_path)
|
||||
return False
|
||||
failed.append(unicoder(sfv_path))
|
||||
return failed
|
||||
root = os.path.split(sfv_path)[0]
|
||||
status = True
|
||||
for line in fp:
|
||||
line = line.strip('\n\r ')
|
||||
if line[0] != ';':
|
||||
if line and line[0] != ';':
|
||||
x = line.rfind(' ')
|
||||
filename = line[:x].strip()
|
||||
checksum = line[x:].strip()
|
||||
path = os.path.join(root, platform_encode(filename))
|
||||
if os.path.exists(path):
|
||||
if crc_check(path, checksum):
|
||||
logging.debug('File %s passed SFV check', path)
|
||||
if x > 0:
|
||||
filename = platform_encode(line[:x].strip())
|
||||
checksum = line[x:].strip()
|
||||
path = os.path.join(root, filename)
|
||||
if os.path.exists(path):
|
||||
if crc_check(path, checksum):
|
||||
logging.debug('File %s passed SFV check', path)
|
||||
else:
|
||||
logging.info('File %s did not pass SFV check', latin1(path))
|
||||
failed.append(unicoder(filename))
|
||||
else:
|
||||
logging.warning('File %s did not pass SFV check', latin1(path))
|
||||
status = False
|
||||
else:
|
||||
logging.warning('File %s mssing in SFV check', latin1(path))
|
||||
logging.info('File %s missing in SFV check', latin1(path))
|
||||
failed.append(unicoder(filename))
|
||||
fp.close()
|
||||
return status
|
||||
return failed
|
||||
|
||||
|
||||
def crc_check(path, target_crc):
|
||||
@@ -1384,6 +1454,7 @@ def crc_check(path, target_crc):
|
||||
def analyse_show(name):
|
||||
""" Do a quick SeasonSort check and return basic facts """
|
||||
job = SeriesSorter(name, None, None)
|
||||
job.match(force=True)
|
||||
if job.is_match():
|
||||
job.get_values()
|
||||
info = job.show_info
|
||||
@@ -1455,7 +1526,10 @@ def list2cmdline(lst):
|
||||
#------------------------------------------------------------------------------
|
||||
# Work-around for the failure of Python2.5 on Windows to support IPV6 with HTTPS
|
||||
|
||||
def get_from_url(url):
|
||||
def get_from_url(url, timeout=None):
|
||||
""" Retrieve URL and return content
|
||||
`timeout` sets non-standard timeout and skips when on Windows
|
||||
"""
|
||||
if 'https:' in url and sabnzbd.WIN32 and sys.version_info < (2,6) and sabnzbd.newsunpack.CURL_COMMAND:
|
||||
command = [sabnzbd.newsunpack.CURL_COMMAND, "-k", url]
|
||||
stup, need_shell, command, creationflags = build_command(command)
|
||||
@@ -1467,6 +1541,12 @@ def get_from_url(url):
|
||||
p.wait()
|
||||
else:
|
||||
import urllib2
|
||||
s = urllib2.urlopen(url)
|
||||
output = s.read()
|
||||
try:
|
||||
if timeout:
|
||||
s = urllib2.urlopen(url, timeout=timeout)
|
||||
else:
|
||||
s = urllib2.urlopen(url)
|
||||
output = s.read()
|
||||
except:
|
||||
output = None
|
||||
return output
|
||||
|
||||
@@ -28,6 +28,7 @@ import logging
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import *
|
||||
import sabnzbd.cfg
|
||||
|
||||
try:
|
||||
from OpenSSL import SSL
|
||||
@@ -84,23 +85,32 @@ def request_server_info(server):
|
||||
|
||||
|
||||
def GetServerParms(host, port):
|
||||
# Make sure port is numeric (unicode input not supported)
|
||||
""" Return processed getaddrinfo() for server
|
||||
"""
|
||||
try:
|
||||
int(port)
|
||||
except:
|
||||
# Could do with a warning here
|
||||
port = 119
|
||||
opt = sabnzbd.cfg.ipv6_servers()
|
||||
try:
|
||||
# Standard IPV4
|
||||
return socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)
|
||||
# Standard IPV4 or IPV6
|
||||
ips = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)
|
||||
if opt == 2 or (_EXTERNAL_IPV6 and opt == 1):
|
||||
# IPv6 reachable and allowed, or forced by user
|
||||
return ips
|
||||
else:
|
||||
# IPv6 unreachable or not allowed by user
|
||||
return [ip for ip in ips if ':' not in ip[4][0]]
|
||||
except:
|
||||
try:
|
||||
# Try IPV6 explicitly
|
||||
return socket.getaddrinfo(host, port, socket.AF_INET6,
|
||||
socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME)
|
||||
except:
|
||||
# Nothing found!
|
||||
return None
|
||||
if opt == 2 or (_EXTERNAL_IPV6 and opt == 1):
|
||||
try:
|
||||
# Try IPV6 explicitly
|
||||
return socket.getaddrinfo(host, port, socket.AF_INET6,
|
||||
socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME)
|
||||
except:
|
||||
# Nothing found!
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def con(sock, host, port, sslenabled, write_fds, nntp):
|
||||
@@ -415,3 +425,25 @@ class SSLConnection(object):
|
||||
return apply(self._ssl_conn.%s, args)
|
||||
finally:
|
||||
self._lock.release()\n""" % (f, f)
|
||||
|
||||
|
||||
def test_ipv6():
|
||||
""" Check if external IPv6 addresses are reachable """
|
||||
# Use google.com to test IPv6 access
|
||||
try:
|
||||
info = socket.getaddrinfo('www.google.com', 80, socket.AF_INET6, socket.SOCK_STREAM,
|
||||
socket.IPPROTO_IP, socket.AI_CANONNAME)
|
||||
except socket.gaierror:
|
||||
return False
|
||||
|
||||
try:
|
||||
af, socktype, proto, canonname, sa = info[0]
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
sock.settimeout(4)
|
||||
sock.connect(sa[0:2])
|
||||
sock.close()
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
_EXTERNAL_IPV6 = test_ipv6()
|
||||
|
||||
@@ -27,14 +27,14 @@ import datetime
|
||||
import sabnzbd
|
||||
from sabnzbd.trylist import TryList
|
||||
from sabnzbd.nzbstuff import NzbObject
|
||||
from sabnzbd.misc import exit_sab, cat_to_opts, verified_flag_file, \
|
||||
from sabnzbd.misc import exit_sab, cat_to_opts, \
|
||||
get_admin_path, remove_all, globber
|
||||
from sabnzbd.panic import panic_queue
|
||||
import sabnzbd.database as database
|
||||
from sabnzbd.decorators import NZBQUEUE_LOCK, synchronized, synchronized_CV
|
||||
from sabnzbd.constants import QUEUE_FILE_NAME, QUEUE_VERSION, FUTURE_Q_FOLDER, JOB_ADMIN, \
|
||||
LOW_PRIORITY, NORMAL_PRIORITY, HIGH_PRIORITY, TOP_PRIORITY, \
|
||||
REPAIR_PRIORITY, STOP_PRIORITY, \
|
||||
REPAIR_PRIORITY, STOP_PRIORITY, VERIFIED_FILE, \
|
||||
PNFO_BYTES_FIELD, PNFO_BYTES_LEFT_FIELD, Status
|
||||
import sabnzbd.cfg as cfg
|
||||
from sabnzbd.articlecache import ArticleCache
|
||||
@@ -147,13 +147,21 @@ class NzbQueue(TryList):
|
||||
|
||||
|
||||
def repair_job(self, folder, new_nzb=None):
|
||||
""" Reconstruct admin for a single job folder, optionally with new NZB """
|
||||
""" Reconstruct admin for a single job folder, optionally with new NZB
|
||||
"""
|
||||
def all_verified(path):
|
||||
""" Return True when all sets have been successfully verified """
|
||||
verified = sabnzbd.load_data(VERIFIED_FILE, path, remove=False) or {'x':False}
|
||||
return not bool([True for x in verified if not verified[x]])
|
||||
|
||||
name = os.path.basename(folder)
|
||||
path = os.path.join(folder, JOB_ADMIN)
|
||||
if new_nzb is None or not new_nzb.filename:
|
||||
if verified_flag_file(folder):
|
||||
filename = ''
|
||||
else:
|
||||
if hasattr(new_nzb, 'filename'):
|
||||
filename = new_nzb.filename
|
||||
else:
|
||||
filename = ''
|
||||
if not filename:
|
||||
if not all_verified(path):
|
||||
filename = globber(path, '*.gz')
|
||||
if len(filename) > 0:
|
||||
logging.debug('Repair job %s by reparsing stored NZB', latin1(name))
|
||||
@@ -164,10 +172,39 @@ class NzbQueue(TryList):
|
||||
self.add(nzo)
|
||||
else:
|
||||
remove_all(path, '*.gz')
|
||||
logging.debug('Repair job %s with new NZB (%s)', latin1(name), latin1(new_nzb.filename))
|
||||
logging.debug('Repair job %s with new NZB (%s)', latin1(name), latin1(filename))
|
||||
sabnzbd.add_nzbfile(new_nzb, pp=None, script=None, cat=None, priority=None, nzbname=name, reuse=True)
|
||||
|
||||
|
||||
def send_back(self, nzo):
|
||||
""" Send back job to queue after successful pre-check """
|
||||
try:
|
||||
nzb_path = globber(nzo.workpath, '*.gz')[0]
|
||||
except:
|
||||
logging.debug('Failed to find NZB file after pre-check (%s)', nzo.nzo_id)
|
||||
return
|
||||
from sabnzbd.dirscanner import ProcessSingleFile
|
||||
res, nzo_ids = ProcessSingleFile(nzo.work_name + '.nzb', nzb_path, reuse=True)
|
||||
if res == 0 and nzo_ids:
|
||||
self.replace_in_q(nzo, nzo_ids[0])
|
||||
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
def replace_in_q(self, nzo, nzo_id):
|
||||
""" Replace nzo by new in at the same spot in the queue, destroy nzo """
|
||||
try:
|
||||
new_nzo = self.get_nzo(nzo_id)
|
||||
pos = self.__nzo_list.index(new_nzo)
|
||||
targetpos = self.__nzo_list.index(nzo)
|
||||
self.__nzo_list[targetpos] = new_nzo
|
||||
self.__nzo_list.pop(pos)
|
||||
del self.__nzo_table[nzo.nzo_id]
|
||||
del nzo
|
||||
except:
|
||||
logging.error('Failed to restart NZB after pre-check (%s)', nzo.nzo_id)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
return
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
def save(self, save_nzo=None):
|
||||
""" Save queue, all nzo's or just the specified one """
|
||||
@@ -295,8 +332,7 @@ class NzbQueue(TryList):
|
||||
|
||||
# If no files are to be downloaded anymore, send to postproc
|
||||
if not nzo.files and not nzo.futuretype:
|
||||
sabnzbd.remove_data(nzo.nzo_id, nzo.workpath)
|
||||
sabnzbd.proxy_postproc(nzo)
|
||||
self.end_job(nzo)
|
||||
return ''
|
||||
|
||||
# Reset try_lists
|
||||
@@ -397,10 +433,10 @@ class NzbQueue(TryList):
|
||||
if nzf:
|
||||
post_done = nzo.remove_nzf(nzf)
|
||||
if post_done:
|
||||
keep_basic = nzo.finished_files
|
||||
if keep_basic:
|
||||
sabnzbd.proxy_postproc(nzo)
|
||||
self.remove(nzo_id, add_to_history = False, keep_basic=keep_basic)
|
||||
if nzo.finished_files:
|
||||
self.end_job(nzo)
|
||||
else:
|
||||
self.remove(nzo_id, add_to_history = False, keep_basic=False)
|
||||
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
@@ -673,7 +709,7 @@ class NzbQueue(TryList):
|
||||
nzf = article.nzf
|
||||
nzo = nzf.nzo
|
||||
|
||||
if nzo.deleted or nzf.deleted:
|
||||
if nzf.deleted:
|
||||
logging.debug("Discarding article %s, no longer in queue", article.article)
|
||||
return
|
||||
|
||||
@@ -712,10 +748,26 @@ class NzbQueue(TryList):
|
||||
"""
|
||||
if self.actives(grabs=False) < 2 and cfg.autodisconnect():
|
||||
# This was the last job, close server connections
|
||||
sabnzbd.downloader.Downloader.do.disconnect()
|
||||
if sabnzbd.downloader.Downloader.do:
|
||||
sabnzbd.downloader.Downloader.do.disconnect()
|
||||
|
||||
# Notify assembler to call postprocessor
|
||||
Assembler.do.process((nzo, None))
|
||||
if not nzo.deleted:
|
||||
nzo.deleted = True
|
||||
if nzo.precheck:
|
||||
nzo.save_attribs()
|
||||
# Check result
|
||||
enough, ratio = nzo.check_quality()
|
||||
if enough:
|
||||
# Enough data present, do real download
|
||||
workdir = nzo.downpath
|
||||
self.cleanup_nzo(nzo, keep_basic=True)
|
||||
self.send_back(nzo)
|
||||
return
|
||||
else:
|
||||
# Not enough data, let postprocessor show it as failed
|
||||
pass
|
||||
Assembler.do.process((nzo, None))
|
||||
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
@@ -736,18 +788,24 @@ class NzbQueue(TryList):
|
||||
def queue_info(self, for_cli=False, max_jobs=0):
|
||||
bytes_left = 0
|
||||
bytes = 0
|
||||
q_size = 0
|
||||
pnfo_list = []
|
||||
n = 0
|
||||
for nzo in self.__nzo_list:
|
||||
pnfo = nzo.gather_info(for_cli = for_cli)
|
||||
if nzo.status != 'Paused':
|
||||
bytes += pnfo[PNFO_BYTES_FIELD]
|
||||
bytes_left += pnfo[PNFO_BYTES_LEFT_FIELD]
|
||||
pnfo_list.append(pnfo)
|
||||
if not max_jobs or n < max_jobs:
|
||||
pnfo = nzo.gather_info(for_cli = for_cli)
|
||||
pnfo_list.append(pnfo)
|
||||
if nzo.status != 'Paused':
|
||||
bytes += pnfo[PNFO_BYTES_FIELD]
|
||||
bytes_left += pnfo[PNFO_BYTES_LEFT_FIELD]
|
||||
q_size += 1
|
||||
elif nzo.status != 'Paused':
|
||||
b, b_left = nzo.total_and_remaining()
|
||||
bytes += b
|
||||
bytes_left += b_left
|
||||
q_size += 1
|
||||
n += 1
|
||||
if max_jobs and n >= max_jobs:
|
||||
break
|
||||
return (bytes, bytes_left, pnfo_list)
|
||||
return (bytes, bytes_left, pnfo_list, q_size)
|
||||
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
@@ -776,6 +834,17 @@ class NzbQueue(TryList):
|
||||
|
||||
ArticleCache.do.purge_articles(nzo.saved_articles)
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
def stop_idle_jobs(self):
|
||||
""" Detect jobs that have zero files left and send them to post processing
|
||||
"""
|
||||
empty = []
|
||||
for nzo in self.__nzo_list:
|
||||
if not nzo.futuretype and not nzo.files and nzo.status not in (Status.PAUSED, Status.GRABBING):
|
||||
empty.append(nzo)
|
||||
for nzo in empty:
|
||||
self.end_job(nzo)
|
||||
|
||||
def get_urls(self):
|
||||
""" Return list of future-types needing URL """
|
||||
lst = []
|
||||
|
||||
@@ -37,7 +37,7 @@ import sabnzbd
|
||||
from sabnzbd.constants import sample_match, GIGI, ATTRIB_FILE, JOB_ADMIN, \
|
||||
DEFAULT_PRIORITY, LOW_PRIORITY, NORMAL_PRIORITY, \
|
||||
HIGH_PRIORITY, PAUSED_PRIORITY, TOP_PRIORITY, DUP_PRIORITY, \
|
||||
Status
|
||||
RENAMES_FILE, Status
|
||||
from sabnzbd.misc import to_units, cat_to_opts, cat_convert, sanitize_foldername, \
|
||||
get_unique_path, get_admin_path, remove_all, format_source_url, \
|
||||
sanitize_filename, globber, sanitize_foldername, int_conv, \
|
||||
@@ -54,7 +54,8 @@ RE_NORMAL = re.compile(r"(.+)(\.nzb)", re.I)
|
||||
SUBJECT_FN_MATCHER = re.compile(r'"([^"]*)"')
|
||||
RE_SAMPLE = re.compile(sample_match, re.I)
|
||||
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)\+(\d*)\.par2', re.I)
|
||||
|
||||
REJECT_PAR2_RE = re.compile(r'\.par2\.\d+', re.I) # Reject duplicate par2 files
|
||||
RE_NORMAL_NAME = re.compile(r'\.\w{2,5}$') # Test reasonably sized extension at the end
|
||||
|
||||
################################################################################
|
||||
# Article #
|
||||
@@ -154,6 +155,7 @@ NzbFileMapper = (
|
||||
('import_finished', 'import_finished'),
|
||||
('md5sum', 'md5sum'),
|
||||
('valid', 'valid'),
|
||||
('completed', 'completed')
|
||||
)
|
||||
|
||||
|
||||
@@ -169,9 +171,7 @@ class NzbFile(TryList):
|
||||
self.filename = None
|
||||
self.type = None
|
||||
|
||||
match = re.search(SUBJECT_FN_MATCHER, subject)
|
||||
if match:
|
||||
self.filename = match.group(1).strip('"')
|
||||
self.filename = name_extractor(subject)
|
||||
|
||||
self.is_par2 = False
|
||||
self.vol = None
|
||||
@@ -191,6 +191,7 @@ class NzbFile(TryList):
|
||||
self.nzo = nzo
|
||||
self.nzf_id = sabnzbd.get_new_id("nzf", nzo.workpath)
|
||||
self.deleted = False
|
||||
self.completed = False
|
||||
|
||||
self.valid = False
|
||||
self.import_finished = False
|
||||
@@ -272,7 +273,7 @@ class NzbFile(TryList):
|
||||
@property
|
||||
def completed(self):
|
||||
""" Is this file completed? """
|
||||
return not bool(self.articles)
|
||||
return self.import_finished and not bool(self.articles)
|
||||
|
||||
@property
|
||||
def lowest_partnum(self):
|
||||
@@ -348,12 +349,8 @@ class NzbParser(xml.sax.handler.ContentHandler):
|
||||
self.in_segments = True
|
||||
|
||||
elif name == 'file' and self.in_nzb:
|
||||
subject = attrs.get('subject', '')
|
||||
match = re.search(SUBJECT_FN_MATCHER, subject)
|
||||
if match:
|
||||
self.filename = match.group(1).strip('"').strip()
|
||||
else:
|
||||
self.filename = subject.strip()
|
||||
subject = attrs.get('subject', '').strip()
|
||||
self.filename = subject
|
||||
|
||||
if self.filter and RE_SAMPLE.search(subject):
|
||||
logging.info('Skipping sample file %s', subject)
|
||||
@@ -810,6 +807,7 @@ class NzbObject(TryList):
|
||||
if nzf in self.files:
|
||||
self.files.remove(nzf)
|
||||
self.finished_files.append(nzf)
|
||||
nzf.import_finished = True
|
||||
nzf.deleted = True
|
||||
return not bool(self.files)
|
||||
|
||||
@@ -818,38 +816,38 @@ class NzbObject(TryList):
|
||||
nzf.reset_all_try_lists()
|
||||
self.reset_try_list()
|
||||
|
||||
def postpone_pars(self, nzf, head):
|
||||
""" Move all vol-par files matching 'head' to the extrapars table """
|
||||
self.partable[head] = nzf
|
||||
self.extrapars[head] = []
|
||||
nzf.extrapars = self.extrapars[head]
|
||||
def postpone_pars(self, nzf, parset):
|
||||
""" Move all vol-par files matching 'parset' to the extrapars table """
|
||||
self.partable[parset] = nzf
|
||||
self.extrapars[parset] = []
|
||||
nzf.extrapars = self.extrapars[parset]
|
||||
lparset = parset.lower()
|
||||
for xnzf in self.files[:]:
|
||||
name = xnzf.filename
|
||||
name = xnzf.filename or platform_encode(xnzf.subject)
|
||||
# Move only when not current NZF and filename was extractable from subject
|
||||
if name and nzf is not xnzf:
|
||||
name = name.lower()
|
||||
if head.lower() in name and '.vol' in name and name.endswith('.par2'):
|
||||
self.extrapars[head].append(xnzf)
|
||||
self.files.remove(xnzf)
|
||||
head, vol, block = analyse_par2(name)
|
||||
if head and matcher(lparset, head.lower()):
|
||||
xnzf.set_par2(parset, vol, block)
|
||||
self.extrapars[parset].append(xnzf)
|
||||
if not self.precheck:
|
||||
self.files.remove(xnzf)
|
||||
|
||||
def handle_par2(self, nzf, file_done):
|
||||
## Special treatment for first part of par2 file
|
||||
""" Check if file is a par2 and build up par2 collection
|
||||
"""
|
||||
fn = nzf.filename
|
||||
if fn:
|
||||
# We have a real filename now
|
||||
fn = fn.strip()
|
||||
lfn = fn.lower()
|
||||
if (not nzf.is_par2) and fn and lfn.endswith('.par2'):
|
||||
par2match = PROBABLY_PAR2_RE.search(fn)
|
||||
if par2match:
|
||||
head = par2match.group(1)
|
||||
vol = par2match.group(2)
|
||||
block = par2match.group(3)
|
||||
elif lfn.endswith('.par2'):
|
||||
head = os.path.splitext(fn)[0]
|
||||
vol = block = 0
|
||||
par2match = True
|
||||
if not nzf.is_par2:
|
||||
head, vol, block = analyse_par2(fn)
|
||||
## Is a par2file and repair mode activated
|
||||
if par2match and (self.repair or cfg.allow_streaming()):
|
||||
if head and (self.repair or cfg.allow_streaming()):
|
||||
## Skip if mini-par2 is not complete
|
||||
if not block and nzf.bytes_left:
|
||||
return
|
||||
nzf.set_par2(head, vol, block)
|
||||
## Already got a parfile for this set?
|
||||
if head in self.partable:
|
||||
@@ -867,15 +865,17 @@ class NzbObject(TryList):
|
||||
## This file either has more blocks,
|
||||
## or initialparfile is already decoded
|
||||
else:
|
||||
if not file_done:
|
||||
nzf.reset_try_list()
|
||||
if file_done:
|
||||
if nzf in self.files: self.files.remove(nzf)
|
||||
self.extrapars[head].append(nzf)
|
||||
if nzf not in self.extrapars[head]: self.extrapars[head].append(nzf)
|
||||
else:
|
||||
nzf.reset_try_list()
|
||||
|
||||
## No par2file in this set yet, set this as
|
||||
## initialparfile
|
||||
else:
|
||||
self.postpone_pars(nzf, head)
|
||||
## Is not a par2file or nothing todo
|
||||
## Is not a par2file or nothing to do
|
||||
else:
|
||||
pass
|
||||
## No filename in seg 1? Probably not uu or yenc encoded
|
||||
@@ -894,7 +894,8 @@ class NzbObject(TryList):
|
||||
if reset:
|
||||
self.reset_try_list()
|
||||
|
||||
self.handle_par2(nzf, file_done)
|
||||
if file_done:
|
||||
self.handle_par2(nzf, file_done)
|
||||
|
||||
post_done = False
|
||||
if not self.files:
|
||||
@@ -911,6 +912,15 @@ class NzbObject(TryList):
|
||||
"""
|
||||
# Get a list of already present files
|
||||
files = [os.path.basename(f) for f in globber(wdir) if os.path.isfile(f)]
|
||||
|
||||
# Substitute renamed files
|
||||
renames = sabnzbd.load_data(RENAMES_FILE, self.workpath, remove=True)
|
||||
if renames:
|
||||
for name in renames:
|
||||
if name in files:
|
||||
files.remove(name)
|
||||
files.append(renames[name])
|
||||
|
||||
# Looking for the longest name first, minimizes the chance on a mismatch
|
||||
files.sort(lambda x, y: len(y) - len(x))
|
||||
|
||||
@@ -924,6 +934,8 @@ class NzbObject(TryList):
|
||||
subject = sanitize_filename(latin1(nzf.subject))
|
||||
if (nzf.filename == filename) or (subject == filename) or (filename in subject):
|
||||
nzf.filename = filename
|
||||
nzf.completed = True
|
||||
nzf.bytes_left = 0
|
||||
self.handle_par2(nzf, file_done=True)
|
||||
self.remove_nzf(nzf)
|
||||
nzfs.remove(nzf)
|
||||
@@ -940,6 +952,8 @@ class NzbObject(TryList):
|
||||
self.files_table[nzf.nzf_id] = nzf
|
||||
self.bytes += nzf.bytes
|
||||
nzf.filename = filename
|
||||
nzf.completed = True
|
||||
nzf.bytes_left = 0
|
||||
self.handle_par2(nzf, file_done=True)
|
||||
self.remove_nzf(nzf)
|
||||
logging.info('File %s added to job', filename)
|
||||
@@ -1007,11 +1021,10 @@ class NzbObject(TryList):
|
||||
self.incomplete = False
|
||||
|
||||
def add_parfile(self, parfile):
|
||||
self.files.append(parfile)
|
||||
if parfile.extrapars:
|
||||
if parfile not in self.files:
|
||||
self.files.append(parfile)
|
||||
if parfile.extrapars and parfile in parfile.extrapars:
|
||||
parfile.extrapars.remove(parfile)
|
||||
else:
|
||||
logging.debug('PARFILE without EXTRAPARS %s', parfile.filename or parfile.subject)
|
||||
|
||||
def remove_parset(self, setname):
|
||||
self.partable.pop(setname)
|
||||
@@ -1110,8 +1123,14 @@ class NzbObject(TryList):
|
||||
if article:
|
||||
break
|
||||
|
||||
# Remove all files for which admin could not be read
|
||||
for nzf in nzf_remove_list:
|
||||
nzf.deleted = True
|
||||
nzf.completed = True
|
||||
self.files.remove(nzf)
|
||||
# If cleanup emptied the active files list, end this job
|
||||
if nzf_remove_list and not self.files:
|
||||
sabnzbd.NzbQueue.do.end_job(self)
|
||||
|
||||
if not article:
|
||||
# No articles for this server, block for next time
|
||||
@@ -1235,6 +1254,15 @@ class NzbObject(TryList):
|
||||
bytes_left += nzf.bytes_left
|
||||
return bytes_left
|
||||
|
||||
def total_and_remaining(self):
|
||||
""" Return total and remaining bytes """
|
||||
bytes = 0
|
||||
bytes_left = 0
|
||||
for nzf in self.files:
|
||||
bytes += nzf.bytes
|
||||
bytes_left += nzf.bytes_left
|
||||
return bytes, bytes_left
|
||||
|
||||
def gather_info(self, for_cli = False):
|
||||
bytes_left_all = 0
|
||||
|
||||
@@ -1377,8 +1405,9 @@ class NzbObject(TryList):
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def nzf_get_filename(nzf):
|
||||
# Return filename, if the filename not set, try the
|
||||
# the full subject line instead. Can produce non-ideal results
|
||||
""" Return filename, if the filename not set, try the
|
||||
the full subject line instead. Can produce non-ideal results
|
||||
"""
|
||||
name = nzf.filename
|
||||
if not name:
|
||||
name = nzf.subject
|
||||
@@ -1387,12 +1416,35 @@ def nzf_get_filename(nzf):
|
||||
return name.lower()
|
||||
|
||||
|
||||
def get_ext_list():
|
||||
""" Return priority extenstion list, with extensions starting with a period
|
||||
"""
|
||||
exts = []
|
||||
for ext in cfg.prio_sort_list():
|
||||
ext = ext.strip()
|
||||
if not ext.startswith('.'):
|
||||
ext = '.' + ext
|
||||
exts.append(ext)
|
||||
return exts
|
||||
|
||||
|
||||
def ext_on_list(name, lst):
|
||||
""" Return True if `name` contains any extension in `lst`
|
||||
"""
|
||||
for ext in lst:
|
||||
if name.rfind(ext) >= 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def nzf_cmp_date(nzf1, nzf2):
|
||||
# Compare files based on date, but give vol-par files preference
|
||||
""" Compare files based on date, but give vol-par files preference.
|
||||
Wrapper needed, because `cmp` function doesn't handle extra parms.
|
||||
"""
|
||||
return nzf_cmp_name(nzf1, nzf2, name=False)
|
||||
|
||||
|
||||
RE_RAR = re.compile(r'(\.rar|\.r\d\d)|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d', re.I)
|
||||
RE_RAR = re.compile(r'(\.rar|\.r\d\d|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d)$', re.I)
|
||||
|
||||
def nzf_cmp_name(nzf1, nzf2, name=True):
|
||||
# The comparison will sort .par2 files to the top of the queue followed by .rar files,
|
||||
@@ -1416,6 +1468,16 @@ def nzf_cmp_name(nzf1, nzf2, name=True):
|
||||
if is_par2 and not is_par1:
|
||||
return -1
|
||||
|
||||
# Anything with a priority extention goes first
|
||||
ext_list = get_ext_list()
|
||||
if ext_list:
|
||||
onlist1 = ext_on_list(name1, ext_list)
|
||||
onlist2 = ext_on_list(name2, ext_list)
|
||||
if onlist1 and not onlist2:
|
||||
return -1
|
||||
if onlist2 and not onlist1:
|
||||
return 1
|
||||
|
||||
if name:
|
||||
# Prioritise .rar files above any other type of file (other than vol-par)
|
||||
# Useful for nzb streaming
|
||||
@@ -1514,8 +1576,10 @@ def get_attrib_file(path, size):
|
||||
return [None for n in xrange(size)]
|
||||
|
||||
for n in xrange(size):
|
||||
line = f.readline().strip('\n ')
|
||||
line = f.readline().strip('\r\n ')
|
||||
if line:
|
||||
if line.lower() == 'none':
|
||||
line = None
|
||||
try:
|
||||
line = int(line)
|
||||
except:
|
||||
@@ -1539,3 +1603,43 @@ def set_attrib_file(path, attribs):
|
||||
f.write('%s\n' % item)
|
||||
f.close()
|
||||
|
||||
|
||||
def analyse_par2(name):
|
||||
""" Check if file is a par2-file and determine vol/block
|
||||
return head, vol, block
|
||||
head is empty when not a par2 file
|
||||
"""
|
||||
head = None
|
||||
vol = block = 0
|
||||
if name and not REJECT_PAR2_RE.search(name):
|
||||
m = PROBABLY_PAR2_RE.search(name)
|
||||
if m:
|
||||
head = m.group(1)
|
||||
vol = m.group(2)
|
||||
block = m.group(3)
|
||||
elif name.lower().find('.par2') > 0:
|
||||
head = os.path.splitext(name)[0].strip()
|
||||
else:
|
||||
head = None
|
||||
return head, vol, block
|
||||
|
||||
|
||||
def name_extractor(subject):
|
||||
""" Try to extract a file name from a subject line, return `subject` if in doubt
|
||||
"""
|
||||
result = subject
|
||||
for name in re.findall(SUBJECT_FN_MATCHER, subject):
|
||||
name = name.strip(' "')
|
||||
if name and RE_NORMAL_NAME.search(name):
|
||||
result = name
|
||||
return platform_encode(result)
|
||||
|
||||
|
||||
def matcher(pattern, txt):
|
||||
""" Return True if `pattern` is sufficiently equal to `txt`
|
||||
"""
|
||||
if txt.endswith(pattern):
|
||||
txt = txt[:txt.rfind(pattern)].strip()
|
||||
return (not txt) or txt.endswith('"')
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -23,9 +23,11 @@ from Foundation import *
|
||||
from AppKit import *
|
||||
from PyObjCTools import AppHelper
|
||||
from objc import YES, NO, nil
|
||||
from threading import Thread
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
|
||||
import cherrypy
|
||||
import Cheetah.DummyTransaction
|
||||
import sys
|
||||
@@ -52,7 +54,7 @@ from sabnzbd.newzbin import Bookmarks
|
||||
from sabnzbd.database import get_history_handle
|
||||
from sabnzbd.encoding import unicoder
|
||||
|
||||
status_icons = {'idle':'../Resources/sab_idle.png','pause':'../Resources/sab_pause.png','clicked':'../Resources/sab_clicked.png'}
|
||||
status_icons = {'idle':'../Resources/sab_idle.tiff','pause':'../Resources/sab_pause.tiff','clicked':'../Resources/sab_clicked.tiff'}
|
||||
start_time = NSDate.date()
|
||||
debug = 0
|
||||
|
||||
@@ -62,6 +64,7 @@ class SABnzbdDelegate(NSObject):
|
||||
icons = {}
|
||||
status_bar = None
|
||||
osx_icon = True
|
||||
history_db = None
|
||||
|
||||
def awakeFromNib(self):
|
||||
#Status Bar iniatilize
|
||||
@@ -344,10 +347,7 @@ class SABnzbdDelegate(NSObject):
|
||||
self.menu_queue.addItem_(menu_queue_item)
|
||||
self.menu_queue.addItem_(NSMenuItem.separatorItem())
|
||||
|
||||
job_nb = 1
|
||||
for pnfo in pnfo_list:
|
||||
if job_nb > 10:
|
||||
break
|
||||
filename = unicoder(pnfo[PNFO_FILENAME_FIELD])
|
||||
msgid = pnfo[PNFO_MSGID_FIELD]
|
||||
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD] / MEBI
|
||||
@@ -357,11 +357,10 @@ class SABnzbdDelegate(NSObject):
|
||||
timeleft = self.calc_timeleft(bytesleftprogess, bpsnow)
|
||||
|
||||
job = "%s\t(%d/%d MB) %s" % (filename, bytesleft, bytes, timeleft)
|
||||
job_nb += 1
|
||||
menu_queue_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(job, '', '')
|
||||
self.menu_queue.addItem_(menu_queue_item)
|
||||
|
||||
self.info = "%d nzb(s)\t( %d / %d MB )" % (len(pnfo_list),(qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI), (qnfo[QNFO_BYTES_FIELD] / MEBI))
|
||||
self.info = "%d nzb(s)\t( %d / %d MB )" % (qnfo[QNFO_Q_SIZE_LIST_FIELD],(qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI), (qnfo[QNFO_BYTES_FIELD] / MEBI))
|
||||
|
||||
else:
|
||||
menu_queue_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(T('Empty'), '', '')
|
||||
@@ -375,8 +374,9 @@ class SABnzbdDelegate(NSObject):
|
||||
def historyUpdate(self):
|
||||
try:
|
||||
# Fetch history items
|
||||
history_db = sabnzbd.database.get_history_handle()
|
||||
items, fetched_items, total_items = history_db.fetch_history(0,10,None)
|
||||
if not self.history_db:
|
||||
self.history_db = sabnzbd.database.get_history_handle()
|
||||
items, fetched_items, total_items = self.history_db.fetch_history(0,10,None)
|
||||
|
||||
self.menu_history = NSMenu.alloc().init()
|
||||
self.failedAttributes = { NSForegroundColorAttributeName:NSColor.redColor(), NSFontAttributeName:NSFont.menuFontOfSize_(14.0) }
|
||||
@@ -683,8 +683,9 @@ class SABnzbdDelegate(NSObject):
|
||||
if mode == "queue":
|
||||
NzbQueue.do.remove_all()
|
||||
elif mode == "history":
|
||||
history_db = sabnzbd.database.get_history_handle()
|
||||
history_db.remove_history()
|
||||
if not self.history_db:
|
||||
self.history_db = sabnzbd.database.get_history_handle()
|
||||
self.history_db.remove_history()
|
||||
|
||||
def pauseAction_(self, sender):
|
||||
minutes = int(sender.representedObject())
|
||||
@@ -785,4 +786,3 @@ def notify(notificationName, message):
|
||||
nc = Foundation.NSDistributedNotificationCenter.defaultCenter()
|
||||
nc.postNotificationName_object_(notificationName, message)
|
||||
del pool
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ import re
|
||||
from sabnzbd.newsunpack import unpack_magic, par2_repair, external_processing, sfv_check
|
||||
from threading import Thread
|
||||
from sabnzbd.misc import real_path, get_unique_path, create_dirs, move_to_path, \
|
||||
get_unique_filename, make_script_path, verified_flag_file, \
|
||||
make_script_path, \
|
||||
on_cleanup_list, renamer, remove_dir, remove_all, globber, \
|
||||
set_permissions
|
||||
set_permissions, cleanup_empty_directories
|
||||
from sabnzbd.tvsort import Sorter
|
||||
from sabnzbd.constants import REPAIR_PRIORITY, POSTPROC_QUEUE_FILE_NAME, \
|
||||
POSTPROC_QUEUE_VERSION, sample_match, JOB_ADMIN, Status
|
||||
from sabnzbd.constants import REPAIR_PRIORITY, TOP_PRIORITY, POSTPROC_QUEUE_FILE_NAME, \
|
||||
POSTPROC_QUEUE_VERSION, sample_match, JOB_ADMIN, Status, VERIFIED_FILE
|
||||
from sabnzbd.encoding import TRANS, unicoder
|
||||
from sabnzbd.newzbin import Bookmarks
|
||||
import sabnzbd.emailer as emailer
|
||||
@@ -184,8 +184,8 @@ class PostProcessor(Thread):
|
||||
sabnzbd.downloader.Downloader.do.wait_for_postproc()
|
||||
|
||||
self.__busy = True
|
||||
if process_job(nzo):
|
||||
self.remove(nzo)
|
||||
process_job(nzo)
|
||||
self.remove(nzo)
|
||||
check_eoq = True
|
||||
|
||||
## Allow download to proceed
|
||||
@@ -206,6 +206,8 @@ def process_job(nzo):
|
||||
par_error = False
|
||||
# keep track of any unpacking errors
|
||||
unpack_error = False
|
||||
# Signal empty download, for when 'empty_postproc' is enabled
|
||||
empty = False
|
||||
nzb_list = []
|
||||
# These need to be initialised incase of a crash
|
||||
workdir_complete = ''
|
||||
@@ -225,19 +227,6 @@ def process_job(nzo):
|
||||
filename = nzo.final_name
|
||||
msgid = nzo.msgid
|
||||
|
||||
if nzo.precheck:
|
||||
# Check result
|
||||
enough, ratio = nzo.check_quality()
|
||||
if enough:
|
||||
# Enough data present, do real download
|
||||
workdir = nzo.downpath
|
||||
sabnzbd.nzbqueue.NzbQueue.do.cleanup_nzo(nzo, keep_basic=True)
|
||||
sabnzbd.nzbqueue.NzbQueue.do.repair_job(workdir)
|
||||
return True
|
||||
else:
|
||||
# Not enough data, flag as failed
|
||||
nzo.save_attribs()
|
||||
|
||||
if cfg.allow_streaming() and not (flag_repair or flag_unpack or flag_delete):
|
||||
# After streaming, force +D
|
||||
nzo.set_pp(3)
|
||||
@@ -254,6 +243,7 @@ def process_job(nzo):
|
||||
# if no files are present (except __admin__), fail the job
|
||||
if len(globber(workdir)) < 2:
|
||||
if nzo.precheck:
|
||||
enough, ratio = nzo.check_quality()
|
||||
req_ratio = float(cfg.req_completion_rate()) / 100.0
|
||||
# Make sure that rounded ratio doesn't equal required ratio
|
||||
# when it is actually below required
|
||||
@@ -264,13 +254,15 @@ def process_job(nzo):
|
||||
emsg = T('Download might fail, only %s of required %s available') % (emsg, emsg2)
|
||||
else:
|
||||
emsg = T('Download failed - Out of your server\'s retention?')
|
||||
empty = True
|
||||
nzo.fail_msg = emsg
|
||||
nzo.set_unpack_info('Fail', emsg)
|
||||
nzo.status = Status.FAILED
|
||||
# do not run unpacking or parity verification
|
||||
flag_repair = flag_unpack = False
|
||||
par_error = unpack_error = True
|
||||
all_ok = False
|
||||
all_ok = cfg.empty_postproc() and empty
|
||||
if not all_ok:
|
||||
par_error = unpack_error = True
|
||||
|
||||
script = nzo.script
|
||||
cat = nzo.cat
|
||||
@@ -311,7 +303,7 @@ def process_job(nzo):
|
||||
## TV/Movie/Date Renaming code part 1 - detect and construct paths
|
||||
file_sorter = Sorter(cat)
|
||||
complete_dir = file_sorter.detect(dirname, complete_dir)
|
||||
if file_sorter.is_sortfile():
|
||||
if file_sorter.sort_file:
|
||||
one_folder = False
|
||||
|
||||
if one_folder:
|
||||
@@ -357,8 +349,9 @@ def process_job(nzo):
|
||||
for file_ in files:
|
||||
path = os.path.join(root, file_)
|
||||
new_path = path.replace(workdir, tmp_workdir_complete)
|
||||
new_path = get_unique_filename(new_path)
|
||||
if not move_to_path(path, new_path, unique=False):
|
||||
ok, new_path = move_to_path(path, new_path)
|
||||
newfiles.append(new_path)
|
||||
if not ok:
|
||||
nzo.set_unpack_info('Unpack', T('Failed moving %s to %s') % (unicoder(path), unicoder(new_path)))
|
||||
all_ok = False
|
||||
break
|
||||
@@ -382,10 +375,7 @@ def process_job(nzo):
|
||||
nzb_list = None
|
||||
if nzb_list:
|
||||
nzo.set_unpack_info('Download', T('Sent %s to queue') % unicoder(nzb_list))
|
||||
try:
|
||||
remove_dir(tmp_workdir_complete)
|
||||
except:
|
||||
pass
|
||||
cleanup_empty_directories(tmp_workdir_complete)
|
||||
else:
|
||||
cleanup_list(tmp_workdir_complete, False)
|
||||
|
||||
@@ -403,13 +393,16 @@ def process_job(nzo):
|
||||
logging.error(Ta('Error renaming "%s" to "%s"'), tmp_workdir_complete, workdir_complete)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
|
||||
job_result = int(par_error) + int(unpack_error)*2
|
||||
if empty:
|
||||
job_result = -1
|
||||
else:
|
||||
job_result = int(par_error) + int(unpack_error)*2
|
||||
|
||||
if cfg.ignore_samples() > 0:
|
||||
remove_samples(workdir_complete)
|
||||
|
||||
## TV/Movie/Date Renaming code part 2 - rename and move files to parent folder
|
||||
if all_ok and file_sorter.is_sortfile():
|
||||
if all_ok and file_sorter.sort_file:
|
||||
if newfiles:
|
||||
file_sorter.rename(newfiles, workdir_complete)
|
||||
workdir_complete, ok = file_sorter.move(workdir_complete)
|
||||
@@ -539,10 +532,14 @@ def process_job(nzo):
|
||||
def parring(nzo, workdir):
|
||||
""" Perform par processing. Returns: (par_error, re_add)
|
||||
"""
|
||||
assert isinstance(nzo, sabnzbd.nzbstuff.NzbObject)
|
||||
filename = nzo.final_name
|
||||
growler.send_notification(T('Post-processing'), nzo.final_name, 'pp')
|
||||
logging.info('Par2 check starting on %s', filename)
|
||||
|
||||
## Get verification status of sets
|
||||
verified = sabnzbd.load_data(VERIFIED_FILE, nzo.workpath, remove=False) or {}
|
||||
|
||||
## Collect the par files
|
||||
if nzo.partable:
|
||||
par_table = nzo.partable.copy()
|
||||
@@ -554,48 +551,66 @@ def parring(nzo, workdir):
|
||||
par_error = False
|
||||
|
||||
if repair_sets:
|
||||
|
||||
for set_ in repair_sets:
|
||||
logging.info("Running repair on set %s", set_)
|
||||
parfile_nzf = par_table[set_]
|
||||
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, set_)
|
||||
if need_re_add:
|
||||
re_add = True
|
||||
else:
|
||||
for setname in repair_sets:
|
||||
if cfg.ignore_samples() > 0 and 'sample' in setname.lower():
|
||||
continue
|
||||
if not verified.get(setname, False):
|
||||
logging.info("Running repair on set %s", setname)
|
||||
parfile_nzf = par_table[setname]
|
||||
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, setname)
|
||||
re_add = re_add or need_re_add
|
||||
if not res and not need_re_add and cfg.sfv_check():
|
||||
res = try_sfv_check(nzo, workdir, setname)
|
||||
verified[setname] = res
|
||||
par_error = par_error or not res
|
||||
|
||||
if re_add:
|
||||
logging.info('Readded %s to queue', filename)
|
||||
nzo.priority = REPAIR_PRIORITY
|
||||
sabnzbd.nzbqueue.add_nzo(nzo)
|
||||
sabnzbd.downloader.Downloader.do.resume_from_postproc()
|
||||
|
||||
logging.info('Par2 check finished on %s', filename)
|
||||
|
||||
if (par_error and not re_add) or not repair_sets:
|
||||
# See if alternative SFV check is possible
|
||||
else:
|
||||
logging.info("No par2 sets for %s", filename)
|
||||
nzo.set_unpack_info('Repair', T('[%s] No par2 sets') % unicoder(filename))
|
||||
if cfg.sfv_check():
|
||||
sfvs = globber(workdir, '*.sfv')
|
||||
else:
|
||||
sfvs = None
|
||||
if sfvs:
|
||||
par_error = False
|
||||
nzo.set_unpack_info('Repair', T('Trying SFV verification'))
|
||||
for sfv in sfvs:
|
||||
if not sfv_check(sfv):
|
||||
nzo.set_unpack_info('Repair', T('Some files failed to verify against "%s"') % unicoder(os.path.basename(sfv)))
|
||||
par_error = True
|
||||
if not par_error:
|
||||
nzo.set_unpack_info('Repair', T('Verified successfully using SFV files'))
|
||||
elif not repair_sets:
|
||||
logging.info("No par2 sets for %s", filename)
|
||||
nzo.set_unpack_info('Repair', T('[%s] No par2 sets') % unicoder(filename))
|
||||
par_error = not try_sfv_check(nzo, workdir, '')
|
||||
verified[''] = not par_error
|
||||
|
||||
if not par_error:
|
||||
verified_flag_file(workdir, create=True)
|
||||
if re_add:
|
||||
logging.info('Readded %s to queue', filename)
|
||||
if nzo.priority != TOP_PRIORITY:
|
||||
nzo.priority = REPAIR_PRIORITY
|
||||
sabnzbd.nzbqueue.add_nzo(nzo)
|
||||
sabnzbd.downloader.Downloader.do.resume_from_postproc()
|
||||
|
||||
sabnzbd.save_data(verified, VERIFIED_FILE, nzo.workpath)
|
||||
|
||||
logging.info('Par2 check finished on %s', filename)
|
||||
return par_error, re_add
|
||||
|
||||
|
||||
def try_sfv_check(nzo, workdir, setname):
|
||||
""" Attempt to verify set using SFV file
|
||||
Return True if verified, False when failed
|
||||
When setname is '', all SFV files will be used, otherwise only the matching one
|
||||
When setname is '' and no SFV files are found, True is returned
|
||||
"""
|
||||
# Get list of SFV names; shortest name first, minimizes the chance on a mismatch
|
||||
sfvs = globber(workdir, '*.sfv')
|
||||
sfvs.sort(lambda x, y: len(x) - len(y))
|
||||
par_error = False
|
||||
found = False
|
||||
for sfv in sfvs:
|
||||
if setname in os.path.basename(sfv):
|
||||
found = True
|
||||
nzo.set_unpack_info('Repair', T('Trying SFV verification'))
|
||||
failed = sfv_check(sfv)
|
||||
if failed:
|
||||
msg = T('Some files failed to verify against "%s"') % unicoder(os.path.basename(sfv))
|
||||
msg += '; '
|
||||
msg += '; '.join(failed)
|
||||
nzo.set_unpack_info('Repair', msg)
|
||||
par_error = True
|
||||
else:
|
||||
nzo.set_unpack_info('Repair', T('Verified successfully using SFV files'))
|
||||
if setname:
|
||||
break
|
||||
return (found or not setname) and not par_error
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
@@ -648,6 +663,11 @@ def cleanup_list(wdir, skip_nzb):
|
||||
except:
|
||||
logging.error(Ta('Removing %s failed'), path)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
if files:
|
||||
try:
|
||||
remove_dir(wdir)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def prefix(path, pre):
|
||||
@@ -663,29 +683,24 @@ def nzb_redirect(wdir, nzbname, pp, script, cat, priority):
|
||||
if so send to queue and remove if on CleanList
|
||||
Returns list of processed NZB's
|
||||
"""
|
||||
lst = []
|
||||
|
||||
try:
|
||||
files = os.listdir(wdir)
|
||||
except:
|
||||
files = []
|
||||
files = []
|
||||
for root, dirs, names in os.walk(wdir):
|
||||
for name in names:
|
||||
files.append(os.path.join(root, name))
|
||||
|
||||
for file_ in files:
|
||||
if os.path.splitext(file_)[1].lower() != '.nzb':
|
||||
return lst
|
||||
return None
|
||||
|
||||
# For a single NZB, use the current job name
|
||||
# For multiple NZBs, cannot use the current job name
|
||||
if len(files) != 1:
|
||||
nzbname = None
|
||||
|
||||
# Process all NZB files
|
||||
for file_ in files:
|
||||
if file_.lower().endswith('.nzb'):
|
||||
dirscanner.ProcessSingleFile(file_, os.path.join(wdir, file_), pp, script, cat,
|
||||
priority=priority, keep=False, dup_check=False, nzbname=nzbname)
|
||||
lst.append(file_)
|
||||
|
||||
return lst
|
||||
dirscanner.ProcessSingleFile(os.path.split(file_)[1], file_, pp, script, cat,
|
||||
priority=priority, keep=False, dup_check=False, nzbname=nzbname)
|
||||
return files
|
||||
|
||||
|
||||
def one_file_or_folder(folder):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user