Compare commits

...

83 Commits
0.7.3 ... 0.7.4

Author SHA1 Message Date
ShyPike
10b7403748 Update text files for 0.7.4 Final. 2012-10-02 20:04:03 +02:00
ShyPike
1ba924cc12 Pre-queue script didn't get the show/season/episode information any longer.
Due to earlier changes in tvsort, the pre-queue script handler now needs to
call an extra method of the SeriesSorter.
2012-10-02 19:56:47 +02:00
ShyPike
11eb034bd3 Prevent crash on startup when a fully downloaded job is still in download queue.
When at startup, there's a job in the download which has no more file to download,
it will be passed to post-processing. However the queue logic will try do disconnect
all servers first. This call will fail because the downloader hasn't start yet.
2012-10-02 19:34:21 +02:00
ShyPike
c3250e15cb Update text files for 0.7.4 Final. 2012-10-01 21:25:19 +02:00
shypike
8ff8a59b4c Update translations 2012-10-01 21:02:44 +02:00
ShyPike
0c646d88b2 New RSS feed should no longer be considered new after first, but empty readout.
Search and bookmark feeds can be empty when entered into SABnzbd.
The "don't download first batch" rule should be discarded when the
first readout is empty (as expected).
Currently the feed remains "new" until the first content is found.
2012-10-01 19:53:22 +02:00
ShyPike
05670ea599 Make "auth" call backward-compatible with 0.6.x releases.
Return "apikey" when no key is sent, instead of "badkey".
2012-09-30 23:17:05 +02:00
ShyPike
e25eb32885 Config->Notifications: email and growl server addresses should not be marked as "url" type. 2012-09-30 16:08:19 +02:00
shypike
250f75f084 OSX: fix top menu queue info.
The OSX queue menu entry shows a maximum of 10 jobs.
However, the counter should show the total amount of active jobs along with
the total size of these jobs.
Instead only the contribution of the 10 visible jobs was shown.
2012-09-29 15:21:30 +02:00
ShyPike
cdd39e6777 Plush: Purge history will now use the active filter.
When selecting one of the buttons in the Purge History dialog will
now use the current filter to select jobs to be deleted.
This is more what people expect to happen.
2012-09-29 12:19:56 +02:00
ShyPike
281ed6766c Update text files for 0.7.4 RC2 2012-09-27 20:38:16 +02:00
shypike
cd78c89de1 Update translations 2012-09-27 20:37:14 +02:00
ShyPike
c6c983e8f2 Linux: add memory usage to status display and add special option to control display.
Show "Total Program size" (V) and "Resident set size" (R).
Option "show_sysload" has three values:
0 = Off
1 = Show CPU load
2 = Show CPU load + memory usage (default)
2012-09-26 20:44:18 +02:00
ShyPike
ef4d1ce54f Fix problems with timing of quota reset at end of period. 2012-09-25 22:51:36 +02:00
ShyPike
b1177f4265 Fix issue with queue and history not updating in Safari-IOS6.
Add "Cache-Control":"no-cache" header to each Ajax POST action.
2012-09-24 21:38:30 +02:00
shypike
02d373e4a6 Remove warning about Growl when user has disabled message class in Growl itself.
Growl 2.0 uses different text than 1.4.
Test is now compatible with both versions.
2012-09-24 17:31:41 +02:00
shypike
58c8608667 Pre-check didn't check all available par2 files.
Result was that any post with a missing article was rejected.
Regression error introduced when fixing stalling fetches of extra par2 files.
2012-09-23 14:35:27 +02:00
ShyPike
848110ac3e Fix transmission of Growl icon when running on Linux/Unix.
There's a bug in the GNTP library that prevents sending the icon as
a binary attribute along with the registration.
Work-around is to send an URL to an external location.
2012-09-22 14:37:21 +02:00
ShyPike
74d677cf09 Update text files for 0.7.4 RC1. 2012-09-22 10:19:15 +02:00
ShyPike
f029c4eb4f Add a default limit to API-call "history" in order to prevent over-sized output.
Some external tools query the full history repeatedly.
Add a default value for the API call "history" as a work-around.
Calls that have a start and limit parameter will work unchanged.
The default limit can be set in the Special "history_limit".
2012-09-22 10:16:31 +02:00
ShyPike
838811f085 Update text files for 0.7.4 RC1 2012-09-21 20:50:40 +02:00
ShyPike
e212ec7ca3 Remove potential stalling when fetching extra par fetches.
Assembler didn't set the "completed" flag on a finished file,
leading to potential deadlock when fetching extra par2 files.
2012-09-21 20:47:25 +02:00
ShyPike
6314a536af Windows: be less eager to run par2-classic instead of the multi-core version. 2012-09-21 20:46:43 +02:00
shypike
df33765ce0 Update translations 2012-09-21 20:39:20 +02:00
ShyPike
2524333e79 Update text files for 0.7.4 RC1 2012-09-20 21:04:42 +02:00
ShyPike
cf81e815ee Classic and Plush skins must specify their character encoding.
Some browsers get it wrong when they need to guess the encoding.
Added:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
2012-09-19 20:29:26 +02:00
ShyPike
7bd7f1826c Win32: fix display of system name in startup notification message. 2012-09-19 20:00:19 +02:00
shypike
9ce7b528e3 Improved OSX DMG template. 2012-09-19 19:49:41 +02:00
shypike
db7b3cf0b5 Update translations 2012-09-18 21:52:26 +02:00
ShyPike
201b30b6c4 Fix packaging for Windows and add tar.gz for OSX again. 2012-09-18 20:47:57 +02:00
ShyPike
36a6bcd1f5 Update text files for 0.7.4 Beta3 2012-09-17 21:00:37 +02:00
shypike
20cfca5bb6 Combine OSX builds into one DMG image.
Split DMG creation off into a separate file.
`package.py` will now just build one OSX platform.
`make_dmg.py` will combine the three builds into one DMG.
2012-09-17 20:55:46 +02:00
ShyPike
63fe4b15c8 Show correct base folder for HTTPS certificate files (corrected). 2012-09-17 19:43:31 +02:00
ShyPike
aabc57a1f8 Show correct base folder for HTTPS certificate files. 2012-09-17 19:34:19 +02:00
ShyPike
9a8eca0993 Some code cleanup. 2012-09-17 19:16:22 +02:00
ShyPike
f8723d7e52 Prevent potential crash due to missing attribute in NzfObject. 2012-09-14 22:10:03 +02:00
ShyPike
59bb5528ed Ignore new schedule without days instead of crashing on it. 2012-09-14 21:46:36 +02:00
ShyPike
247c10692a Put "missing ssl support" warning in Wizard page One too. 2012-09-10 22:09:40 +02:00
shypike
63bed3c127 Update text files for 0.7.4Beta2 2012-09-10 19:27:58 +02:00
shypike
ca96743bad Update translations 2012-09-10 19:07:53 +02:00
shypike
525fb4de61 Update POT file, for new texts. 2012-09-10 19:06:54 +02:00
shypike
f294084dbc Build three separate DMG files for OSX.
Lion and MountainLion use the most recent native Python.
Leopard/SnowLeopard uses ActiveState Python 2.6.3.
Use a separate template for each OS version.
2012-09-09 22:19:10 +02:00
shypike
ca1327a9ae Improve handling of badly formatted subject lines in NZBs.
The NZB parser overwrote the subject even when there was no proper filename extracted.
This compromized recognition of par2 files after a first failed verification run.
Improve ability to extract a proper file name so that sorting and displaying is better.
The extraction still won't work for every malformed subject, but at least the par2 recognition should always work now.
2012-09-09 14:49:30 +02:00
shypike
dcb1b0b3dc Improve startup notification.
- Move hostname() from misc.py to growler.py.
- Improve startup notification
2012-09-08 21:00:28 +02:00
shypike
ec4b613498 Modify growler.py for new GNTP module. Fix transmission of icon.
- use 'localhost' for local Growl instead of "None".
- Sending binary icon now works properly, so external icon not needed any more.
- No need to send icon with every message, only when registering.
- Log more info about gntp-error exceptions
2012-09-08 20:23:59 +02:00
shypike
c3f4eccfbc Re-apply custom patches to new GNTP.
Reduce logging verbosity for sent messages.
Convert "info" logging to "debug" logging.
Suppress 404 + "user has disabled" error (which isn't really an error).
Time-out patch no longer needed, built-in now.
2012-09-08 20:18:13 +02:00
shypike
1aafe25a83 Update GNTP module.
Version 0.8 from github.
2012-09-08 19:52:46 +02:00
shypike
cc25ef0af0 Prevent filing an error message when the user has disabled message classes in Growl-preferences.
Problem is in the GNTP library, while at the same time it is very odd that Growl sends an error response for this.
2012-09-08 15:45:17 +02:00
shypike
5c221f4a14 After successful pre-check, preserve a job's spot in the queue.
Previously a pre-checked job re-entered at the end of the queue.
Moved the quality check from the post-processor to nzbqueue, so a successful job doesn't need to go through the post-processor.
Improvement: all other ways for a job to be sent to post-processing need to go through the end_job() method.
2012-09-08 15:01:14 +02:00
shypike
03221fc645 Config skin: "check new releases" setting did not work properly. 2012-09-08 09:46:40 +02:00
ShyPike
febf81e597 Update text files for 0.7.4Beta1 2012-09-06 21:53:52 +02:00
shypike
447ec55822 OSX: create one build for ML and one for others.
Also remove 64bit code from binaries.
2012-09-06 21:51:12 +02:00
ShyPike
eee1f49c4a Fix translation of notification classes in Config->Notification. 2012-09-05 20:00:33 +02:00
shypike
140b903783 Update translations 2012-09-05 19:43:57 +02:00
ShyPike
8361bc9f3a A job with priority "forced" should keep that when fetching more par2 files.
It was now set to "repair" priority, which doesn't ignore paused state.
Other priorities will still be promoted to "repair".
2012-09-04 19:10:13 +02:00
ShyPike
744290c228 Log failed attempts to login to the Web UI. 2012-09-03 23:18:08 +02:00
ShyPike
20768df430 Remove leak of SQLite database handles.
The non-UI threads need to close the db handles after using.
Move the unnecessary proxy for the database sizes for the bpsmeter.
2012-09-03 22:35:41 +02:00
ShyPike
7ec7e8d432 Allow compression of more MIME type in API-calls. 2012-09-03 19:30:29 +02:00
shypike
7b657a85ba Update POT files. 2012-09-01 13:23:04 +02:00
ShyPike
258699f1db Add scheduled task "Remove failed jobs". 2012-09-01 12:43:33 +02:00
Lucas Parry
0412f45323 Make scheduler more flexible
Schedules can now be set for any arbitrary group of days of the week.
2012-09-01 12:09:11 +02:00
shypike
997eb93cd9 Don't do an SFV-based verification when the job is already marked as verified.
When retrying a job that has passed succesful verification, the par2 files will already have been removed. When doing an SFV-based verification, this may lead to errors because the PAR2 files are gone. So when the "__verified__" flag-file is present and there's no par2-set, skip the SFV check.
2012-09-01 11:42:41 +02:00
shypike
b94192486b Add special option "overwrite_files".
This will overwrite existing files in the "complete" folders.
Useful for effective in "Single folder" mode and "Season Sorting".
2012-08-29 22:08:07 +02:00
shypike
73bdd2c5bf Prevent error and pause when last file of a removed job happens to be written to (and fails). 2012-08-25 12:49:07 +02:00
shypike
56e9b54cd9 Add parameter "pp_active" to the "qstatus" api call, showing state of Postprocessing. 2012-08-25 11:13:51 +02:00
shypike
3308074f81 Don't also show job in post-processing queue while fetching more par2 files. 2012-08-23 22:24:46 +02:00
shypike
c2305034a1 Handle unexpected missing par2 file in par2-check properly. 2012-08-23 20:06:44 +02:00
shypike
d77b22be37 Add support for HTTPS chain certificate file.
Just enabling the user to specify the file and then passing it on to CherryPy.
2012-08-18 14:51:21 +02:00
ShyPike
9717912ff7 When a numeric option has no value, it should be set to the default and not to the minimum value. 2012-08-16 13:47:29 +02:00
ShyPike
cfa79c08b2 When retrying post processing don't attempt to download existing par2 files again. 2012-08-16 12:10:48 +02:00
ShyPike
4f65f87ad6 Fix syntax of command used in server test. 2012-08-16 11:37:57 +02:00
shypike
aea0d21fd2 Log the output of the PAR2 command.
This will help diagnosing repair trouble that users might have.
2012-08-15 19:17:43 +02:00
shypike
ba77b43364 Default for Growl is off when on MountainLion. 2012-08-15 18:36:41 +02:00
shypike
7727eb58a8 Sort 'Done' list on RSS page in reverse time order (latest first). 2012-08-15 18:31:16 +02:00
shypike
dbd2e3f54b Make list of RSS sites that use weird titles a "Special" option, "misc, rss_odd_titles". 2012-08-15 18:13:41 +02:00
shypike
7ac6e07576 Update translatable texts 2012-08-14 20:42:34 +02:00
shypike
24ffd90fb4 sleepless.keep_awake() call needs a Unicode argument 2012-08-14 20:37:57 +02:00
shypike
011b680337 Fix path for OSX-Leopard build 2012-08-11 17:18:22 +02:00
shypike
5328e07a93 OSX: add notifier package and licencse files to DMG. 2012-08-11 14:37:37 +02:00
shypike
2d07af7cc8 Add parameter to sleepless.keep_awake() call. 2012-08-11 13:23:32 +02:00
shypike
a40d2da2ab Improve OSX Mountain Lion "stay awake" support with native OS calls.
Also add Special option "keep_awake" for Windows and OSX.
2012-08-10 22:49:41 +02:00
shypike
25ac101751 Add OSX Notification Center support.
Add option when support available on OSX ML.
Add class selection for notifications. Useful for N-Center and NtfOSD.
2012-08-09 22:55:07 +02:00
ShyPike
a666165a5e Plush: remove mailto link in Help pop-up. 2012-08-04 13:44:22 +02:00
70 changed files with 12449 additions and 11501 deletions

View File

@@ -1,5 +1,5 @@
*******************************************
*** This is SABnzbd 0.7.3 ***
*** This is SABnzbd 0.7.4 ***
*******************************************
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically,

View File

@@ -1,3 +1,62 @@
-------------------------------------------------------------------------------
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
-------------------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
SABnzbd 0.7.3
SABnzbd 0.7.4
-------------------------------------------------------------------------------
0) LICENSE

View File

@@ -1,7 +1,7 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 0.7.3
Summary: SABnzbd-0.7.3
Version: 0.7.4
Summary: SABnzbd-0.7.4
Home-page: http://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org

View File

@@ -1,23 +1,36 @@
Release Notes - SABnzbd 0.7.3
Release Notes - SABnzbd 0.7.4
===============================
## Fixes in 0.7.3
## Highlights in 0.7.4
- Prevent queue deadlock in case of fatally damaged par2 files
- Try to keep OSX Mountain Lion awake as long as downloading and postprocessing run
- Add RSS filter-enable checkboxes to Plush, Smpl and Classic skins
- Fix problem with saving modified parameters of an already enabled server
- Extend "check new release" option with test releases
- Correct several errors in Sort function
- Improve organization of Config->Servers
- Make detection of samples less aggressive
- Ignore pseudo NZB files that start with a period in the name
- OSX Mountain Lion: Notification Center support
- OSX Mountain Lion: improved "keep awake" support
- Scheduler: action can now run on multiple weekdays
- Fix: pre-check failed to consider extra par2 files
The Special option "random_server_ip" has been renamed to "randomize_server_ip"
and the default is now "Off".
The reason is that this option kills the speed of some Usenet providers.
If you really need this feature, you have to re-enable it.
## Features
- Support for HTTPS chain files (needed when you buy your own certificate)
- Special option: rss_odd_titles, see [Wiki](http://wiki.sabnzbd.org/configure-special-0-7/ "Wiki")
- Special option: 'overwrite_files', see [Wiki](http://wiki.sabnzbd.org/configure-special-0-7/ "Wiki")
- Show memory usage on Linux systems
- Scheduler: add "remove failed jobs" action
## Bug fixes
- After successful pre-check, preserve a job's position in the queue
- Restore SABnzbd icon for Growl
- Make Windows version less eager to use par2-classic
- Prevent jobs from showing up in queue and history simultaneously
- Fix failure to fetch more par2-files for posts with badly formatted subject lines
- Fix for third-party tools requesting too much history
- 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
- Fixed unjustified warning that can occur with OSX Growl 2.0
- 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.
- Fix incorrect end-of-month quota reset
- Fix UI refresh issue when using Safari on iOS6 (Safari bug)
## What's new in 0.7.0

View File

@@ -671,7 +671,7 @@ 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
"""
if not (sabnzbd.cfg.no_ipv6() and '::1' in host):
@@ -680,6 +680,7 @@ def attach_server(host, port, cert=None, key=None):
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()
@@ -1350,6 +1351,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)):
@@ -1379,14 +1384,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)
@@ -1404,6 +1410,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,
@@ -1413,7 +1430,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,
@@ -1504,8 +1521,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
@@ -1574,7 +1591,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()
@@ -1813,4 +1830,4 @@ if __name__ == '__main__':
main()
else:
main()
main()

View File

@@ -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")

View File

@@ -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':

View File

@@ -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')

View File

@@ -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#-->

View File

@@ -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"/>

View File

@@ -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" />

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -14,9 +14,9 @@
<div class="field-pair alt">
<label class="config" for="check_new_rel">$T('opt-check_new_rel')</label>
<select name="check_new_rel" id="check_new_rel">
<option value="0" <!--#if $check_new_rel == "1" then 'selected="selected" class="selected"' else ""#--> >$T('off')</option>
<option value="1" <!--#if $check_new_rel == "2" then 'selected="selected" class="selected"' else ""#--> >$T('on')</option>
<option value="2" <!--#if $check_new_rel == "3" then 'selected="selected" class="selected"' else ""#--> >$T('also-test')</option>
<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>

View File

File diff suppressed because one or more lines are too long

View File

@@ -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"/>

View File

@@ -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 &lt;<a href="mailto:team@sabnzbd.org">team@sabnzbd.org</a>&gt;</small></p>
<p><small>Copyright (C) 2008-2012, The SABnzbd Team &lt;team@sabnzbd.org&gt;</small></p>
<p><small>$T('yourRights')</small></p>
</div>

View File

@@ -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">

View File

@@ -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,

View File

@@ -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">

View File

@@ -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) {

View File

@@ -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" />

103
make_dmg.py Normal file
View 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
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

Binary file not shown.

View File

@@ -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):
@@ -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
@@ -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)
@@ -259,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)
@@ -298,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)
@@ -364,31 +367,11 @@ if target == 'app':
# 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')
@@ -428,16 +411,22 @@ 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("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")
@@ -446,52 +435,39 @@ if target == 'app':
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.remove('dist/SABnzbd.app/Contents/Resources/site.py')
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/")
# 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/")
os.system("sleep 5")
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))
# copy app to mounted sparseimage
os.system('cp -r dist/SABnzbd.app "/Volumes/%s/OS X 10.6 and Above/" >/dev/null' % volume)
# 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))
# 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) )
# Generate README.rtf
os.system("cp dist/SABnzbd.app/Contents/Resources/Credits.rtf /Volumes/%s/README.rtf" % 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)
'./ >/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!"
@@ -533,10 +509,10 @@ elif target in ('binary', 'installer'):
name, ext = os.path.splitext(file)
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
@@ -573,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
@@ -598,15 +574,15 @@ elif target in ('binary', 'installer'):
############################
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)
@@ -684,4 +660,3 @@ else:
CreateTar('srcdist', fileSrc, prod)
os.system(GitRevertVersion)

View File

@@ -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-07-07 04:53+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-07-08 04:55+0000\n"
"X-Generator: Launchpad (build 15558)\n"
"X-Launchpad-Export-Date: 2012-08-04 05:38+0000\n"
"X-Generator: Launchpad (build 15742)\n"
#: email/email.tmpl:1
msgid ""
@@ -75,8 +75,8 @@ msgstr ""
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har <!--#if $status then \"hentet\" else \"fejlet\" #--> job "
"$name\n"
"Subject: SABnzbd har <!--#if $status then \"hentet\" else \"fejlet\" #--> "
"job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Efter dette kommer body, den tomme linje kræves!\n"

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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."

View File

@@ -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."

View File

@@ -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-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-07-31 05:17+0000\n"
"X-Generator: Launchpad (build 15702)\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 !"
@@ -41,31 +41,31 @@ 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?"
@@ -73,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."
@@ -94,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."

View File

@@ -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."

View File

@@ -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 ""

View File

@@ -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."

View File

@@ -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."

View File

@@ -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."

View File

@@ -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."

View File

@@ -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."

View File

@@ -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
@@ -345,11 +349,6 @@ def start():
logging.debug('Starting urlgrabber')
URLGrabber.do.start()
if DARWIN_ML:
logging.debug('Starting OSX keep-awake service')
osxmenu.OsxAwake()
osxmenu.OsxAwake.do.start()
@synchronized(INIT_LOCK)
def halt():
@@ -409,9 +408,6 @@ def halt():
except:
logging.error('Fatal error at saving state', exc_info=True)
if DARWIN_ML:
osxmenu.OsxAwake.do.stop()
# The Scheduler cannot be stopped when the stop was scheduled.
# Since all warm-restarts have been removed, it's not longer
@@ -764,16 +760,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
"""
if KERNEL32 or DARWIN_ML:
if not sabnzbd.downloader.Downloader.do.paused:
if (not PostProcessor.do.empty()) or not NzbQueue.do.is_empty():
if KERNEL32:
# set ES_SYSTEM_REQUIRED
KERNEL32.SetThreadExecutionState(ctypes.c_int(0x00000001))
else:
osxmenu.OsxAwake.do.stay_awake = True
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():
@@ -1075,16 +1076,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()

View File

@@ -422,6 +422,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 +432,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(',')
@@ -554,10 +556,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)
@@ -1272,6 +1277,7 @@ def qstatus_data():
status = {
"state" : state,
"pp_active" : not PostProcessor.do.empty(),
"paused" : Downloader.do.paused,
"pause_int" : scheduler.pause_int(),
"kbpersec" : bpsnow / KIBI,
@@ -1673,9 +1679,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:
@@ -1741,6 +1749,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)
@@ -1932,3 +1943,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

View File

@@ -88,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
@@ -110,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)
@@ -201,18 +206,19 @@ 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 = {}
if not flag_file(os.path.split(fname)[0], QCHECK_FILE):
if force or not flag_file(os.path.split(fname)[0], QCHECK_FILE):
try:
f = open(fname, 'rb')
except:
return table, new_encoding
new_encoding = False
try:
header = f.read(8)
while header:

View File

@@ -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
@@ -140,7 +140,10 @@ class BPSMeter(object):
""" 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()
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 = {}
@@ -307,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])
@@ -380,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:

View File

@@ -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, \
@@ -92,6 +92,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)
@@ -129,6 +130,7 @@ safe_postproc = OptionBool('misc', 'safe_postproc', True)
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')
@@ -205,6 +207,7 @@ 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')
@@ -227,8 +230,10 @@ 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')
@@ -237,11 +242,14 @@ 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)
#------------------------------------------------------------------------------
# Set root folders for Folder config-items
@@ -261,3 +269,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())

View File

@@ -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)

View File

@@ -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
@@ -157,3 +158,5 @@ class Status():
REPAIRING = 'Repairing'
RUNNING = 'Running'
VERIFYING = 'Verifying'
NOTIFY_KEYS = ('startup', 'download', 'pp', 'complete', 'other')

View File

@@ -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:

View File

@@ -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 ''

View File

@@ -1188,12 +1188,15 @@ SPECIAL_BOOL_LIST = \
'queue_complete_pers', 'api_warnings', 'allow_64bit_tools', 'par2_multicore',
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames',
'osx_menu', 'osx_speed', 'win_menu', 'uniconfig', 'use_pickle', 'allow_incomplete_nzb',
'randomize_server_ip', 'no_ipv6'
'randomize_server_ip', 'no_ipv6', 'keep_awake', 'overwrite_files'
)
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'
)
SPECIAL_LIST_LIST = \
( 'rss_odd_titles',
)
class ConfigSpecial(object):
def __init__(self, web_dir, root, prim):
@@ -1212,6 +1215,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 +1226,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)
@@ -1237,7 +1241,7 @@ class ConfigSpecial(object):
GENERAL_LIST = (
'host', 'port', 'username', 'password', 'disable_api_key',
'refresh_rate', 'cache_limit',
'enable_https', 'https_port', 'https_cert', 'https_key'
'enable_https', 'https_port', 'https_cert', 'https_key', 'https_chain'
)
class ConfigGeneral(object):
@@ -1335,6 +1339,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 +1348,7 @@ class ConfigGeneral(object):
conf['cache_limit'] = cfg.cache_limit()
conf['cleanup_list'] = cfg.cleanup_list.get_string()
conf['nzb_key'] = cfg.nzb_key()
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)
@@ -1885,7 +1891,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):
@@ -1914,7 +1920,7 @@ class ConfigScheduling(object):
actions = []
actions.extend(_SCHED_ACTIONS)
days = get_days()
day_names = get_days()
conf['schedlines'] = []
snum = 1
conf['taskinfo'] = []
@@ -1922,7 +1928,7 @@ class ConfigScheduling(object):
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()
@@ -1942,7 +1948,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, 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
@@ -1967,7 +1983,7 @@ 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', '')])
action = kwargs.get('action')
arguments = kwargs.get('arguments')
@@ -1977,7 +1993,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'
@@ -1996,7 +2012,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()
@@ -2488,10 +2504,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):
@@ -2575,7 +2596,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):
@@ -2595,11 +2616,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)
@@ -2618,6 +2643,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

View File

@@ -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, VERIFIED_FILE, Status, MEBI
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.encoding import unicoder, latin1
@@ -716,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
@@ -797,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)
@@ -818,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)
@@ -1000,15 +999,37 @@ 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 None
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:
p = '%.2f | %.2f | %.2f' % os.getloadavg()
if opt > 1 and _HAVE_STATM:
p = '%s | %s' % (p, memory_usage())
return p
def format_time_string(seconds, days=0):

View File

@@ -125,7 +125,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)):
@@ -133,7 +133,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:
@@ -258,8 +258,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)
@@ -276,8 +276,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)
@@ -297,7 +297,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':
@@ -384,6 +385,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:
@@ -438,7 +440,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)
@@ -457,6 +459,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:
@@ -524,17 +528,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():
@@ -551,7 +561,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
@@ -628,7 +638,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
proc.close()
p.wait()
return (fail, (), ())
return fail, (), ()
if proc:
proc.close()
@@ -670,7 +680,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
@@ -715,7 +725,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)
@@ -894,7 +904,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:
@@ -911,6 +921,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
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,
@@ -951,8 +962,15 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
if line == '':
continue
# And off we go
if line.startswith('Invalid option specified'):
if 'Repairing:' not in line:
lines.append(line)
if 'The recovery file does not exist' in line:
logging.info('%s', line)
nzo.set_unpack_info('Repair', unicoder(line), set=setname)
nzo.status = Status.FAILED
elif 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)
nzo.status = Status.FAILED
@@ -1056,7 +1074,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
if extrapar_list:
new_nzf = extrapar_list.pop()
nzo.add_parfile(new_nzf)
extrapars.remove(new_nzf)
if new_nzf in extrapars: extrapars.remove(new_nzf)
added_blocks += block_size
else:
@@ -1085,7 +1103,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'):
@@ -1149,11 +1167,13 @@ 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 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
#-------------------------------------------------------------------------------
@@ -1204,7 +1224,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):
@@ -1272,7 +1292,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):
@@ -1353,7 +1373,6 @@ def sfv_check(sfv_path):
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] != ';':
@@ -1396,6 +1415,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

View File

@@ -170,6 +170,34 @@ class NzbQueue(TryList):
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
nzo_id = ProcessSingleFile(os.path.split(nzb_path)[1], nzb_path, reuse=True)[1][0]
self.replace_in_q(nzo, nzo_id)
@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.pop(pos)
self.__nzo_list[targetpos] = new_nzo
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 """
@@ -297,8 +325,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
@@ -399,10 +426,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)
@@ -714,11 +741,24 @@ 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
if not nzo.deleted:
nzo.deleted = True
if nzo.precheck:
# 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
nzo.save_attribs()
Assembler.do.process((nzo, None))
@@ -740,18 +780,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)

View File

@@ -55,6 +55,7 @@ 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
@@ -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)
@@ -834,7 +831,8 @@ class NzbObject(TryList):
if head and lparset in head.lower():
xnzf.set_par2(parset, vol, block)
self.extrapars[parset].append(xnzf)
self.files.remove(xnzf)
if not self.precheck:
self.files.remove(xnzf)
def handle_par2(self, nzf, file_done):
""" Check if file is a par2 and build up par2 collection
@@ -875,7 +873,7 @@ class NzbObject(TryList):
## 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
@@ -942,6 +940,7 @@ class NzbObject(TryList):
self.files_table[nzf.nzf_id] = nzf
self.bytes += nzf.bytes
nzf.filename = filename
nzf.completed = True
self.handle_par2(nzf, file_done=True)
self.remove_nzf(nzf)
logging.info('File %s added to job', filename)
@@ -1242,6 +1241,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
@@ -1565,3 +1573,14 @@ def analyse_par2(name):
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 result

View File

@@ -64,6 +64,7 @@ class SABnzbdDelegate(NSObject):
icons = {}
status_bar = None
osx_icon = True
history_db = None
def awakeFromNib(self):
#Status Bar iniatilize
@@ -346,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
@@ -359,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'), '', '')
@@ -377,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) }
@@ -685,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())
@@ -787,41 +786,3 @@ def notify(notificationName, message):
nc = Foundation.NSDistributedNotificationCenter.defaultCenter()
nc.postNotificationName_object_(notificationName, message)
del pool
#------------------------------------------------------------------------------
class OsxAwake(Thread):
""" Keep running 'caffeinate' as long as the 'stay_awake' flag is set
"""
do = None
def __init__(self):
Thread.__init__(self)
self.stay_awake = False
self.__stop_now = False
self.__proc = None
logging.info('Starting caffeinate task')
OsxAwake.do = self
def run(self):
command = ['caffeinate', '-i', '-s', '-t300']
while not self.__stop_now:
if self.stay_awake:
self.stay_awake = False
logging.debug('Launching caffeinate')
self.__proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
self.__proc.wait()
self.__proc = None
else:
time.sleep(30)
def stop(self):
logging.info('Stopping caffeinate task')
self.__stop_now = True
self.stay_awake = False
if self.__proc:
logging.debug('Stopping caffeinate')
try:
self.__proc.terminate()
except:
pass

View File

@@ -35,7 +35,7 @@ from sabnzbd.misc import real_path, get_unique_path, create_dirs, move_to_path,
on_cleanup_list, renamer, remove_dir, remove_all, globber, \
set_permissions
from sabnzbd.tvsort import Sorter
from sabnzbd.constants import REPAIR_PRIORITY, POSTPROC_QUEUE_FILE_NAME, \
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
@@ -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
@@ -225,19 +225,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 +241,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
@@ -357,9 +345,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)
ok, new_path = move_to_path(path, new_path)
newfiles.append(new_path)
if not move_to_path(path, new_path, unique=False):
if not ok:
nzo.set_unpack_info('Unpack', T('Failed moving %s to %s') % (unicoder(path), unicoder(new_path)))
all_ok = False
break
@@ -567,7 +555,8 @@ def parring(nzo, workdir):
if re_add:
logging.info('Readded %s to queue', filename)
nzo.priority = REPAIR_PRIORITY
if nzo.priority != TOP_PRIORITY:
nzo.priority = REPAIR_PRIORITY
sabnzbd.nzbqueue.add_nzo(nzo)
sabnzbd.downloader.Downloader.do.resume_from_postproc()
@@ -575,7 +564,7 @@ def parring(nzo, workdir):
if (par_error and not re_add) or not repair_sets:
# See if alternative SFV check is possible
if cfg.sfv_check():
if cfg.sfv_check() and not (flag_file(workdir, VERIFIED_FILE) and not repair_sets):
sfvs = globber(workdir, '*.sfv')
else:
sfvs = None

View File

@@ -31,7 +31,7 @@ from sabnzbd.constants import *
from sabnzbd.decorators import synchronized
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.misc import cat_convert, sanitize_foldername, wildcard_to_re, cat_to_opts
from sabnzbd.misc import cat_convert, sanitize_foldername, wildcard_to_re, cat_to_opts, match_str
import sabnzbd.emailer as emailer
from sabnzbd.encoding import latin1, unicoder, xml_name
@@ -303,13 +303,7 @@ class RSSQueue(object):
regcount = len(regexes)
# Set first if this is the very first scan of this URI
if feed not in self.jobs:
self.jobs[feed] = {}
first = not bool(self.jobs[feed])
jobs = self.jobs[feed]
first = first and ignoreFirst
first = (feed not in self.jobs) and ignoreFirst
# Add sabnzbd's custom User Agent
feedparser.USER_AGENT = 'SABnzbd+/%s' % sabnzbd.version.__version__
@@ -319,6 +313,8 @@ class RSSQueue(object):
uri += '&dl=1'
# Read the RSS feed
msg = None
entries = None
if readout:
uri = uri.replace(' ', '%20')
logging.debug("Running feedparser on %s", uri)
@@ -343,6 +339,12 @@ class RSSQueue(object):
if not entries:
msg = Ta('RSS Feed %s was empty') % uri
logging.info(msg)
if feed not in self.jobs:
self.jobs[feed] = {}
jobs = self.jobs[feed]
if readout:
if not entries:
return unicoder(msg)
else:
entries = jobs.keys()
@@ -656,12 +658,12 @@ def _get_link(uri, entry):
def special_rss_site(url):
""" Return True if url describes an RSS site with odd titles
"""
return cfg.rss_filenames() or 'nzbindex.nl/' in url or 'nzbindex.com/' in url or 'nzbclub.com/' in url
return cfg.rss_filenames() or match_str(url, cfg.rss_odd_titles())
_ENCL_SITES = ('nzbindex.nl', 'nzbindex.com', 'animeusenet.org', 'nzbclub.com')
def encl_sites(url, link):
""" Return True if url or link match sites that use enclosures
""" Return True if url or link matches sites that use enclosures
"""
for site in _ENCL_SITES:
if site in url or (link and site in link):

View File

@@ -116,6 +116,8 @@ def init():
elif action_name == 'rss_scan':
action = rss.run_method
rss_planned = True
elif action_name == 'remove_failed':
action = sabnzbd.api.history_remove_failed
else:
logging.warning(Ta('Unknown action: %s'), action_name)
continue

View File

@@ -62,6 +62,7 @@ SKIN_TEXT = {
'sch-resume_post' : TT('Resume post-processing'), #: #: Config->Scheduler
'sch-scan_folder' : TT('Scan watched folder'), #: #: Config->Scheduler
'sch-rss_scan' : TT('Read RSS feeds'), #: #: Config->Scheduler
'sch-remove_failed' : TT('Remove failed jobs'), #: Config->Scheduler
# General texts
'default' : TT('Default'), #: Default value, used in dropdown menus
@@ -298,6 +299,8 @@ SKIN_TEXT = {
'explain-https_cert' : TT('File name or path to HTTPS Certificate.'),
'opt-https_key' : TT('HTTPS Key'),
'explain-https_key' : TT('File name or path to HTTPS Key.'),
'opt-https_chain' : TT('HTTPS Chain Certifcates'),
'explain-https_chain' : TT('File name or path to HTTPS Chain.'),
'tuning' : TT('Tuning'),
'opt-refresh_rate' : TT('Queue auto refresh interval:'),
'explain-refresh_rate' : TT('Refresh interval of the queue web-interface page(sec, 0= none).'),
@@ -551,6 +554,10 @@ SKIN_TEXT = {
'explain-growl_password' : TT('Optional password for Growl server'), #: Don't translate "Growl"
'opt-ntfosd_enable' : TT('Enable NotifyOSD'), #: Don't translate "NotifyOSD"
'explain-ntfosd_enable' : TT('Send notifications to NotifyOSD'), #: Don't translate "NotifyOSD"
'opt-ncenter_enable' : TT('Notification Center'),
'explain-ncenter_enable' : TT('Send notifications to Notification Center'),
'opt-notify_classes' : TT('Notification classes'),
'explain-notify_classes' : TT('Enable classes of messages to be reported (none, one or multiple)'),
'testNotify' : TT('Test Notification'),
# Config->Newzbin

View File

@@ -84,8 +84,8 @@ def move_to_parent_folder(workdir):
for _file in files:
path = os.path.join(root, _file)
new_path = path.replace(workdir, dest)
new_path = get_unique_filename(new_path)
if not move_to_path(path, new_path, False):
ok, new_path = move_to_path(path, new_path)
if not ok:
return dest, False
cleanup_empty_directories(workdir)

View File

@@ -105,7 +105,7 @@ def test_nntp_server(host, port, server=None, username=None, password=None, ssl=
if not username or not password:
nw.nntp.sock.sendall('ARTICLE test@home\r\n')
nw.nntp.sock.sendall('ARTICLE <test@home>\r\n')
try:
nw.lines = []
nw.recv_chunk(block=True)