Compare commits

..

235 Commits

Author SHA1 Message Date
ShyPike
a82df9bf2e Update text files for 0.7.7 2012-12-14 20:47:59 +01:00
ShyPike
986604f27c Update feedparser.py to from 5.1 to 5.1.3 2012-12-14 19:04:50 +01:00
shypike
59324c7453 Remove "Indexers" support page from the Wizard. 2012-12-09 13:03:03 +01:00
shypike
91613a5b37 Remove "Indexers" page from all skins. 2012-12-09 12:28:02 +01:00
shypike
5ca05fd2c0 OSX: remove association with "rar" and "zip" files. 2012-12-08 14:44:54 +01:00
shypike
4d4045cff4 Update translations 2012-12-05 21:27:04 +01:00
ShyPike
1f209a42d8 Filter unusable folders from lists generated by pathbrowser. 2012-12-03 21:13:13 +01:00
shypike
bffbb362db Update POT file. 2012-12-01 10:53:05 +01:00
shypike
435eed8818 Add 'B' to download totals on main page. 2012-12-01 10:48:27 +01:00
shypike
f86656543a Add schedule: when schedule has no days, assume daily instead of ignoring. 2012-12-01 10:06:11 +01:00
ShyPike
9c510c6dd1 When re-evaluating RSS feed, the original sort order was not preserved.
When re-using RSS entries from memory, use the original feed order and not
Python's random dictionary order.
2012-11-30 20:48:03 +01:00
ShyPike
f81ab3d1c0 Prevent crash when nzbxxx.com's category cannot be determined. 2012-11-30 18:47:38 +01:00
ShyPike
d1585c28a9 Remove all visible features for newzbin. 2012-11-29 22:07:45 +01:00
ShyPike
9c314532c0 Add optional web-watchdog to the watchdog. 2012-11-29 21:47:14 +01:00
ShyPike
853bda5d86 Add 'B' to server amounts. 2012-11-28 21:18:02 +01:00
ShyPike
d05e31f7f0 Remove placeholder for Cleanup filter in Config->General. Confusing for users. 2012-11-28 20:49:16 +01:00
ShyPike
383354871d Generic sort didn't always rename media files in multi-part jobs properly.
`rename_similar()` should be called outside of the media rename loop.
`rar_extract_core` should always return full paths.
2012-11-26 22:10:48 +01:00
ShyPike
2086a217e0 Don't use SFV check when more par2 files can still be downloaded. 2012-11-24 22:38:04 +01:00
ShyPike
34f3574746 Update unrar for Windows to 4.20 2012-11-24 21:50:14 +01:00
ShyPike
1dfe0b957e Catch a renaming bug in par2-tbb and retry with par2-classic. 2012-11-24 21:33:58 +01:00
shypike
17d14bc3b4 OSX: update unrar to version 4.20 2012-11-24 21:33:36 +01:00
ShyPike
885032e436 Improve handling of orphaned jobs.
Re-queue of a set without a nzb.gz file would not recognize par2 sets.
Convert "None" strings in the attrib file to None.
2012-11-24 21:21:06 +01:00
ShyPike
ceee95aaf7 Orphan re-queue and delete failed when path contains non-plain ASCII characters.
Convert UTF-8 name coming from CherryPy to a platform compatible name.
2012-11-24 20:38:29 +01:00
ShyPike
bc6b3091eb Update text files for 0.7.6Final. 2012-11-17 14:01:38 +01:00
ShyPike
4be1a13316 Add the "User-Agent" header of each API call to logging and warnings. 2012-11-17 10:56:36 +01:00
ShyPike
a77327ee7f Support NZB re-queuing also for NZB files in sub-folders. 2012-11-15 22:01:41 +01:00
ShyPike
aa706012af Update text files for 0.7.6Beta2 2012-11-14 21:01:01 +01:00
ShyPike
f5b6203194 Make check for running SABnzbd instance more robust.
Cancel bad side-effect of removing the version check.
Under some circumstances SABnzbd can draw the unjustified conclusion
that another instance is running. Now check for a proper version pattern
in the received output.
2012-11-14 20:57:02 +01:00
ShyPike
1ced9a54e4 Fix evaluation of schedules at startup.
With the introduction of multiple-day schedules, the schedule evaluator failed.
Fixed the evaluation.
A side-effect is that Config->Scheduler will no longer show the schedules in
the order they will occur from now. Instead they will be shown in order of
occurrence from Monday to Sunday.
2012-11-14 20:23:40 +01:00
ShyPike
06c7089a77 Correct indentation in interface.py 2012-11-13 20:59:50 +01:00
ShyPike
ee1d864eea Update text files for 0.7.6Beta2 2012-11-12 21:47:19 +01:00
ShyPike
d703338935 Repair failed when mini-par2 file was in NZB but did not result in a file.
An incomplete mini-par2 file is now skipped in favor of the next available vol-par2 file.
A missing or damaged par2 file must make the next par2 file the primary par2-file
in the next repair run.
2012-11-12 21:10:27 +01:00
ShyPike
e87b24c460 Update text files for 0.7.6Beta1 2012-11-09 19:30:29 +01:00
shypike
3404ef6516 Update translations 2012-11-09 19:23:04 +01:00
shypike
181897e92b Prevent the Decoder from choking the Assembler.
Because the Decoder is CPU-bound, it has no reason to relinquish control.
This will choke the Assembler which cannot write finished and cached articles
to the designated file. The result is an increasing cache, which either grows
indefinitely or until the Decoder must flush articles.
By simply adding a sleep(0.001), the Decoder will trigger the task-scheduler
after each article, giving the Assembler a chance to do its work.
2012-11-08 23:12:15 +01:00
ShyPike
26a504e3e2 Prepare code for intro of zoned access to UI and API. 2012-11-07 21:41:04 +01:00
ShyPike
b72ed09011 Prevent IPv6 Usenet servers from being tried when they're not reachable.
Detect whether external IPv6 addresses are reachable.
If so, allow IPv6 IPs to be picked.
Add a special option 'ipv6_servers' to allow the user to forbid (0), allow (1) or force (2)
the use of IPv6. Value 2 can be used in case the detection by SABnzbd doesn't work reliable.
2012-11-07 20:07:25 +01:00
ShyPike
bb99c0d58e Fix problem with late detection of win32api absence. 2012-11-06 23:40:45 +01:00
ShyPike
4516027fdb Repair side-effect of SFV improvements.
A download without par2 files and without SFV files should not be failed.
2012-11-05 22:51:51 +01:00
ShyPike
e35f2ea3cd Prevent crash on Unix-Pythons that don't have the os.getloadavg() function.
Some Unix Pythons are defective in not providing os.getloadavg().
Add simple exception handler to cover this case.
2012-11-05 20:40:21 +01:00
ShyPike
6b79fad626 Remove version check when looking for a running instance of SABnzbd.
This will lower the chance of inadvertently launching multiple instances.
User will need to use --new to force a new instance.
2012-11-05 19:19:32 +01:00
ShyPike
ac311be430 Successfully pre-checked job lost its attributes when those were changed during check.
For successful jobs, the attributes were not saved to disk (they were for failed ones).
Solution is to save attributes independent of result.
2012-11-05 19:09:10 +01:00
shypike
4fb32bff5f Fix crash when a job is sent to postprocessing immediaterly after startup.
The Assembler wasn't running when job was sent to post processing at
the startup of the queue. The Assembler is used as a relay to send
a job to post-processing.
Solution is to start Assembler before initializing the queue.
2012-11-05 18:54:15 +01:00
ShyPike
5fda342a55 Don't try to repair/verify par sets that have "sample" in their names.
Only when sample deletion is enabled.
2012-11-03 20:34:17 +01:00
ShyPike
e23aab4710 Improve SFV handling, preventing odd side-effects in multi-set NZBs.
SFV verification per PAR-set using only the matching SFV file.
When no par2 files are found, use all available SFV files.
Remember the verification status of each set in the "verified" marker file.
Improve par-set matcher, so that there's no mix-up when one set name
is a substring of another set name.
2012-11-03 16:57:32 +01:00
ShyPike
3837d5dace Handle par-sets that have been renamed after generation of the par2 files.
Requires a wildcard to be added as a par2 parameter to make it scan all applicable files.
The rename actions need to be stored in a persistent file to prevent re-downloading in a Retry.
The status of correct sets must be remembered while fetching extra par file for failed sets.
2012-11-03 16:56:53 +01:00
shypike
f61e7cb1ed Update text files for 0.7.5 Final. 2012-11-03 16:15:22 +01:00
ShyPike
3de0c0e4ac Add missing "%dn" (original folder name) formula to Generic Sorting. 2012-11-01 21:24:51 +01:00
ShyPike
63796d3feb Improve logging for RSS readouts. 2012-11-01 19:47:48 +01:00
ShyPike
6b07529300 Update text files for 0.7.5RC1 2012-10-30 20:35:57 +01:00
ShyPike
e10676710c Support for news in Config. 2012-10-30 20:17:51 +01:00
shypike
77f67c6666 Merge pull request #59 from akuiraz/newzxxx2_fix
Fixed regex for newzbin rss filtering
2012-10-30 11:58:20 -07:00
ShyPike
bdbcdd61e1 Mask password in "Add Server" dialog. 2012-10-30 19:51:12 +01:00
ShyPike
4ab7ec754d Add periodic detection of completed but hanging jobs in the queue.
The 30 second watchdog now detects jobs without pending files.
Those jobs will be sent to the post-processor.
2012-10-30 18:47:18 +01:00
akuiraz
20f98f48bc Fixed regex for newzbin filtering by adding xxx2, now rss feeds from newzxxx2.ch will successfully download 2012-10-30 01:27:36 -04:00
shypike
84e0502e50 Prevent crash when trying to open non-existing "complete" folder from Windows System-tray icon. 2012-10-28 12:39:34 +01:00
shypike
2aa1b00dbb Prevent CherryPy crash when reading a cookie from another app which has a non-standard name. 2012-10-27 13:33:33 +02:00
ShyPike
972078a514 Fix problem with "Read" button when RSS feed name contains "&".
The feed's name wasn't properly encoded in the URL.
2012-10-24 19:34:45 +02:00
shypike
be8382d25b Add special option 'empty_postproc'.
Setting this option will run the user script on an empty download.
Normally this isn't done.
The status sent to the user script is -1, meaning "no files were downloaded".
2012-10-21 18:23:25 +02:00
shypike
8d46e88cd8 Update translations 2012-10-21 12:56:00 +02:00
shypike
6b6b1b79ad Add 'prio_sort_list' special.
This is a list of file name extensions.
Matching files will be the first to be downloaded within an NZB.

Also, if the user sets a simple space-seperated list, this will be converted to a standardized list.
2012-10-21 12:16:13 +02:00
shypike
e1fd40b34d OSX: Retina compatible menu-bar icons. 2012-10-20 19:57:11 +02:00
ShyPike
bc1f8f97a8 Prefix categories of nzbxxx.com with "XXX:". 2012-10-20 16:03:09 +02:00
shypike
b51705f458 Fix issues with accented and special characters in names of downloaded files.
name_extractor() returned Unicode instead of platform-compatible encoding.
QuickCheck assumed incorrectly that file names are not yet platform-compatible.
2012-10-20 15:11:08 +02:00
ShyPike
aaed5f4797 Adjust nzbmatrix category table. 2012-10-17 21:30:22 +02:00
ShyPike
a8eedef1d2 Prevent stuck jobs at end of pre-check. 2012-10-17 21:22:36 +02:00
shypike
9407e21e1e Prevent unusual SFV files from crashing post-processing. 2012-10-13 10:56:05 +02:00
ShyPike
ba6dcfd467 Don't show speed and ETA when download is paused during post-processing. 2012-10-08 21:21:15 +02:00
shypike
e2c1de5008 Prevent soft-crash when api-function "addfile" is called without parameters. 2012-10-06 21:57:48 +02:00
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
ShyPike
e24aedc6ac Update text files for 0.7.3 Final. 2012-08-04 11:05:58 +02:00
ShyPike
d10d69e44b Correct typo in dirscanner.py. 2012-08-04 10:54:40 +02:00
ShyPike
af5c01ee3a Update Windows install size. 2012-08-03 22:58:34 +02:00
ShyPike
bf350cddc8 Update text files for 0.7.3
Fix package.py for README.mkd handling.
2012-08-03 22:28:44 +02:00
ShyPike
9bc4d909b5 Use 'pandoc' to generate README.rtf from README.mkd. 2012-08-03 21:05:08 +02:00
ShyPike
42236de5bd Update text files for 0.7.3 Final 2012-08-03 19:41:17 +02:00
ShyPike
02e2fe2cc8 Correct errors in Danish email templates. 2012-08-03 19:27:30 +02:00
ShyPike
b75bcb90f4 Server 502 message about expired subscription will now block server. 2012-08-03 19:08:21 +02:00
shypike
56b88eb406 Update translations 2012-08-03 16:01:20 +02:00
ShyPike
93741ea9ab SFV-check: don't issue a warning for each problem file, but just list in History entry. 2012-08-03 11:57:13 +02:00
ShyPike
f7509132fc Rename 'random_server_ip' to 'randomize_server_ip' and make the default 'False'.
This is to avoid confusing speed loss with some providers. Now the user has to enable this consciously.
2012-08-02 21:18:03 +02:00
ShyPike
2d0d62ec00 The "watched folder" scan should ignore all files starting with a period (hidden on Linux and OSX). 2012-08-01 00:27:23 +02:00
ShyPike
4e07a84102 Update POT files. 2012-08-01 00:25:35 +02:00
ShyPike
32a048a879 Update text files for 0.7.3 Beta2 2012-07-30 16:08:48 +02:00
shypike
caac28fcbc Try to keep OSX Mountain Lion awake as long as downloading/postprocessing runs.
Quick & Dirty solution by launching 'caffeinate' every 5 minutes.
2012-07-30 15:40:13 +02:00
ShyPike
5b0bbf57c6 Prevent queue deadlock in case of fatally damaged par2 files.
Basic problem: prevent par2 files that have already been downloaded from re-entering the queue.
The issue was introduced after removing the habit of 0.6.x to download a part of each par2 file.
The advantage of the 0.6.x method was that more was known about the par2 files,
the disadvantage that quite some data was downloaded that was never used.

Add job flag that prevents further QuickCheck attempts on known to be damaged jobs.
The method of inspecting large par2 files is time-consuming if nothing is ever found.
2012-07-30 12:41:55 +02:00
ShyPike
072af938c2 Prevent deadlock in fetching more par2 files when first par2 file is so corrupt that no info can be retrieved. 2012-07-29 14:48:27 +02:00
ShyPike
9e8202371e Add filter-enable check-boxes to Classic, Plush and smpl skinms.
Necessary because new filters created with those skins were not enabled.
2012-07-29 00:09:53 +02:00
ShyPike
27dd253c5d Fix UI crash on saving parameters of an already enabled server in Config->Servers. 2012-07-28 23:35:12 +02:00
ShyPike
8d651af2f8 Add some forward compatibility with changes coming to Scheduler in 0.8.x 2012-07-28 14:05:27 +02:00
ShyPike
6358312272 Replaced a few missed strings to be substituted with constants.Status values. 2012-07-24 21:35:03 +02:00
ShyPike
42c8367e13 Extend "check for release" with option "Also test releases". 2012-07-24 20:38:44 +02:00
ShyPike
c1e38b5e81 Update text files for 0.7.3 Beta1 2012-07-24 15:06:49 +02:00
ShyPike
1b4ce24037 Config->Servers: optimize layout 2012-07-24 11:17:40 +02:00
ShyPike
cf440750b6 Config->Servers: hide server details initially and have a simple checkbox to enable/disable servers quickly. 2012-07-24 09:14:02 +02:00
ShyPike
30c480df36 Add unofficial support for nzbmatrix's adult cousin. 2012-07-23 14:29:02 +02:00
ShyPike
9c3dbd39ef Make detection of samples less aggressive. 2012-07-22 16:35:34 +02:00
ShyPike
1af2f92828 Improve the Sort functions.
- Renaming secondary files after the main file didn't always work
- Collapsing folder structures could fail
- Joined (instead of unpacked) media files were not seen as candidates for renaming
- Presence of DVD/BRD special folders will now block collapsing
- Non-functional improvements: coding std, removed unused code and needless access functions, added doc-strings.
2012-07-22 15:43:26 +02:00
ShyPike
45277bb00f Add another keyword for canceled article detection. 2012-07-20 19:31:31 +02:00
ShyPike
10e21a3af9 Remove extra '\r' in Windows logging. 2012-07-20 19:16:35 +02:00
ShyPike
19cbadd420 Plush: fix odd formatting of multi-line warnings on Status page. 2012-07-20 19:11:52 +02:00
ShyPike
a28cbe52b9 Recovery from corrupt totals9.sab doesn't always remove bad entry. 2012-07-19 21:00:01 +02:00
shypike
37f1d64e46 Fix incorrect explanation of --no_ipv6 flag. 2012-07-18 18:00:39 +03:00
ShyPike
7170325df5 Update text files for 0.7.2 Final 2012-07-17 21:46:17 +02:00
ShyPike
c44d98da66 When localhost resolves to ['127.0.0.1', '::1'] instead of ['::1', '127.0.0.1'], SABnzbd would try to register twice on '::1'. 2012-07-17 21:29:33 +02:00
ShyPike
018410afb0 Validate the values read from totals9.sab 2012-07-17 19:38:49 +02:00
ShyPike
fc47238a7a Temporary fix for missing NZB icon when associating NZB files on a system that had a 0.7.0 release installed. 2012-07-16 18:51:39 +02:00
ShyPike
9561b8a64e Update text files for 0.7.2 RC2 2012-07-14 17:01:07 +02:00
ShyPike
7b0e56b55f Fix little anomaly in Wizard-Four. 2012-07-14 15:16:30 +02:00
ShyPike
c6d5a79776 Improve web-host address selection on systems where the hostname does not resolve to an IP address. 2012-07-14 14:27:18 +02:00
ShyPike
faa4cacd3e Improve detection of bad articles.
For now, don't let precheck use "HEAD" until method is improved.
2012-07-14 13:12:22 +02:00
ShyPike
56e417eea1 Change nzbsrus support to use their API response codes. 2012-07-14 09:40:46 +02:00
ShyPike
5f02ec00f9 Don't show nonsense NZB age when still trying to get it from an indexer. 2012-07-13 21:29:37 +02:00
ShyPike
5ea35db922 Add limited API support for nzbsrus.com 2012-07-13 21:19:09 +02:00
ShyPike
5dcf26a56c Update text files for 0.7.2RC1 2012-07-12 21:45:13 +02:00
ShyPike
35b598d10e Improve detection (and ignoring) of invalid articles.
This also requires reading headers during pre-check instead of just asking for article presence.
2012-07-12 21:24:38 +02:00
ShyPike
5e7b27c4ef Prevent potential crash in NzbQueue.repair_job() 2012-07-12 18:28:17 +02:00
ShyPike
9ed408d35b Windows Installer: don't uninstall settings by default. 2012-07-12 18:19:19 +02:00
ShyPike
6c782fe255 Fix nzbrus.com fatal error (when <nzb is not within the first 100 characters of the file). 2012-07-10 23:01:11 +02:00
ShyPike
1689323dc3 Fix sorting of files in a job so that .rar comes before .r00 again. 2012-07-10 22:12:04 +02:00
ShyPike
a3c50a907a Fix wrapping problem of queue titles in Plush skin by inserting zero-width spaces in titles. 2012-07-10 19:59:59 +02:00
ShyPike
36a3792846 After setting quota for the first time, the initial "quota left" was set to the already consumed amount of the current period, instead of the actual still available amount (which is quota-consumed). 2012-07-08 11:45:09 +02:00
ShyPike
4cd0c0691a Windows installer: fix NZB association so that a Windows reboot is not required to register the NZB icon. 2012-07-08 11:33:57 +02:00
ShyPike
6ac98dcacd Handle incorrect regular expression in RSS filters.
Ignore during scanning and signal in user interface.
2012-07-08 10:45:57 +02:00
ShyPike
0a0d00930a Update text files for 0.7.1 Final. 2012-07-06 16:50:00 +02:00
ShyPike
28a0d041f9 Correction on fix 7258e56a20.
nzf.completed is a @property attribute and should not be set directly.
2012-07-06 16:40:36 +02:00
ShyPike
85bb91a7ea Disable VC90 check in Windows Installer as long as we're still on Python 2.5 2012-07-04 21:10:49 +02:00
ShyPike
6561e0abfa Make Windows path joining of base and category paths more robust to work around different behaviors in Python and Windows versions combinations. 2012-07-04 20:36:52 +02:00
ShyPike
6715e61a68 Try to check for "maintenance" mode of nzbsrus.com 2012-07-04 19:34:00 +02:00
ShyPike
a886b284b6 Prevent "not committed" flag in baseline variable for official builds. 2012-07-04 19:09:55 +02:00
ShyPike
a349c82b6f Update text files for 0.7.1RC5 2012-07-03 21:38:20 +02:00
ShyPike
1f4df0ebf4 Only send "bad fetch" emails when emails are enabled. 2012-07-03 21:28:59 +02:00
ShyPike
0221e7bf93 Add some support for nzbsrus.com's awkward non-VIP limiting. 2012-07-03 21:14:30 +02:00
ShyPike
f9cf14e7d8 Update text files for 0.7.1 Final. 2012-07-03 19:59:36 +02:00
ShyPike
7258e56a20 When retrying a job, existing files were not flagged properly as "completed", leading to an endless loop in par2 fetching. 2012-07-03 19:58:02 +02:00
ShyPike
90bd495d44 Update text files fr 0.7.1 Final. 2012-07-03 17:54:18 +02:00
shypike
6c216d6dfe Update translations 2012-07-03 17:49:10 +02:00
ShyPike
e1f3fae6c7 Windows installer: detect incompatible older version by looking for python27.dll. 2012-07-02 22:31:28 +02:00
ShyPike
29f126ca47 Make warning about Python3 easier to read. 2012-07-02 22:13:01 +02:00
shypike
8b4b742466 Remove remaining .py files from OSX DMG image, to prevent invalid signatures after first run. 2012-07-02 18:44:36 +02:00
ShyPike
57a9d362bc Update text files for 0.7.1RC4 2012-07-01 17:26:00 +02:00
ShyPike
b7d54c2bea Fix failure to grab NZBs from indexers that send compressed files. 2012-07-01 17:23:16 +02:00
ShyPike
59f9833076 Update text files for 0.7.1 RC3 2012-07-01 14:03:59 +02:00
ShyPike
8e360fe53e Improve retry handling of URL fetches.
Add detection of error messages from nzbsrus.com and handle retries accordingly.
2012-07-01 13:49:24 +02:00
ShyPike
afc5005382 Make sure that badly formatted or otherwise incorrect articles are retried on other servers (if fail_on_crc option is on).
Make default of fail_on_crc True.
2012-07-01 13:47:55 +02:00
ShyPike
3a531c6d2b API calls "addurl" and "addid" can use the same code so that either will take newzbin IDs or regular URLs. 2012-07-01 12:53:54 +02:00
ShyPike
f056ad6347 Solve problem of stalling par2 fetching.
The attributes of the vol-par files were analyzed to late,
leading to a race condition.
2012-07-01 10:01:56 +02:00
ShyPike
5c1342a663 Tell user that Python 3.0 won't work. 2012-07-01 09:12:25 +02:00
ShyPike
dfe8a47a2a Verification/repair would not be executed properly when one more RAR files missed their first article. 2012-06-26 23:26:38 +02:00
ShyPike
e293a439dd Don't set __verified__ flag file when more par2 files need to be fetched. 2012-06-26 22:24:01 +02:00
ShyPike
7e0027922a Prevent QuickCheck crash when expected par2 file wasn't downloaded (due to missing articles). 2012-06-26 19:12:56 +02:00
shypike
00b5302ba9 Update text files for 0.7.1 Final. 2012-06-25 22:40:39 +02:00
shypike
79488c4785 Add option to suppress listening on web host address ::1 2012-06-25 22:34:07 +02:00
112 changed files with 14827 additions and 13286 deletions

View File

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

View File

@@ -1,3 +1,192 @@
-------------------------------------------------------------------------------
0.7.7Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Windows/OSX: Update unrar to 4.20
- Fix some issues with orphaned items
- Generic sort didn't always rename media files in multi-part jobs properly
- Optional web-ui watchdog
- Always show RSS items in the same order as the original RSS feed
- Remove unusable folders from folder selector (Plush skin)
- Remove newzbin support
-------------------------------------------------------------------------------
0.7.6Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Recursive scanning when re-queuing downloaded NZB files
- Log "User-Agent" header of API calls
-------------------------------------------------------------------------------
0.7.6Beta2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- A damaged smallest par2 can block fetching of more par2 files
- Fix evaluation of schedules at startup
- Make check for running SABnzbd instance more robust
-------------------------------------------------------------------------------
0.7.6Beta1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Handle par2 sets that were renamed after creation
- Prevent blocking assembly of completed files, ( this resulted in
excessive CPU and memory usage)
- Fix speed issues with some Usenet servers due to unreachable IPv6 addresses
- Fix issues with SFV-base checks
- Prevent crash on Unix-Pythons that don't have the os.getloadavg() function
- Successfully pre-checked job lost its attributes when those were changed during check
- Remove version check when looking for a running instance of SABnzbd
-------------------------------------------------------------------------------
0.7.5Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Add missing %dn formula to Generic Sort
- Improve RSS logging
-------------------------------------------------------------------------------
0.7.5RC1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Prevent stuck jobs at end of pre-check.
- Fix issues with accented and special characters in names of downloaded files.
- Adjust nzbmatrix category table.
- Add 'prio_sort_list' special
- Add special option 'empty_postproc'.
- Prevent CherryPy crash when reading a cookie from another app which has a non-standard name.
- Prevent crash when trying to open non-existing "complete" folder from Windows System-tray icon.
- Fix problem with "Read" button when RSS feed name contains "&".
- Prevent unusual SFV files from crashing post-processing.
- OSX: Retina compatible menu-bar icons.
- Don't show speed and ETA when download is paused during post-processing
- Prevent soft-crash when api-function "addfile" is called without parameters.
- Add news channel frame
-------------------------------------------------------------------------------
0.7.4Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Pre-queue script no longer got the show/season/episode information.
- Prevent crash on startup when a fully downloaded job is still in download queue.
- New RSS feed should no longer be considered new after first, but empty readout.
- Make "auth" call backward-compatible with 0.6.x releases.
- Config->Notifications: email and growl server addresses should not be marked as "url" type.
- OSX: fix top menu queue info so that it shows total queue size
-------------------------------------------------------------------------------
0.7.4RC2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Pre-check failed to consider extra par2 files
- Fixed unjustified warning that can occur with OSX Growl 2.0
- Show memory usage on Linux systems
- Fix incorrect end-of-month quota reset
- Fix UI refresh issue when using Safari on iOS6
-------------------------------------------------------------------------------
0.7.4RC1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Remove potential queue stalling when downloading extra par2 files
- Make Windows version less eager to use par2-classic
- Fixed DMG images
- Add missing encoding directive to Plush and Classic skins
- Prevent oversized data in API-call "history"
-------------------------------------------------------------------------------
0.7.4Beta3 by The SABnzbd-Team
-------------------------------------------------------------------------------
- All three OSX build in one DMG again
- Minor bugfixes
-------------------------------------------------------------------------------
0.7.4Beta2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix failure to fetch more par2-files for posts with badly formatted subject lines
- After successful pre-check, preserve a job's position in the queue
- Restore SABnzbd icon for Growl
- Fix "check new releases" option in Config skin
- Separate DMG files for OSX Leopard/SL, Lion and MLion
-------------------------------------------------------------------------------
0.7.4Beta1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- OSX Mountain Lion Notification Center support
- OSX Mountain Lion improved "keep awake" support
- OSX: separate builds: one for Mountain Lion and one for all others
- OSX removed 64bit code
- Scheduler: action can now run on multiple weekdays
- Scheduler: add "remove failed jobs" action
- Special option: rss_odd_titles (see Wiki)
- Support for HTTPS chain files (needed when you buy your own certificate)
- Prevent jobs from showing up in queue and history simultaneously
- Add parameter 'pp_active' to history elements in qstatus API call
- Fix some minor par2 handling bugs
- Prevent potential crash when an actively downloading job is deleted from the queue
- Special option: 'overwrite_files' (See Wiki)
- Don't try an SFV check when a retried job was already successfully verified by par2
- Enable compression of API call results
- Log failed attempts to log in to the Web UI
- A job with "forced" priority should keep that when fetching more par2 files
- Updated translations
-------------------------------------------------------------------------------
0.7.3Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Rename Special "random_server_ip" to "randomize_server_ip" so that we
can force the default to "Off". "On" kills speed on some servers.
- Ignore pseudo NZB files that start with a period in the name
- SFV failure now listed in History instead of issuing warnings
- Translation updates
- "502" errors about payments/credits will now block a server
-------------------------------------------------------------------------------
0.7.3Beta2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Try to keep OSX Mountain Lion awake as long as downloading/postprocessing runs
- Prevent queue deadlock in case of fatally damaged par2 files
- Add RSS filter-enable checkboxes to Plush, Smpl and Classic skins
- Fix problem with saving modified paramters of an already enabled server
- Extend "check new release" option with test releases
-------------------------------------------------------------------------------
0.7.3Beta1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Correct several errors in Sort function
- Improve organization of Config->Servers
- Support for nzbxxx.com
- Make detection of samples less aggressive
- Some minor corrections
-------------------------------------------------------------------------------
0.7.2Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix for NZB-icon issue when 0.7.0 was previously installed
- Check validity of totals9.sab file
- Fix startup problem when localhost has unexpected order of IP addresses
-------------------------------------------------------------------------------
0.7.2RC2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Improve support for nzbsrus.com
- Don't try to show NZB age when not known yet
- Prevent systems with unresolvable hostnames from always using 0.0.0.0
-------------------------------------------------------------------------------
0.7.2RC1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix fatal error in nzbsrus.com support
- Initial "quota left" was not set correctly when enabling quota
- Report incorrect RSS filter expressions (instead of aborting analysis)
- Improve detection of invalid articles (so that backup server will be tried)
- Windows installer: improve NZB association so that a reboot isn't needed
- Windows installer: don't remove settimngs by default when uninstalling
- Fix sorting of rar files in job so that .rar preceeds .r00
-------------------------------------------------------------------------------
0.7.1Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Disable VC90 check in Windows Installer as long as we're still on Python 2.5
- Windows: make sure \\server\share notation is never seen as a relative path
-------------------------------------------------------------------------------
0.7.1RC5 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix signing of OSX DMG
- Fix endless par2-fetch loop after retrying failed job
- Don't send "bad fetch" email when emailing is off
- Add some support for nzbrus.com's non-VIP limiting
-------------------------------------------------------------------------------
0.7.1RC4 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix failure to grab NZBs from indexers that send compressed files.
-------------------------------------------------------------------------------
0.7.1RC3 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fixed stalling par2 fetches (after first verification run)
- Fixed retry behaviour of NZB fetching from URL
and add handling of nzbsrus.com error codes
- Make sure that all malformed articles are retried on another server
- Add no_ipv6 option that suppresses listing on ::1
(to be used if your system cannot handle that)
- Prevent crash in QuickCheck when expected par2 file wasn't downloaded
- Verification/repair would not be executed properly when one more RAR files
missed their first article.
- API calls "addurl" and "addid" (newzbin) can be used interchangeably
(Fixes a problem in Qouch)
-------------------------------------------------------------------------------
0.7.1RC2 by The SABnzbd-Team
-------------------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
SABnzbd 0.7.1
SABnzbd 0.7.7
-------------------------------------------------------------------------------
0) LICENSE
@@ -60,7 +60,8 @@ Unix/Linux/OSX
OSX Leopard/SnowLeopard
Python 2.6 http://www.activestate.com
OSX Lion Apple Python 2.7 (included in OSX)
OSX Lion/MountainLion
Apple Python 2.7 Included in OSX (default)
Windows
Python-2.7.latest http://www.activestate.com

View File

@@ -21,6 +21,7 @@
!include "MUI2.nsh"
!include "registerExtension.nsh"
!include "FileFunc.nsh"
!include "LogicLib.nsh"
!include "WinVer.nsh"
!include "WinSxSQuery.nsh"
@@ -245,6 +246,7 @@ Function .onInit
;--------------------------------
;make sure that the requires MS Runtimes are installed
;
goto nodownload ; Not needed while still using Python25
runtime_loop:
push 'msvcr90.dll'
push 'Microsoft.VC90.CRT,version="9.0.21022.8",type="win32",processorArchitecture="x86",publicKeyToken="1fc8b3b9a1e18e3b"'
@@ -297,7 +299,7 @@ SetOutPath "$INSTDIR"
;------------------------------------------------------------------
; Make sure old versions are gone
IfFileExists $INSTDIR\sabnzbd.exe 0 endWarnExist
IfFileExists $INSTDIR\python25.dll 0 endWarnExist
IfFileExists $INSTDIR\python27.dll 0 endWarnExist
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(MsgRemoveOld)$\n$\n$(MsgRemoveOld2)" IDOK uninst
Abort
uninst:
@@ -318,7 +320,7 @@ WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninst
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "URLUpdateInfo" 'http://sabnzbd.org/'
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "Comments" 'The automated Usenet download tool'
WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "DisplayIcon" '$INSTDIR\interfaces\Classic\templates\static\images\favicon.ico'
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 18400
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "EstimatedSize" 25674
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoRepair" -1
WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\SABnzbd" "NoModify" -1
; write out uninstaller
@@ -350,7 +352,7 @@ SectionEnd ; end of desktop icon section
Section /o $(MsgAssoc) assoc
${registerExtension} "$INSTDIR\icons\nzb.ico" "$INSTDIR\SABnzbd.exe" ".nzb" "NZB File"
;${registerExtension} "$INSTDIR\SABnzbd.exe" ".nzb" "NZB File"
${RefreshShellIcons}
SectionEnd ; end of file association section
; begin uninstall settings/section
@@ -406,11 +408,11 @@ Section "un.$(MsgDelProgram)" Uninstall
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
${unregisterExtension} ".nzb" "NZB File"
${RefreshShellIcons}
SectionEnd ; end of uninstall section
Section "un.$(MsgDelSettings)" DelSettings
Section /o "un.$(MsgDelSettings)" DelSettings
DetailPrint "Uninstall settings $LOCALAPPDATA"
Delete "$LOCALAPPDATA\sabnzbd\sabnzbd.ini"
RMDir /r "$LOCALAPPDATA\sabnzbd"

View File

@@ -1,8 +1,8 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 0.7.1RC2
Summary: SABnzbd-0.7.1RC2
Home-page: http://sourceforge.net/projects/sabnzbdplus
Version: 0.7.7
Summary: SABnzbd-0.7.7
Home-page: http://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org
License: GNU General Public License 2 (GPL2 or later)

View File

@@ -1,19 +1,15 @@
Release Notes - SABnzbd 0.7.1RC2
==================================
Release Notes - SABnzbd 0.7.7
===============================
## Fixes in 0.7.1
### RC2
- Improved backup of sabnzbd.ini file, now uses backup when original is gone or corrupt
- Swedish translation extended
### RC1
- Plush skin: fix problems with pull-down menus in Mobile Safari
- On some Linux and OSX systems using localhost would still make SABnzbd
give access to other computers
- Windows: the installer did not set an icon when associating NZB files with SABnzbd
- Fix problem that the Opera browser had with Config->Servers
- Retry a few times when accessing a mounted drive to create the
final destination folder
- Minor fixes in Window Tray icon and OSX top menu
## Features
- Updated unrar to 4.20 (OSX/Windows)
- Optional web-ui watchdog (diagnostic tool)
- Removed account support for defunct indexers
## Bug fixes
- Fix some issues with orphaned items
- Generic sort didn't always rename media files in multi-part jobs properly
- Always show RSS items in the same order as the original RSS feed
## What's new in 0.7.0
@@ -43,4 +39,30 @@ Release Notes - SABnzbd 0.7.1RC2
built-in post-processing options that automatically verify, repair,
extract and clean up posts downloaded from Usenet.
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2012 by "The SABnzbd-team" \<team@sabnzbd.org\>
### IMPORTANT INFORMATION about release 0.7.x
<http://wiki.sabnzbd.org/introducing-0-7-0>
### Known problems and solutions
- Read the file "ISSUES.txt"
### Upgrading from 0.6.x
- Stop SABnzbd
- Install new version
- Start SABnzbd
### Upgrading from 0.5.x
- Stop SABnzbd
- Install new version
- Start SABnzbd.
The organization of the download queue is different from 0.5.x.
0.7.x will finish downloading an existing queue, but you
cannot go back to an older version without losing your queue.
Also, your sabnzbd.ini file will be upgraded, making it
incompatible with release 0.5.x
### Upgrading from 0.4.x
Download your current queue before upgrading.

View File

@@ -1,111 +0,0 @@
{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\paperw11900\paperh16840\vieww16360\viewh15680\viewkind0
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
\f0\b\fs48 \cf0 SABnzbd 0.7.1RC2\
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
\b0\fs26 \cf0 \
\b Fixes in 0.7.1
\b0 \
RC2\
- Improved backup of sabnzbd.ini file, now uses backup when original is gone or corrupt\
- Swedish translation is extended\
RC1\
- Plush skin: fix problems with pull-down menus in Mobile Safari\
- On some Linux and OSX systems using localhost would still make SABnzbd give access to other computers\
- Windows: the installer did not set an icon when associating NZB files with SABnzbd\
- Fix problem that the Opera browser had with Config->Servers\
- Retry a few times when accessing a mounted drive to create the final destination folder\
- Minor fixes in Window Tray icon and OSX top menu\
\
\b What's new
\b0 \
- Download quota management\
- Windows: simple system tray menu\
- Multi-platform Growl support\
- NotifyOSD support for Linux distros that have it\
- Option to set maximum number of retries for servers (prevents deadlock)\
- Pre-download check to estimate completeness (reliability is limited)\
- Prevent partial downloading of par2 files that are not needed yet\
- Config->Special for settings previously only available in the sabnzbd.ini file\
- For Usenet servers with multiple IP addresses, pick a random one per connection\
- Add pseudo-priority "Stop" that will send the job immediately to the post-processing queue\
- Allow jobs still waiting for post-processing to be deleted too\
- More persistent retries for unreliable indexers\
- Single Configuration skin for all others skins (there is an option for the old style)\
- Config->Special for settings that were previously only changeable in the sabnzbd.ini file\
- Add Spanish, Portuguese (Brazil) and Polish translations\
- Individual RSS filter toggle\
- Unified OSX DMG\
\
\b About
\b0 \
SABnzbd is an open-source cross-platform binary newsreader.\
It simplifies the process of downloading from Usenet dramatically,\
thanks to its web-based user interface and advanced\
built-in post-processing options that automatically verify, repair,\
extract and clean up posts downloaded from Usenet.\
SABnzbd also has a fully customizable user interface,\
and offers a complete API for third-party applications to hook into.\
\
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>\
\
There is an extensive Wiki on the use of SABnzbd.\
{\field{\*\fldinst{HYPERLINK "http://wiki.sabnzbd.org/"}}{\fldrslt http://wiki.sabnzbd.org/}}\
\
\b IMPORTANT INFORMATION
\b0 about release 0.7.1:\
{\field{\*\fldinst{HYPERLINK "http://wiki.sabnzbd.org/introducing-0-7-0"}}{\fldrslt http://wiki.sabnzbd.org/introducing-0-7-0}}\
\
\b Known problems and solutions\
\b0 Read the file "ISSUES.txt"
\b \
\b0 \
\
\b\fs40 Upgrading from 0.6.x
\b0\fs26 \
Stop SABnzbd.\
Install new version\
Start SABnzbd.\
\
\b\fs40 Upgrading from 0.5.x
\b0\fs26 \
Stop SABnzbd.\
Uninstall current version, keeping the data.\
Install new version\
Start SABnzbd.\
\
The organization of the download queue is different from 0.5.x.\
0.6.x will finish downloading an existing queue, but you\
cannot go back to an older version without losing your queue.\
Also, your sabnzbd.ini file will be upgraded, making it\
incompatible with release 0.5.x\
\
\b\fs40 \
Upgrading from 0.4.x
\b0\fs26 \
\
\b PLEASE DOWNLOAD YOUR CURRENT QUEUE BEFORE UPGRADING
\b0 \
\
When upgrading from a 0.4.x release such as 0.4.12 your old settings will be kept.\
You will however be given a fresh queue and history. If you have items in your queue\
from the older version of SABnzbd, you can either re-import the nzb files if you kept\
an nzb backup folder, or temporarily go back to 0.4.x until your queue is complete.\
The history is now stored in a better format meaning future upgrades should be backwards\
compatible.\
}

View File

@@ -16,8 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
if sys.version_info < (2,5):
print "Sorry, requires Python 2.5 or higher."
if sys.version_info < (2, 5):
print "Sorry, requires Python 2.5, 2.6 or 2.7."
sys.exit(1)
import logging
@@ -28,6 +28,7 @@ import signal
import socket
import platform
import time
import re
try:
import Cheetah
@@ -253,6 +254,7 @@ def print_help():
print " --log-all Log all article handling (for developers)"
print " --console Force console logging for OSX app"
print " --new Run a new instance of SABnzbd"
print " --no_ipv6 Do not listen on IPv6 address [::1]"
def print_version():
print """
@@ -524,30 +526,24 @@ def all_localhosts():
return ips
def ipv_localhost(v):
""" Return True if localhost resolves to some IPV4 ('4') or IPV6 ('6') address
def check_resolve(host):
""" Return True if 'host' resolves
"""
try:
info = socket.getaddrinfo('localhost', None)
info = socket.getaddrinfo(host, None)
except:
# localhost does not resolve
# Does not resolve
return False
for item in info:
item = item[4][0]
if v == '4' and ':' not in item:
return True
elif v == '6' and ':' in item:
return True
return False
return True
#------------------------------------------------------------------------------
def get_webhost(cherryhost, cherryport, https_port):
""" Determine the webhost address and port,
return (host, port, browserhost)
"""
if cherryhost == '0.0.0.0' and not ipv_localhost('4'):
if cherryhost == '0.0.0.0' and not check_resolve('127.0.0.1'):
cherryhost = ''
elif cherryhost == '::' and not ipv_localhost('6'):
elif cherryhost == '::' and not check_resolve('::1'):
cherryhost = ''
if cherryhost is None:
@@ -562,14 +558,18 @@ def get_webhost(cherryhost, cherryport, https_port):
try:
info = socket.getaddrinfo(socket.gethostname(), None)
except:
# Hostname does not resolve, use 0.0.0.0
if cherryhost not in ('localhost', '127.0.0.1', '::1'):
cherryhost = '0.0.0.0'
# Hostname does not resolve
try:
info = socket.getaddrinfo(localhost, None)
# Valid user defined name?
info = socket.getaddrinfo(cherryhost, None)
except:
info = socket.getaddrinfo('127.0.0.1', None)
localhost = '127.0.0.1'
if cherryhost not in ('localhost', '127.0.0.1', '::1'):
cherryhost = '0.0.0.0'
try:
info = socket.getaddrinfo(localhost, None)
except:
info = socket.getaddrinfo('127.0.0.1', None)
localhost = '127.0.0.1'
for item in info:
ip = str(item[4][0])
if ip.startswith('169.254.'):
@@ -672,28 +672,27 @@ def get_webhost(cherryhost, cherryport, https_port):
return cherryhost, cherryport, browserhost, https_port
def attach_server(host, port, cert=None, key=None):
def attach_server(host, port, cert=None, key=None, chain=None):
""" Define and attach server, optionally HTTPS
"""
http_server = _cpwsgi_server.CPWSGIServer()
http_server.bind_addr = (host, port)
if cert and key:
http_server.ssl_certificate = cert
http_server.ssl_private_key = key
adapter = _cpserver.ServerAdapter(cherrypy.engine, http_server, http_server.bind_addr)
adapter.subscribe()
if not (sabnzbd.cfg.no_ipv6() and '::1' in host):
http_server = _cpwsgi_server.CPWSGIServer()
http_server.bind_addr = (host, port)
if cert and key:
http_server.ssl_certificate = cert
http_server.ssl_private_key = key
http_server.ssl_certificate_chain = chain
adapter = _cpserver.ServerAdapter(cherrypy.engine, http_server, http_server.bind_addr)
adapter.subscribe()
def is_sabnzbd_running(url):
def is_sabnzbd_running(url, timeout=None):
""" Return True when there's already a SABnzbd instance running.
"""
try:
url = '%s&mode=version' % (url)
ver = sabnzbd.newsunpack.get_from_url(url)
if ver and ver.strip(' \n\r\t') == sabnzbd.__version__:
return True
else:
return False
ver = sabnzbd.newsunpack.get_from_url(url, timeout=timeout)
return bool(ver and re.search(r'\d+\.\d+\.', ver))
except:
return False
@@ -713,7 +712,7 @@ def find_free_port(host, currentport):
def check_for_sabnzbd(url, upload_nzbs, allow_browser=True):
""" Check for a running instance of sabnzbd(same version) on this port
""" Check for a running instance of sabnzbd on this port
allow_browser==True|None will launch the browser, False will not.
"""
if allow_browser is None:
@@ -841,7 +840,7 @@ def commandline_handler(frozen=True):
try:
opts, args = getopt.getopt(info, "phdvncw:l:s:f:t:b:2:",
['pause', 'help', 'daemon', 'nobrowser', 'clean', 'logging=',
'weblogging=', 'server=', 'templates',
'weblogging=', 'server=', 'templates', 'no_ipv6',
'template2', 'browser=', 'config-file=', 'force',
'version', 'https=', 'autorestarted', 'repair', 'repair-all',
'log-all', 'no-login', 'pid=', 'new', 'sessions', 'console',
@@ -919,6 +918,7 @@ def main():
new_instance = False
force_sessions = False
osx_console = False
no_ipv6 = False
service, sab_opts, serv_opts, upload_nzbs = commandline_handler()
@@ -1005,6 +1005,8 @@ def main():
elif opt in ('--console',):
re_argv.append(opt)
osx_console = True
elif opt in ('--no_ipv6',):
no_ipv6 = True
sabnzbd.MY_FULLNAME = os.path.normpath(os.path.abspath(sabnzbd.MY_FULLNAME))
sabnzbd.MY_NAME = os.path.basename(sabnzbd.MY_FULLNAME)
@@ -1087,6 +1089,9 @@ def main():
# Set root folders for HTTPS server file paths
sabnzbd.cfg.set_root_folders2()
if no_ipv6:
sabnzbd.cfg.no_ipv6.set(True)
# Determine web host address
cherryhost, cherryport, browserhost, https_port = get_webhost(cherryhost, cherryport, https_port)
enable_https = sabnzbd.cfg.enable_https()
@@ -1205,8 +1210,6 @@ def main():
sabnzbd.cfg.log_backups())
format = '%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s'
if sabnzbd.WIN32:
format += '\r'
rollover_log.setFormatter(logging.Formatter(format))
rollover_log.addFilter(FilterCP3())
sabnzbd.LOGHANDLER = rollover_log
@@ -1346,6 +1349,10 @@ def main():
https_cert = sabnzbd.cfg.https_cert.get_path()
https_key = sabnzbd.cfg.https_key.get_path()
https_chain = sabnzbd.cfg.https_chain.get_path()
if not (sabnzbd.cfg.https_chain() and os.path.exists(https_chain)):
https_chain = None
if enable_https:
# If either the HTTPS certificate or key do not exist, make some self-signed ones.
if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)):
@@ -1364,8 +1371,8 @@ def main():
hosts[1] = '::1'
# The Windows binary requires numeric localhost as primary address
if multilocal and cherryhost == 'localhost' and hosts[1] == '127.0.0.1':
cherryhost = '::1'
if multilocal and cherryhost == 'localhost':
cherryhost = hosts[0]
if enable_https:
if https_port:
@@ -1375,14 +1382,15 @@ def main():
# Extra HTTP port for secondary localhost
attach_server(hosts[1], cherryport)
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], https_port, https_cert, https_key)
attach_server(hosts[1], https_port, https_cert, https_key, https_chain)
cherryport = https_port
elif multilocal:
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], cherryport, https_cert, https_key)
cherrypy.config.update({'server.ssl_certificate' : https_cert,
'server.ssl_private_key' : https_key })
'server.ssl_private_key' : https_key,
'server.ssl_certificate_chain' : https_chain})
elif multilocal:
# Extra HTTP port for secondary localhost
attach_server(hosts[1], cherryport)
@@ -1400,6 +1408,17 @@ def main():
else:
sessions = None
mime_gzip = ('text/html',
'text/plain',
'text/css',
'text/xml',
'text/javascript',
'application/javascript',
'text/x-javascript',
'application/x-javascript',
'text/x-json',
'application/json'
)
cherrypy.config.update({'server.environment': 'production',
'server.socket_host': cherryhost,
'server.socket_port': cherryport,
@@ -1409,7 +1428,7 @@ def main():
'engine.reexec_retry' : 100,
'tools.encode.on' : True,
'tools.gzip.on' : True,
'tools.gzip.mime_types' : ['text/html', 'text/plain', 'text/javascript', 'text/css', 'application/x-javascript'],
'tools.gzip.mime_types' : mime_gzip,
'tools.sessions.on' : bool(sessions),
'tools.sessions.storage_type' : 'file',
'tools.sessions.storage_path' : sessions,
@@ -1500,8 +1519,8 @@ def main():
if sabnzbd.FOUNDATION:
import sabnzbd.osxmenu
sabnzbd.osxmenu.notify("SAB_Launched", None)
growler.send_notification('SABnzbd %s' % (sabnzbd.__version__),
"http://%s:%s/sabnzbd" % (browserhost, cherryport), 'startup')
growler.send_notification('SABnzbd%s' % growler.hostname(),
T('SABnzbd %s started') % sabnzbd.__version__, 'startup')
# Now's the time to check for a new version
check_latest_version()
autorestarted = False
@@ -1544,7 +1563,7 @@ def main():
add_local(f)
# Have to keep this running, otherwise logging will terminate
timer = 0
timer = timer5 = 0
while not sabnzbd.SABSTOP:
if sabnzbd.WIN_SERVICE:
rc = win32event.WaitForMultipleObjects((sabnzbd.WIN_SERVICE.hWaitStop,
@@ -1570,7 +1589,7 @@ def main():
### 30 sec polling tasks
if timer > 9:
timer = 0
# Keep Windows awake (if needed)
# Keep OS awake (if needed)
sabnzbd.keep_awake()
# Restart scheduler (if needed)
scheduler.restart()
@@ -1584,6 +1603,15 @@ def main():
if sabnzbd.WIN_SERVICE and mail:
mail.send('active')
if timer5 > 9:
### 5 minute polling tasks
timer5 = 0
if sabnzbd.cfg.web_watchdog() and not is_sabnzbd_running('%s/api?tickleme=1' % sabnzbd.BROWSER_URL, 120):
autorestarted = True
cherrypy.engine.execv = True
else:
timer5 += 1
else:
timer += 1

View File

@@ -658,7 +658,10 @@ class Request(object):
# Handle cookies differently because on Konqueror, multiple
# cookies come on different lines with the same key
if name == 'Cookie':
self.cookie.load(value)
try:
self.cookie.load(value)
except:
pass
if not dict.__contains__(headers, 'Host'):
# All Internet-based HTTP/1.1 servers MUST respond with a 400

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

@@ -6,59 +6,6 @@
<!--#set global $submenu="newzbin"#-->
<!--#include $webdir + "/inc_cmenu.tmpl"#-->
<h2>Newzbin</h2>
$T('explain-newzbin')<br/><br/>
<form action="saveNewzbin" method="post" autocomplete="off">
<div class="EntryBlock">
<fieldset class="EntryFieldSet">
<legend>$T('accountInfo')</legend>
<strong>$T('opt-username_newzbin'):</strong><br>
$T('explain-username_newzbin')<br>
<input type="text" name="username_newzbin" value="$username_newzbin">
<br>
<br>
<strong>$T('opt-password_newzbin'):</strong><br>
$T('explain-password_newzbin')<br>
<input type="password" name="password_newzbin" value="$password_newzbin">
</fieldset>
</div>
<div class="EntryBlock">
<fieldset class="EntryFieldSet">
<legend>$T('newzbinBookmarks')</legend>
<label><input type="checkbox" name="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks > 0 then "checked=1" else ""#--> <strong>$T('opt-newzbin_bookmarks'):</strong></label><br>
$T('explain-newzbin_bookmarks')<br>
<a href="getBookmarks?session=$session">$T('link-getBookmarks')</a>
<br>
<!--#if $bookmarks_list#-->
<a href="hideBookmarks?session=$session">$T('link-HideBM')</a>
<!--#else#-->
<a href="showBookmarks?session=$session">$T('link-ShowBM')</a>
<!--#end if#-->
<br/>
<br/>
<label><input type="checkbox" name="newzbin_unbookmark" value="1" <!--#if $newzbin_unbookmark > 0 then "checked=1" else ""#--> /> <strong>$T('opt-newzbin_unbookmark'):</strong></label><br>
$T('explain-newzbin_unbookmark')<br>
<br/>
<strong>$T('opt-bookmark_rate'):</strong><br>
$T('explain-bookmark_rate')<br>
<input type="text" name="bookmark_rate" value="$bookmark_rate">
</fieldset>
</div>
<!--#if $bookmarks_list#-->
<fieldset class="EntryFieldSet">
<legend>$T('processedBM')</legend>
<!--#for $msgid in $bookmarks_list#-->
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a>&nbsp;
<!--#end for#-->
</fieldset>
<!--#end if#-->
<input type="hidden" name="session" value="$session">
<p><input type="submit" value="$T('button-saveChanges')"></p>
</form>
<hr/>
<h2>NzbMatrix</h2>
$T('explain-nzbmatrix')<br/><br/>

View File

@@ -73,6 +73,7 @@
<table>
<tr>
<th>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
<th>&nbsp;</th>
<th>$T('rss-order')</th>
<th>$T('rss-type')</th>
<th>$T('rss-filter')</th>
@@ -86,6 +87,7 @@
<form action="upd_rss_filter" method="get">
<tr class="odd">
<td></td>
<td><input type="checkbox" name="enabled" value="1" checked="checked" /></td>
<td></td>
<td>
<select name="filter_type">
@@ -152,6 +154,10 @@
</td>
<form action="upd_rss_filter" method="get">
<td>
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6] == '1' then 'checked="checked"' else ""#--> />
</td>
<td>
<input type="text" size="3" name="new_index" value=$fnum>
</td>
@@ -208,6 +214,7 @@
<input type="hidden" name="feed" value="$feed"/>
<input type="hidden" name="session" value="$session">
<input type="submit" value="$T('button-save')"/>
<!--#if not $rss[$feed].filter_states[$fnum]#-->&nbsp;&nbsp;$T('Incorrect filter')<!--#end if#-->
</td>
</form>
</tr>

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

@@ -47,11 +47,13 @@
<a href="$cpath/notify/">$T('cmenu-notif')</a> |
<!--#end if#-->
<!--#if 0#-->
<!--#if $submenu=="indexers"#-->
<a class="current" href="./">$T('cmenu-newzbin')</a> |
<!--#else#-->
<a href="$cpath/indexers/">$T('cmenu-newzbin')</a> |
<!--#end if#-->
<!--#end if#-->
<!--#if $submenu=="categories"#-->
<a class="current" href="./">$T('cmenu-cat')</a> |

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

@@ -17,8 +17,7 @@
<div class="EntryBlock">
<form action="addID" method="get">
<fieldset class="EntryFieldSet">
<legend>$T('add')
<!--#if $varExists('newzbinDetails')#--> $T('reportId') / <!--#end if#-->URL</legend>
<legend>$T('add') URL</legend>
<input type="text" name="id">
<!--#if $cat_list#-->
<select name="cat" >

View File

@@ -50,7 +50,9 @@
\$(document).ready(function () {
\$('.col2 H3').click(function () { \$(this).parent().next().toggle() });
#if $pane != "Servers"#
\$('.col2 H3').click(function () { \$(this).parent().next().toggle() });
#end if#
\$('.sabnzbd_restart').click(function () {
\$('.sabnzbd_restart').each(function () {
@@ -97,7 +99,7 @@
});
});
</script>
</head>
@@ -126,9 +128,11 @@
<a href="${root}config/notify/">
<div #if $pane == "Email" then 'class="active"' else ""#>$T('cmenu-notif')</div>
</a>
<!--#if 0#-->
<a href="${root}config/indexers/">
<div #if $pane == "Index Sites" then 'class="active"' else ""#>$T('cmenu-newzbin')</div>
</a>
<!--#end if#-->
<a href="${root}config/categories/">
<div #if $pane == "Categories" then 'class="active"' else ""#>$T('cmenu-cat')</div>
</a>

View File

@@ -24,6 +24,11 @@
<h5 class="copyright">Copyright &copy; 2008-2012 The SABnzbd Team &lt;<span style="color: #0000ff;">team@sabnzbd.org</span>&gt;</h5>
<p class="copyright"><small>$T('yourRights')</small></p>
</div>
<!--#if $news_items#-->
<div class="padding">
<iframe frameborder=0 width=100% src="http://sabnzbdplus.sourceforge.net/version/news.html"></iframe>
</div>
<!--#end if#-->
</div>
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->

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" />
@@ -152,7 +157,7 @@
</div>
<div class="field-pair alt">
<label class="config" for="cleanup_list">$T('opt-cleanup_list')</label>
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list" size="50" placeholder=".nfo, .sfv" />
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list" size="50"/>
<span class="desc">$T('explain-cleanup_list')</span>
</div>
<div class="field-pair">

View File

@@ -33,84 +33,6 @@
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<div class="section">
<div class="col2">
<h3>Newzbin $T('accountInfo')</h3>
<p>$T('explain-newzbin')</p>
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<div class="field-pair alt">
<label class="config" for="username_newzbin">$T('opt-username_newzbin')</label>
<input type="text" name="username_newzbin" id="username_newzbin" value="$username_newzbin" size="30" />
<span class="desc">$T('explain-username_newzbin')</span>
</div>
<div class="field-pair">
<label class="config" for="password_newzbin">$T('opt-password_newzbin')</label>
<input type="password" name="password_newzbin" id="password_newzbin" value="$password_newzbin" size="30" />
<span class="desc">$T('explain-password_newzbin')</span>
</div>
<div class="field-pair">
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<div class="section">
<div class="col2">
<h3>Newzbin $T('newzbinBookmarks')</h3>
<p>
<input type="button" id="getBookmarks" value="$T('link-getBookmarks')" />
<span id="getBookmarks-result" class="icon">&nbsp;</span>
<br/><br/>
<!--#if $bookmarks_list#-->
<input type="button" id="hideBookmarks" value="$T('link-HideBM')" />
<!--#else#-->
<input type="button" id="showBookmarks" value="$T('link-ShowBM')" />
<!--#end if#-->
</p>
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<div class="field-pair alt">
<label class="config" for="newzbin_bookmarks">$T('opt-newzbin_bookmarks')</label>
<input type="checkbox" name="newzbin_bookmarks" id="newzbin_bookmarks" value="1" <!--#if int($newzbin_bookmarks) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-newzbin_bookmarks')</span>
</div>
<div class="field-pair">
<label class="config" for="newzbin_unbookmark">$T('opt-newzbin_unbookmark')</label>
<input type="checkbox" name="newzbin_unbookmark" id="newzbin_unbookmark" value="1" <!--#if int($newzbin_unbookmark) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-newzbin_unbookmark')</span>
</div>
<div class="field-pair alt">
<label class="config" for="bookmark_rate">$T('opt-bookmark_rate')</label>
<input type="number" name="bookmark_rate" id="bookmark_rate" value="$bookmark_rate" size="8" min="15" max="1440" />
<span class="desc">$T('explain-bookmark_rate')</span>
</div>
<div class="field-pair">
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<!--#if $bookmarks_list#-->
<div class="section">
<div class="col2">
<h3>Newzbin Bookmarks</h3>
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<!--#set $odd = False#-->
<!--#for $msgid in $bookmarks_list#-->
<!--#set $odd = not $odd#-->
<div class="field-pair <!--#if $odd then "alt" else ""#-->">
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a><br/>
</div>
<!--#end for#-->
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<!--#end if#-->
<div class="padding alt">
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
<input type="button" value="$T('button-restart') SABnzbd" class="sabnzbd_restart" />

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

@@ -100,7 +100,7 @@
</fieldset>
</div><!-- /col1 -->
</form>
</div>
</div>
<!--#end if#-->
@@ -341,6 +341,7 @@
<td>
<input type="submit" class="Save" value="$T('button-save')" />
<input type="button" class="delFilter" value="$T('button-x')" />
<!--#if not $rss[$feed].filter_states[$fnum]#-->&nbsp;&nbsp;$T('Incorrect filter')<!--#end if#-->
</td>
</tr>
</tbody>
@@ -497,6 +498,10 @@
</div><!-- /colmask -->
<script>
function urlencode(str) {
return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
}
\$(document).ready(function(){
\$('.editFeed').click(function(){
var oldURI = \$(this).prev().val();
@@ -510,7 +515,7 @@
}).done(function( msg ) {
location.reload();
});
} else {
} else {
return false;
}
});
@@ -536,7 +541,7 @@
url: "test_rss_feed",
data: {feed: whichFeed, session: "$session" }
}).done(function( msg ) {
location = '?feed=' + whichFeed;
location = '?feed=' + urlencode(whichFeed);
// location.reload();
});
});

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

@@ -35,7 +35,7 @@
</div>
<div class="field-pair alt">
<label class="config" for="password">$T('srv-password')</label>
<input type="text" name="password" id="password" size="30" />
<input type="password" name="password" id="password" size="30" />
</div>
<div class="field-pair">
<label class="config" for="connections">$T('srv-connections')</label>
@@ -86,18 +86,16 @@
<div class="section">
<div class="col2">
<h3>$servers[$server]['name']</h3>
<!--#if 'amounts' in $servers[$server]#-->
<p>
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $servers[$server]['amounts'][0]<br/>
$T('today'): $servers[$server]['amounts'][3]<br/>
$T('thisWeek'): $servers[$server]['amounts'][2]<br/>
$T('thisMonth'): $servers[$server]['amounts'][1]
<table><tr>
<td><input type="checkbox" class="toggleServerCheckbox" name="q_enable" value="1" <!--#if int($servers[$server]['enable']) != 0 then 'checked="checked"' else ""#--> rel="$server" /></td>
<td>&nbsp;$T('enabled')</td>
</tr></table>
</p>
<input type="button" value="$T('button-clrServer')" class="clrServer" />
<!--#end if#-->
<input type="button" value="$T('showDetails')" class="showserver" />
</div><!-- /col2 -->
<div class="col1">
<div class="col1" style="display:none;">
<fieldset>
<div class="field-pair alt">
<label class="config" for="enable$cur">$T('srv-enable')</label>
@@ -154,6 +152,15 @@
</div>
</fieldset>
</div><!-- /col1 -->
<div class="col2" style="display:block;">
<!--#if 'amounts' in $servers[$server]#-->
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(servers[$server]['amounts'][0])B<br/>
$T('today'): $(servers[$server]['amounts'][3])B<br/>
$T('thisWeek'): $(servers[$server]['amounts'][2])B<br/>
$T('thisMonth'): $(servers[$server]['amounts'][1])B
<!--#end if#-->
</div>
</div><!-- /section -->
</form>
<!--#end for#-->
@@ -162,6 +169,15 @@
<script>
\$(document).ready(function(){
\$('.showserver').click(function () {
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).attr("value") == "$T('showDetails')") {
\$(this).attr("value", "$T('hideDetails')");
} else {
\$(this).attr("value", "$T('showDetails')");
}
});
\$('#addServerButton').click(function(){
\$('#addServer').hide();
\$('#addServerContent').show();
@@ -188,6 +204,16 @@
\$(this).parents('form:first').attr('action','clrServer').submit();
return false;
});
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("rel");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
location.reload();
});
});
});
</script>

View File

@@ -265,6 +265,11 @@
<td>$T('sort-File')</td>
</tr>
<tr>
<td class="align-right"><b>$T('orgDirname'):</b></td>
<td>%dn</td>
<td>$T("sort-Folder")</td>
</tr>
<tr class="even">
<td class="align-right"><b>$T('lowercase'):</b></td>
<td>{$T('TEXT')}</td>
<td>$T('text')</td>
@@ -432,7 +437,7 @@
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
}
}
})();
function tvSet(val) {

View File

@@ -13,7 +13,11 @@
<fieldset>
<div class="field-pair alt">
<label class="config" for="check_new_rel">$T('opt-check_new_rel')</label>
<input type="checkbox" name="check_new_rel" id="check_new_rel" value="1" <!--#if int($check_new_rel) > 0 then 'checked="checked"' else ""#--> />
<select name="check_new_rel" id="check_new_rel">
<option value="0" <!--#if $check_new_rel == 0 then 'selected="selected" class="selected"' else ""#--> >$T('off')</option>
<option value="1" <!--#if $check_new_rel == 1 then 'selected="selected" class="selected"' else ""#--> >$T('on')</option>
<option value="2" <!--#if $check_new_rel == 2 then 'selected="selected" class="selected"' else ""#--> >$T('also-test')</option>
</select>
<span class="desc">$T('explain-check_new_rel')</span>
</div>
<div class="field-pair">

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"/>
@@ -126,8 +126,10 @@
<div class="config_sprite_container sprite_config_nav_scheduling">$T('Plush-cmenu-scheduling')</div></a></li>
<li><a class="#if $pane=="Email"#nav_active#end if#" id="config_nav_email" href="${path}config/notify/">
<div class="config_sprite_container sprite_config_nav_email">$T('cmenu-notif')</div></a></li>
<!--#if 0#-->
<li><a class="#if $pane=="Index Sites"#nav_active#end if#" id="config_nav_index_sites" href="${path}config/indexers/">
<div class="config_sprite_container sprite_config_nav_indexsites">$T('cmenu-newzbin')</div></a></li>
<!--#end if#-->
<li><a class="#if $pane=="Categories"#nav_active#end if#" id="config_nav_categories" href="${path}config/categories/">
<div class="config_sprite_container sprite_config_nav_categories">$T('cmenu-cat')</div></a></li>
<li><a class="#if $pane=="Sorting"#nav_active#end if#" id="config_nav_sorting" href="${path}config/sorting/">

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

@@ -43,90 +43,6 @@
</fieldset>
</div><!-- /component-group1 -->
<div id="core-component-group2" class="component-group clearfix">
<div class="component-group-desc">
<h3>Newzbin $T('accountInfo')</h3>
<p>$T('explain-newzbin')</p>
</div>
<fieldset class="component-group-list">
<div class="field-pair">
<label class="nocheck clearfix" for="username_newzbin">
<span class="component-title">$T('opt-username_newzbin')</span>
<input type="text" name="username_newzbin" id="username_newzbin" value="$username_newzbin"/>
</label>
<label class="nocheck clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">$T('explain-username_newzbin')</span>
</label>
</div>
<div class="field-pair alt">
<label class="nocheck clearfix" for="password_newzbin">
<span class="component-title">$T('opt-password_newzbin')</span>
<input type="password" name="password_newzbin" id="password_newzbin" value="$password_newzbin"/>
</label>
<label class="nocheck clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">$T('explain-password_newzbin')</span>
</label>
</div>
</fieldset>
</div><!-- /component-group2 -->
<div id="core-component-group3" class="component-group clearfix">
<div class="component-group-desc">
<h3>Newzbin $T('newzbinBookmarks')</h3>
<p>
<input type="button" class="juiButton" id="getBookmarks" value="$T('link-getBookmarks')" />
<br/><br/>
<!--#if $bookmarks_list#-->
<input type="button" class="juiButton" id="hideBookmarks" value="$T('link-HideBM')" />
<!--#else#-->
<input type="button" class="juiButton" id="showBookmarks" value="$T('link-ShowBM')" />
<!--#end if#-->
</p>
</div>
<fieldset class="component-group-list">
<div class="field-pair">
<input type="checkbox" name="newzbin_bookmarks" id="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks > 0 then "checked=1" else ""#--> />
<label class="clearfix" for="newzbin_bookmarks">
<span class="component-title">$T('opt-newzbin_bookmarks')</span>
<span class="component-desc">$T('explain-newzbin_bookmarks')</span>
</label>
</div>
<div class="field-pair alt">
<input type="checkbox" name="newzbin_unbookmark" id="newzbin_unbookmark" value="1" <!--#if $newzbin_unbookmark > 0 then "checked=1" else ""#--> />
<label class="clearfix" for="newzbin_unbookmark">
<span class="component-title">$T('opt-newzbin_unbookmark')</span>
<span class="component-desc">$T('explain-newzbin_unbookmark')</span>
</label>
</div>
<div class="field-pair">
<label class="nocheck clearfix" for="bookmark_rate">
<span class="component-title">$T('opt-bookmark_rate')</span>
<input type="text" name="bookmark_rate" id="bookmark_rate" size="6" value="$bookmark_rate"/>
</label>
<label class="nocheck clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">$T('explain-bookmark_rate')</span>
</label>
</div>
</fieldset>
</div><!-- /component-group3 -->
<!--#if $bookmarks_list#-->
<div id="core-component-group4" class="component-group clearfix">
<div class="component-group-desc">
<h3>Newzbin $T('accountInfo')</h3>
<p>$T('explain-newzbin')</p>
</div>
<fieldset class="component-group-list">
<!--#for $msgid in $bookmarks_list#-->
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a><br/>
<!--#end for#-->
</fieldset>
</div><!-- /component-group4 -->
<!--#end if#-->
<div class="component-group-last clearfix">
<div class="component-group-desc">
<h3>&nbsp;</h3>

View File

@@ -174,6 +174,7 @@ $T('explain-RSS')
<table class="rssTable">
<tr>
<th>$T('Plush-rss-delete')</th>
<th>&nbsp;</th>
<th>$T('rss-order')</th>
<th>$T('rss-type')</th>
<th>$T('rss-filter')</th>
@@ -187,6 +188,7 @@ $T('explain-RSS')
<form action="upd_rss_filter" method="get">
<tr class="odd">
<td></td>
<td><input type="checkbox" name="enabled" value="1" checked="checked" /></td>
<td></td>
<td>
<select name="filter_type">
@@ -255,7 +257,10 @@ $T('explain-RSS')
</td>
<form action="upd_rss_filter" method="get">
<td>
<td>
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6] == '1' then 'checked="checked"' else ""#--> />
</td>
<td>
<input type="text" size="3" name="new_index" value=$fnum>
</td>
<td>
@@ -310,6 +315,7 @@ $T('explain-RSS')
<input type="hidden" name="feed" value="$feed"/>
<input type="hidden" name="session" value="$session">
<input type="submit" class="juiButton" value="$T('button-save')"/>
<!--#if not $rss[$feed].filter_states[$fnum]#-->&nbsp;&nbsp;$T('Incorrect filter')<!--#end if#-->
</td>
</form>
</tr>

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

@@ -46,7 +46,7 @@
<div class="field-pair alt">
<label class="nocheck clearfix" for="password">
<span class="component-title">$T('srv-password')</span>
<input type="text" size="25" name="password"/>
<input type="password" size="25" name="password"/>
</label>
</div>
<div class="field-pair">
@@ -156,7 +156,7 @@
<div class="field-pair alt">
<label class="nocheck clearfix" for="password">
<span class="component-title">$T('srv-password')</span>
<input type="text" size="25" name="password" value="$servers[$server]['password']" />
<input type="password" size="25" name="password" value="$servers[$server]['password']" />
</label>
</div>
<div class="field-pair">

View File

@@ -26,7 +26,6 @@
<a class="sf-with-ul">$T('menu-queue')</a>
<ul>
<!--#if $have_quota#--><li><a id="reset_quota_now" class="pointer">$T('link-resetQuota')</a></li><!--#end if#-->
<!--#if $varExists('newzbinDetails')#--><li><a id="get_bookmarks_now" class="pointer">$T('link-getBookmarks')</a></li><!--#end if#-->
<!--#if $have_rss_defined#--><li><a id="get_rss_now" class="pointer">$T('button-rssNow')</a></li><!--#end if#-->
<!--#if $have_watched_dir#--><li><a id="get_watched_now" class="pointer">$T('sch-scan_folder')</a></li><!--#end if#-->
<li><a id="topmenu_toggle" class="pointer">$T('Plush-topMenu')</a></li>

View File

@@ -56,7 +56,7 @@
</td>
<td class="download-title">
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.missing#-->$T('missingArt'): $slot.missing<!--#end if#-->">$slot.filename</a>
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.missing#-->$T('missingArt'): $slot.missing<!--#end if#-->">$slot.filename.replace('.', '.&#8203;').replace('_', '_&#8203;')</a>
</td>
<td>

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,
@@ -1216,12 +1242,16 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
SetQueueETAStats : function(speed,kbpersec,timeleft,eta) {
// ETA/speed stats at top of queue
if (kbpersec < 1 && $.plush.paused)
if (kbpersec < 1 || $.plush.paused) {
$('#stats_eta').html('&mdash;');
else
$('#stats_speed').html('&mdash;');
$('#time-left').attr('title','&mdash;'); // Tooltip on "time left"
}
else {
$('#stats_eta').html(timeleft);
$('#stats_speed').html(speed+"B/s");
$('#time-left').attr('title',eta); // Tooltip on "time left"
$('#stats_speed').html(speed+"B/s");
$('#time-left').attr('title',eta); // Tooltip on "time left"
}
},

View File

@@ -31,7 +31,7 @@
<!--#for $warn in $warnings#-->
<!--#set $odd = not $odd#-->
<tr class="<!--#if $odd then "odd" else "even"#-->">
<td>$warn.replace("\n","</td><td>")</td></tr>
<td>$warn.replace("\n","</td><td>", 2)</td></tr>
<!--#end for#-->
</table>
<!--#else#-->

View File

@@ -1,69 +1,4 @@
<a href="${helpuri}Configure+Indexers-0-7" id="help" target="_blank">$T('menu-help')</a><h3>Newzbin</h3>
<form id="configNewzbin" class="cmxform" autocomplete="off">
$T('explain-newzbin')<br/>
<br/>
<div class="EntryBlock">
<fieldset class="EntryFieldSet">
<legend>$T('accountInfo')</legend>
<hr />
<label class="label">$T('opt-username_newzbin'):</label>
<input type="text" name="username_newzbin" value="$username_newzbin">
<span class="tips">$T('explain-username_newzbin')</span>
<br class="clear" />
<label class="label">$T('opt-password_newzbin'):</label>
<input type="password" name="password_newzbin" value="$password_newzbin">
<span class="tips">$T('explain-password_newzbin')</span>
<br class="clear" />
</fieldset>
<fieldset class="EntryFieldSet">
<legend>$T('newzbinBookmarks')</legend>
<hr />
<label><span class="label">$T('newzbinBookmarks'):</span>
<input class="radio" type="checkbox" name="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks > 0 then "checked=1" else ""#--> />
<span class="tips">$T('explain-newzbin_bookmarks')</span></label>
<br class="clear" />
<label><span class="label">$T('opt-newzbin_unbookmark'):</span>
<input class="radio" type="checkbox" name="newzbin_unbookmark" value="1" <!--#if $newzbin_unbookmark > 0 then "checked=1" else ""#--> />
<span class="tips">$T('explain-newzbin_unbookmark')</span></label>
<br class="clear" />
<label class="label">$T('opt-bookmark_rate'):</label>
<input type="text" name="bookmark_rate" value="$bookmark_rate">
<span class="tips">$T('explain-bookmark_rate')</span>
<br class="clear" />
</fieldset>
<a class="config" onClick="getBookmarks();">$T('link-getBookmarks')</a>
<!--#if $bookmarks_list#-->
<a class="config" onClick="lr('config/indexers/hideBookmarks');">$T('link-HideBM')</a>
<!--#else#-->
<a class="config" onClick="lr('config/indexers/showBookmarks');">$T('link-ShowBM')</a>
<!--#end if#-->
<!--#if $bookmarks_list#-->
<fieldset class="EntryFieldSet">
<legend>$T('processedBM')</legend>
<hr />
<!--#for $msgid in $bookmarks_list#-->
<a href="https://$newzbin_url/browse/post/$msgid/" target="_blank">$msgid</a>&nbsp;
<!--#end for#-->
<br class="clear" />
</fieldset>
<!--#end if#-->
</div>
<br/><hr/>
<a href="${helpuri}Configure+Indexers-0-7" id="help" target="_blank">$T('menu-help')</a>
<h3>NzbMatrix</h3><br/>
$T('explain-nzbmatrix')<br/>

View File

@@ -79,6 +79,7 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
<thead>
<tr>
<th>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
<th>&nbsp;</th>
<th>$T('rss-order')</th>
<th>$T('rss-type')</th>
<th>$T('rss-filter')</th>
@@ -92,6 +93,7 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
<tbody>
<tr id="$feed+new_filter">
<td></td>
<td><input type="checkbox" name="enabled" value="1" checked="checked" /></td>
<td></td>
<td>
<select name="filter_type">
@@ -157,7 +159,9 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
<td>
<input type="submit" value="$T('rss-delFilter')" onclick="javascript:lr('config/rss/del_rss_filter','index=$fnum&feed=$feed')" />
</td>
<td>
<input type="checkbox" name="enabled" value="1" <!--#if $filter[6] == '1' then 'checked="checked"' else ""#--> />
</td>
<td>
<input type="text" size="3" name="new_index" value=$fnum>
</td>
@@ -213,6 +217,7 @@ MochiKit.DOM.addLoadEvent(location = "../../#config-rss");
<input type="hidden" name="index" value="$fnum"/>
<input type="hidden" name="feed" value="$feed"/>
<input type="submit" value="$T('button-save')" onclick="javascript:submitconfig('config/rss/upd_rss_filter', this,'$feed+$fnum','1' )"/>
<!--#if not $rss[$feed].filter_states[$fnum]#-->&nbsp;&nbsp;$T('Incorrect filter')<!--#end if#-->
</td>
</tr>

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) {
@@ -1133,7 +1133,9 @@ function loadingJSON(){
<li><a class="config" href="$prefix/config/scheduling/" onclick="lr('config/scheduling/','', 0, 0);">$T('cmenu-scheduling')</a> </li>
<li><a class="config" href="$prefix/config/rss/" onclick="lr('config/rss/','', 0, 0);">$T('cmenu-rss')</a> </li>
<li><a class="config" href="$prefix/config/notify/" onclick="lr('config/notify/','', 0, 0);">$T('cmenu-notif')</a></li>
<!--#if 0#-->
<li><a class="config" href="$prefix/config/indexers/" onclick="lr('config/indexers/', '', 0, 0);">$T('cmenu-newzbin')</a></li>
<!--#end if#-->
<li><a class="config" href="$prefix/config/categories/" onclick="lr('config/categories/', '', 0, 0);">$T('cmenu-cat')</a></li>
<li><a class="config" href="$prefix/config/sorting/" onclick="lr('config/sorting/', '', 0, 0);">$T('cmenu-sorting')</a></li>
</ul>
@@ -1152,9 +1154,6 @@ function loadingJSON(){
<li><a class="config" onclick="javascript:timedPause()">$T("smpl-custom")</a></li>
</ul>
<!--#if $varExists('newzbinDetails')#-->
<li><a onclick="getBookmarks()">$T('smpl-getbookmarks')</a></li>
<!--#end if#-->
<!--#if $have_quota#-->
<li><a onclick="resetQuota()">$T('link-resetQuota')</a></li>
<!--#end if#-->
@@ -1183,7 +1182,7 @@ function loadingJSON(){
<div id="RightContainer" class="left-border">
<div id="addNew" class="centerLinks" style="overflow: hidden; display: none;">
<form action="addID" method="get">
<input type="text" style="width:218px;" name="id" value="$T('enterURL')<!--#if $varExists('newzbinDetails') then $T('enterID') else '' #-->" onfocus="clearForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')" onblur="setForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')">
<input type="text" style="width:218px;" name="id" value="$T('enterURL')" onfocus="clearForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')" onblur="setForm(this, 'Enter URL<!--#if $varExists('newzbinDetails') then " or Report ID" else "" #-->')">
<!--#if $cat_list#-->
<select name="cat" >
<optgroup label="$T('category')">

View File

@@ -1,35 +0,0 @@
<!--#include $webdir + "/inc_top.tmpl"#-->
<script type="text/javascript" src="static/javascript/jquery.js"></script>
<script type="text/javascript" src="static/javascript/restart.js"></script>
<br/><br/>
<h4 id="restarting" class="align-center">$T('wizard-restarting')</h4>
<h4 id="complete" class="align-center success hidden">$T('wizard-complete')</h4>
<br />
<br/>
<div id="tips" class="hidden">
$T('wizard-tip1') <span class="bold">$T('wizard-tip2')</span><br/>
<!--#if len($urls) > 1#--><!--#set $s = 's'#--><!--#else#--><!--#set $s = ''#--><!--#end if#-->
<!--#set $tip3 = $T('wizard-tip3') % $s#-->
$tip3:<br/><br/>
<div class="quoteBlock">
<!--#set $i = 0#-->
<!--#for $url in $urls#-->
<!--#set $i = $i+1#-->
<a href="$url">$url</a><!--#if $i != len($urls)#--><br /><!--#end if#-->
<!--#end for#-->
</div><br/>
$T('wizard-tip4')
<br/><br/>
$T('wizard-tip-wiki') <a href="$helpuri">wiki</a>
</div>
</div>
<hr /><br/>
<div class="full-width">
<table class="full-width">
<tr class="align-center">
<td><input type="hidden" name="session" id="apikey" value="$session"><input class="bigbutton disabled" type="button" onclick="document.location ='$access_url'" value="$T('wizard-goto')" disabled="disabled"/></td>
</tr>
</table>
</div>
<!--#include $webdir + "/inc_bottom.tmpl"#-->

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

View File

@@ -1,39 +1,34 @@
<!--#include $webdir + "/inc_top.tmpl"#-->
<form action="./four" method="post" autocomplete="off">
<p>$T('wizard-index-explain')</p>
<div id="serverDetails">
<h3><a href="http://$newzbin_url" target="_blank">Newzbin2.es</a> ($T('wizard-optional'))</h3>
<label class="label">$T('srv-username'):</label><input type="text" size="20" value="$newzbin_user" name="newzbin_user">
<br class="clear" />
<label class="label">$T('srv-password'):</label><input type="password" size="20" value="$newzbin_pass" name="newzbin_pass">
<br class="clear" />
<input type="checkbox" name="newzbin_bookmarks" id="newzbin_bookmarks" value="1" <!--#if $newzbin_bookmarks == 1 then 'checked="checked"' else ''#-->> <label for="newzbin_bookmarks">$T('wizard-index-bookmark')</label><br />
<h3><a href="http://nzbmatrix.com" target="_blank">NZBMatrix.com</a> ($T('wizard-optional'))</h3>
<label class="label">$T('srv-username'):</label><input type="text" size="20" value="$matrix_user" name="matrix_user">
<br class="clear" />
<label class="label">$T('opt-apikey'):</label><input type="text" size="20" value="$matrix_apikey" name="matrix_apikey">
</div></div>
<script type="text/javascript" src="static/javascript/jquery.js"></script>
<script type="text/javascript" src="static/javascript/restart.js"></script>
<br/><br/>
<h4 id="restarting" class="align-center">$T('wizard-restarting')</h4>
<h4 id="complete" class="align-center success hidden">$T('wizard-complete')</h4>
<br />
<br/>
<div id="tips" class="hidden">
$T('wizard-tip1') <span class="bold">$T('wizard-tip2')</span><br/>
<!--#set $tip3 = $T('wizard-tip3') % ''#-->
$tip3<br/><br/>
<div class="quoteBlock">
<!--#set $i = 0#-->
<!--#for $url in $urls#-->
<!--#set $i = $i+1#-->
<a href="$url">$url</a><!--#if $i != len($urls)#--><br /><!--#end if#-->
<!--#end for#-->
</div><br/>
$T('wizard-tip4')
<br/><br/>
$T('wizard-tip-wiki') <a href="$helpuri">wiki</a>
</div>
</div>
<hr /><br/>
<div class="full-width">
<table class="full-width">
<tr>
<td><input class="bigbutton" type="button" onclick="document.location ='./two'" value="&lsaquo; $T('wizard-previous')" /></td>
<td>
<div class="align-center">
<!--#for $step in xrange($steps)#-->
<!--#set $step = $step + 1#-->
<span class="<!--#if $step == $number then 'selected' else 'unselected'#-->">$step</span>
<!--#end for#-->
</div>
</td>
<td class="align-right"><input class="bigbutton" type="submit" value="$T('wizard-next') &raquo;" /></td>
<tr class="align-center">
<td><input type="hidden" name="session" id="apikey" value="$session"><input class="bigbutton disabled" type="button" onclick="document.location ='$access_url'" value="$T('wizard-goto')" disabled="disabled"/></td>
</tr>
</table>
</div>
</form>
<!--#include $webdir + "/inc_bottom.tmpl"#-->
<!--#include $webdir + "/inc_bottom.tmpl"#-->

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 B

BIN
osx/resources/sab_idle.tiff Normal file
View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

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):
@@ -95,7 +96,7 @@ def PatchVersion(name):
try:
pipe = subprocess.Popen(GitStatus, shell=True, stdout=subprocess.PIPE).stdout
for line in pipe.read().split('\n'):
if 'nothing to commit' in line:
if 'nothing to commit' in line or 'nothing added to commit' in line:
state = ''
break
pipe.close()
@@ -151,8 +152,7 @@ def PairList(src):
lst.append((path, flist))
else:
path, name = os.path.split(item)
items = []
items.append(name)
items = [name]
lst.append((path, items))
return lst
@@ -207,7 +207,7 @@ def Dos2Unix(name):
def Unix2Dos(name):
""" Read file, remove \r, replace \n by \r\n and write back """
base, ext = os.path.splitext(name)
if ext.lower() not in ('.py', '.txt', '.css', '.js', '.tmpl', '.sh', '.cmd'):
if ext.lower() not in ('.py', '.txt', '.css', '.js', '.tmpl', '.sh', '.cmd', '.mkd'):
return
print name
@@ -230,9 +230,9 @@ def Unix2Dos(name):
def rename_file(folder, old, new):
oldpath = "%s/%s" % (folder, old)
newpath = "%s/%s" % (folder, new)
try:
oldpath = "%s/%s" % (folder, old)
newpath = "%s/%s" % (folder, new)
if os.path.exists(newpath):
os.remove(newpath)
os.rename(oldpath, newpath)
@@ -246,6 +246,11 @@ print sys.argv[0]
Git = CheckPath('git')
ZipCmd = CheckPath('zip')
UnZipCmd = CheckPath('unzip')
if os.name != 'nt':
PanDoc = CheckPath('pandoc')
else:
PanDoc = None
if os.name == 'nt':
msg = 'Requires the standard version of NSIS'
NSIS = CheckPath('makensis')
@@ -254,7 +259,7 @@ if os.name == 'nt':
os.system('%s >%s' % (NSIS, log))
if 'Unicode' not in open(log).read():
msg = ''
DeleteFiles(log)
delete_files(log)
if msg:
print msg
exit(1)
@@ -293,11 +298,14 @@ Win32TempName = 'SABnzbd-windows.exe'
fileIns = prod + '-win32-setup.exe'
fileBin = prod + '-win32-bin.zip'
fileSrc = prod + '-src.tar.gz'
fileDmg = prod + '-osx.dmg'
fileDmgLp = prod + '-osx-leopard.dmg'
fileDmg_ml = prod + '-osx-mountainlion.dmg'
fileDmg_lion = prod + '-osx-lion.dmg'
fileDmg_sl = prod + '-osx-snowleopard.dmg'
fileOSr = prod + '-osx-src.tar.gz'
fileImg = prod + '.sparseimage'
if OSX_SL: postfix = 'sl'
if OSX_LION: postfix = 'lion'
if OSX_ML: postfix = 'ml'
PatchVersion(release)
@@ -305,7 +313,7 @@ PatchVersion(release)
# List of data elements, directories end with a '/'
data_files = [
'ABOUT.txt',
'README.txt',
'README.mkd',
'INSTALL.txt',
'GPL2.txt',
'GPL3.txt',
@@ -351,34 +359,19 @@ if target == 'app':
os.system(GitRevertVersion)
exit(1)
if not PanDoc:
print "Sorry, requires pandoc in the $PATH"
os.system(GitRevertVersion)
exit(1)
# Check which Python flavour
apple_py = 'ActiveState' not in sys.copyright
if OSX_LION:
# Check if Leopard build is present
leopard_build = '/project/leopard/%s' % str(my_version)
if not os.path.isdir(leopard_build):
print 'Leopard build not found at %s' % leopard_build
exit(1)
# Create sparseimage from template
os.system("unzip -o osx/image/template.sparseimage.zip")
os.rename('template.sparseimage', fileImg)
# mount sparseimage and modify volume label
os.system("hdiutil mount %s | grep /Volumes/SABnzbd >mount.log" % (fileImg))
# Rename the volume
fp = open('mount.log', 'r')
data = fp.read()
fp.close()
os.remove('mount.log')
m = re.search(r'/dev/(\w+)\s+', data)
volume = 'SABnzbd-' + str(my_version)
os.system('disktool -n %s %s' % (m.group(1), volume))
options['description'] = 'SABnzbd ' + str(my_version)
# Remove previous build result
os.system('rm -rf dist/ build/')
# Create MO files
os.system('python ./tools/make_mo.py')
@@ -389,7 +382,7 @@ if target == 'app':
DATA_FILES = ['interfaces', 'locale', 'email', ('', glob.glob("osx/resources/*"))]
NZBFILE = dict(
CFBundleTypeExtensions = [ "nzb","zip","rar" ],
CFBundleTypeExtensions = [ "nzb" ],
CFBundleTypeIconFile = 'nzbfile.icns',
CFBundleTypeMIMETypes = [ "text/nzb" ],
CFBundleTypeName = 'NZB File',
@@ -418,67 +411,63 @@ if target == 'app':
setup_requires=['py2app'],
)
# copy unrar & par2 binary to avoid striping
# Remove 64bit code
if not OSX_SL:
os.system("mv dist/SABnzbd.app dist/SABnzbd.app.temp")
os.system("ditto --arch i386 --arch ppc dist/SABnzbd.app.temp dist/SABnzbd.app/")
os.system("rm -rf dist/SABnzbd.app.temp")
# copy unrar & par2 binary
os.system("mkdir dist/SABnzbd.app/Contents/Resources/osx>/dev/null")
os.system("mkdir dist/SABnzbd.app/Contents/Resources/osx/par2>/dev/null")
os.system("cp -pR osx/par2/ dist/SABnzbd.app/Contents/Resources/osx/par2>/dev/null")
os.system("mkdir dist/SABnzbd.app/Contents/Resources/osx/unrar>/dev/null")
os.system("cp -pR osx/unrar/license.txt dist/SABnzbd.app/Contents/Resources/osx/unrar/ >/dev/null")
if OSX_LION:
os.system("cp -pR osx/unrar/unrar dist/SABnzbd.app/Contents/Resources/osx/unrar/ >/dev/null")
else:
if OSX_SL:
os.system("cp -pR osx/unrar/unrar-leopard dist/SABnzbd.app/Contents/Resources/osx/unrar/unrar >/dev/null")
else:
os.system("cp -pR osx/unrar/unrar dist/SABnzbd.app/Contents/Resources/osx/unrar/ >/dev/null")
os.system("cp icons/sabnzbd.ico dist/SABnzbd.app/Contents/Resources >/dev/null")
os.system("cp README.rtf dist/SABnzbd.app/Contents/Resources/Credits.rtf >/dev/null")
os.system("pandoc -f markdown -t rtf -s -o dist/SABnzbd.app/Contents/Resources/Credits.rtf README.mkd >/dev/null")
os.system("find dist/SABnzbd.app -name .git | xargs rm -rf")
if OSX_LION:
# Sign the App if possible
authority = os.environ.get('SIGNING_AUTH')
if authority:
os.system('codesign -f -i "%s-lion" -s "%s" dist/SABnzbd.app' % (volume, authority))
os.system('codesign -f -i "%s-leopard" -s "%s" %s/dist/SABnzbd.app' % (volume, authority, leopard_build))
# Remove source files to prevent re-compilation, which would invalidate signing
py_ver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
os.system("find dist/SABnzbd.app/Contents/Resources/lib/python%s/Cheetah -name '*.py' | xargs rm" % py_ver)
os.system("find dist/SABnzbd.app/Contents/Resources/lib/python%s/xml -name '*.py' | xargs rm" % py_ver)
os.system('rm dist/SABnzbd.app/Contents/Resources/site.py')
# Add the SabNotifier app
if OSX_ML and os.path.exists('/project/sabnotifier/SABnzbd.app'):
os.system("cp -pR /project/sabnotifier/SABnzbd.app dist/SABnzbd.app/Contents/Resources/")
# copy app to mounted sparseimage
os.system('cp -r dist/SABnzbd.app "/Volumes/%s/OS X 10.6 and Above/" >/dev/null' % volume)
# Add License files
os.mkdir("dist/SABnzbd.app/Contents/Resources/licenses/")
os.system("cp -p licenses/*.txt dist/SABnzbd.app/Contents/Resources/licenses/")
os.system("cp -p *.txt dist/SABnzbd.app/Contents/Resources/licenses/")
# Copy the Leopard build
os.system('cp -r %s/dist/SABnzbd.app "/Volumes/%s/OS X 10.5 and Below/" >/dev/null' % (leopard_build, volume))
os.system("sleep 5")
# Archive result to share
dest_path = '/Volumes/VMware Shared Folders/osx'
if not os.path.exists(dest_path):
dest_path = '$HOME/project/osx'
cpio_path = os.path.join(dest_path, prod) + '-' + postfix + '.cpio'
print 'Create CPIO file %s' % cpio_path
delete_files(cpio_path)
os.system('ditto -c -z dist/ "%s"' % cpio_path)
if OSX_ML:
print 'Create src %s' % fileOSr
delete_files(fileOSr)
os.system('tar -czf %s --exclude ".git*" --exclude "sab*.zip" --exclude "SAB*.tar.gz" --exclude "*.cmd" --exclude "*.pyc" '
'--exclude "*.sparseimage" --exclude "dist" --exclude "build" --exclude "*.nsi" --exclude "win" --exclude "*.dmg" '
'./ >/dev/null' % (fileOSr) )
# Copy README.txt
os.system("cp README.rtf /Volumes/%s/" % volume)
# Remove site.py to prevent re-compilation (otherwise the OSX Firewall may complain)
os.remove('/Volumes/%s/OS X 10.6 and Above/SABnzbd.app/Contents/Resources/site.py' % volume)
os.remove('/Volumes/%s/OS X 10.5 and Below/SABnzbd.app/Contents/Resources/site.py' % volume)
#Unmount sparseimage
os.system("hdiutil eject /Volumes/%s/>/dev/null" % volume)
os.system("sleep 5")
# Convert sparseimage to read only compressed dmg
if os.path.exists(fileDmg):
os.remove(fileDmg)
os.system("hdiutil convert %s -format UDBZ -o %s>/dev/null" % (fileImg, fileDmg))
# Remove sparseimage
os.system("rm %s>/dev/null" % (fileImg))
# Make image internet-enabled
os.system("hdiutil internet-enable %s" % fileDmg)
else:
dest = '/Volumes/VMware Shared Folders/leopard/%s' % str(my_version)
os.system('rm -rf "%s"' % dest)
os.makedirs('%s/dist' % dest)
os.system('cp -r dist/SABnzbd.app "%s/dist/" >/dev/null' % dest)
'--exclude "*.sparseimage*" --exclude "dist" --exclude "build" --exclude "*.nsi" --exclude "win" --exclude "*.dmg" '
'./ >/dev/null' % os.path.join(dest_path, fileOSr) )
os.system(GitRevertApp + VERSION_FILEAPP)
os.system(GitRevertApp + VERSION_FILE)
elif target in ('binary', 'installer'):
if not py2exe:
print "Sorry, only works on Windows!"
@@ -518,12 +507,12 @@ elif target in ('binary', 'installer'):
for tup in options['data_files']:
for file in tup[1]:
name, ext = os.path.splitext(file)
if ext.lower() in ('.txt', '.cmd'):
if ext.lower() in ('.txt', '.cmd', '.mkd'):
Unix2Dos("dist/%s" % file)
DeleteFiles('dist/Sample-PostProc.sh')
DeleteFiles('dist/PKG-INFO')
delete_files('dist/Sample-PostProc.sh')
delete_files('dist/PKG-INFO')
DeleteFiles('*.ini')
delete_files('*.ini')
############################
# Generate the windowed-app
@@ -560,14 +549,14 @@ elif target in ('binary', 'installer'):
############################
# Remove unwanted system DLL files that Py2Exe copies when running on Win7
DeleteFiles(r'dist\lib\API-MS-Win-*.dll')
DeleteFiles(r'dist\lib\MSWSOCK.DLL')
DeleteFiles(r'dist\lib\POWRPROF.DLL')
DeleteFiles(r'dist\lib\KERNELBASE.dll')
delete_files(r'dist\lib\API-MS-Win-*.dll')
delete_files(r'dist\lib\MSWSOCK.DLL')
delete_files(r'dist\lib\POWRPROF.DLL')
delete_files(r'dist\lib\KERNELBASE.dll')
############################
# Remove .git residue
DeleteFiles(r'dist\interfaces\Config\.git')
delete_files(r'dist\interfaces\Config\.git')
############################
# Copy Curl if needed
@@ -575,18 +564,25 @@ elif target in ('binary', 'installer'):
# Curl for Python 2.5
os.system(r'unzip -o win\curl\curl.zip -d dist\lib')
############################
# Fix icon issue with NZB association
os.system(r'copy dist\icons\nzb.ico dist')
############################
# Rename MKD file
rename_file('dist', 'README.mkd', 'README.txt')
############################
if target == 'installer':
DeleteFiles(fileIns)
delete_files(fileIns)
os.system('makensis.exe /v3 /DSAB_PRODUCT=%s /DSAB_VERSION=%s /DSAB_FILE=%s NSIS_Installer.nsi.tmp' % \
(prod, release, fileIns))
DeleteFiles('NSIS_Installer.nsi.tmp')
delete_files('NSIS_Installer.nsi.tmp')
if not os.path.exists(fileIns):
print 'Fatal error creating %s' % fileIns
exit(1)
DeleteFiles(fileBin)
delete_files(fileBin)
os.rename('dist', prod)
os.system('zip -9 -r -X %s %s' % (fileBin, prod))
time.sleep(1.0)
@@ -653,6 +649,10 @@ else:
shutil.copy2(file, dest)
Dos2Unix(fullname)
############################
# Rename MKD file
rename_file(root, 'README.mkd', 'README.txt')
os.chdir(root)
os.chdir('..')
@@ -660,4 +660,3 @@ else:
CreateTar('srcdist', fileSrc, prod)
os.system(GitRevertVersion)

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-03-14 04:51+0000\n"
"Last-Translator: Rene <Unknown>\n"
"PO-Revision-Date: 2012-08-03 17:24+0000\n"
"Last-Translator: shypike <Unknown>\n"
"Language-Team: Danish <da@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2012-08-04 05:38+0000\n"
"X-Generator: Launchpad (build 15742)\n"
#: email/email.tmpl:1
msgid ""
@@ -65,42 +65,42 @@ msgid ""
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"## Standard Email skabelon til SABnzbd\n"
"## Dette er en Cheetah skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"## Linjeskift og blanktegn er betydelig!\n"
"##\n"
"## These are the email headers\n"
"## Disse er e-mail-headerne \n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd has <!--#if $status then \"completed\" else \"failed\" #--> "
"Subject: SABnzbd har <!--#if $status then \"hentet\" else \"fejlet\" #--> "
"job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"## Efter dette kommer body, den tomme linje kræves!\n"
"\n"
"Hej,\n"
"<!--#if $status #-->\n"
"SABnzbd har downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else "
"\"(newzbin #\" + $msgid + \")\"#-->\n"
"SABnzbd har hentet \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin "
"#\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd har ikke downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else "
"SABnzbd kunne ikke hente \"$name\" <!--#if $msgid==\"\" then \"\" else "
"\"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Færdig kl. $end_time\n"
"Downloaded $size\n"
"Hentet $size\n"
"\n"
"Resultat af job:\n"
"<!--#for $stage in $stages #-->\n"
"Stage $stage <!--#slurp#-->\n"
"Etape $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Output from user script \"$script\" (Exit code = $script_ret):\n"
"Output fra bruger script \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
@@ -138,20 +138,20 @@ msgid ""
"Bye\n"
msgstr ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"## RSS Email skabelon til SABnzbd\n"
"## Dette er Cheetah skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"## Linjeskift og blanktegn er betydelig!\n"
"##\n"
"## These are the email headers\n"
"## Dette er email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har tilføjet $antal jobs til køen\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"## Efter dette kommer body, den tomme linje kræves!\n"
"\n"
"Hej,\n"
"\n"
@@ -189,24 +189,24 @@ msgid ""
"Bye\n"
msgstr ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"## Dårlig URL Fetch E-mail skabelon for SABnzbd\n"
"## Dette er en Cheetah skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"## Linjeskift og blanktegn er betydelig!\n"
"##\n"
"## These are the email headers\n"
"## Dette er email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd failed to fetch an NZB\n"
"Subject: SABnzbd kunne ikke hente en NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"## Efter dette kommer body, den tomme linje kræves!\n"
"\n"
"Hi,\n"
"Hej,\n"
"\n"
"SABnzbd has failed to retrieve the NZB from $url.\n"
"The error message was: $msg\n"
"SABnzbd kunne ikke hente NZB fra $url.\n"
"Fejl meddelelsen er: $msg\n"
"\n"
"Bye\n"
"Farvel\n"

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

@@ -13,71 +13,71 @@ msgstr ""
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 7bit\n"
#: NSIS_Installer.nsi:417
#: NSIS_Installer.nsi:425
msgid "Go to the SABnzbd Wiki"
msgstr ""
#: NSIS_Installer.nsi:419
#: NSIS_Installer.nsi:427
msgid "Show Release Notes"
msgstr ""
#: NSIS_Installer.nsi:421
#: NSIS_Installer.nsi:429
msgid "Support the project, Donate!"
msgstr ""
#: NSIS_Installer.nsi:423
#: NSIS_Installer.nsi:431
msgid "Please close \"SABnzbd.exe\" first"
msgstr ""
#: NSIS_Installer.nsi:425
#: NSIS_Installer.nsi:433
msgid " >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
msgstr ""
#: NSIS_Installer.nsi:427
#: NSIS_Installer.nsi:435
msgid "This will uninstall SABnzbd from your system"
msgstr ""
#: NSIS_Installer.nsi:429
#: NSIS_Installer.nsi:437
msgid "Run at startup"
msgstr ""
#: NSIS_Installer.nsi:431
#: NSIS_Installer.nsi:439
msgid "Desktop Icon"
msgstr ""
#: NSIS_Installer.nsi:433
#: NSIS_Installer.nsi:441
msgid "NZB File association"
msgstr ""
#: NSIS_Installer.nsi:435
#: NSIS_Installer.nsi:443
msgid "Delete Program"
msgstr ""
#: NSIS_Installer.nsi:437
#: NSIS_Installer.nsi:445
msgid "Delete Settings"
msgstr ""
#: NSIS_Installer.nsi:439
#: NSIS_Installer.nsi:447
msgid "This system requires the Microsoft runtime library VC90 to be installed first. Do you want to do that now?"
msgstr ""
#: NSIS_Installer.nsi:441
#: NSIS_Installer.nsi:449
msgid "Downloading Microsoft runtime installer..."
msgstr ""
#: NSIS_Installer.nsi:443
#: NSIS_Installer.nsi:451
msgid "Download error, retry?"
msgstr ""
#: NSIS_Installer.nsi:445
#: NSIS_Installer.nsi:453
msgid "Cannot install without runtime library, retry?"
msgstr ""
#: NSIS_Installer.nsi:447
#: NSIS_Installer.nsi:455
msgid "You cannot overwrite an existing installation. \\n\\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade."
msgstr ""
#: NSIS_Installer.nsi:449
#: NSIS_Installer.nsi:457
msgid "Your settings and data will be preserved."
msgstr ""

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,63 +7,65 @@ msgid ""
msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-05-01 18:51+0000\n"
"PO-Revision-Date: 2012-06-07 03:07+0000\n"
"Last-Translator: Carlos Hernandez <imaginingdreamer@yahoo.com>\n"
"POT-Creation-Date: 2012-08-14 18:42+0000\n"
"PO-Revision-Date: 2012-07-30 09:38+0000\n"
"Last-Translator: Victor Herrero <victorhera@gmail.com>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-06-08 05:13+0000\n"
"X-Generator: Launchpad (build 15376)\n"
"X-Launchpad-Export-Date: 2012-08-15 05:11+0000\n"
"X-Generator: Launchpad (build 15801)\n"
#: NSIS_Installer.nsi:417
#: NSIS_Installer.nsi:425
msgid "Go to the SABnzbd Wiki"
msgstr "Ir al wiki de SABnzbd"
#: NSIS_Installer.nsi:419
#: NSIS_Installer.nsi:427
msgid "Show Release Notes"
msgstr "Mostrar notas de la versión"
#: NSIS_Installer.nsi:421
#: NSIS_Installer.nsi:429
msgid "Support the project, Donate!"
msgstr "¡Apoye el proyecto, haga una donación!"
#: NSIS_Installer.nsi:423
#: NSIS_Installer.nsi:431
msgid "Please close \"SABnzbd.exe\" first"
msgstr "Por favor cierre primero \"SABnzbd.exe\""
#: NSIS_Installer.nsi:425
#: NSIS_Installer.nsi:433
msgid ""
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
msgstr ""
" >>>> ATENCION <<<<\\r\\n\\r\\nPor favor, compruebe las "
"notas de version o visite http://wiki.sabnzbd.org/introducing-0-7-0 !"
#: NSIS_Installer.nsi:427
#: NSIS_Installer.nsi:435
msgid "This will uninstall SABnzbd from your system"
msgstr "Esto desinstalará SABnzbd de su sistema"
#: NSIS_Installer.nsi:429
#: NSIS_Installer.nsi:437
msgid "Run at startup"
msgstr "Ejecutar al inicio"
#: NSIS_Installer.nsi:431
#: NSIS_Installer.nsi:439
msgid "Desktop Icon"
msgstr "Icono del escritorio"
#: NSIS_Installer.nsi:433
#: NSIS_Installer.nsi:441
msgid "NZB File association"
msgstr "Asociación de archivos NZB"
#: NSIS_Installer.nsi:435
#: NSIS_Installer.nsi:443
msgid "Delete Program"
msgstr "Eliminar programa"
#: NSIS_Installer.nsi:437
#: NSIS_Installer.nsi:445
msgid "Delete Settings"
msgstr "Eliminar Ajustes"
#: NSIS_Installer.nsi:439
#: NSIS_Installer.nsi:447
msgid ""
"This system requires the Microsoft runtime library VC90 to be installed "
"first. Do you want to do that now?"
@@ -71,20 +73,20 @@ msgstr ""
"Este sistema requiere la ejecución de la biblioteca Microsoft runtime VC90 "
"que debe ser instalada. ¿Quieres hacerlo ahora?"
#: NSIS_Installer.nsi:441
#: NSIS_Installer.nsi:449
msgid "Downloading Microsoft runtime installer..."
msgstr "Descargando el instalador de Microsoft runtime..."
#: NSIS_Installer.nsi:443
#: NSIS_Installer.nsi:451
msgid "Download error, retry?"
msgstr "Error en la descarga, ¿probamos de nuevo?"
#: NSIS_Installer.nsi:445
#: NSIS_Installer.nsi:453
msgid "Cannot install without runtime library, retry?"
msgstr ""
"No se puede instalar sin la biblioteca runtime, ¿Lo volvemos a intentar?"
#: NSIS_Installer.nsi:447
#: NSIS_Installer.nsi:455
msgid ""
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
"the previous version or `Cancel` to cancel this upgrade."
@@ -92,7 +94,7 @@ msgstr ""
"No es posible sobrescribir una instalación existente. \\n\\nPresione `OK' "
"para quitar la versión anterior o 'Cancelar' para cancelar la actualización."
#: NSIS_Installer.nsi:449
#: NSIS_Installer.nsi:457
msgid "Your settings and data will be preserved."
msgstr "Tus ajustes y datos se mantendrán intactos."

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
@@ -59,6 +63,10 @@ elif os.name == 'posix':
pass
if platform.machine() == 'i386':
DARWIN_INTEL = True
if DARWIN:
DARWIN_ML = [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 8]
else:
DARWIN_ML = False
#------------------------------------------------------------------------
@@ -294,10 +302,11 @@ def initialize(pause_downloader = False, clean_up = False, evalSched=False, repa
PostProcessor()
NzbQueue()
NzbQueue.do.read_queue(repair)
Assembler()
NzbQueue.do.read_queue(repair)
Downloader(pause_downloader or paused)
DirScanner()
@@ -752,13 +761,21 @@ def empty_queues():
def keep_awake():
""" If we still have work to do, keep Windows system awake
""" If we still have work to do, keep Windows/OSX system awake
"""
global KERNEL32
if KERNEL32 and not sabnzbd.downloader.Downloader.do.paused:
if (not PostProcessor.do.empty()) or not NzbQueue.do.is_empty():
# set ES_SYSTEM_REQUIRED
KERNEL32.SetThreadExecutionState(ctypes.c_int(0x00000001))
if KERNEL32 or sleepless:
if sabnzbd.cfg.keep_awake():
awake = False
if not sabnzbd.downloader.Downloader.do.paused:
if (not PostProcessor.do.empty()) or not NzbQueue.do.is_empty():
awake = True
if KERNEL32:
# set ES_SYSTEM_REQUIRED
KERNEL32.SetThreadExecutionState(ctypes.c_int(0x00000001))
else:
sleepless.keep_awake(u'SABnzbd is busy downloading and/or post-processing')
if not awake and sleepless:
sleepless.allow_sleep()
def CheckFreeSpace():
@@ -1021,6 +1038,9 @@ def check_all_tasks():
# Check one-shot pause
sabnzbd.scheduler.pause_check()
# Check (and terminate) idle jobs
sabnzbd.nzbqueue.NzbQueue.do.stop_idle_jobs()
return True
@@ -1060,16 +1080,9 @@ def active_primaries():
return sabnzbd.downloader.Downloader.do.active_primaries()
def proxy_postproc(nzo):
sabnzbd.postproc.PostProcessor.do.process(nzo)
def proxy_pre_queue(name, pp, cat, script, priority, size, groups):
return sabnzbd.newsunpack.pre_queue(name, pp, cat, script, priority, size, groups)
def proxy_get_history_size():
history_db = sabnzbd.database.get_history_handle()
return history_db.get_history_size()
def proxy_build_history():
""" Proxy to let nzbqueue call api """
return sabnzbd.api.build_history()

View File

@@ -293,9 +293,11 @@ def _api_addfile(name, output, kwargs):
#Side effect of next line is that attribute .value is created
#which is needed to make add_nzbfile() work
size = name.length
else:
elif hasattr(name, 'value'):
size = len(name.value)
if name is not None and name.filename and size:
else:
size = 0
if name is not None and size and name.filename:
cat = kwargs.get('cat')
xcat = kwargs.get('xcat')
if not cat and xcat:
@@ -422,6 +424,8 @@ def _api_history(name, output, kwargs):
limit = kwargs.get('limit')
search = kwargs.get('search')
failed_only = kwargs.get('failed_only')
if not limit:
limit = cfg.history_limit()
if name == 'delete':
special = value.lower()
@@ -430,10 +434,10 @@ def _api_history(name, output, kwargs):
history_db = cherrypy.thread_data.history_db
if special in ('all', 'failed'):
if del_files:
del_job_files(history_db.get_failed_paths())
history_db.remove_failed()
del_job_files(history_db.get_failed_paths(search))
history_db.remove_failed(search)
if special in ('all', 'completed'):
history_db.remove_completed()
history_db.remove_completed(search)
return report(output)
elif value:
jobs = value.split(',')
@@ -461,33 +465,6 @@ def _api_get_files(name, output, kwargs):
else:
return report(output, _MSG_NO_VALUE)
def _api_addurl(names, output, kwargs):
""" API: accepts name, output, pp, script, cat, priority, nzbname """
pp = kwargs.get('pp')
script = kwargs.get('script')
cat = kwargs.get('cat')
priority = kwargs.get('priority')
nzbnames = kwargs.get('nzbname')
if not isinstance(names, list):
names = [names]
if not isinstance(nzbnames, list):
nzbnames = [nzbnames]
for n in xrange(len(names)):
name = names[n]
if n < len(nzbnames):
nzbname = nzbnames[n]
else:
nzbname = ''
if name:
name = name.strip()
sabnzbd.add_url(name, pp, script, cat, priority, nzbname)
if len(names) > 0:
return report(output)
else:
return report(output, _MSG_NO_VALUE)
_RE_NEWZBIN_URL = re.compile(r'/browse/post/(\d+)')
def _api_addid(names, output, kwargs):
@@ -581,10 +558,13 @@ def _api_auth(name, output, kwargs):
if not cfg.disable_key():
auth = 'badkey'
key = kwargs.get('key', '')
if key == cfg.nzb_key():
auth = 'nzbkey'
if key == cfg.api_key():
if not key:
auth = 'apikey'
else:
if key == cfg.nzb_key():
auth = 'nzbkey'
if key == cfg.api_key():
auth = 'apikey'
elif cfg.username() and cfg.password():
auth = 'login'
return report(output, keyword='auth', data=auth)
@@ -796,7 +776,7 @@ _api_table = {
'fullstatus' : _api_fullstatus,
'history' : _api_history,
'get_files' : _api_get_files,
'addurl' : _api_addurl,
'addurl' : _api_addid,
'addid' : _api_addid,
'pause' : _api_pause,
'resume' : _api_resume,
@@ -1127,7 +1107,7 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
slot['mbdone_fmt'] = locale.format('%d', int(mb-mbleft), True)
slot['size'] = format_bytes(bytes)
slot['sizeleft'] = format_bytes(bytesleft)
if not Downloader.do.paused and status != 'Paused' and status != 'Fetching' and not found_active:
if not Downloader.do.paused and status not in (Status.PAUSED, Status.FETCHING) and not found_active:
if status == Status.CHECKING:
slot['status'] = Status.CHECKING
else:
@@ -1151,7 +1131,7 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
slot['percentage'] = "%s" % (int(((mb-mbleft) / mb) * 100))
slot['missing'] = missing
if status in (Status.PAUSED, Status.CHECKING):
if Downloader.do.paused or Downloader.do.postproc or status not in (Status.DOWNLOADING, Status.QUEUED):
slot['timeleft'] = '0:00:00'
slot['eta'] = 'unknown'
else:
@@ -1165,7 +1145,11 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
datestart = datetime.datetime.now()
slot['eta'] = 'unknown'
slot['avg_age'] = calc_age(average_date, bool(trans))
if status == Status.GRABBING:
slot['avg_age'] = '---'
else:
slot['avg_age'] = calc_age(average_date, bool(trans))
slot['verbosity'] = ""
if web_dir:
finished = []
@@ -1289,12 +1273,13 @@ def qstatus_data():
state = "IDLE"
if Downloader.do.paused:
state = "PAUSED"
state = Status.PAUSED
elif qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI > 0:
state = "DOWNLOADING"
state = Status.DOWNLOADING
status = {
"state" : state,
"pp_active" : not PostProcessor.do.empty(),
"paused" : Downloader.do.paused,
"pause_int" : scheduler.pause_int(),
"kbpersec" : bpsnow / KIBI,
@@ -1559,7 +1544,8 @@ def build_header(prim, webdir=''):
if not color:
color = ''
header = { 'T': Ttemplate, 'Tspec': Tspec, 'Tx' : Ttemplate, 'version':sabnzbd.__version__, 'paused': Downloader.do.paused,
header = { 'T': Ttemplate, 'Tspec': Tspec, 'Tx' : Ttemplate, 'version':sabnzbd.__version__,
'paused': Downloader.do.paused or Downloader.do.postproc,
'pause_int': scheduler.pause_int(), 'paused_all': sabnzbd.PAUSED_ALL,
'uptime':uptime, 'color_scheme':color }
speed_limit = Downloader.do.get_limit()
@@ -1610,13 +1596,13 @@ def build_header(prim, webdir=''):
header['left_quota'] = to_units(BPSMeter.do.left)
status = ''
if Downloader.do.paused:
if Downloader.do.paused or Downloader.do.postproc:
status = Status.PAUSED
elif bytespersec > 0:
status = Status.DOWNLOADING
else:
status = 'Idle'
header['status'] = "%s" % status
header['status'] = status
anfo = ArticleCache.do.cache_info()
@@ -1696,9 +1682,11 @@ def build_history(start=None, limit=None, verbose=False, verbose_list=None, sear
# Aquire the db instance
try:
history_db = cherrypy.thread_data.history_db
close_db = False
except:
# Required for repairs at startup because Cherrypy isn't active yet
history_db = get_history_handle()
close_db = True
# Fetch history items
if not h_limit:
@@ -1764,6 +1752,9 @@ def build_history(start=None, limit=None, verbose=False, verbose_list=None, sear
total_items += full_queue_size
fetched_items = len(items)
if close_db:
history_db.close()
return (items, fetched_items, total_items)
@@ -1955,3 +1946,13 @@ def del_from_section(kwargs):
else:
return False
#------------------------------------------------------------------------------
def history_remove_failed():
""" Remove all failed jobs from history, including files """
logging.info('Scheduled removal of all failed jobs')
history_db = get_history_handle()
del_job_files(history_db.get_failed_paths())
history_db.remove_failed()
history_db.close()
del history_db

View File

@@ -35,7 +35,8 @@ except:
import sabnzbd
from sabnzbd.misc import get_filepath, sanitize_filename, get_unique_path, renamer, \
set_permissions
set_permissions, flag_file
from sabnzbd.constants import QCHECK_FILE
import sabnzbd.cfg as cfg
from sabnzbd.articlecache import ArticleCache
from sabnzbd.postproc import PostProcessor
@@ -60,18 +61,18 @@ class Assembler(Thread):
def stop(self):
self.process(None)
def process(self, nzf):
self.queue.put(nzf)
def process(self, job):
self.queue.put(job)
def run(self):
import sabnzbd.nzbqueue
while 1:
nzo_nzf_tuple = self.queue.get()
if not nzo_nzf_tuple:
job = self.queue.get()
if not job:
logging.info("Shutting down")
break
nzo, nzf = nzo_nzf_tuple
nzo, nzf = job
if nzf:
sabnzbd.CheckFreeSpace()
@@ -87,13 +88,17 @@ class Assembler(Thread):
try:
filepath = _assemble(nzf, filepath, dupe)
except IOError, (errno, strerror):
# 28 == disk full => pause downloader
if errno == 28:
logging.error(Ta('Disk full! Forcing Pause'))
if nzo.deleted:
# Job was deleted, ignore error
pass
else:
logging.error(Ta('Disk error on creating file %s'), latin1(filepath))
# Pause without saving
sabnzbd.downloader.Downloader.do.pause(save=False)
# 28 == disk full => pause downloader
if errno == 28:
logging.error(Ta('Disk full! Forcing Pause'))
else:
logging.error(Ta('Disk error on creating file %s'), latin1(filepath))
# Pause without saving
sabnzbd.downloader.Downloader.do.pause(save=False)
except:
logging.error('Fatal error in Assembler', exc_info = True)
break
@@ -109,6 +114,7 @@ class Assembler(Thread):
if check_encrypted_rar(nzo, filepath):
logging.warning(Ta('WARNING: Paused job "%s" because of encrypted RAR file'), latin1(nzo.final_name))
nzo.pause()
nzf.completed = True
else:
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo.nzo_id, add_to_history=False, cleanup=False)
PostProcessor.do.process(nzo)
@@ -133,7 +139,7 @@ def _assemble(nzf, path, dupe):
decodetable = nzf.decodetable
for articlenum in decodetable:
sleep(0.01)
sleep(0.001)
article = decodetable[articlenum]
data = ArticleCache.do.load_article(article)
@@ -174,7 +180,7 @@ def _assemble(nzf, path, dupe):
fout.flush()
fout.close()
set_permissions(path)
set_permissions(path)
if md5:
nzf.md5sum = md5.digest()
del md5
@@ -200,35 +206,37 @@ def file_has_articles(nzf):
# For a full description of the par2 specification, visit:
# http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html
def GetMD5Hashes(fname):
def GetMD5Hashes(fname, force=False):
""" Get the hash table from a PAR2 file
Return as dictionary, indexed on names and True for utf8-encoded names
"""
new_encoding = False
new_encoding = True
table = {}
try:
f = open(fname, 'rb')
except:
return table
if force or not flag_file(os.path.split(fname)[0], QCHECK_FILE):
try:
f = open(fname, 'rb')
except:
return table, new_encoding
try:
header = f.read(8)
while header:
name, hash = ParseFilePacket(f, header)
new_encoding |= is_utf8(name)
if name:
table[name] = hash
new_encoding = False
try:
header = f.read(8)
while header:
name, hash = ParseFilePacket(f, header)
new_encoding |= is_utf8(name)
if name:
table[name] = hash
header = f.read(8)
except (struct.error, IndexError):
logging.info('Cannot use corrupt par2 file for QuickCheck, "%s"', fname)
table = {}
except:
logging.debug('QuickCheck parser crashed in file %s', fname)
logging.info('Traceback: ', exc_info = True)
table = {}
except (struct.error, IndexError):
logging.info('Cannot use corrupt par2 file for QuickCheck, "%s"', fname)
table = {}
except:
logging.debug('QuickCheck parser crashed in file %s', fname)
logging.info('Traceback: ', exc_info = True)
table = {}
f.close()
f.close()
return table, new_encoding

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
@@ -136,8 +136,28 @@ class BPSMeter(object):
sabnzbd.save_admin(data, BYTES_FILE_NAME)
def defaults(self):
""" Get the latest data from the database and assign to a fake server
"""
logging.debug('Setting default BPS meter values')
import sabnzbd.database
history_db = sabnzbd.database.get_history_handle()
grand, month, week = history_db.get_history_size()
history_db.close()
self.grand_total = {}
self.month_total = {}
self.week_total = {}
self.day_total = {}
if grand: self.grand_total['x'] = grand
if month: self.month_total['x'] = month
if week: self.week_total['x'] = week
self.quota = self.left = cfg.quota_size.get_float()
def read(self):
""" Read admin from disk """
""" Read admin from disk, return True when pause is needed
"""
res = False
quota = self.left = cfg.quota_size.get_float() # Quota for this period
self.have_quota = bool(cfg.quota_size())
data = sabnzbd.load_admin(BYTES_FILE_NAME)
@@ -155,16 +175,14 @@ class BPSMeter(object):
self.quota = self.left = cfg.quota_size.get_float()
res = self.reset_quota()
except:
# Get the latest data from the database and assign to a fake server
logging.debug('Setting default BPS meter values')
grand, month, week = sabnzbd.proxy_get_history_size()
if grand: self.grand_total['x'] = grand
if month: self.month_total['x'] = month
if week: self.week_total['x'] = week
self.quota = self.left = cfg.quota_size.get_float()
res = False
# Force update of counters
self.update()
self.defaults()
# Force update of counters and validate data
try:
for server in self.grand_total:
self.update(server)
except TypeError:
self.defaults()
self.update()
return res
@@ -292,20 +310,21 @@ class BPSMeter(object):
tm = time.localtime(t)
if self.q_period == 'd':
nx = (tm[0], tm[1], tm[2], self.q_hour, self.q_minute, 0, 0, 0, tm[8])
if (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60):
if (tm.tm_hour * 60 + tm.tm_min) >= (self.q_hour * 60 + self.q_minute):
# If today's moment has passed, it will happen tomorrow
t = time.mktime(nx) + 24 * 3600
tm = time.localtime(t)
elif self.q_period == 'w':
if self.q_day < tm.tm_wday+1 or (self.q_day == tm.tm_wday+1 and (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60)):
if self.q_day < tm.tm_wday+1 or (self.q_day == tm.tm_wday+1 and (tm.tm_hour * 60 + tm.tm_min) >= (self.q_hour * 60 + self.q_minute)):
tm = time.localtime(next_week(t))
dif = abs(self.q_day - tm.tm_wday - 1)
t = time.mktime(tm) + dif * 24 * 3600
tm = time.localtime(t)
elif self.q_period == 'm':
if self.q_day < tm.tm_mday or (self.q_day == tm.tm_mday and (tm.tm_hour + tm.tm_min * 60) >= (self.q_hour + self.q_minute * 60)):
if self.q_day < tm.tm_mday or (self.q_day == tm.tm_mday and (tm.tm_hour * 60 + tm.tm_min) >= (self.q_hour * 60 + self.q_minute)):
tm = time.localtime(next_month(t))
tm = (tm[0], tm[1], self.q_day, self.q_hour, self.q_minute, 0, 0, 0, tm[8])
day = min(last_month_day(tm), self.q_day)
tm = (tm[0], tm[1], day, self.q_hour, self.q_minute, 0, 0, 0, tm[8])
else:
return
tm = (tm[0], tm[1], tm[2], self.q_hour, self.q_minute, 0, 0, 0, tm[8])
@@ -330,7 +349,12 @@ class BPSMeter(object):
self.have_quota = bool(cfg.quota_size())
if self.have_quota:
quota = cfg.quota_size.get_float()
self.left = quota - (self.quota - self.left)
if self.quota:
# Quota change, recalculate amount left
self.left = quota - (self.quota - self.left)
else:
# If previously no quota, self.left holds this period's usage
self.left = quota - self.left
self.quota = quota
else:
self.quota = self.left = 0L
@@ -360,8 +384,14 @@ class BPSMeter(object):
if m:
self.q_hour = int(m.group(1))
self.q_minute = int(m.group(2))
self.q_day = max(1, self.q_day)
self.q_day = min(7, self.q_day)
if self.q_period == 'w':
self.q_day = max(1, self.q_day)
self.q_day = min(7, self.q_day)
elif self.q_period == 'm':
self.q_day = max(1, self.q_day)
self.q_day = min(31, self.q_day)
else:
self.q_day = 1
self.change_quota(allow_resume=False)
return quota_handler, self.q_hour, self.q_minute
else:

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, \
@@ -65,7 +65,7 @@ else:
# Configuration instances
#
quick_check = OptionBool('misc', 'quick_check', True)
fail_on_crc = OptionBool('misc', 'fail_on_crc', False)
fail_on_crc = OptionBool('misc', 'fail_on_crc', True)
send_group = OptionBool('misc', 'send_group', False)
sfv_check = OptionBool('misc', 'sfv_check', True)
@@ -79,7 +79,8 @@ email_full = OptionBool('misc', 'email_full', False)
email_dir = OptionDir('misc', 'email_dir', create=True)
email_rss = OptionBool('misc', 'email_rss', False)
version_check = OptionBool('misc', 'check_new_rel', True)
version_check = OptionNumber('misc', 'check_new_rel', 1)
news_items = OptionBool('misc', 'news_items', True)
autobrowser = OptionBool('misc', 'auto_browser', True)
replace_illegal = OptionBool('misc', 'replace_illegal', True)
pre_script = OptionStr('misc', 'pre_script', 'None')
@@ -92,6 +93,7 @@ enable_tsjoin = OptionBool('misc', 'enable_tsjoin', True)
enable_par_cleanup = OptionBool('misc', 'enable_par_cleanup', True)
never_repair = OptionBool('misc', 'never_repair', False)
ignore_unrar_dates = OptionBool('misc', 'ignore_unrar_dates', False)
overwrite_files = OptionBool('misc', 'overwrite_files', False)
par_option = OptionStr('misc', 'par_option', '', validation=no_nonsense)
nice = OptionStr('misc', 'nice', '', validation=no_nonsense)
@@ -124,11 +126,14 @@ auto_sort = OptionBool('misc', 'auto_sort', False)
folder_rename = OptionBool('misc', 'folder_rename', True)
folder_max_length = OptionNumber('misc', 'folder_max_length', DEF_FOLDER_MAX, 20, 65000)
pause_on_pwrar = OptionBool('misc', 'pause_on_pwrar', True)
prio_sort_list = OptionList('misc', 'prio_sort_list')
safe_postproc = OptionBool('misc', 'safe_postproc', True)
empty_postproc = OptionBool('misc', 'empty_postproc', False)
pause_on_post_processing = OptionBool('misc', 'pause_on_post_processing', False)
ampm = OptionBool('misc', 'ampm', False)
rss_filenames = OptionBool('misc', 'rss_filenames', False)
rss_odd_titles = OptionList('misc', 'rss_odd_titles', ['nzbindex.nl/', 'nzbindex.com/', 'nzbclub.com/'])
schedules = OptionList('misc', 'schedlines')
@@ -151,6 +156,8 @@ date_categories = OptionStr('misc', 'date_categories', ['tv'])
matrix_username = OptionStr('nzbmatrix', 'username')
matrix_apikey = OptionStr('nzbmatrix', 'apikey')
matrix_del_bookmark = OptionBool('nzbmatrix', 'del_bookmark', True)
xxx_username = OptionStr('nzbxxx', 'username')
xxx_apikey = OptionStr('nzbxxx', 'apikey')
configlock = OptionBool('misc', 'config_lock', 0)
@@ -203,13 +210,15 @@ log_new = OptionBool('logging', 'log_new', False)
https_cert = OptionDir('misc', 'https_cert', 'server.cert', create=False)
https_key = OptionDir('misc', 'https_key', 'server.key', create=False)
https_chain = OptionDir('misc','https_chain', create=False)
enable_https = OptionBool('misc', 'enable_https', False)
language = OptionStr('misc', 'language', 'en')
ssl_type = OptionStr('misc', 'ssl_type', 'v23')
unpack_check = OptionBool('misc', 'unpack_check', True)
no_penalties = OptionBool('misc', 'no_penalties', False)
random_server_ip = OptionBool('misc', 'random_server_ip', True)
randomize_server_ip = OptionBool('misc', 'randomize_server_ip', False)
ipv6_servers = OptionNumber('misc', 'ipv6_servers', 1, 0, 2)
# Internal options, not saved in INI file
debug_delay = OptionNumber('misc', 'debug_delay', 0, add=False)
@@ -218,14 +227,18 @@ api_key = OptionStr('misc', 'api_key', create_api_key())
nzb_key = OptionStr('misc', 'nzb_key', create_api_key())
disable_key = OptionBool('misc', 'disable_api_key', False)
api_warnings = OptionBool('misc', 'api_warnings', True)
local_range = OptionStr('misc', 'local_range')
max_art_tries = OptionNumber('misc', 'max_art_tries', 3, 2)
max_art_opt = OptionBool('misc', 'max_art_opt', False)
use_pickle = OptionBool('misc', 'use_pickle', False)
no_ipv6 = OptionBool('misc', 'no_ipv6', False)
growl_server = OptionStr('growl', 'growl_server')
growl_password = OptionPassword('growl', 'growl_password')
growl_enable = OptionBool('growl', 'growl_enable', True)
growl_enable = OptionBool('growl', 'growl_enable', not sabnzbd.DARWIN_ML)
ntfosd_enable = OptionBool('growl', 'ntfosd_enable', not sabnzbd.WIN32 and not sabnzbd.DARWIN)
ncenter_enable = OptionBool('growl', 'ncenter_enable', sabnzbd.DARWIN)
notify_classes = OptionList('growl', 'notify_classes', NOTIFY_KEYS)
quota_size = OptionStr('misc', 'quota_size')
quota_day = OptionStr('misc', 'quota_day')
@@ -234,11 +247,15 @@ quota_period = OptionStr('misc', 'quota_period', 'm')
osx_menu = OptionBool('misc', 'osx_menu', True)
osx_speed = OptionBool('misc', 'osx_speed', True)
keep_awake = OptionBool('misc', 'keep_awake', True)
win_menu = OptionBool('misc', 'win_menu', True)
uniconfig = OptionBool('misc', 'uniconfig', True)
allow_incomplete_nzb = OptionBool('misc', 'allow_incomplete_nzb', False)
marker_file = OptionStr('misc', 'nomedia_marker', '')
wait_ext_drive = OptionNumber('misc', 'wait_ext_drive', 5, 1, 60)
history_limit = OptionNumber('misc', 'history_limit', 50, 0)
show_sysload = OptionNumber('misc', 'show_sysload', 2, 0, 2)
web_watchdog = OptionBool('misc', 'web_watchdog', False)
#------------------------------------------------------------------------------
# Set root folders for Folder config-items
@@ -258,3 +275,4 @@ def set_root_folders(home, lcldata):
def set_root_folders2():
https_cert.set_root(admin_dir.get_path())
https_key.set_root(admin_dir.get_path())
https_chain.set_root(admin_dir.get_path())

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)
@@ -224,7 +224,10 @@ class OptionList(Option):
error = None
if value is not None:
if not isinstance(value, list):
value = listquote.simplelist(value)
if '"' not in value and ',' not in value:
value = value.split()
else:
value = listquote.simplelist(value)
if self.__validation:
error, value = self.__validation(value)
if not error:

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
@@ -63,6 +64,8 @@ TERM_FLAG_FILE = 'running.sab'
FUTURE_Q_FOLDER = 'future'
JOB_ADMIN = '__ADMIN__'
VERIFIED_FILE = '__verified__'
QCHECK_FILE = '__skip_qcheck__'
RENAMES_FILE = '__renames__'
ATTRIB_FILE = 'SABnzbd_attrib'
REPAIR_REQUEST = 'repair-all.sab'
@@ -139,7 +142,7 @@ date_match = [r'(\d{4})\W(\d{1,2})\W(\d{1,2})', #2008-10-16
year_match = r'[\W]([1|2]\d{3})([^\w]|$)' # Something '(YYYY)' or '.YYYY.' or ' YYYY '
sample_match = r'((^|[\W_])sample\d*[\W_])|(-s\.)' # something-sample.avi something-s.avi
sample_match = r'((^|[\W_])sample\d*[\W_])|(-s\.\w+$)' # something-sample.avi something-s.avi
class Status():
COMPLETED = 'Completed'
@@ -156,3 +159,5 @@ class Status():
REPAIRING = 'Repairing'
RUNNING = 'Running'
VERIFYING = 'Verifying'
NOTIFY_KEYS = ('startup', 'download', 'pp', 'complete', 'other')

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

@@ -23,6 +23,7 @@ import Queue
import binascii
import logging
import re
from time import sleep
from threading import Thread
try:
import _yenc
@@ -37,6 +38,7 @@ from sabnzbd.articlecache import ArticleCache
import sabnzbd.downloader
import sabnzbd.cfg as cfg
from sabnzbd.encoding import name_fixer
from sabnzbd.misc import match_str
#-------------------------------------------------------------------------------
@@ -71,6 +73,7 @@ class Decoder(Thread):
def run(self):
from sabnzbd.nzbqueue import NzbQueue
while 1:
sleep(0.001)
art_tup = self.queue.get()
if not art_tup:
break
@@ -85,18 +88,19 @@ class Decoder(Thread):
data = None
register = True # Finish article
found = True # Article found (only relevant for precheck)
found = False # Proper article found
if lines:
logme = None
try:
if nzo.precheck and '223' in lines[0]:
raise IndexError
if nzo.precheck:
raise BadYenc
register = True
logging.debug("Decoding %s", article)
data = decode(article, lines)
nzf.article_count += 1
found = True
except IOError, e:
logme = Ta('Decoding %s failed') % article
logging.info(logme)
@@ -120,25 +124,45 @@ class Decoder(Thread):
register = False
logme = None
except BadYenc, e:
logme = Ta('Badly formed yEnc article in %s') % article
logging.info(logme)
except BadYenc:
# Handles precheck and badly formed articles
killed = False
found = False
if nzo.precheck and lines and lines[0].startswith('223 '):
# STAT was used, so we only get a status code
found = True
else:
# Examine headers (for precheck) or body (for download)
# And look for DMCA clues (while skipping "X-" headers)
for line in lines:
if not line.startswith('X-') and match_str(line.lower(), ('dmca', 'removed', 'cancel', 'blocked')):
logging.info('Article removed from server (%s)', article)
killed = True
break
if nzo.precheck:
if found or not killed:
# Pre-check, proper article found, just register
logging.debug('Server has article %s', article)
register = True
elif not killed and not found:
logme = Ta('Badly formed yEnc article in %s') % article
logging.info(logme)
if cfg.fail_on_crc():
if not found:
new_server_found = self.__search_new_server(article)
if new_server_found:
register = False
logme = None
except IndexError:
# Pre-check, article found, just register
register = True
except:
logme = Ta('Unknown Error while decoding %s') % article
logging.info(logme)
logging.info("Traceback: ", exc_info = True)
pass
new_server_found = self.__search_new_server(article)
if new_server_found:
register = False
logme = None
if logme:
article.nzf.nzo.inc_log('bad_art_log', logme)
@@ -211,19 +235,22 @@ def decode(article, data):
#Deal with non-yencoded posts
if not ybegin:
found = False
for i in xrange(10):
if data[i].startswith('begin '):
nzf.filename = name_fixer(data[i].split(None, 2)[2])
nzf.type = 'uu'
found = True
break
if found:
for n in xrange(i+1):
data.pop(0)
if data[-1] == 'end':
data.pop()
if data[-1] == '`':
try:
for i in xrange(10):
if data[i].startswith('begin '):
nzf.filename = name_fixer(data[i].split(None, 2)[2])
nzf.type = 'uu'
found = True
break
if found:
for n in xrange(i+1):
data.pop(0)
if data[-1] == 'end':
data.pop()
if data[-1] == '`':
data.pop()
except IndexError:
raise BadYenc()
decoded_data = '\r\n'.join(data)

View File

@@ -289,7 +289,7 @@ class DirScanner(threading.Thread):
for filename in files:
path = os.path.join(folder, filename)
if os.path.isdir(path) or path in self.ignored:
if os.path.isdir(path) or path in self.ignored or filename[0] == '.':
continue
ext = os.path.splitext(path)[1].lower()

View File

@@ -86,7 +86,7 @@ class Server(object):
def hostip(self):
""" Return a random entry from the possible IPs
"""
if cfg.random_server_ip() and self.info and len(self.info) > 1:
if cfg.randomize_server_ip() and self.info and len(self.info) > 1:
rnd = random.randint(0, len(self.info)-1)
ip = self.info[rnd][4][0]
logging.debug('For server %s, using IP %s' % (self.host, ip))
@@ -531,7 +531,10 @@ class Downloader(Thread):
if server.active:
server.errormsg = Ta('Cannot connect to server %s [%s]') % ('', display_msg)
logging.warning(Ta('Cannot connect to server %s [%s]'), '%s:%s' % (server.host, server.port), msg)
penalty = _PENALTY_502
if clues_pay(msg):
penalty = _PENALTY_PERM
else:
penalty = _PENALTY_502
block = True
else:
# Unknown error, just keep trying
@@ -818,3 +821,13 @@ def clues_too_many_ip(text):
if clue in text:
return True
return False
def clues_pay(text):
""" Check for messages about payments
"""
text = text.lower()
for clue in ('credits', 'paym', 'expired'):
if clue in text:
return True
return False

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

@@ -84,6 +84,12 @@ def check_server(host, port):
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port))
def check_access():
""" Check if external address is allowed """
referrer = cherrypy.request.remote.ip
return referrer in ('127.0.0.1', '::1') or referrer.startswith(cfg.local_range())
def ConvertSpecials(p):
""" Convert None to 'None' and 'Default' to ''
"""
@@ -158,6 +164,8 @@ def set_auth(conf):
def check_session(kwargs):
""" Check session key """
if not check_access():
return u'No access'
key = kwargs.get('session')
if not key:
key = kwargs.get('apikey')
@@ -176,6 +184,10 @@ def check_apikey(kwargs, nokey=False):
""" Check api key or nzbkey
Return None when OK, otherwise an error message
"""
def log_warning(txt):
txt = '%s %s' % (txt, cherrypy.request.headers.get('User-Agent', '??'))
logging.warning('%s', txt)
output = kwargs.get('output')
mode = kwargs.get('mode', '')
callback = kwargs.get('callback')
@@ -188,19 +200,22 @@ def check_apikey(kwargs, nokey=False):
# For NZB upload calls, a separate key can be used
nzbkey = kwargs.get('mode', '') in ('addid', 'addurl', 'addfile', 'addlocalfile')
if not nzbkey and not check_access():
return report(output, 'No access')
# First check APIKEY, if OK that's sufficient
if not (cfg.disable_key() or nokey):
key = kwargs.get('apikey')
if not key:
if not special:
logging.warning(Ta('API Key missing, please enter the api key from Config->General into your 3rd party program:'))
log_warning(Ta('API Key missing, please enter the api key from Config->General into your 3rd party program:'))
return report(output, 'API Key Required', callback=callback)
elif nzbkey and key == cfg.nzb_key():
return None
elif key == cfg.api_key():
return None
else:
logging.warning(Ta('API Key incorrect, Use the api key from Config->General in your 3rd party program:'))
log_warning(Ta('API Key incorrect, Use the api key from Config->General in your 3rd party program:'))
return report(output, 'API Key Incorrect', callback=callback)
# No active APIKEY, check web credentials instead
@@ -209,7 +224,7 @@ def check_apikey(kwargs, nokey=False):
pass
else:
if not special:
logging.warning(Ta('Authentication missing, please enter username/password from Config->General into your 3rd party program:'))
log_warning(Ta('Authentication missing, please enter username/password from Config->General into your 3rd party program:'))
return report(output, 'Missing authentication', callback=callback)
return None
@@ -249,6 +264,8 @@ class MainPage(object):
@cherrypy.expose
def index(self, **kwargs):
if not check_access(): return Protected()
if sabnzbd.OLD_QUEUE and not cfg.warned_old_queue():
cfg.warned_old_queue.set(True)
config.save_config()
@@ -293,6 +310,7 @@ class MainPage(object):
def add_handler(self, kwargs):
if not check_access(): return Protected()
id = kwargs.get('id', '')
if not id:
id = kwargs.get('url', '')
@@ -397,7 +415,9 @@ class MainPage(object):
def api(self, **kwargs):
"""Handler for API over http, with explicit authentication parameters
"""
logging.debug('API-call from %s %s', cherrypy.request.remote.ip, kwargs)
if not kwargs.get('tickleme') or not cfg.web_watchdog():
logging.debug('API-call from %s [%s] %s', cherrypy.request.remote.ip, \
cherrypy.request.headers.get('User-Agent', '??'), kwargs)
if kwargs.get('mode', '') not in ('version', 'auth'):
msg = check_apikey(kwargs)
if msg: return msg
@@ -407,6 +427,7 @@ class MainPage(object):
def scriptlog(self, **kwargs):
""" Duplicate of scriptlog of History, needed for some skins """
# No session key check, due to fixed URLs
if not check_access(): return Protected()
name = kwargs.get('name')
if name:
@@ -458,7 +479,7 @@ class NzoPage(object):
# /nzb/SABnzbd_nzo_xxxxx/files
# /nzb/SABnzbd_nzo_xxxxx/bulk_operation
# /nzb/SABnzbd_nzo_xxxxx/save
if not check_access(): return Protected()
nzo_id = None
for a in args:
if a.startswith('SABnzbd_nzo'):
@@ -629,6 +650,7 @@ class QueuePage(object):
@cherrypy.expose
def index(self, **kwargs):
if not check_access(): return Protected()
start = kwargs.get('start')
limit = kwargs.get('limit')
dummy2 = kwargs.get('dummy2')
@@ -845,6 +867,7 @@ class HistoryPage(object):
@cherrypy.expose
def index(self, **kwargs):
if not check_access(): return Protected()
start = kwargs.get('start')
limit = kwargs.get('limit')
search = kwargs.get('search')
@@ -863,9 +886,11 @@ class HistoryPage(object):
#history_items, total_bytes, bytes_beginning = sabnzbd.history_info()
#history['bytes_beginning'] = "%.2f" % (bytes_beginning / GIGI)
postfix = T('B') #: Abbreviation for bytes, as in GB
grand, month, week, day = BPSMeter.do.get_sums()
history['total_size'], history['month_size'], history['week_size'], history['day_size'] = \
to_units(grand), to_units(month), to_units(week), to_units(day)
to_units(grand, postfix=postfix), to_units(month, postfix=postfix), \
to_units(week, postfix=postfix), to_units(day, postfix=postfix)
history['lines'], history['fetched'], history['noofslots'] = build_history(limit=limit, start=start, verbose=self.__verbose, verbose_list=self.__verbose_list, search=search, failed_only=failed_only)
@@ -963,7 +988,7 @@ class HistoryPage(object):
def scriptlog(self, **kwargs):
""" Duplicate of scriptlog of History, needed for some skins """
# No session key check, due to fixed URLs
if not check_access(): return Protected()
name = kwargs.get('name')
if name:
history_db = cherrypy.thread_data.history_db
@@ -1009,6 +1034,7 @@ class ConfigPage(object):
@cherrypy.expose
def index(self, **kwargs):
if not check_access(): return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
conf['configfn'] = config.get_filename()
@@ -1018,6 +1044,7 @@ class ConfigPage(object):
for svr in config.get_servers():
new[svr] = {}
conf['servers'] = new
conf['news_items'] = cfg.news_items()
conf['folders'] = sabnzbd.nzbqueue.scan_jobs(all=False, action=False)
@@ -1065,12 +1092,14 @@ class ConfigPage(object):
def orphan_delete(kwargs):
path = kwargs.get('name')
if path:
path = platform_encode(path)
path = os.path.join(cfg.download_dir.get_path(), path)
remove_all(path, recursive=True)
def orphan_add(kwargs):
path = kwargs.get('name')
if path:
path = platform_encode(path)
path = os.path.join(cfg.download_dir.get_path(), path)
sabnzbd.nzbqueue.repair_job(path, None)
@@ -1090,7 +1119,7 @@ class ConfigFolders(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -1146,7 +1175,7 @@ class ConfigSwitches(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -1186,14 +1215,18 @@ class ConfigSwitches(object):
SPECIAL_BOOL_LIST = \
( 'start_paused', 'no_penalties', 'ignore_wrong_unrar', 'create_group_folders',
'queue_complete_pers', 'api_warnings', 'allow_64bit_tools', 'par2_multicore',
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames',
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames', 'news_items',
'osx_menu', 'osx_speed', 'win_menu', 'uniconfig', 'use_pickle', 'allow_incomplete_nzb',
'random_server_ip'
'randomize_server_ip', 'no_ipv6', 'keep_awake', 'overwrite_files', 'empty_postproc',
'web_watchdog'
)
SPECIAL_VALUE_LIST = \
( 'size_limit', 'folder_max_length', 'fsys_type', 'movie_rename_limit', 'nomedia_marker',
'req_completion_rate', 'wait_ext_drive'
'req_completion_rate', 'wait_ext_drive', 'history_limit', 'show_sysload', 'ipv6_servers'
)
SPECIAL_LIST_LIST = \
( 'rss_odd_titles', 'prio_sort_list'
)
class ConfigSpecial(object):
def __init__(self, web_dir, root, prim):
@@ -1203,7 +1236,7 @@ class ConfigSpecial(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -1212,6 +1245,7 @@ class ConfigSpecial(object):
conf['switches'] = [ (kw, config.get_config('misc', kw)(), config.get_config('misc', kw).default()) for kw in SPECIAL_BOOL_LIST]
conf['entries'] = [ (kw, config.get_config('misc', kw)(), config.get_config('misc', kw).default()) for kw in SPECIAL_VALUE_LIST]
conf['entries'].extend( [ (kw, config.get_config('misc', kw).get_string(), '') for kw in SPECIAL_LIST_LIST] )
template = Template(file=os.path.join(self.__web_dir, 'config_special.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
@@ -1222,7 +1256,7 @@ class ConfigSpecial(object):
msg = check_session(kwargs)
if msg: return msg
for kw in SPECIAL_BOOL_LIST + SPECIAL_VALUE_LIST:
for kw in SPECIAL_BOOL_LIST + SPECIAL_VALUE_LIST + SPECIAL_LIST_LIST:
item = config.get_config('misc', kw)
value = kwargs.get(kw)
msg = item.set(value)
@@ -1236,8 +1270,8 @@ class ConfigSpecial(object):
#------------------------------------------------------------------------------
GENERAL_LIST = (
'host', 'port', 'username', 'password', 'disable_api_key',
'refresh_rate', 'cache_limit',
'enable_https', 'https_port', 'https_cert', 'https_key'
'refresh_rate', 'cache_limit', 'local_range',
'enable_https', 'https_port', 'https_cert', 'https_key', 'https_chain'
)
class ConfigGeneral(object):
@@ -1271,7 +1305,7 @@ class ConfigGeneral(object):
else:
return ''
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -1335,6 +1369,7 @@ class ConfigGeneral(object):
conf['https_port'] = cfg.https_port()
conf['https_cert'] = cfg.https_cert()
conf['https_key'] = cfg.https_key()
conf['https_chain'] = cfg.https_chain()
conf['enable_https'] = cfg.enable_https()
conf['username'] = cfg.username()
conf['password'] = cfg.password.get_stars()
@@ -1343,6 +1378,8 @@ class ConfigGeneral(object):
conf['cache_limit'] = cfg.cache_limit()
conf['cleanup_list'] = cfg.cleanup_list.get_string()
conf['nzb_key'] = cfg.nzb_key()
conf['local_range'] = cfg.local_range()
conf['my_lcldata'] = cfg.admin_dir.get_path()
template = Template(file=os.path.join(self.__web_dir, 'config_general.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
@@ -1450,7 +1487,7 @@ class ConfigServer(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -1506,6 +1543,18 @@ class ConfigServer(object):
BPSMeter.do.clear_server(server)
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def toggleServer(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
server = kwargs.get('server')
if server:
svr = config.get_config('servers', server)
svr.enable.set(not svr.enable())
config.save_config()
Downloader.do.update_server(server, server)
raise dcRaiser(self.__root, kwargs)
#------------------------------------------------------------------------------
def unique_svr_name(server):
@@ -1597,7 +1646,7 @@ class ConfigRss(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -1616,6 +1665,7 @@ class ConfigRss(object):
rss[feed] = feeds[feed].get_dict()
filters = feeds[feed].filters()
rss[feed]['filters'] = filters
rss[feed]['filter_states'] = [bool(sabnzbd.rss.convert_filter(f[4])) for f in filters]
rss[feed]['filtercount'] = len(filters)
rss[feed]['pick_cat'] = pick_cat
@@ -1872,7 +1922,7 @@ class ConfigRss(object):
#------------------------------------------------------------------------------
_SCHED_ACTIONS = ('resume', 'pause', 'pause_all', 'shutdown', 'restart', 'speedlimit',
'pause_post', 'resume_post', 'scan_folder', 'rss_scan')
'pause_post', 'resume_post', 'scan_folder', 'rss_scan', 'remove_failed')
class ConfigScheduling(object):
def __init__(self, web_dir, root, prim):
@@ -1894,22 +1944,22 @@ class ConfigScheduling(object):
days["7"] = T('Sunday')
return days
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
actions = []
actions.extend(_SCHED_ACTIONS)
days = get_days()
day_names = get_days()
conf['schedlines'] = []
snum = 1
conf['taskinfo'] = []
for ev in scheduler.sort_schedules(forward=True):
for ev in scheduler.sort_schedules(all_events=False):
line = ev[3]
conf['schedlines'].append(line)
try:
m, h, day, action = line.split(' ', 3)
m, h, day_numbers, action = line.split(' ', 3)
except:
continue
action = action.strip()
@@ -1929,7 +1979,17 @@ class ConfigScheduling(object):
act = ''
if act in ('enable_server', 'disable_server'):
action = Ttemplate("sch-" + act) + ' ' + server
item = (snum, '%02d' % int(h), '%02d' % int(m), days.get(day, '**'), '%s %s' % (action, value))
if day_numbers == "1234567":
days_of_week = "Daily"
elif day_numbers == "12345":
days_of_week = "Weekdays"
elif day_numbers == "67":
days_of_week = "Weekends"
else:
days_of_week = ", ".join([day_names.get(i, "**") for i in day_numbers])
item = (snum, '%02d' % int(h), '%02d' % int(m), days_of_week, '%s %s' % (action, value))
conf['taskinfo'].append(item)
snum += 1
@@ -1954,7 +2014,9 @@ class ConfigScheduling(object):
minute = kwargs.get('minute')
hour = kwargs.get('hour')
dayofweek = kwargs.get('dayofweek')
days_of_week = ''.join([str(x) for x in kwargs.get('daysofweek', '')])
if not days_of_week:
days_of_week = '1234567'
action = kwargs.get('action')
arguments = kwargs.get('arguments')
@@ -1964,7 +2026,7 @@ class ConfigScheduling(object):
elif arguments in ('off','disable'):
arguments = '0'
if minute and hour and dayofweek and action:
if minute and hour and days_of_week and action:
if action == 'speedlimit':
if not (arguments and arguments.isdigit()):
action = '0'
@@ -1983,7 +2045,7 @@ class ConfigScheduling(object):
if action:
sched = cfg.schedules()
sched.append('%s %s %s %s %s' %
(minute, hour, dayofweek, action, arguments))
(minute, hour, days_of_week, action, arguments))
cfg.schedules.set(sched)
config.save_config()
@@ -2014,7 +2076,7 @@ class ConfigIndexers(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -2097,7 +2159,7 @@ class ConfigCats(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -2171,7 +2233,7 @@ class ConfigSorting(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -2224,6 +2286,7 @@ class Status(object):
@cherrypy.expose
def index(self, **kwargs):
if not check_access(): return Protected()
header, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
header['logfile'] = sabnzbd.LOGFILE
@@ -2475,10 +2538,15 @@ def GetRssLog(feed):
# Sort in the order the jobs came from the feed
names.sort(lambda x, y: jobs[x].get('order', 0) - jobs[y].get('order', 0))
done = [xml_name(jobs[job]['title']) for job in names if jobs[job]['status'] == 'D']
good = [make_item(jobs[job]) for job in names if jobs[job]['status'][0] == 'G']
bad = [make_item(jobs[job]) for job in names if jobs[job]['status'][0] == 'B']
# Sort in reverse order of time stamp for 'Done'
dnames = [job for job in jobs.keys() if jobs[job]['status'] == 'D']
dnames.sort(lambda x, y: jobs[y].get('timestamp', 0) - jobs[x].get('timestamp', 0))
done = [xml_name(jobs[job]['title']) for job in dnames]
return done, good, bad
def ShowRssLog(feed, all):
@@ -2562,7 +2630,7 @@ LIST_EMAIL = (
'email_server', 'email_to', 'email_from',
'email_account', 'email_pwd', 'email_dir', 'email_rss'
)
LIST_GROWL = ('growl_enable', 'growl_server', 'growl_password', 'ntfosd_enable')
LIST_GROWL = ('growl_enable', 'growl_server', 'growl_password', 'ntfosd_enable', 'ncenter_enable')
class ConfigNotify(object):
def __init__(self, web_dir, root, prim):
@@ -2573,7 +2641,7 @@ class ConfigNotify(object):
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
if cfg.configlock() or not check_access():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim, self.__web_dir)
@@ -2582,11 +2650,15 @@ class ConfigNotify(object):
conf['lastmail'] = self.__lastmail
conf['have_growl'] = True
conf['have_ntfosd'] = sabnzbd.growler.have_ntfosd()
conf['have_ncenter'] = sabnzbd.DARWIN_ML and bool(sabnzbd.growler.ncenter_path())
for kw in LIST_EMAIL:
conf[kw] = config.get_config('misc', kw).get_string()
for kw in LIST_GROWL:
conf[kw] = config.get_config('growl', kw).get_string()
conf['notify_list'] = NOTIFY_KEYS
conf['notify_classes'] = cfg.notify_classes.get_string()
conf['notify_texts'] = sabnzbd.growler.NOTIFICATION
template = Template(file=os.path.join(self.__web_dir, 'config_notify.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
@@ -2605,6 +2677,7 @@ class ConfigNotify(object):
msg = config.get_config('growl', kw).set(platform_encode(kwargs.get(kw)))
if msg:
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)))
cfg.notify_classes.set(kwargs.get('notify_classes', ''))
config.save_config()
self.__lastmail = None

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, Status, MEBI
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.encoding import unicoder, latin1
@@ -259,10 +259,10 @@ def sanitize_foldername(name):
#------------------------------------------------------------------------------
def verified_flag_file(path, create=False):
def flag_file(path, flag, create=False):
""" Create verify flag file or return True if it already exists """
path = os.path.join(path, JOB_ADMIN)
path = os.path.join(path, VERIFIED_FILE)
path = os.path.join(path, flag)
if create:
try:
f = open(path, 'w')
@@ -317,6 +317,8 @@ def real_path(loc, path):
When 'path' is absolute, return normalized path
A path starting with ~ will be located in the user's Home folder
"""
# The Windows part is a bit convoluted because
# os.path.join() doesn't behave the same for all Python versions
if path:
path = path.strip()
else:
@@ -325,9 +327,15 @@ def real_path(loc, path):
if not sabnzbd.WIN32 and path.startswith('~/'):
path = path.replace('~', sabnzbd.DIR_HOME, 1)
if sabnzbd.WIN32:
if path[0].isalpha() and len(path) > 1 and path[1] == ':':
if len(path) == 2 or path[2] not in '\\/':
path = path.replace('/', '\\')
if len(path) > 1 and path[0].isalpha() and path[1] == ':':
if len(path) == 2 or path[2] != '\\':
path = path.replace(':', ':\\', 1)
elif path.startswith('\\\\'):
pass
elif path.startswith('\\'):
if len(loc) > 1 and loc[0].isalpha() and loc[1] == ':':
path = loc[:2] + path
else:
path = os.path.join(loc, path)
elif path[0] != '/':
@@ -582,11 +590,20 @@ def check_latest_version():
logging.debug("Checked for a new release, cur= %s, latest= %s (on %s)", current, latest, url)
if latest_test and cfg.version_check() > 1:
# User always wants to see the latest test release
latest = latest_test
url = url_beta
if testver and current < latest:
# This is a test version, but user has't seen the
# "Final" of this one yet, so show the Final
sabnzbd.NEW_VERSION = "%s;%s" % (latest_label, url)
elif current < latest:
# This one is behind, show latest final
sabnzbd.NEW_VERSION = "%s;%s" % (latest_label, url)
elif testver and current < latest_test:
# This is a test version beyond the latest Final, so show latest Alpha/Beta/RC
sabnzbd.NEW_VERSION = "%s;%s" % (latest_testlabel, url_beta)
@@ -614,7 +631,7 @@ def from_units(val):
else:
return 0.0
def to_units(val, spaces=0, dec_limit=2):
def to_units(val, spaces=0, dec_limit=2, postfix=''):
""" Convert number to K/M/G/T/P notation
Add "spaces" if not ending in letter
dig_limit==1 show single decimal for M and higher
@@ -643,8 +660,8 @@ def to_units(val, spaces=0, dec_limit=2):
else:
decimals = 0
format = '%%s%%.%sf %%s' % decimals
return format % (sign, val, unit)
format = '%%s%%.%sf %%s%%s' % decimals
return format % (sign, val, unit, postfix)
#------------------------------------------------------------------------------
def same_file(a, b):
@@ -699,17 +716,6 @@ def split_host(srv):
return (host, port)
#------------------------------------------------------------------------------
def hostname():
""" Return host's pretty name """
if sabnzbd.WIN32:
return os.environ.get('computername', 'unknown')
try:
return os.uname()[1]
except:
return 'unknown'
#------------------------------------------------------------------------------
def check_mount(path):
""" Return False if volume isn't mounted on Linux or OSX
@@ -780,13 +786,23 @@ def create_dirs(dirpath):
@synchronized(DIR_LOCK)
def move_to_path(path, new_path, unique=True):
""" Move a file to a new path, optionally give unique filename """
if unique:
new_path = get_unique_path(new_path, create_dir=False)
def move_to_path(path, new_path):
""" Move a file to a new path, optionally give unique filename
Return (ok, new_path)
"""
ok = True
overwrite = cfg.overwrite_files()
if overwrite and os.path.exists(new_path):
try:
os.remove(new_path)
except:
overwrite = False
if not overwrite:
new_path = get_unique_filename(new_path)
if new_path:
logging.debug("Moving. Old path:%s new path:%s unique?:%s",
path,new_path, unique)
logging.debug("Moving. Old path:%s new path:%s overwrite?:%s",
path, new_path, overwrite)
try:
# First try cheap rename
renamer(path, new_path)
@@ -801,8 +817,8 @@ def move_to_path(path, new_path, unique=True):
if not (cfg.marker_file() and cfg.marker_file() in path):
logging.error(Ta('Failed moving %s to %s'), path, new_path)
logging.info("Traceback: ", exc_info = True)
new_path = None
return new_path
ok = False
return ok, new_path
@synchronized(DIR_LOCK)
@@ -821,6 +837,10 @@ def cleanup_empty_directories(path):
pass
if not repeat:
break
try:
remove_dir(path)
except:
pass
@synchronized(DIR_LOCK)
@@ -939,8 +959,9 @@ def bad_fetch(nzo, url, msg='', retry=False, content=False):
if isinstance(url, int) or url.isdigit():
url = 'Newzbin #%s' % url
growler.send_notification(T('URL Fetching failed; %s') % '', '%s\n%s' % (msg, url), 'other')
#import sabnzbd.emailer
sabnzbd.emailer.badfetch_mail(msg, url)
if cfg.email_endjob() > 0:
#import sabnzbd.emailer
sabnzbd.emailer.badfetch_mail(msg, url)
from sabnzbd.nzbqueue import NzbQueue
assert isinstance(NzbQueue.do, NzbQueue)
@@ -978,15 +999,40 @@ def get_filename(path):
except:
return ''
def memory_usage():
try:
# Probably only works on Linux because it uses /proc/<pid>/statm
t = open('/proc/%d/statm' % os.getpid())
v = t.read().split()
t.close()
virt = int(_PAGE_SIZE * int(v[0]) / MEBI)
res = int(_PAGE_SIZE * int(v[1]) / MEBI)
return "V=%sM R=%sM" % (virt, res)
except:
return ''
try:
_PAGE_SIZE = os.sysconf("SC_PAGE_SIZE")
except:
_PAGE_SIZE = 0
_HAVE_STATM = _PAGE_SIZE and memory_usage()
def loadavg():
""" Return 1, 5 and 15 minute load average of host or "" if not supported
"""
if sabnzbd.WIN32 or sabnzbd.DARWIN:
return ""
try:
return "%.2f | %.2f | %.2f" % os.getloadavg()
except:
return ""
p = ''
if not sabnzbd.WIN32 and not sabnzbd.DARWIN:
opt = cfg.show_sysload()
if opt:
try:
p = '%.2f | %.2f | %.2f' % os.getloadavg()
except:
pass
if opt > 1 and _HAVE_STATM:
p = '%s | %s' % (p, memory_usage())
return p
def format_time_string(seconds, days=0):
@@ -1035,7 +1081,11 @@ def int_conv(value):
# Diskfree
if sabnzbd.WIN32:
# windows diskfree
import win32api
try:
# Careful here, because win32api test hasn't been done yet!
import win32api
except:
pass
def diskfree(_dir):
""" Return amount of free diskspace in GBytes
"""

View File

@@ -31,10 +31,12 @@ import sabnzbd
from sabnzbd.encoding import TRANS, UNTRANS, unicode2local, name_fixer, \
reliable_unpack_names, unicoder, latin1, platform_encode
from sabnzbd.utils.rarfile import RarFile, is_rarfile
from sabnzbd.misc import format_time_string, find_on_path, make_script_path, int_conv
from sabnzbd.misc import format_time_string, find_on_path, make_script_path, int_conv, \
flag_file, real_path
from sabnzbd.tvsort import SeriesSorter
import sabnzbd.cfg as cfg
from constants import Status
from sabnzbd.constants import Status, QCHECK_FILE, RENAMES_FILE
load_data = save_data = None
if sabnzbd.WIN32:
try:
@@ -77,6 +79,7 @@ CURL_COMMAND = None
def find_programs(curdir):
"""Find external programs
"""
global load_data, save_data
def check(path, program):
p = os.path.abspath(os.path.join(path, program))
if os.access(p, os.X_OK):
@@ -84,6 +87,10 @@ def find_programs(curdir):
else:
return None
# Another crazy Python import bug work-around
load_data = sabnzbd.load_data
save_data = sabnzbd.save_data
if sabnzbd.DARWIN:
try:
os_version = subprocess.Popen("sw_vers -productVersion", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.read()
@@ -124,7 +131,7 @@ def find_programs(curdir):
def external_processing(extern_proc, complete_dir, filename, msgid, nicename, cat, group, status):
""" Run a user postproc script, return console output and exit value
"""
command = [str(extern_proc), str(complete_dir), str(filename), \
command = [str(extern_proc), str(complete_dir), str(filename),
str(nicename), str(msgid), str(cat), str(group), str(status)]
if extern_proc.endswith('.py') and (sabnzbd.WIN32 or not os.access(extern_proc, os.X_OK)):
@@ -132,7 +139,7 @@ def external_processing(extern_proc, complete_dir, filename, msgid, nicename, ca
stup, need_shell, command, creationflags = build_command(command)
env = fix_env()
logging.info('Running external script %s(%s, %s, %s, %s, %s, %s, %s)', \
logging.info('Running external script %s(%s, %s, %s, %s, %s, %s, %s)',
extern_proc, complete_dir, filename, nicename, msgid, cat, group, status)
try:
@@ -257,8 +264,8 @@ def match_ts(file):
def clean_up_joinables(names):
''' Remove joinable files and their .1 backups
'''
""" Remove joinable files and their .1 backups
"""
for name in names:
if os.path.exists(name):
logging.debug("Deleting %s", name)
@@ -275,8 +282,8 @@ def clean_up_joinables(names):
pass
def get_seq_number(name):
''' Return sequence number if name as an int
'''
""" Return sequence number if name as an int
"""
head, tail = os.path.splitext(name)
if tail == '.ts':
match, set, num = match_ts(name)
@@ -296,7 +303,8 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
# Create matching sets from the list of files
joinable_sets = {}
set = match = num = None
joinable_set = None
set = num = None
for joinable in joinables:
head, tail = os.path.splitext(joinable)
if tail == '.ts':
@@ -353,7 +361,7 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
# Finish up
joined_file.flush()
joined_file.close()
newfiles.append(joinable_set)
newfiles.append(filename)
if seq_error:
msg = T('Incomplete sequence of joinable files')
@@ -383,6 +391,7 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
When 'one_folder' is set, all files will be in a single folder
"""
extracted_files = []
success = False
rar_sets = {}
for rar in rars:
@@ -437,7 +446,7 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
except OSError:
logging.warning(Ta('Deleting %s failed!'), latin1(rar))
brokenrar = '%s.1' % (rar)
brokenrar = '%s.1' % rar
if os.path.exists(brokenrar):
logging.info("Deleting %s", brokenrar)
@@ -456,6 +465,8 @@ def rar_extract(rarfile, numrars, one_folder, nzo, setname, extraction_path):
"""
fail = 0
new_files = None
rars = []
if nzo.password:
passwords = [nzo.password]
else:
@@ -523,17 +534,23 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
action = 'e'
else:
action = 'x'
if cfg.overwrite_files():
overwrite = '-o+' # Enable overwrite
rename = '-o+' # Dummy
else:
overwrite = '-o-' # Disable overwrite
rename = '-or' # Auto renaming
if sabnzbd.WIN32:
# Use all flags
command = ['%s' % RAR_COMMAND, action, '-idp', '-o-', '-or', '-ai', password,
command = ['%s' % RAR_COMMAND, action, '-idp', overwrite, rename, '-ai', password,
'%s' % rarfile, '%s/' % extraction_path]
elif RAR_PROBLEM:
# Use only oldest options (specifically no "-or")
command = ['%s' % RAR_COMMAND, action, '-idp', '-o-', password,
command = ['%s' % RAR_COMMAND, action, '-idp', overwrite, password,
'%s' % rarfile, '%s/' % extraction_path]
else:
# Don't use "-ai" (not needed for non-Windows)
command = ['%s' % RAR_COMMAND, action, '-idp', '-o-', '-or', password,
command = ['%s' % RAR_COMMAND, action, '-idp', overwrite, rename, password,
'%s' % rarfile, '%s/' % extraction_path]
if cfg.ignore_unrar_dates():
@@ -550,7 +567,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
if p.stdin:
p.stdin.close()
nzo.set_action_line(T('Unpacking'), '00/%02d' % (numrars))
nzo.set_action_line(T('Unpacking'), '00/%02d' % numrars)
# Loop over the output from rar!
curr = 0
@@ -620,14 +637,14 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
else:
m = re.search(r'^(Extracting|Creating|...)\s+(.*?)\s+OK\s*$', line)
if m:
extracted.append(TRANS(m.group(2)))
extracted.append(real_path(extraction_path, TRANS(m.group(2))))
if fail:
if proc:
proc.close()
p.wait()
return (fail, (), ())
return fail, (), ()
if proc:
proc.close()
@@ -669,7 +686,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
nzo.set_unpack_info('Unpack', '[%s] %s' % (unicoder(setname), msg), set=setname)
logging.info('%s', msg)
return (0, extracted, rarfiles)
return 0, extracted, rarfiles
#------------------------------------------------------------------------------
# (Un)Zip Functions
@@ -714,7 +731,7 @@ def unzip(nzo, workdir, workdir_complete, delete, one_folder, zips):
except OSError:
logging.warning(Ta('Deleting %s failed!'), latin1(_zip))
brokenzip = '%s.1' % (_zip)
brokenzip = '%s.1' % _zip
if os.path.exists(brokenzip):
logging.info("Deleting %s", brokenzip)
@@ -779,6 +796,7 @@ def par2_repair(parfile_nzf, nzo, workdir, setname):
result = True
if not result:
flag_file(workdir, QCHECK_FILE, True)
nzo.status = Status.REPAIRING
result = False
readd = False
@@ -876,6 +894,8 @@ def par2_repair(parfile_nzf, nzo, workdir, setname):
_RE_BLOCK_FOUND = re.compile('File: "([^"]+)" - found \d+ of \d+ data blocks from "([^"]+)"')
_RE_IS_MATCH_FOR = re.compile('File: "([^"]+)" - is a match for "([^"]+)"')
def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
""" Run par2 on par-set """
if cfg.never_repair():
@@ -892,7 +912,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
logging.debug('Par2-classic = %s', classic)
import sabnzbd.assembler
if (sabnzbd.assembler.GetMD5Hashes(parfile)[1] and not classic) or not PAR2C_COMMAND:
if (sabnzbd.assembler.GetMD5Hashes(parfile, True)[1] and not classic) or not PAR2C_COMMAND:
if cfg.par_option():
command = [str(PAR2_COMMAND), cmd, str(cfg.par_option().strip()), parfile]
else:
@@ -906,9 +926,13 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
if setname in joinable:
command.append(joinable)
# Append the wildcard for this set
command.append('%s*' % os.path.join(os.path.split(parfile)[0], setname))
stup, need_shell, command, creationflags = build_command(command)
logging.debug('Starting par2: %s', command)
lines = []
try:
p = subprocess.Popen(command, shell=need_shell, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
@@ -922,6 +946,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
# Set up our variables
pars = []
datafiles = []
renames = {}
linebuf = ''
finished = 0
@@ -949,7 +974,9 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
if line == '':
continue
# And off we go
if 'Repairing:' not in line:
lines.append(line)
if line.startswith('Invalid option specified'):
msg = T('[%s] PAR2 received incorrect options, check your Config->Switches settings') % unicoder(setname)
nzo.set_unpack_info('Repair', msg, set=setname)
@@ -970,8 +997,8 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
start = time()
verified = 1
elif line.startswith('Main packet not found'):
## Initialparfile probaly didn't decode properly,
elif line.startswith('Main packet not found') or 'The recovery file does not exist' in line:
## Initialparfile probably didn't decode properly,
logging.info(Ta('Main packet not found...'))
extrapars = parfile_nzf.extrapars
@@ -981,14 +1008,21 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
## Look for the smallest par2file
block_table = {}
for nzf in extrapars:
block_table[int_conv(nzf.blocks)] = nzf
if not nzf.completed:
block_table[int_conv(nzf.blocks)] = nzf
if block_table:
nzf = block_table[min(block_table.keys())]
logging.info("Found new par2file %s", nzf.filename)
## Move from extrapar list to files to be downloaded
nzo.add_parfile(nzf)
extrapars.remove(nzf)
## Now set new par2 file as primary par2
nzo.partable[setname] = nzf
nzf.extrapars= extrapars
parfile_nzf = []
## mark for readd
readd = True
else:
@@ -1050,7 +1084,9 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
extrapar_list = block_table[block_size]
if extrapar_list:
nzo.add_parfile(extrapar_list.pop())
new_nzf = extrapar_list.pop()
nzo.add_parfile(new_nzf)
if new_nzf in extrapars: extrapars.remove(new_nzf)
added_blocks += block_size
else:
@@ -1079,7 +1115,7 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
elif line.startswith('Repairing:'):
chunks = line.split()
per = float(chunks[-1][:-1])
nzo.set_action_line(T('Repairing'), '%2d%%' % (per))
nzo.set_action_line(T('Repairing'), '%2d%%' % per)
nzo.status = Status.REPAIRING
elif line.startswith('Repair complete'):
@@ -1105,6 +1141,26 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
# Hit a bug in par2-tbb, retry with par2-classic
retry_classic = True
elif ' cannot be renamed to ' in line:
if not classic and sabnzbd.WIN32:
# Hit a bug in par2-tbb, retry with par2-classic
retry_classic = True
else:
msg = unicoder(line.strip())
nzo.fail_msg = msg
msg = u'[%s] %s' % (unicoder(setname), msg)
nzo.set_unpack_info('Repair', msg, set=setname)
nzo.status = Status.FAILED
# File: "oldname.rar" - is a match for "newname.rar".
elif 'is a match for' in line:
m = _RE_IS_MATCH_FOR.search(line)
if m:
old_name = m.group(1)
new_name = m.group(2)
logging.debug('PAR2 will rename "%s" to "%s"', old_name, new_name)
renames[new_name] = old_name
elif not verified:
if line.startswith('Verifying source files'):
nzo.set_action_line(T('Verifying'), '01/%02d' % verifytotal)
@@ -1143,11 +1199,20 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
else:
raise WindowsError(err)
logging.debug('PAR2 output was\n%s', '\n'.join(lines))
# If successful, add renamed files to the collection
if finished and renames:
previous = load_data(RENAMES_FILE, nzo.workpath, remove=False)
for name in previous or {}:
renames[name] = previous[name]
save_data(renames, RENAMES_FILE, nzo.workpath)
if retry_classic:
logging.debug('Retry PAR2-joining with par2-classic')
return PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=True)
else:
return (finished, readd, pars, datafiles, used_joinables)
return finished, readd, pars, datafiles, used_joinables
#-------------------------------------------------------------------------------
@@ -1198,7 +1263,7 @@ def build_command(command):
if need_shell:
command = list2cmdline(command)
return (stup, need_shell, command, creationflags)
return stup, need_shell, command, creationflags
# Sort the various RAR filename formats properly :\
def rar_sort(a, b):
@@ -1254,7 +1319,10 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
zips = [f for f in filelist if ZIP_RE.search(f)]
rars = [f for f in filelist if RAR_RE.search(f) and is_rarfile(f)]
if check_rar:
rars = [f for f in filelist if RAR_RE.search(f) and is_rarfile(f)]
else:
rars = [f for f in filelist if RAR_RE.search(f)]
ts = [f for f in filelist if TS_RE.search(f) and f not in joinables]
@@ -1263,7 +1331,7 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
logging.debug("build_filelists(): rars: %s", rars)
logging.debug("build_filelists(): ts: %s", ts)
return (joinables, zips, rars, ts)
return joinables, zips, rars, ts
def QuickCheck(set, nzo):
@@ -1277,13 +1345,12 @@ def QuickCheck(set, nzo):
nzf_list = nzo.finished_files
for file in md5pack:
file = name_fixer(file)
if sabnzbd.misc.on_cleanup_list(file, False):
result = True
continue
found = False
for nzf in nzf_list:
if file == name_fixer(nzf.filename):
if file == nzf.filename:
found = True
if (nzf.md5sum is not None) and nzf.md5sum == md5pack[file]:
logging.debug('Quick-check of file %s OK', file)
@@ -1334,32 +1401,35 @@ def unrar_check(rar):
def sfv_check(sfv_path):
""" Verify files using SFV file,
input: full path of sfv, file are assumed to be relative to sfv
returns: True when all files verified OK
returns: List of failing files or [] when all is OK
"""
failed = []
try:
fp = open(sfv_path, 'r')
except:
logging.info('Cannot open SFV file %s', sfv_path)
return False
failed.append(unicoder(sfv_path))
return failed
root = os.path.split(sfv_path)[0]
status = True
for line in fp:
line = line.strip('\n\r ')
if line[0] != ';':
if line and line[0] != ';':
x = line.rfind(' ')
filename = line[:x].strip()
checksum = line[x:].strip()
path = os.path.join(root, platform_encode(filename))
if os.path.exists(path):
if crc_check(path, checksum):
logging.debug('File %s passed SFV check', path)
if x > 0:
filename = platform_encode(line[:x].strip())
checksum = line[x:].strip()
path = os.path.join(root, filename)
if os.path.exists(path):
if crc_check(path, checksum):
logging.debug('File %s passed SFV check', path)
else:
logging.info('File %s did not pass SFV check', latin1(path))
failed.append(unicoder(filename))
else:
logging.warning('File %s did not pass SFV check', latin1(path))
status = False
else:
logging.warning('File %s mssing in SFV check', latin1(path))
logging.info('File %s missing in SFV check', latin1(path))
failed.append(unicoder(filename))
fp.close()
return status
return failed
def crc_check(path, target_crc):
@@ -1384,6 +1454,7 @@ def crc_check(path, target_crc):
def analyse_show(name):
""" Do a quick SeasonSort check and return basic facts """
job = SeriesSorter(name, None, None)
job.match(force=True)
if job.is_match():
job.get_values()
info = job.show_info
@@ -1455,7 +1526,10 @@ def list2cmdline(lst):
#------------------------------------------------------------------------------
# Work-around for the failure of Python2.5 on Windows to support IPV6 with HTTPS
def get_from_url(url):
def get_from_url(url, timeout=None):
""" Retrieve URL and return content
`timeout` sets non-standard timeout and skips when on Windows
"""
if 'https:' in url and sabnzbd.WIN32 and sys.version_info < (2,6) and sabnzbd.newsunpack.CURL_COMMAND:
command = [sabnzbd.newsunpack.CURL_COMMAND, "-k", url]
stup, need_shell, command, creationflags = build_command(command)
@@ -1467,6 +1541,12 @@ def get_from_url(url):
p.wait()
else:
import urllib2
s = urllib2.urlopen(url)
output = s.read()
try:
if timeout:
s = urllib2.urlopen(url, timeout=timeout)
else:
s = urllib2.urlopen(url)
output = s.read()
except:
output = None
return output

View File

@@ -28,6 +28,7 @@ import logging
import sabnzbd
from sabnzbd.constants import *
import sabnzbd.cfg
try:
from OpenSSL import SSL
@@ -84,23 +85,32 @@ def request_server_info(server):
def GetServerParms(host, port):
# Make sure port is numeric (unicode input not supported)
""" Return processed getaddrinfo() for server
"""
try:
int(port)
except:
# Could do with a warning here
port = 119
opt = sabnzbd.cfg.ipv6_servers()
try:
# Standard IPV4
return socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)
# Standard IPV4 or IPV6
ips = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)
if opt == 2 or (_EXTERNAL_IPV6 and opt == 1):
# IPv6 reachable and allowed, or forced by user
return ips
else:
# IPv6 unreachable or not allowed by user
return [ip for ip in ips if ':' not in ip[4][0]]
except:
try:
# Try IPV6 explicitly
return socket.getaddrinfo(host, port, socket.AF_INET6,
socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME)
except:
# Nothing found!
return None
if opt == 2 or (_EXTERNAL_IPV6 and opt == 1):
try:
# Try IPV6 explicitly
return socket.getaddrinfo(host, port, socket.AF_INET6,
socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME)
except:
# Nothing found!
pass
return None
def con(sock, host, port, sslenabled, write_fds, nntp):
@@ -415,3 +425,25 @@ class SSLConnection(object):
return apply(self._ssl_conn.%s, args)
finally:
self._lock.release()\n""" % (f, f)
def test_ipv6():
""" Check if external IPv6 addresses are reachable """
# Use google.com to test IPv6 access
try:
info = socket.getaddrinfo('www.google.com', 80, socket.AF_INET6, socket.SOCK_STREAM,
socket.IPPROTO_IP, socket.AI_CANONNAME)
except socket.gaierror:
return False
try:
af, socktype, proto, canonname, sa = info[0]
sock = socket.socket(af, socktype, proto)
sock.settimeout(4)
sock.connect(sa[0:2])
sock.close()
return True
except socket.error:
return False
_EXTERNAL_IPV6 = test_ipv6()

View File

@@ -27,14 +27,14 @@ import datetime
import sabnzbd
from sabnzbd.trylist import TryList
from sabnzbd.nzbstuff import NzbObject
from sabnzbd.misc import exit_sab, cat_to_opts, verified_flag_file, \
from sabnzbd.misc import exit_sab, cat_to_opts, \
get_admin_path, remove_all, globber
from sabnzbd.panic import panic_queue
import sabnzbd.database as database
from sabnzbd.decorators import NZBQUEUE_LOCK, synchronized, synchronized_CV
from sabnzbd.constants import QUEUE_FILE_NAME, QUEUE_VERSION, FUTURE_Q_FOLDER, JOB_ADMIN, \
LOW_PRIORITY, NORMAL_PRIORITY, HIGH_PRIORITY, TOP_PRIORITY, \
REPAIR_PRIORITY, STOP_PRIORITY, \
REPAIR_PRIORITY, STOP_PRIORITY, VERIFIED_FILE, \
PNFO_BYTES_FIELD, PNFO_BYTES_LEFT_FIELD, Status
import sabnzbd.cfg as cfg
from sabnzbd.articlecache import ArticleCache
@@ -147,13 +147,21 @@ class NzbQueue(TryList):
def repair_job(self, folder, new_nzb=None):
""" Reconstruct admin for a single job folder, optionally with new NZB """
""" Reconstruct admin for a single job folder, optionally with new NZB
"""
def all_verified(path):
""" Return True when all sets have been successfully verified """
verified = sabnzbd.load_data(VERIFIED_FILE, path, remove=False) or {'x':False}
return not bool([True for x in verified if not verified[x]])
name = os.path.basename(folder)
path = os.path.join(folder, JOB_ADMIN)
if new_nzb is None or not new_nzb.filename:
if verified_flag_file(folder):
filename = ''
else:
if hasattr(new_nzb, 'filename'):
filename = new_nzb.filename
else:
filename = ''
if not filename:
if not all_verified(path):
filename = globber(path, '*.gz')
if len(filename) > 0:
logging.debug('Repair job %s by reparsing stored NZB', latin1(name))
@@ -164,10 +172,39 @@ class NzbQueue(TryList):
self.add(nzo)
else:
remove_all(path, '*.gz')
logging.debug('Repair job %s with new NZB (%s)', latin1(name), latin1(new_nzb.filename))
logging.debug('Repair job %s with new NZB (%s)', latin1(name), latin1(filename))
sabnzbd.add_nzbfile(new_nzb, pp=None, script=None, cat=None, priority=None, nzbname=name, reuse=True)
def send_back(self, nzo):
""" Send back job to queue after successful pre-check """
try:
nzb_path = globber(nzo.workpath, '*.gz')[0]
except:
logging.debug('Failed to find NZB file after pre-check (%s)', nzo.nzo_id)
return
from sabnzbd.dirscanner import ProcessSingleFile
res, nzo_ids = ProcessSingleFile(nzo.work_name + '.nzb', nzb_path, reuse=True)
if res == 0 and nzo_ids:
self.replace_in_q(nzo, nzo_ids[0])
@synchronized(NZBQUEUE_LOCK)
def replace_in_q(self, nzo, nzo_id):
""" Replace nzo by new in at the same spot in the queue, destroy nzo """
try:
new_nzo = self.get_nzo(nzo_id)
pos = self.__nzo_list.index(new_nzo)
targetpos = self.__nzo_list.index(nzo)
self.__nzo_list[targetpos] = new_nzo
self.__nzo_list.pop(pos)
del self.__nzo_table[nzo.nzo_id]
del nzo
except:
logging.error('Failed to restart NZB after pre-check (%s)', nzo.nzo_id)
logging.info("Traceback: ", exc_info = True)
return
@synchronized(NZBQUEUE_LOCK)
def save(self, save_nzo=None):
""" Save queue, all nzo's or just the specified one """
@@ -295,8 +332,7 @@ class NzbQueue(TryList):
# If no files are to be downloaded anymore, send to postproc
if not nzo.files and not nzo.futuretype:
sabnzbd.remove_data(nzo.nzo_id, nzo.workpath)
sabnzbd.proxy_postproc(nzo)
self.end_job(nzo)
return ''
# Reset try_lists
@@ -397,10 +433,10 @@ class NzbQueue(TryList):
if nzf:
post_done = nzo.remove_nzf(nzf)
if post_done:
keep_basic = nzo.finished_files
if keep_basic:
sabnzbd.proxy_postproc(nzo)
self.remove(nzo_id, add_to_history = False, keep_basic=keep_basic)
if nzo.finished_files:
self.end_job(nzo)
else:
self.remove(nzo_id, add_to_history = False, keep_basic=False)
@synchronized(NZBQUEUE_LOCK)
@@ -673,7 +709,7 @@ class NzbQueue(TryList):
nzf = article.nzf
nzo = nzf.nzo
if nzo.deleted or nzf.deleted:
if nzf.deleted:
logging.debug("Discarding article %s, no longer in queue", article.article)
return
@@ -712,10 +748,26 @@ class NzbQueue(TryList):
"""
if self.actives(grabs=False) < 2 and cfg.autodisconnect():
# This was the last job, close server connections
sabnzbd.downloader.Downloader.do.disconnect()
if sabnzbd.downloader.Downloader.do:
sabnzbd.downloader.Downloader.do.disconnect()
# Notify assembler to call postprocessor
Assembler.do.process((nzo, None))
if not nzo.deleted:
nzo.deleted = True
if nzo.precheck:
nzo.save_attribs()
# Check result
enough, ratio = nzo.check_quality()
if enough:
# Enough data present, do real download
workdir = nzo.downpath
self.cleanup_nzo(nzo, keep_basic=True)
self.send_back(nzo)
return
else:
# Not enough data, let postprocessor show it as failed
pass
Assembler.do.process((nzo, None))
@synchronized(NZBQUEUE_LOCK)
@@ -736,18 +788,24 @@ class NzbQueue(TryList):
def queue_info(self, for_cli=False, max_jobs=0):
bytes_left = 0
bytes = 0
q_size = 0
pnfo_list = []
n = 0
for nzo in self.__nzo_list:
pnfo = nzo.gather_info(for_cli = for_cli)
if nzo.status != 'Paused':
bytes += pnfo[PNFO_BYTES_FIELD]
bytes_left += pnfo[PNFO_BYTES_LEFT_FIELD]
pnfo_list.append(pnfo)
if not max_jobs or n < max_jobs:
pnfo = nzo.gather_info(for_cli = for_cli)
pnfo_list.append(pnfo)
if nzo.status != 'Paused':
bytes += pnfo[PNFO_BYTES_FIELD]
bytes_left += pnfo[PNFO_BYTES_LEFT_FIELD]
q_size += 1
elif nzo.status != 'Paused':
b, b_left = nzo.total_and_remaining()
bytes += b
bytes_left += b_left
q_size += 1
n += 1
if max_jobs and n >= max_jobs:
break
return (bytes, bytes_left, pnfo_list)
return (bytes, bytes_left, pnfo_list, q_size)
@synchronized(NZBQUEUE_LOCK)
@@ -776,6 +834,17 @@ class NzbQueue(TryList):
ArticleCache.do.purge_articles(nzo.saved_articles)
@synchronized(NZBQUEUE_LOCK)
def stop_idle_jobs(self):
""" Detect jobs that have zero files left and send them to post processing
"""
empty = []
for nzo in self.__nzo_list:
if not nzo.futuretype and not nzo.files and nzo.status not in (Status.PAUSED, Status.GRABBING):
empty.append(nzo)
for nzo in empty:
self.end_job(nzo)
def get_urls(self):
""" Return list of future-types needing URL """
lst = []

View File

@@ -37,7 +37,7 @@ import sabnzbd
from sabnzbd.constants import sample_match, GIGI, ATTRIB_FILE, JOB_ADMIN, \
DEFAULT_PRIORITY, LOW_PRIORITY, NORMAL_PRIORITY, \
HIGH_PRIORITY, PAUSED_PRIORITY, TOP_PRIORITY, DUP_PRIORITY, \
Status
RENAMES_FILE, Status
from sabnzbd.misc import to_units, cat_to_opts, cat_convert, sanitize_foldername, \
get_unique_path, get_admin_path, remove_all, format_source_url, \
sanitize_filename, globber, sanitize_foldername, int_conv, \
@@ -54,7 +54,8 @@ RE_NORMAL = re.compile(r"(.+)(\.nzb)", re.I)
SUBJECT_FN_MATCHER = re.compile(r'"([^"]*)"')
RE_SAMPLE = re.compile(sample_match, re.I)
PROBABLY_PAR2_RE = re.compile(r'(.*)\.vol(\d*)\+(\d*)\.par2', re.I)
REJECT_PAR2_RE = re.compile(r'\.par2\.\d+', re.I) # Reject duplicate par2 files
RE_NORMAL_NAME = re.compile(r'\.\w{2,5}$') # Test reasonably sized extension at the end
################################################################################
# Article #
@@ -154,6 +155,7 @@ NzbFileMapper = (
('import_finished', 'import_finished'),
('md5sum', 'md5sum'),
('valid', 'valid'),
('completed', 'completed')
)
@@ -169,9 +171,7 @@ class NzbFile(TryList):
self.filename = None
self.type = None
match = re.search(SUBJECT_FN_MATCHER, subject)
if match:
self.filename = match.group(1).strip('"')
self.filename = name_extractor(subject)
self.is_par2 = False
self.vol = None
@@ -191,6 +191,7 @@ class NzbFile(TryList):
self.nzo = nzo
self.nzf_id = sabnzbd.get_new_id("nzf", nzo.workpath)
self.deleted = False
self.completed = False
self.valid = False
self.import_finished = False
@@ -272,7 +273,7 @@ class NzbFile(TryList):
@property
def completed(self):
""" Is this file completed? """
return not bool(self.articles)
return self.import_finished and not bool(self.articles)
@property
def lowest_partnum(self):
@@ -348,12 +349,8 @@ class NzbParser(xml.sax.handler.ContentHandler):
self.in_segments = True
elif name == 'file' and self.in_nzb:
subject = attrs.get('subject', '')
match = re.search(SUBJECT_FN_MATCHER, subject)
if match:
self.filename = match.group(1).strip('"').strip()
else:
self.filename = subject.strip()
subject = attrs.get('subject', '').strip()
self.filename = subject
if self.filter and RE_SAMPLE.search(subject):
logging.info('Skipping sample file %s', subject)
@@ -810,6 +807,7 @@ class NzbObject(TryList):
if nzf in self.files:
self.files.remove(nzf)
self.finished_files.append(nzf)
nzf.import_finished = True
nzf.deleted = True
return not bool(self.files)
@@ -818,38 +816,38 @@ class NzbObject(TryList):
nzf.reset_all_try_lists()
self.reset_try_list()
def postpone_pars(self, nzf, head):
""" Move all vol-par files matching 'head' to the extrapars table """
self.partable[head] = nzf
self.extrapars[head] = []
nzf.extrapars = self.extrapars[head]
def postpone_pars(self, nzf, parset):
""" Move all vol-par files matching 'parset' to the extrapars table """
self.partable[parset] = nzf
self.extrapars[parset] = []
nzf.extrapars = self.extrapars[parset]
lparset = parset.lower()
for xnzf in self.files[:]:
name = xnzf.filename
name = xnzf.filename or platform_encode(xnzf.subject)
# Move only when not current NZF and filename was extractable from subject
if name and nzf is not xnzf:
name = name.lower()
if head.lower() in name and '.vol' in name and name.endswith('.par2'):
self.extrapars[head].append(xnzf)
self.files.remove(xnzf)
head, vol, block = analyse_par2(name)
if head and matcher(lparset, head.lower()):
xnzf.set_par2(parset, vol, block)
self.extrapars[parset].append(xnzf)
if not self.precheck:
self.files.remove(xnzf)
def handle_par2(self, nzf, file_done):
## Special treatment for first part of par2 file
""" Check if file is a par2 and build up par2 collection
"""
fn = nzf.filename
if fn:
# We have a real filename now
fn = fn.strip()
lfn = fn.lower()
if (not nzf.is_par2) and fn and lfn.endswith('.par2'):
par2match = PROBABLY_PAR2_RE.search(fn)
if par2match:
head = par2match.group(1)
vol = par2match.group(2)
block = par2match.group(3)
elif lfn.endswith('.par2'):
head = os.path.splitext(fn)[0]
vol = block = 0
par2match = True
if not nzf.is_par2:
head, vol, block = analyse_par2(fn)
## Is a par2file and repair mode activated
if par2match and (self.repair or cfg.allow_streaming()):
if head and (self.repair or cfg.allow_streaming()):
## Skip if mini-par2 is not complete
if not block and nzf.bytes_left:
return
nzf.set_par2(head, vol, block)
## Already got a parfile for this set?
if head in self.partable:
@@ -867,15 +865,17 @@ class NzbObject(TryList):
## This file either has more blocks,
## or initialparfile is already decoded
else:
if not file_done:
nzf.reset_try_list()
if file_done:
if nzf in self.files: self.files.remove(nzf)
self.extrapars[head].append(nzf)
if nzf not in self.extrapars[head]: self.extrapars[head].append(nzf)
else:
nzf.reset_try_list()
## No par2file in this set yet, set this as
## initialparfile
else:
self.postpone_pars(nzf, head)
## Is not a par2file or nothing todo
## Is not a par2file or nothing to do
else:
pass
## No filename in seg 1? Probably not uu or yenc encoded
@@ -894,7 +894,8 @@ class NzbObject(TryList):
if reset:
self.reset_try_list()
self.handle_par2(nzf, file_done)
if file_done:
self.handle_par2(nzf, file_done)
post_done = False
if not self.files:
@@ -911,6 +912,15 @@ class NzbObject(TryList):
"""
# Get a list of already present files
files = [os.path.basename(f) for f in globber(wdir) if os.path.isfile(f)]
# Substitute renamed files
renames = sabnzbd.load_data(RENAMES_FILE, self.workpath, remove=True)
if renames:
for name in renames:
if name in files:
files.remove(name)
files.append(renames[name])
# Looking for the longest name first, minimizes the chance on a mismatch
files.sort(lambda x, y: len(y) - len(x))
@@ -924,6 +934,8 @@ class NzbObject(TryList):
subject = sanitize_filename(latin1(nzf.subject))
if (nzf.filename == filename) or (subject == filename) or (filename in subject):
nzf.filename = filename
nzf.completed = True
nzf.bytes_left = 0
self.handle_par2(nzf, file_done=True)
self.remove_nzf(nzf)
nzfs.remove(nzf)
@@ -940,6 +952,8 @@ class NzbObject(TryList):
self.files_table[nzf.nzf_id] = nzf
self.bytes += nzf.bytes
nzf.filename = filename
nzf.completed = True
nzf.bytes_left = 0
self.handle_par2(nzf, file_done=True)
self.remove_nzf(nzf)
logging.info('File %s added to job', filename)
@@ -1007,11 +1021,10 @@ class NzbObject(TryList):
self.incomplete = False
def add_parfile(self, parfile):
self.files.append(parfile)
if parfile.extrapars:
if parfile not in self.files:
self.files.append(parfile)
if parfile.extrapars and parfile in parfile.extrapars:
parfile.extrapars.remove(parfile)
else:
logging.debug('PARFILE without EXTRAPARS %s', parfile.filename or parfile.subject)
def remove_parset(self, setname):
self.partable.pop(setname)
@@ -1110,8 +1123,14 @@ class NzbObject(TryList):
if article:
break
# Remove all files for which admin could not be read
for nzf in nzf_remove_list:
nzf.deleted = True
nzf.completed = True
self.files.remove(nzf)
# If cleanup emptied the active files list, end this job
if nzf_remove_list and not self.files:
sabnzbd.NzbQueue.do.end_job(self)
if not article:
# No articles for this server, block for next time
@@ -1235,6 +1254,15 @@ class NzbObject(TryList):
bytes_left += nzf.bytes_left
return bytes_left
def total_and_remaining(self):
""" Return total and remaining bytes """
bytes = 0
bytes_left = 0
for nzf in self.files:
bytes += nzf.bytes
bytes_left += nzf.bytes_left
return bytes, bytes_left
def gather_info(self, for_cli = False):
bytes_left_all = 0
@@ -1377,8 +1405,9 @@ class NzbObject(TryList):
#-------------------------------------------------------------------------------
def nzf_get_filename(nzf):
# Return filename, if the filename not set, try the
# the full subject line instead. Can produce non-ideal results
""" Return filename, if the filename not set, try the
the full subject line instead. Can produce non-ideal results
"""
name = nzf.filename
if not name:
name = nzf.subject
@@ -1387,12 +1416,35 @@ def nzf_get_filename(nzf):
return name.lower()
def get_ext_list():
""" Return priority extenstion list, with extensions starting with a period
"""
exts = []
for ext in cfg.prio_sort_list():
ext = ext.strip()
if not ext.startswith('.'):
ext = '.' + ext
exts.append(ext)
return exts
def ext_on_list(name, lst):
""" Return True if `name` contains any extension in `lst`
"""
for ext in lst:
if name.rfind(ext) >= 0:
return True
return False
def nzf_cmp_date(nzf1, nzf2):
# Compare files based on date, but give vol-par files preference
""" Compare files based on date, but give vol-par files preference.
Wrapper needed, because `cmp` function doesn't handle extra parms.
"""
return nzf_cmp_name(nzf1, nzf2, name=False)
RE_RAR = re.compile(r'(\.rar|\.r\d\d)|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d', re.I)
RE_RAR = re.compile(r'(\.rar|\.r\d\d|\.s\d\d|\.t\d\d|\.u\d\d|\.v\d\d)$', re.I)
def nzf_cmp_name(nzf1, nzf2, name=True):
# The comparison will sort .par2 files to the top of the queue followed by .rar files,
@@ -1416,6 +1468,16 @@ def nzf_cmp_name(nzf1, nzf2, name=True):
if is_par2 and not is_par1:
return -1
# Anything with a priority extention goes first
ext_list = get_ext_list()
if ext_list:
onlist1 = ext_on_list(name1, ext_list)
onlist2 = ext_on_list(name2, ext_list)
if onlist1 and not onlist2:
return -1
if onlist2 and not onlist1:
return 1
if name:
# Prioritise .rar files above any other type of file (other than vol-par)
# Useful for nzb streaming
@@ -1514,8 +1576,10 @@ def get_attrib_file(path, size):
return [None for n in xrange(size)]
for n in xrange(size):
line = f.readline().strip('\n ')
line = f.readline().strip('\r\n ')
if line:
if line.lower() == 'none':
line = None
try:
line = int(line)
except:
@@ -1539,3 +1603,43 @@ def set_attrib_file(path, attribs):
f.write('%s\n' % item)
f.close()
def analyse_par2(name):
""" Check if file is a par2-file and determine vol/block
return head, vol, block
head is empty when not a par2 file
"""
head = None
vol = block = 0
if name and not REJECT_PAR2_RE.search(name):
m = PROBABLY_PAR2_RE.search(name)
if m:
head = m.group(1)
vol = m.group(2)
block = m.group(3)
elif name.lower().find('.par2') > 0:
head = os.path.splitext(name)[0].strip()
else:
head = None
return head, vol, block
def name_extractor(subject):
""" Try to extract a file name from a subject line, return `subject` if in doubt
"""
result = subject
for name in re.findall(SUBJECT_FN_MATCHER, subject):
name = name.strip(' "')
if name and RE_NORMAL_NAME.search(name):
result = name
return platform_encode(result)
def matcher(pattern, txt):
""" Return True if `pattern` is sufficiently equal to `txt`
"""
if txt.endswith(pattern):
txt = txt[:txt.rfind(pattern)].strip()
return (not txt) or txt.endswith('"')
else:
return False

View File

@@ -23,9 +23,11 @@ from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
from objc import YES, NO, nil
from threading import Thread
import os
import subprocess
from threading import Thread
import cherrypy
import Cheetah.DummyTransaction
import sys
@@ -52,7 +54,7 @@ from sabnzbd.newzbin import Bookmarks
from sabnzbd.database import get_history_handle
from sabnzbd.encoding import unicoder
status_icons = {'idle':'../Resources/sab_idle.png','pause':'../Resources/sab_pause.png','clicked':'../Resources/sab_clicked.png'}
status_icons = {'idle':'../Resources/sab_idle.tiff','pause':'../Resources/sab_pause.tiff','clicked':'../Resources/sab_clicked.tiff'}
start_time = NSDate.date()
debug = 0
@@ -62,6 +64,7 @@ class SABnzbdDelegate(NSObject):
icons = {}
status_bar = None
osx_icon = True
history_db = None
def awakeFromNib(self):
#Status Bar iniatilize
@@ -344,10 +347,7 @@ class SABnzbdDelegate(NSObject):
self.menu_queue.addItem_(menu_queue_item)
self.menu_queue.addItem_(NSMenuItem.separatorItem())
job_nb = 1
for pnfo in pnfo_list:
if job_nb > 10:
break
filename = unicoder(pnfo[PNFO_FILENAME_FIELD])
msgid = pnfo[PNFO_MSGID_FIELD]
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD] / MEBI
@@ -357,11 +357,10 @@ class SABnzbdDelegate(NSObject):
timeleft = self.calc_timeleft(bytesleftprogess, bpsnow)
job = "%s\t(%d/%d MB) %s" % (filename, bytesleft, bytes, timeleft)
job_nb += 1
menu_queue_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(job, '', '')
self.menu_queue.addItem_(menu_queue_item)
self.info = "%d nzb(s)\t( %d / %d MB )" % (len(pnfo_list),(qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI), (qnfo[QNFO_BYTES_FIELD] / MEBI))
self.info = "%d nzb(s)\t( %d / %d MB )" % (qnfo[QNFO_Q_SIZE_LIST_FIELD],(qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI), (qnfo[QNFO_BYTES_FIELD] / MEBI))
else:
menu_queue_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(T('Empty'), '', '')
@@ -375,8 +374,9 @@ class SABnzbdDelegate(NSObject):
def historyUpdate(self):
try:
# Fetch history items
history_db = sabnzbd.database.get_history_handle()
items, fetched_items, total_items = history_db.fetch_history(0,10,None)
if not self.history_db:
self.history_db = sabnzbd.database.get_history_handle()
items, fetched_items, total_items = self.history_db.fetch_history(0,10,None)
self.menu_history = NSMenu.alloc().init()
self.failedAttributes = { NSForegroundColorAttributeName:NSColor.redColor(), NSFontAttributeName:NSFont.menuFontOfSize_(14.0) }
@@ -683,8 +683,9 @@ class SABnzbdDelegate(NSObject):
if mode == "queue":
NzbQueue.do.remove_all()
elif mode == "history":
history_db = sabnzbd.database.get_history_handle()
history_db.remove_history()
if not self.history_db:
self.history_db = sabnzbd.database.get_history_handle()
self.history_db.remove_history()
def pauseAction_(self, sender):
minutes = int(sender.representedObject())
@@ -785,4 +786,3 @@ def notify(notificationName, message):
nc = Foundation.NSDistributedNotificationCenter.defaultCenter()
nc.postNotificationName_object_(notificationName, message)
del pool

View File

@@ -31,12 +31,12 @@ import re
from sabnzbd.newsunpack import unpack_magic, par2_repair, external_processing, sfv_check
from threading import Thread
from sabnzbd.misc import real_path, get_unique_path, create_dirs, move_to_path, \
get_unique_filename, make_script_path, verified_flag_file, \
make_script_path, \
on_cleanup_list, renamer, remove_dir, remove_all, globber, \
set_permissions
set_permissions, cleanup_empty_directories
from sabnzbd.tvsort import Sorter
from sabnzbd.constants import REPAIR_PRIORITY, POSTPROC_QUEUE_FILE_NAME, \
POSTPROC_QUEUE_VERSION, sample_match, JOB_ADMIN, Status
from sabnzbd.constants import REPAIR_PRIORITY, TOP_PRIORITY, POSTPROC_QUEUE_FILE_NAME, \
POSTPROC_QUEUE_VERSION, sample_match, JOB_ADMIN, Status, VERIFIED_FILE
from sabnzbd.encoding import TRANS, unicoder
from sabnzbd.newzbin import Bookmarks
import sabnzbd.emailer as emailer
@@ -184,8 +184,8 @@ class PostProcessor(Thread):
sabnzbd.downloader.Downloader.do.wait_for_postproc()
self.__busy = True
if process_job(nzo):
self.remove(nzo)
process_job(nzo)
self.remove(nzo)
check_eoq = True
## Allow download to proceed
@@ -206,6 +206,8 @@ def process_job(nzo):
par_error = False
# keep track of any unpacking errors
unpack_error = False
# Signal empty download, for when 'empty_postproc' is enabled
empty = False
nzb_list = []
# These need to be initialised incase of a crash
workdir_complete = ''
@@ -225,19 +227,6 @@ def process_job(nzo):
filename = nzo.final_name
msgid = nzo.msgid
if nzo.precheck:
# Check result
enough, ratio = nzo.check_quality()
if enough:
# Enough data present, do real download
workdir = nzo.downpath
sabnzbd.nzbqueue.NzbQueue.do.cleanup_nzo(nzo, keep_basic=True)
sabnzbd.nzbqueue.NzbQueue.do.repair_job(workdir)
return True
else:
# Not enough data, flag as failed
nzo.save_attribs()
if cfg.allow_streaming() and not (flag_repair or flag_unpack or flag_delete):
# After streaming, force +D
nzo.set_pp(3)
@@ -254,6 +243,7 @@ def process_job(nzo):
# if no files are present (except __admin__), fail the job
if len(globber(workdir)) < 2:
if nzo.precheck:
enough, ratio = nzo.check_quality()
req_ratio = float(cfg.req_completion_rate()) / 100.0
# Make sure that rounded ratio doesn't equal required ratio
# when it is actually below required
@@ -264,13 +254,15 @@ def process_job(nzo):
emsg = T('Download might fail, only %s of required %s available') % (emsg, emsg2)
else:
emsg = T('Download failed - Out of your server\'s retention?')
empty = True
nzo.fail_msg = emsg
nzo.set_unpack_info('Fail', emsg)
nzo.status = Status.FAILED
# do not run unpacking or parity verification
flag_repair = flag_unpack = False
par_error = unpack_error = True
all_ok = False
all_ok = cfg.empty_postproc() and empty
if not all_ok:
par_error = unpack_error = True
script = nzo.script
cat = nzo.cat
@@ -311,7 +303,7 @@ def process_job(nzo):
## TV/Movie/Date Renaming code part 1 - detect and construct paths
file_sorter = Sorter(cat)
complete_dir = file_sorter.detect(dirname, complete_dir)
if file_sorter.is_sortfile():
if file_sorter.sort_file:
one_folder = False
if one_folder:
@@ -357,8 +349,9 @@ def process_job(nzo):
for file_ in files:
path = os.path.join(root, file_)
new_path = path.replace(workdir, tmp_workdir_complete)
new_path = get_unique_filename(new_path)
if not move_to_path(path, new_path, unique=False):
ok, new_path = move_to_path(path, new_path)
newfiles.append(new_path)
if not ok:
nzo.set_unpack_info('Unpack', T('Failed moving %s to %s') % (unicoder(path), unicoder(new_path)))
all_ok = False
break
@@ -382,10 +375,7 @@ def process_job(nzo):
nzb_list = None
if nzb_list:
nzo.set_unpack_info('Download', T('Sent %s to queue') % unicoder(nzb_list))
try:
remove_dir(tmp_workdir_complete)
except:
pass
cleanup_empty_directories(tmp_workdir_complete)
else:
cleanup_list(tmp_workdir_complete, False)
@@ -403,13 +393,16 @@ def process_job(nzo):
logging.error(Ta('Error renaming "%s" to "%s"'), tmp_workdir_complete, workdir_complete)
logging.info("Traceback: ", exc_info = True)
job_result = int(par_error) + int(unpack_error)*2
if empty:
job_result = -1
else:
job_result = int(par_error) + int(unpack_error)*2
if cfg.ignore_samples() > 0:
remove_samples(workdir_complete)
## TV/Movie/Date Renaming code part 2 - rename and move files to parent folder
if all_ok and file_sorter.is_sortfile():
if all_ok and file_sorter.sort_file:
if newfiles:
file_sorter.rename(newfiles, workdir_complete)
workdir_complete, ok = file_sorter.move(workdir_complete)
@@ -539,10 +532,14 @@ def process_job(nzo):
def parring(nzo, workdir):
""" Perform par processing. Returns: (par_error, re_add)
"""
assert isinstance(nzo, sabnzbd.nzbstuff.NzbObject)
filename = nzo.final_name
growler.send_notification(T('Post-processing'), nzo.final_name, 'pp')
logging.info('Par2 check starting on %s', filename)
## Get verification status of sets
verified = sabnzbd.load_data(VERIFIED_FILE, nzo.workpath, remove=False) or {}
## Collect the par files
if nzo.partable:
par_table = nzo.partable.copy()
@@ -554,48 +551,66 @@ def parring(nzo, workdir):
par_error = False
if repair_sets:
for set_ in repair_sets:
logging.info("Running repair on set %s", set_)
parfile_nzf = par_table[set_]
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, set_)
if need_re_add:
re_add = True
else:
for setname in repair_sets:
if cfg.ignore_samples() > 0 and 'sample' in setname.lower():
continue
if not verified.get(setname, False):
logging.info("Running repair on set %s", setname)
parfile_nzf = par_table[setname]
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, setname)
re_add = re_add or need_re_add
if not res and not need_re_add and cfg.sfv_check():
res = try_sfv_check(nzo, workdir, setname)
verified[setname] = res
par_error = par_error or not res
if re_add:
logging.info('Readded %s to queue', filename)
nzo.priority = REPAIR_PRIORITY
sabnzbd.nzbqueue.add_nzo(nzo)
sabnzbd.downloader.Downloader.do.resume_from_postproc()
logging.info('Par2 check finished on %s', filename)
if (par_error and not re_add) or not repair_sets:
# See if alternative SFV check is possible
else:
logging.info("No par2 sets for %s", filename)
nzo.set_unpack_info('Repair', T('[%s] No par2 sets') % unicoder(filename))
if cfg.sfv_check():
sfvs = globber(workdir, '*.sfv')
else:
sfvs = None
if sfvs:
par_error = False
nzo.set_unpack_info('Repair', T('Trying SFV verification'))
for sfv in sfvs:
if not sfv_check(sfv):
nzo.set_unpack_info('Repair', T('Some files failed to verify against "%s"') % unicoder(os.path.basename(sfv)))
par_error = True
if not par_error:
nzo.set_unpack_info('Repair', T('Verified successfully using SFV files'))
elif not repair_sets:
logging.info("No par2 sets for %s", filename)
nzo.set_unpack_info('Repair', T('[%s] No par2 sets') % unicoder(filename))
par_error = not try_sfv_check(nzo, workdir, '')
verified[''] = not par_error
if not par_error:
verified_flag_file(workdir, create=True)
if re_add:
logging.info('Readded %s to queue', filename)
if nzo.priority != TOP_PRIORITY:
nzo.priority = REPAIR_PRIORITY
sabnzbd.nzbqueue.add_nzo(nzo)
sabnzbd.downloader.Downloader.do.resume_from_postproc()
sabnzbd.save_data(verified, VERIFIED_FILE, nzo.workpath)
logging.info('Par2 check finished on %s', filename)
return par_error, re_add
def try_sfv_check(nzo, workdir, setname):
""" Attempt to verify set using SFV file
Return True if verified, False when failed
When setname is '', all SFV files will be used, otherwise only the matching one
When setname is '' and no SFV files are found, True is returned
"""
# Get list of SFV names; shortest name first, minimizes the chance on a mismatch
sfvs = globber(workdir, '*.sfv')
sfvs.sort(lambda x, y: len(x) - len(y))
par_error = False
found = False
for sfv in sfvs:
if setname in os.path.basename(sfv):
found = True
nzo.set_unpack_info('Repair', T('Trying SFV verification'))
failed = sfv_check(sfv)
if failed:
msg = T('Some files failed to verify against "%s"') % unicoder(os.path.basename(sfv))
msg += '; '
msg += '; '.join(failed)
nzo.set_unpack_info('Repair', msg)
par_error = True
else:
nzo.set_unpack_info('Repair', T('Verified successfully using SFV files'))
if setname:
break
return (found or not setname) and not par_error
#------------------------------------------------------------------------------
@@ -648,6 +663,11 @@ def cleanup_list(wdir, skip_nzb):
except:
logging.error(Ta('Removing %s failed'), path)
logging.info("Traceback: ", exc_info = True)
if files:
try:
remove_dir(wdir)
except:
pass
def prefix(path, pre):
@@ -663,29 +683,24 @@ def nzb_redirect(wdir, nzbname, pp, script, cat, priority):
if so send to queue and remove if on CleanList
Returns list of processed NZB's
"""
lst = []
try:
files = os.listdir(wdir)
except:
files = []
files = []
for root, dirs, names in os.walk(wdir):
for name in names:
files.append(os.path.join(root, name))
for file_ in files:
if os.path.splitext(file_)[1].lower() != '.nzb':
return lst
return None
# For a single NZB, use the current job name
# For multiple NZBs, cannot use the current job name
if len(files) != 1:
nzbname = None
# Process all NZB files
for file_ in files:
if file_.lower().endswith('.nzb'):
dirscanner.ProcessSingleFile(file_, os.path.join(wdir, file_), pp, script, cat,
priority=priority, keep=False, dup_check=False, nzbname=nzbname)
lst.append(file_)
return lst
dirscanner.ProcessSingleFile(os.path.split(file_)[1], file_, pp, script, cat,
priority=priority, keep=False, dup_check=False, nzbname=nzbname)
return files
def one_file_or_folder(folder):

Some files were not shown because too many files have changed in this diff Show More