Compare commits

...

116 Commits

Author SHA1 Message Date
shypike
c897b2252d Update text files for 0.7.17Beta2 2013-12-16 18:44:57 +01:00
shypike
2aadcd032d Fix coding errors in OZnzb rating support. 2013-12-16 18:43:22 +01:00
shypike
23e9bf112e Fix problem with running user’s PP script. 2013-12-16 18:42:52 +01:00
shypike
9b577df408 Fix error in ozNZB support. 2013-12-13 20:40:29 +01:00
shypike
e6d75b45ae Update text files for 0.7.17Beta1 2013-12-12 21:49:26 +01:00
shypike
f68de5df4c Add some basic support for X-DNZB-Failure and X-DNZB-Details headers coming from indexers.
”Failure” will send an extra URL parameter to the post processing script.
”Details” will override ”More-info” in the History.
2013-12-12 21:45:22 +01:00
shypike
8bda8efa2f Add provisional support for unrar 5.
The report text for encrypted files has changed in unrar 5.
Thank you, Sander!
2013-12-12 19:51:12 +01:00
shypike
62792f859b Improve scanning of passwords in file names.
Replace regexes by plain logic to allow "/" and "{{" and "}}” in passwords.
2013-12-12 19:24:53 +01:00
shypike
79fa42f90f Update translations 2013-12-12 18:14:33 +01:00
shypike
acb3ed2c77 Update translation templates. 2013-12-10 21:19:18 +01:00
shypike
50f411fb0b Add the command line parameter —pidfile to set an explicit PID-file name. 2013-12-10 20:37:48 +01:00
shypike
38c13bc4f0 Merge pull request #127 from oznzb-dev/0.7.x
0.7.x ratings, feedback and comments
2013-12-10 11:16:28 -08:00
oznzb-dev
7a7bf0f4e4 Merge branch '0.7.x' of https://github.com/oznzb-dev/sabnzbd into 0.7.x 2013-11-25 18:03:12 +10:00
oznzb-dev
4d1b02fa64 Add backport of OrderedDict to support earlier versions of Python. 2013-11-25 17:57:36 +10:00
oznzb-dev
612e68b5e6 Update three.html 2013-11-23 20:05:58 +10:00
oznzb-dev
9645f947b8 Add modified files related ratings. 2013-11-23 19:19:45 +10:00
oznzb-dev
c211969a81 Added support for ratings and integration with OZnzb. 2013-11-23 19:15:10 +10:00
shypike
86916cdf90 Fix another issue with commit 3b3759e81e (NZB-meta data). 2013-11-12 22:06:40 +01:00
shypike
fa3e0f941b Always rename files in Sorting, regardless of casing. 2013-11-12 20:58:07 +01:00
shypike
32c524c18d Fix issue with commit 3b3759e81e (NZB-meta data). 2013-11-12 20:47:11 +01:00
shypike
7ba1a4c20f Add Solaris manifest to tar.gz distribution file. 2013-11-03 15:45:20 +01:00
shypike
3b3759e81e Add usage of NZB-meta data and X-headers for Sorting.
Meta records: "episodename", "propername" and "year".
X-headers: "x-dnzb-episodename", "x-dnzb-propername" and "x-dnzb-year".
Controlled by an option.
2013-11-02 17:35:58 +01:00
shypike
ba3aaab3dc Pass extra parameter to OSX Notification Center tool to enable Mavericks support. 2013-10-29 20:36:04 +01:00
shypike
30ec3c430d Show job's ETA when its priority is forced, but queue is paused. 2013-09-19 22:03:33 +02:00
shypike
dac568fc35 Another fix for false encryption reports. 2013-09-19 20:09:46 +02:00
shypike
1ee00e12ce Fix crash in API-call "queue-rename" when "value3" is empty or undefined. 2013-09-18 20:56:15 +02:00
shypike
15ae1ae5fd Merge pull request #111 from jim80net/0.7.x
Adds Solaris SMF manifest.
2013-09-02 14:05:28 -07:00
shypike
14f39a21e3 Update text files for 0.7.16 2013-09-02 22:05:58 +02:00
shypike
97ea4ee2eb Special "news_items" wasn't removed completely, leading to UI crash. 2013-09-02 22:01:36 +02:00
Jim80net
c68fa9f0c5 Adds solaris manifest 2013-09-02 19:42:44 +00:00
shypike
06576baf5c Update text files for 0.7.15 2013-08-26 19:12:28 +02:00
shypike
7b5fcbe0af For Unix systems, expand wildcards for the par2 tool to prevent problems with some builds of par2cmdline. 2013-08-20 21:31:46 +02:00
shypike
003ee07dee Revert "Use ".admin" instead of "__ADMIN__" as job admin folder to support some non-standard Unix systems."
This reverts commit 1b05bc9ed2.
2013-08-20 21:29:01 +02:00
shypike
654b5e9a24 Remove "news" section in Config skin's main page.
Was never used and caused mixed mode https/http issues.
2013-08-15 19:23:26 +02:00
shypike
1b05bc9ed2 Use ".admin" instead of "__ADMIN__" as job admin folder to support some non-standard Unix systems.
".admin" will be treated as a hidden folder by non-Windows systems, avoiding a problem with
wildcard expansion for par2cmdline on some Unix systems.
2013-08-12 22:18:13 +02:00
shypike
dc328c545b Add password entry box to "File Details" page (Plush only).
Also extend api call "queue_rename" with a password parameter (value3).
2013-08-09 18:34:59 +02:00
shypike
823816ddc4 Prevent "special" sub-folders on file servers from being scanned during unpacking. 2013-07-28 14:00:14 +02:00
shypike
8979598f23 Add special option 'sanitize_safe' to remove bad Windows chars on other platforms. 2013-07-16 21:54:02 +02:00
shypike
f26bf9b21f Fix false positive encryption alarm for some posts, 2013-07-16 21:36:09 +02:00
shypike
5d3a0cc593 Merge pull request #104 from manandre/rss_guid
Add of GUID field in History and Queue RSS feeds
2013-07-16 12:32:02 -07:00
manandre
21d445b7a6 Add of GUID field in Queue RSS feed
The NZO id is used as unique id for the queue RSS feed to help some RSS
readers (like Thunderbird) to identify articles when the link field is
the same for all articles
2013-07-07 18:38:17 +02:00
manandre
9c0df30d34 Add of GUID field in History RSS feed
The NZO id is used as unique id for the history RSS feed to help some RSS readers (like Thunderbird) to identify articles when the link field is the same for all articles.
2013-07-07 18:16:24 +02:00
shypike
bc9be3f92b Update text files for 0.7.14 2013-07-07 13:12:15 +02:00
shypike
2dc5c329c9 Fix special case of unjustified encryption warning. 2013-07-07 13:11:01 +02:00
shypike
67817978f4 Missing mini-par2 sometimes prevents the other par2 files from being downloaded. 2013-06-27 20:41:57 +02:00
shypike
e2ab8c6ce4 Make sure even invalid RAR files are fed to unrar and handle its reporting. 2013-06-27 20:29:04 +02:00
shypike
f33a952536 Update text files for 0.7.13 (again). 2013-06-13 21:35:14 +02:00
shypike
cc582b5321 Accept "nzbname" parameter in api-call "add url" even when a ZIP file is retrieved. 2013-06-13 21:33:00 +02:00
shypike
bdc526c91b Update text files for 0.7.13 2013-06-12 22:59:28 +02:00
shypike
52039c29b4 Accept partial par2 file when no others are available. 2013-06-12 21:03:29 +02:00
shypike
1dc4175f82 Add "special" option enable_recursion to control recursive unpacking. 2013-06-09 09:59:38 +02:00
shypike
92f70fc177 When post has just one par2-set, use full wildcard so that all files are repair and par candidates. 2013-06-01 11:21:00 +02:00
shypike
fd573208bd Fix encryption detection again. 2013-05-28 19:47:35 +02:00
shypike
ca9f10c12f Update text files for 0.7.12 2013-05-21 21:47:02 +02:00
shypike
49a72d0902 Update translations 2013-05-21 21:34:25 +02:00
shypike
6aafe3c531 Fix problem in encryption detection. 2013-05-07 21:17:06 +02:00
shypike
9e84696f96 Config and Wizard skins: fix problem with Unicode when using Chrome.
The Config skin and the Wizard were missing a proper Content-Type in <head>.
2013-04-14 12:02:33 +02:00
shypike
120c133d7a Implement robots.txt to keep web crawlers out.
Should not really be needed, because users should password-protect any
SABnzbd instance exposed to internet.
2013-04-12 21:25:56 +02:00
shypike
cf9713a4b0 Don't try to join a set of just one file (e.g. IMAGE.000) and reduce memory usage when joining large segments.
When there a single file called something like IMAGE.000, don't try to join it.
The joining procedure tries to read an entire segment file into memory, this may lead to a string overflow.
Use shutil.copyfileobj() with a 24 MB buffer instead.
2013-04-12 21:24:53 +02:00
shypike
d12e9889e7 Make encryption detection more careful. 2013-04-09 19:30:25 +02:00
shypike
711a546989 Make name sorting of the queue case-insensitive. 2013-03-20 23:12:13 +01:00
shypike
7f78e6fac1 Save job admin to disk when setting password or changing other attributes. 2013-03-02 13:09:24 +01:00
shypike
72533eefa4 Plush: add "resume pp" entry to pulldown menu, when pause_pp event is scheduled.
The option allows manual resume of a scheduled paused post-processing.
2013-02-26 20:33:58 +01:00
shypike
d9643d9ea8 Improve RAR detection. 2013-02-25 22:08:26 +01:00
shypike
2de71bb96c Enable "abort if hopeless" for pre-check as well. 2013-02-13 20:40:31 +01:00
shypike
07be241112 Update text files for 0.7.11 2013-02-07 20:21:59 +01:00
shypike
fbdd264653 Update translations 2013-02-07 19:38:48 +01:00
shypike
a8bc793132 Fix regression error that could result in slow verification of NZBs with multiple rar/par sets.
The detection of obfuscated files failed, causing each par2 run to parse all files.
2013-02-07 19:27:03 +01:00
shypike
6bce423f23 Fix "Sorting" file renaming for RAR files that contain an extra folder level.
collapse_folder() should convert filename list accoerding to the renaming it does.
It's also its task to remove the _unpack_ markers from those filenames.
Rename this function to rename_and_collapse().
2013-02-04 22:47:53 +01:00
shypike
51cabf85a1 Plush: default refresh-rate now 4 sec and multi-ops bar visible. 2013-02-04 21:43:12 +01:00
shypike
38fdc2c7c8 Show warning when decoder encounters I/O-errors. 2013-02-03 12:25:33 +01:00
shypike
b91a2af9df Some badly encoded articles can be accepted as valid data.
Regression error since 0.7.9 (commit ccfbb07).
decoder.decode() no long ran into an excepton when no valid data was found.
Solved now by using the "found" flag.
2013-02-02 14:57:28 +01:00
shypike
71ee5969a8 Update text files for 0.7.10 2013-01-30 21:04:24 +01:00
shypike
1b1c772b55 Update text files for 0.7.10 2013-01-30 20:34:13 +01:00
shypike
458eb3a417 Update translations 2013-01-29 19:31:57 +01:00
shypike
98b753f015 Change access to build share, even more improved. 2013-01-28 23:31:25 +01:00
shypike
a3284e12d2 Change access to build share, improved. 2013-01-28 22:51:35 +01:00
shypike
c85120bb4b Change access to build share. 2013-01-28 21:36:12 +01:00
shypike
903925e06b Update main POT file. 2013-01-24 10:35:27 +01:00
shypike
03196f25e4 Accept NNTP error 400 without "too many connection" clues as a transient error.
Previously it would file a warning and lock out the server for a few minutes.
Reduce the lockout to 6 seconds.
400 should report "too many connections" but some servers use it for temporary connectivity issues.
2013-01-24 10:34:18 +01:00
ShyPike
e3f3f962b6 Handle unrar error messages better (like "path too long").
In the case of fully encrypted RAR files, SABnzbd cannot check the resulting files.
Handling unrar errors explicitly is better anyway.
2013-01-23 22:23:11 +01:00
shypike
153f92e697 "Failed" message should also appear in email notifications. 2013-01-22 23:27:21 +01:00
shypike
c1dcafe953 Display next RSS scan moment in Config->RSS 2013-01-19 21:34:31 +01:00
ShyPike
b53d97732b Reset the "today" byte counters at midnight even when idle.
Set a scheduled event at midnight for resetting the "today" byte counters.
Otherwise, when idle, the bpsmeter isn't called at all.
2013-01-17 22:24:12 +01:00
ShyPike
8f47cce9c8 Try to process obfuscated rar/par sets as good as possible.
When detecting a main par2 file without extra pars, use full wildcard for par2-run.
During par2-run, register which par2 files contain matching blocks.
Remove matching par2 files after the repair.
Skip sets of which the main par2 file has been deleted (due to having been used
in another set).
2013-01-15 22:42:59 +01:00
ShyPike
3cf42a7f94 Accept %fn (next to %fn.%ext) as end parameter in sorting strings. 2013-01-14 20:52:37 +01:00
ShyPike
ae74370cfb Add IP address of unauthenticated API-call to warning. 2013-01-14 19:23:47 +01:00
ShyPike
2aaa283991 Plush: repair and unpack icons in History were swapped.
Fixing does mean that order changes too, but that was the only way to
make the icons correspond with the hover popups.
2013-01-11 21:55:14 +01:00
ShyPike
dca7a8ccdb Plush: show speed when forced job is running in Paused mode. 2013-01-11 19:48:17 +01:00
shypike
3172d6e987 Disable scheduled task for newzbin bookmarks. 2013-01-10 21:36:43 +01:00
ShyPike
c237ddfef4 Update text files for 0.7.9 2013-01-06 20:11:33 +01:00
ShyPike
b543dcb5ac Fix text in dropdowns being hard to see in chrome. 2013-01-06 19:33:31 +01:00
ShyPike
ccfbb07333 Take servers that only support ARTICLE into account.
When only full articles are available, the decoder needs to scan more lines
to find the start of the payload.
2013-01-06 19:33:19 +01:00
ShyPike
256ccbd6a1 Prevent crash in decoder.py 2013-01-06 19:06:15 +01:00
ShyPike
d8d507f110 Update text files for 0.7.8 2013-01-03 19:24:27 +01:00
ShyPike
7b3309649f Cancel encryption detection if meta-data if NZB contains password. 2013-01-03 18:44:31 +01:00
shypike
9a7a6652e8 Update text files for 0.7.8 2013-01-03 18:41:50 +01:00
shypike
db4891748f Update copyright year. 2013-01-03 18:40:49 +01:00
ShyPike
3dce2e8908 Support NZB 1.1 meta data; currently "category" and "password" are used.
"category" will trigger category conversion.
"password" value(s) will be used when an encrypted download is encountered.
The latter will also suppress the on-the-fly encryption detection.
2013-01-02 23:05:15 +01:00
ShyPike
c91291c315 Don't retry an empty but correct NZB retrieved from an indexer.
Also add special option "warn_empty_nzb" to control warning about empty NZBs.
2013-01-02 19:37:49 +01:00
shypike
a2a5a1f8e4 Make sure "Abort" error message ends up in download report. 2013-01-01 22:04:24 +01:00
shypike
7651f709ad API functions "addfile" and "addlocalfile" now support "nzbname" parameter for ZIP files with single NZB.
Also, prevent crash on calling "cat_convert" in those api functions (undefined).
2012-12-31 14:11:09 +01:00
shypike
a565077348 Update translations 2012-12-31 12:27:13 +01:00
shypike
6cf99e7d3a Add handling of an extra par2 error message. 2012-12-30 15:06:35 +01:00
shypike
f730a82005 Check for IPv6 connectivity should not use specific exceptions. 2012-12-30 13:16:46 +01:00
ShyPike
5449607c1d Update POT file. 2012-12-28 14:12:36 +01:00
ShyPike
c62415abfd Add "Abort" option to encryption detection.
The option pause_on_pwrar gets an extra value (2) which will
abort an encrypted job. Retrying the job will disable the check.
2012-12-28 14:10:53 +01:00
ShyPike
dcbea3057c Register removed articles and list in download report. 2012-12-28 13:23:34 +01:00
ShyPike
91642d16c8 Update POT file. 2012-12-27 22:26:33 +01:00
ShyPike
2f2773149d Fix missing Retry link for "Out of retention" jobs. 2012-12-27 22:26:05 +01:00
ShyPike
adaba03f50 Option to terminate download if too much data is missing.
Option 'fail_hopeless' Config->Switches.
On-the-fly check for possible completion after each file is processed.
Abort if it's no longer possible to download at least 99% of total data (payload + par2).
Don't do the check when retrying from History.
2012-12-27 21:56:00 +01:00
shypike
58a5e09540 Prevent web-watchdog from crashing when using Python 2.5 2012-12-27 11:28:06 +01:00
shypike
20dc906095 Support servers that don't support STAT and BODY commands.
When server sends error 500, use alternative "HEADER" and "ARTICLE" instead,
which are less efficient.
2012-12-24 20:12:01 +01:00
shypike
e2f41d3761 Add special "wait_for_dfolder", will wait for "temp download folder" at startup.
At startup, wait for the temporary download folder to come on line.
Supports situations where external drives are used, which do not mount before
SABnzbd starts up.
2012-12-23 12:11:18 +01:00
ShyPike
ab1372c7fc Prevent crash in DateSorter. 2012-12-20 18:46:45 +01:00
ShyPike
e305678cf4 In Sorting the %fn substitution sometimes fails to rename the file properly.
The Sorting code assumed that the file name used as the source for %fn
is always a base name. Sometimes it can be a full name. Deal with it.
2012-12-19 21:51:00 +01:00
91 changed files with 14664 additions and 11309 deletions

View File

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

View File

@@ -1,3 +1,98 @@
-------------------------------------------------------------------------------
0.7.17Beta2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix regression errors in Beta1
-------------------------------------------------------------------------------
0.7.17Beta1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Add command line option --pidfile
- Another fix for false encryption reports
- Fix issue with OSX Mavericks Notification Center
- Add support for 'x-dnzb-propername', 'x-dnzb-episodename', 'x-dnzb-year'
in meta-data of NZB. To be used in TV Sorting
- Add OZnzb features need to be enabled in config ->switches
- Add integration with OZnzb indexer enhanced functionality, allows user access to ratings and reporting directly from SABnzbd interface.
- Add automatic feedback to OZnzb on failed downloads (if enabled)
- Add X-DNZB-Failure and X-DNZB-Details support
- Fix issue with passwords embedded in file names
- Updated translations
-------------------------------------------------------------------------------
0.7.16Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix Config->Special UI crash
-------------------------------------------------------------------------------
0.7.15Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix false encryption alarms for some posts
- Add "password" dialog to Plush's job details page
- Add special "sanitize_safe" to remove bad Windows characters on other platforms
- Remove "news" section from Config skin
- Fix for faulty par2cmdline on some embbeded Unix systems
- Add GUID fields to the History RSS feed.
-------------------------------------------------------------------------------
0.7.14Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Another encryption detection fix (special case)
- Missing mini-par2 sometimes prevents the other par2 files from being downloaded.
- Make sure even invalid RAR files are fed to unrar and handle its reporting.
-------------------------------------------------------------------------------
0.7.13Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Another encryption detection fix
- Special option "enable_recursion" to control recursive unpacking
- When post has just one par2 set, use wildcard so that all files are used
- Accept partial par2 file when only one is available
- Accept "nzbname" parameter in api-call "add url" even when a ZIP file is retrieved.
-------------------------------------------------------------------------------
0.7.12Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix issue in encryption detection
- Don't try to "join" a single X.000 file
- Fix memory overflow caused by very large files to be joined
- Make name sorting of the queue case-insensitive
- Save data to disk after changing job password or other attributes
- Add "resume_pp" entry to Plush pull-down menu when pause_pp event is scheduled
- Deploy "abort when completion not possible" method also in pre-download check
-------------------------------------------------------------------------------
0.7.11Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Bad articles from some servers were accepted as valid data
- Show warning when the decoder encounters I/O errors
- Generic Sort failed to rename files when an extra folder level was present in the RAR files
- Obfuscated file name support caused regular NZBs to verify slower
-------------------------------------------------------------------------------
0.7.10Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Disable obsolete newzbin bookmark readout
- Show speed when downloading in Forced mode while paused
- Plush History icons repair and unpack were swapped
- Try to repair rar/par sets with obfuscated names
- Reset "today" byte counters at midnight even when idle
- Display next RSS scan moment in Cfg->RSS
- An email about a failed should say that the download failed
- Report errors coming from fully encrypted rar files
- Accept NNTP error 400 without "too many connection" clues as a transient error.
- Accept %fn (next to %fn.%ext) as end parameter in sorting strings.
-------------------------------------------------------------------------------
0.7.9Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix fatal error in decoder when encountering a malformed article
- Fix compatibility with free.xsusenet.com
- Small fix in smpl-black CSS
-------------------------------------------------------------------------------
0.7.8Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix problem with %fn substitution in Sorting
- Add special "wait_for_dfolder", enables waiting for external temp download folder
- Work-around for servers that do not support STAT command
- Removed articles are now listed seperately in download report
- Add "abort" option to encryption detection
- Fix missing Retry link for "Out of retention" jobs.
- Option to abort download when it is clear that not enough data is available
- Support "nzbname" parameter in addfile/addlocalfile api calls for
ZIP files with a single NZB
- Support NZB-1.1 meta data "password" and "category"
- Don't retry an empty but correct NZB from an indexer
-------------------------------------------------------------------------------
0.7.7Final by The SABnzbd-Team
-------------------------------------------------------------------------------

View File

@@ -1,5 +1,5 @@
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2013 by "The SABnzbd-team" <team@sabnzbd.org>
The SABnzbd-team is:
@@ -7,6 +7,7 @@ Active team:
ShyPike <shypike@sabnzbd.org>
inpheaux <inpheaux@sabnzbd.org>
zoggy <zoggy@sabnzbd.org>
OZnzb-dev <sabdev@oznzb.com>
Sleeping members
sw1tch <switch@sabnzbd.org>
pairofdimes <pairofdimes@sabnzbd.org>

View File

@@ -1,10 +1,10 @@
SABnzbd 0.7.7
SABnzbd 0.7.17
-------------------------------------------------------------------------------
0) LICENSE
-------------------------------------------------------------------------------
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2013 by "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

View File

@@ -1,4 +1,4 @@
(c) Copyright 2007-2012 by "The SABnzbd-team" <team@sabnzbd.org>
(c) Copyright 2007-2013 by "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

View File

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

View File

@@ -1,15 +1,19 @@
Release Notes - SABnzbd 0.7.7
===============================
Release Notes - SABnzbd 0.7.17Beta2
=====================================
## Features
- Updated unrar to 4.20 (OSX/Windows)
- Optional web-ui watchdog (diagnostic tool)
- Removed account support for defunct indexers
- Support for more meta-data in NZB files (to be used in TV Sort)
- Optional integration with the OZnzb indexer, allows user access to ratings and reporting directly from SABnzbd interface.
- Optional automatic feedback to OZnzb on failed downloads
- Commandline option --pidfile to set your own PID-file name/path (Unix and OSX)
- Basic support for X-DNZB-Failure and X-DNZB-Details headers, sent by some 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
- Fix false encryption alarms for some posts
- Fix issue with OSX Mavericks Notification Center support
- Fix issue with passwords embedded in file names
## What's new in 0.7.0
@@ -39,7 +43,7 @@ Release Notes - SABnzbd 0.7.7
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-2013 by "The SABnzbd-team" \<team@sabnzbd.org\>
### IMPORTANT INFORMATION about release 0.7.x

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python -OO
# Copyright 2008-2012 The SABnzbd-Team <team@sabnzbd.org>
# Copyright 2008-2013 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
@@ -240,7 +240,8 @@ def print_help():
print " -d --daemon Use when run as a service"
else:
print " -d --daemon Fork daemon process"
print " --pid <path> Create a PID file in the listed folder (full path)"
print " --pid <path> Create a PID file in the given folder (full path)"
print " --pidfile <path> Create a PID file with the given name (full path)"
print
print " --force Discard web-port timeout (see Wiki!)"
print " -h --help Print this message"
@@ -260,7 +261,7 @@ def print_version():
print """
%s-%s
Copyright (C) 2008-2012, The SABnzbd-Team <team@sabnzbd.org>
Copyright (C) 2008-2013, The SABnzbd-Team <team@sabnzbd.org>
SABnzbd comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. It is licensed under the
@@ -843,7 +844,7 @@ def commandline_handler(frozen=True):
'weblogging=', 'server=', 'templates', 'no_ipv6',
'template2', 'browser=', 'config-file=', 'force',
'version', 'https=', 'autorestarted', 'repair', 'repair-all',
'log-all', 'no-login', 'pid=', 'new', 'sessions', 'console',
'log-all', 'no-login', 'pid=', 'new', 'sessions', 'console', 'pidfile=',
# Below Win32 Service options
'password=', 'username=', 'startup=', 'perfmonini=', 'perfmondll=',
'interactive', 'wait=',
@@ -915,6 +916,7 @@ def main():
no_login = False
re_argv = [sys.argv[0]]
pid_path = None
pid_file = None
new_instance = False
force_sessions = False
osx_console = False
@@ -997,6 +999,10 @@ def main():
pid_path = arg
re_argv.append(opt)
re_argv.append(arg)
elif opt in ('--pidfile',):
pid_file = arg
re_argv.append(opt)
re_argv.append(arg)
elif opt in ('--new',):
new_instance = True
elif opt in ('--sessions',):
@@ -1545,8 +1551,8 @@ def main():
# Write URL directly to registry
set_connection_info(api_url)
if pid_path:
sabnzbd.pid_file(pid_path, cherryport)
if pid_path or pid_file:
sabnzbd.pid_file(pid_path, pid_file, cherryport)
# Start all SABnzbd tasks
logging.info('Starting %s-%s', sabnzbd.MY_NAME, sabnzbd.__version__)

View File

@@ -1,5 +1,5 @@
#
# Copyright 2008-2012 The SABnzbd-Team <team@sabnzbd.org>
# Copyright 2008-2013 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

View File

@@ -31,7 +31,15 @@ $T('thisWeek'): $week_size&nbsp;&nbsp;|&nbsp;&nbsp;$T('thisMonth'): $month_size
<% from sabnzbd.misc import time_format %>
<!--#if $lines#-->
<table id="historyTable">
<tr><th></th><th>$T('completed')</th><th>$T('name')</th><th>$T('size')</th><th>$T('status')</th><th></th></tr>
<tr>
<th></th>
<th>$T('completed')</th>
<th>$T('name')</th>
<th>$T('size')</th>
<th>$T('status')</th>
<!--#if $rating_enable#--><th>Rating</th><!--#end if#-->
<th></th>
</tr>
<!--#set $odd = False#-->
<!--#for $line in $lines #-->
<%
@@ -44,7 +52,20 @@ compl = datetime.datetime.fromtimestamp(float(line['completed'])).strftime(time_
</a></td>
<td>$compl</td>
<td>$line.name<!--#if $line.action_line#--> - $line.action_line<!--#else if $line.fail_message#--> - <span class="fail_message">$line.fail_message</span><!--#end if#--></td>
<td>$line.size</td><td>$Tx('post-'+$line.status)</td>
<td>$line.size</td>
<td>$Tx('post-'+$line.status)</td>
<!--#if $rating_enable#-->
<!--#if $line.has_rating#-->
<td><div class="rating_overall">$T('video')&nbsp;$line.rating_avg_video $T('audio')&nbsp;$line.rating_avg_audio</div>
<form method="GET" action="./show_edit_rating">
<input type="hidden" name="job" value="$line.nzo_id">
<input type="hidden" name="session" value="$session">
<input type="submit" value="$T('report')">
</form></td>
<!--#else#-->
<td></td>
<!--#end if#-->
<!--#end if#-->
<td>
<!--#if not $line.loaded#-->
<!--#if $line.retry#-->
@@ -66,6 +87,41 @@ compl = datetime.datetime.fromtimestamp(float(line['completed'])).strftime(time_
<!--#end if#-->
</td>
</tr>
<!--#if $line.edit_rating#-->
<!--#set $oddLine = not False#-->
<tr class="<!--#if $oddLine then "oddLine" else "evenLine"#-->"><td></td><td></td>
<td colspan="3">
<form action="action_edit_rating" method="post" enctype="multipart/form-data">
<input type="hidden" value="$line.nzo_id" name="job">
<input type="hidden" value="$session" name="session" >
<div class="rating_item">$T('video')&nbsp;
<select name="video">
<!--#if not $line.rating_user_video#--><option>-</option><!--#end if#-->
<!--#for $val in $range(1, 11)#--><option <!--#if $line.rating_user_video==$val#-->selected<!--#end if#--> >$val</option><!--#end for#-->
</select>
</div>
<div class="rating_item">$T('audio')&nbsp;
<select name="audio">
<!--#if not $line.rating_user_audio#--><option>-</option><!--#end if#-->
<!--#for $val in $range(1, 11)#--><option <!--#if $line.rating_user_audio==$val#-->selected<!--#end if#--> >$val</option><!--#end for#-->
</select>
</div>
<div class="rating_item">
<input type="radio" name="rating_flag" value="spam">&nbsp;$T('spam')
<input type="radio" name="rating_flag" value="encrypted">&nbsp;$T('encrypted')
<input type="radio" name="rating_flag" value="expired">&nbsp;$T('expired')
<input type="text" name="expired_host" style="margin-left:10px" value="<$T('host')>">
</div>
<div class="rating_item">
<input type="submit" name="send" value="$T('send')">
<input type="submit" name="cancel" value="$T('cancel')">
</div>
</form>
</td>
<td></td>
<td></td>
</tr>
<!--#end if#-->
<!--#if $line.show_details#-->
<!--#set $oddLine = not False#-->
<tr class="<!--#if $oddLine then "oddLine" else "evenLine"#-->"><td></td><td></td>
@@ -91,6 +147,7 @@ compl = datetime.datetime.fromtimestamp(float(line['completed'])).strftime(time_
</dl>
</td>
<td></td>
<!--#if $rating_enable#--><td></td><!--#end if#-->
</tr>
<!--#end if#-->
<!--#end for#-->

View File

@@ -136,3 +136,12 @@ color:black;
.feedEnabled{color:green;}
.feedDisabled{color:red;}
.rating_overall {
margin:0px 5px 3px 0px;
}
.rating_item {
float:left;
margin:5px 15px 5px 5px;
}

View File

@@ -1,7 +1,7 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>SABnzbd $version - $T('queued'): $mbleft $T('MB')</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

View File

@@ -17,18 +17,14 @@
<tr class="alt"><td class="infoTableHeader">$T('menu-forums') </td><td class="infoTableCell"><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td></tr>
<tr><td class="infoTableHeader">$T('source') </td><td class="infoTableCell"><a href="https://github.com/sabnzbd/sabnzbd" target="_blank">https://github.com/sabnzbd/sabnzbd</a></td></tr>
<tr class="alt"><td class="infoTableHeader">$T('menu-irc') </td><td class="infoTableCell"><a href="irc://irc.synirc.net/#sabnzbd"><i>#sabnzbd</i> on <i>irc.synirc.net</i></a> $T('or') (<a href="http://sabnzbd.org/live-chat/" target="_blank">webchat</a>)</td></tr>
<tr><td class="infoTableHeader">$T('oznzb')</td><td class="infoTableCell"><a href="https://www.oznzb.com/register" target="_blank">https://www.oznzb.com/register</a></td></tr>
</tbody>
</table>
</div>
<div class="padding alt">
<h5 class="copyright">Copyright &copy; 2008-2012 The SABnzbd Team &lt;<span style="color: #0000ff;">team@sabnzbd.org</span>&gt;</h5>
<h5 class="copyright">Copyright &copy; 2008-2013 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

@@ -95,6 +95,7 @@
<label class="config narrow" for="rss_rate">$T('opt-rss_rate')</label>
<input type="number" name="rss_rate" id="rss_rate" value="$rss_rate" size="8" min="15" max="1440" />
<input type="submit" value="$T('button-save')" />
<span class "config narrow">&nbsp;&nbsp;$T('Next scan at:')&nbsp;$rss_next</span>
<span class="desc narrow">$T('explain-rss_rate')</span>
</div>
</fieldset>

View File

@@ -83,6 +83,11 @@
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<div class="field-pair">
<label class="config" for="fail_hopeless">$T('opt-fail_hopeless')</label>
<input type="checkbox" name="fail_hopeless" id="fail_hopeless" value="1" <!--#if int($fail_hopeless) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-fail_hopeless')</span>
</div>
<div class="field-pair alt">
<label class="config" for="pre_check">$T('opt-pre_check')</label>
<input type="checkbox" name="pre_check" id="pre_check" value="1" <!--#if int($pre_check) > 0 then 'checked="checked"' else ""#--> />
@@ -104,7 +109,11 @@
</div>
<div class="field-pair">
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
<input type="checkbox" name="pause_on_pwrar" id="pause_on_pwrar" value="1" <!--#if int($pause_on_pwrar) > 0 then 'checked="checked"' else ""#--> />
<select name="pause_on_pwrar" id="pause_on_pwrar">
<option value="0" <!--#if int($pause_on_pwrar) == 0 then 'selected="selected" class="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="1" <!--#if int($pause_on_pwrar) == 1 then 'selected="selected" class="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="2" <!--#if int($pause_on_pwrar) == 2 then 'selected="selected" class="selected"' else ""#--> >$T('abort')</option>
</select>
<span class="desc">$T('explain-pause_on_pwrar')</span>
</div>
<div class="field-pair alt">
@@ -296,6 +305,33 @@
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<div class="section">
<div class="col2">
<h3>$T('swtag-indexing')</h3>
</div><!-- /col2 -->
<div class="col1">
<fieldset>
<div class="field-pair">
<label class="config" for="rating_enable">$T('opt-rating_enable')</label>
<input type="checkbox" name="rating_enable" id="rating_enable" value="1" <!--#if int($rating_enable) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-rating_enable')</span>
</div>
<div class="field-pair alt">
<label class="config" for="rating_api_key">$T('opt-rating_api_key')</label>
<input type="text" name="rating_api_key" id="rating_api_key" value="$rating_api_key" size="35" />
<span class="desc">$T('explain-rating_api_key')</span>
</div>
<div class="field-pair">
<label class="config" for="rating_feedback">$T('opt-rating_feedback')</label>
<input type="checkbox" name="rating_feedback" id="rating_feedback" value="1" <!--#if int($rating_feedback) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-rating_feedback')</span>
</div>
<div class="field-pair">
<input type="submit" value="$T('button-saveChanges')" class="saveButton" />
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<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

@@ -14,6 +14,7 @@
<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="${path}rss?mode=history"/>
<link rel="stylesheet" type="text/css" href="${path}static/stylesheets/jqueryui/overcast/jquery-ui-1.8.15.custom.css?$version"/>
<link rel="stylesheet" type="text/css" href="${path}static/stylesheets/rateit/rateit.css"/>
#if $color_scheme#
<link rel="shortcut icon" type="image/ico" href="${path}static/stylesheets/colorschemes/$color_scheme/images/sabnzbdplus.ico"/>
<link rel="stylesheet" type="text/css" href="${path}static/stylesheets/colorschemes/$color_scheme/${color_scheme}.css?$version"/>

View File

@@ -1,3 +1,21 @@
<script type="text/javascript">
function expired_host_changed(self) {
var host = document.getElementsByName('expired_host')[0];
host.value = self.value;
host.readOnly = self.value.length > 0;
}
function flag_modal_submit(self) {
var radios = document.getElementsByName('rating_flag');
for (var i = 0; i < radios.length; i++) {
if (radios[i].checked) {
document.getElementById('noopt').setAttribute('style', 'display:none;size:1');
document.getElementById('submitbtn').click();
return;
}
}
document.getElementById('noopt').removeAttribute('style');
}
</script>
<!-- modals -->
<div style='display:none'>
@@ -10,7 +28,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;team@sabnzbd.org&gt;</small></p>
<p><small>Copyright (C) 2008-2013, The SABnzbd Team &lt;team@sabnzbd.org&gt;</small></p>
<p><small>$T('yourRights')</small></p>
</div>
@@ -142,6 +160,51 @@ $T('Plush-containerWidth'):
<input type="submit" id="delete_nzb_modal_remove_files" value="$T('removeNZB-Files')" class="juiButton" />
</div>
<div id="flag_modal">
<input type="hidden" id="flag_modal_job" />
<div class="rating_flag_radio"><input type="radio" name="rating_flag" value="spam">&nbsp;$T('spam')</div>
<div class="rating_flag_radio"><input type="radio" name="rating_flag" value="encrypted">&nbsp;$T('encrypted')</div>
<div class="rating_flag_radio">
<input type="radio" name="rating_flag" value="expired">&nbsp;$T('expired')
<div class="rating_modal_extra">
<div class="rating_modal_expired">$T('host')&nbsp;&nbsp;<input type="text" name="expired_host" value="www.altopia.com" readonly></div>
<select name="common_host" onchange="expired_host_changed(this)">
<option value='www.altopia.com' selected>Altopia</option>
<option value='www.astraweb.com'>Astraweb</option>
<option value='www.euroaccess.ln'>EuroAccess</option>
<option value='www.forteinc.com'>Forte Agent</option>
<option value='www.giganews.com'>Giganews</option>
<option value='www.highwinds.com'>Highwinds</option>
<option value='www.newsdemon.com'>Newsdemon</option>
<option value='www.newsgroupdirect.com'>NewsGroupDirect</option>
<option value='www.newshosting.com'>NewsHosting</option>
<option value='www.readnews.com'>Readnews</option>
<option value='www.supernews.com'>SuperNews</option>
<option value='www.thundernews.com'>ThunderNews</option>
<option value='www.tweaknews.eu'>Tweaknews</option>
<option value='www.usenetserver.com'>UsenetServer</option>
<option value='www.xentech.net'>XenTech</option>
<option value='www.xsnews.nl'>XSnews</option>
<option value=''>$T('other')</option>
</select>
</div>
</div>
<div class="rating_flag_radio">
<input type="radio" name="rating_flag" value="other">&nbsp;$T('otherProblem')
<div class="rating_modal_extra"><input style="width:99%" type="text" name="other"></div>
</div>
<div class="rating_flag_radio">
<input type="radio" name="rating_flag" value="comment">&nbsp;$T('comment')
<div class="rating_modal_extra"><input style="width:99%" type="text" name="comment"></div>
</div>
<br/>
<div class="center">
<input id="submitbtn" type="submit" style="display:none;size:1"/>
<input value="Send" class="juiButton" onclick="flag_modal_submit(this)"/>
<label id="noopt" class="rating_modal_noopt" style="display:none;size:1">No option selected</label>
</div>
</div>
#end if#
</div>

View File

@@ -24,7 +24,7 @@
<td class="nzb_status_col">
&nbsp;<div class="nzb_status <!--#if $line.action_line or $line.status=="Queued"#-->Loaded<!--#else if $line.status=="Failed"#-->main_sprite_container sprite_hv_error<!--#else#-->main_sprite_container sprite_hv_star<!--#end if#-->">&nbsp;</div>
</td>
<td class="historyTitle">
<td class="historyTitle" <!--#if $rating_enable#-->style="width:35%"<!--#end if#-->>
<a href="scriptlog?name=$line.nzo_id" class="modal-detail" rel="details">$line.name</a>
<div style="display:none">
@@ -106,6 +106,44 @@
<!--#end if#-->
</td>
<!--#if $rating_enable#-->
<!--#if $line.has_rating#-->
<td>
<div class="rating_stars_block_r">
<div class="rating_stars">
<div class="rating_icon_vision"></div><span class="avg_rate" value="$line.rating_avg_video"></span>
<input class="user_combo" type="hidden" value="$line.rating_user_video">
<select class="user_combo video" style="background:transparent">
<!--#if not $line.rating_user_video#--><option>-</option><!--#end if#-->
<!--#for $val in $range(1, 11)#--><option>$val</option><!--#end for#-->
</select>
</div>
<div class="rating_stars">
<div class="rating_icon_sound"></div><span class="avg_rate" value="$line.rating_avg_audio"></span>
<input class="user_combo" type="hidden" value="$line.rating_user_audio">
<select class="user_combo audio" style="background:transparent">
<!--#if not $line.rating_user_audio#--><option>-</option><!--#end if#-->
<!--#for $val in $range(1, 11)#--><option>$val</option><!--#end for#-->
</select>
</div>
</div>
</td>
<td>
<div class="rating_vote_block">
<div class="rating_icon_thumbup user_vote up"></div>
<!--#if $line.rating_user_vote==1#--><b><!--#end if#-->$line.rating_avg_vote_up<!--#if $line.rating_user_vote==1#--></b><!--#end if#-->
<div class="rating_icon_thumbdown user_vote down"></div>
<!--#if $line.rating_user_vote==2#--><b><!--#end if#-->$line.rating_avg_vote_down<!--#if $line.rating_user_vote==2#--></b><!--#end if#-->
</div>
<div class="rating_flag">
<a href="#" class="show_flags">$T('report')</a>
</div>
</td>
<!--#else#-->
<td></td><td></td>
<!--#end if#-->
<!--#end if#-->
<td class="options nowrap">
<!--#if not $line.loaded#-->
<% d = datetime.datetime.fromtimestamp(float(line['completed'])) %>

View File

@@ -28,6 +28,7 @@
<!--#if $have_quota#--><li><a id="reset_quota_now" class="pointer">$T('link-resetQuota')</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#-->
<!--#if $pp_pause_event#--><li><a id="resume_pp" class="pointer">$T('sch-resume_post')</a></li><!--#end if#-->
<li><a id="topmenu_toggle" class="pointer">$T('Plush-topMenu')</a></li>
<li><a id="multiops_toggle" class="pointer">$T('Plush-multiOperations')</a></li>
<li>

View File

@@ -6,7 +6,8 @@
<form action="save" method="post" class="nzo_save_form">
<input type="hidden" name="session" value="$session">
<input type="text" name="name" size="70" value="$slot.filename" />
<input type="text" name="name" size="70" value="$slot.filename_clean" />
<input type="text" name="password" size="15" value="$slot.password" placeholder="$T('srv-password')"/>
<div>
<select name="index"><optgroup label="$T('order')">

View File

@@ -55,10 +55,27 @@
<% # <!--#else if $slot.status == "Downloading"#-->main_sprite_container sprite_ql_grip_active %>
</td>
<td class="download-title">
<td class="download-title" <!--#if $rating_enable#-->style="width:35%"<!--#end if#-->>
<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>
<!--#if $rating_enable#-->
<!--#if $slot.has_rating#-->
<td>
<div class="rating_stars_block_c">
<div class="rating_stars">
<div class="rating_icon_vision"></div><span class="avg_rate" value="$slot.rating_avg_video"></span>
</div>
<div class="rating_stars">
<div class="rating_icon_sound"></div><span class="avg_rate" value="$slot.rating_avg_audio"></span>
</div>
</div>
</td>
<!--#else#-->
<td></td>
<!--#end if#-->
<!--#end if#-->
<td>
<div class="main_sprite_container sprite_progressbar_bg">
<div class="main_sprite_container sprite_progress_done" style="background-position: -<!--#if $slot.mb == "0.00" then "120" else int(120 - 120.0 / 100.0 * int(100 - float($slot.mbleft) / float($slot.mb) * 100))#-->px -401px">
@@ -69,7 +86,7 @@
</td>
<td class="eta nowrap">
<!--#if not $paused and $slot.status not in ("Paused", "Checking")#-->
<!--#if (not $paused and $slot.status not in ("Paused", "Checking")) or $slot.priority == 'Force'#-->
<span title="$slot.eta">$slot.timeleft&nbsp;$T('Plush-left')</span>
<!--#else#-->
$T('post-'+$slot.status)

View File

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ jQuery(function($){
// ***************************************************************
// Plush defaults
refreshRate: $.cookie('plushRefreshRate') ? $.cookie('plushRefreshRate') : 30, // refresh rate in seconds
refreshRate: $.cookie('plushRefreshRate') ? $.cookie('plushRefreshRate') : 4, // refresh rate in seconds
containerWidth: $.cookie('plushContainerWidth') ? $.cookie('plushContainerWidth') : '100%', // width of all elements on page
queuePerPage: $.cookie('plushQueuePerPage') ? $.cookie('plushQueuePerPage') : 5, // pagination - nzbs per page
histPerPage: $.cookie('plushHistPerPage') ? $.cookie('plushHistPerPage') : 5, // pagination - nzbs per page
@@ -16,7 +16,7 @@ jQuery(function($){
confirmDeleteHistory: $.cookie('plushConfirmDeleteHistory') == 0 ? false : true, // confirm history nzb removal
blockRefresh: $.cookie('plushBlockRefresh') == 0 ? false : true, // prevent refreshing when hovering queue
failedOnly: $.cookie('plushFailedOnly') == 1 ? 1 : 0, // prevent refreshing when hovering queue
multiOps: $.cookie('plushMultiOps') == 1 ? true : false, // is multi-operations menu visible in queue
multiOps: $.cookie('plushMultiOps') == 0 ? false : true, // is multi-operations menu visible in queue
noTopMenu: $.cookie('plushNoTopMenu') == 1 ? false : true, // is top menu visible
multiOpsChecks: null,
@@ -329,6 +329,17 @@ jQuery(function($){
});
});
// Resume Post Processing
$('#resume_pp').click(function() {
$.ajax({
headers: {"Cache-Control": "no-cache"},
type: "POST",
url: "tapi",
data: {mode:'resume_pp', apikey: $.plush.apikey},
success: $.plush.RefreshQueue
});
});
$('#multiops_toggle').click(function(){
if( $('#multiops_bar').is(':visible') ) { // hide
$('#multiops_bar').hide();
@@ -1036,6 +1047,12 @@ $("a","#multiops_inputs").click(function(e){
title:function(){return $(this).text();},
innerWidth:"80%", innerHeight:"300px", initialWidth:"80%", initialHeight:"300px", speed:0, opacity:0.7 });
// modal for reporting issues
$("#historyTable .modal-report").colorbox({ inline:true,
href: function(){return "#report-"+$(this).parent().parent().parent().attr('id');},
title:function(){return $(this).text();},
innerWidth:"250px", innerHeight:"110px", initialWidth:"250px", initialHeight:"110px", speed:0, opacity:0.7 });
// Build pagination only when needed
if ($.plush.histPerPage=="1") // disabled history
$("#history-pagination").html(''); // remove pages if history empty
@@ -1062,6 +1079,55 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
}); // end livequery
$('.user_combo').livequery('change', function(){
var nzo_id = $(this).parent().parent().parent().parent().attr('id');
var videoAudio = $(this).hasClass('video') ? 'video' : 'audio';
$.ajax({
headers: {"Cache-Control": "no-cache"},
type: "POST",
url: "tapi",
data: {mode:'queue', name:'rating', value: nzo_id, type: videoAudio, setting: $(this).val(), apikey: $.plush.apikey},
success: $.plush.RefreshHistory
});
});
$('.user_vote').livequery('click', function(){
var nzo_id = $(this).parent().parent().parent().attr('id');
var upDown = $(this).hasClass('up') ? 'up' : 'down';
$.ajax({
headers: {"Cache-Control": "no-cache"},
type: "POST",
url: "tapi",
data: {mode:'queue', name:'rating', value: nzo_id, type: 'vote', setting: upDown, apikey: $.plush.apikey},
success: $.plush.RefreshHistory
});
});
$('#history .show_flags').live('click', function(){
$('#flag_modal_job').val( $(this).parent().parent().parent().attr('id') );
$.colorbox({ inline:true, href:"#flag_modal", title:$(this).text(),
innerWidth:"500px", innerHeight:"185px", initialWidth:"500px", initialHeight:"185px", speed:0, opacity:0.7
});
return false;
});
$('#flag_modal input:submit').click(function(){
var nzo_id = $('#flag_modal_job').val();
var flag = $('input[name=rating_flag]:checked', '#flag_modal').val();
var expired_host = $('input[name=expired_host]', '#flag_modal').val();
var other = $('input[name=other]', '#flag_modal').val();
var comment = $('input[name=comment]', '#flag_modal').val();
var _detail = (flag == 'comment') ? comment : ((flag == 'other') ? other : expired_host);
$.colorbox.close();
$.plush.modalOpen=false;
$.ajax({
headers: {"Cache-Control": "no-cache"},
type: "POST",
url: "tapi",
data: {mode:'queue', name:'rating', value: nzo_id, type: 'flag', setting: flag, detail: _detail, apikey: $.plush.apikey},
success: $.plush.RefreshHistory
});
});
}, // end $.plush.InitHistory()
@@ -1121,6 +1187,8 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
$('.left_stats .initial-loading').hide();
$('#queue').html(result); // Replace queue contents with queue.tmpl
$('#queue .avg_rate').rateit({readonly: true, resetable: false, step: 0.5});
$('#queue .avg_rate').each(function() { $(this).rateit('value', $(this).attr('value') / 2); });
if ($.plush.multiOps) // add checkboxes
$('<input type="checkbox" class="multiops" />').appendTo('#queue tr td.nzb_status_col');
@@ -1176,6 +1244,11 @@ $.plush.histprevslots = $.plush.histnoofslots; // for the next refresh
}
$('.left_stats .initial-loading').hide();
$('#history').html(result); // Replace history contents with history.tmpl
$('#history .avg_rate').rateit({readonly: true, resetable: false, step: 0.5});
$('#history .avg_rate').each(function() { $(this).rateit('value', $(this).attr('value') / 2); });
$('#history .user_combo option').filter(function() {
return $(this).attr('value') == $(this).parent().parent().find('input.user_combo').attr('value');
}).attr('selected', true);
$('#history-pagination span').removeClass('loading'); // Remove spinner graphic from pagination
}
});
@@ -1242,7 +1315,7 @@ $.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 < 100 && $.plush.paused) {
$('#stats_eta').html('&mdash;');
$('#stats_speed').html('&mdash;');
$('#time-left').attr('title','&mdash;'); // Tooltip on "time left"

View File

@@ -401,12 +401,12 @@ body {
.sprite_hv_download{ background-position: 0 -167px; }
.sprite_hv_error{ background-position: 0 -193px; }
.sprite_hv_filejoin{ background-position: 0 -219px; }
.sprite_hv_repair{ background-position: 0 -245px; }
.sprite_hv_unpack{ background-position: 0 -245px; }
.sprite_hv_report{ background-position: 0 -271px; }
.sprite_hv_script{ background-position: 0 -297px; }
.sprite_hv_star{ background-position: 0 -323px; }
.sprite_hv_stats{ background-position: 0 -349px; }
.sprite_hv_unpack{ background-position: 0 -375px; }
.sprite_hv_repair{ background-position: 0 -375px; }
.sprite_progress_done{ background-position: 0 -401px; } /* queue.tmpl */
.sprite_progressbar_bg{ background-position: 0 -430px; }
.sprite_q_menu_pausefor{ background-position: 0 -459px; }
@@ -1073,7 +1073,81 @@ tr:hover .history_added { color: black; }
.pointer { cursor: pointer; }
/* ---------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------
Ratings
------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------- */
.rating_stars_block_r {
text-align: right;
}
.rating_stars_block_c {
text-align: center;
}
.rating_vote_block {
float: left;
}
.rating_stars {
display: inline-block;
margin-right: 10px;
}
.rating_flag {
margin: 4px 10px 0px 85px;
}
.rating_flag_radio {
margin: 5px 15px 5px 5px;
}
.rating_modal_extra {
float: right;
width: 330px
}
.rating_modal_expired {
float: right;
}
.rating_modal_noopt {
color: red;
margin-left: 10px;
}
.rating_icon_vision {
width: 16px;
height: 16px;
display: inline-block;
margin-right: 5px;
background: url('images/vision16.png') no-repeat top center;
}
.rating_icon_sound {
width: 16px;
height: 16px;
display: inline-block;
margin-right: 5px;
background: url('images/sound16.png') no-repeat top center;
}
.rating_icon_thumbup {
width: 20px;
height: 20px;
display: inline-block;
background: url('images/thumbup20.png') no-repeat top center;
}
.rating_icon_thumbdown {
width: 20px;
height: 20px;
display: inline-block;
background: url('images/thumbdown20.png') no-repeat top center;
}
/* ---------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

View File

@@ -0,0 +1,98 @@
.rateit {
display: -moz-inline-box;
display: inline-block;
position: relative;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}
.rateit .rateit-range
{
position: relative;
display: -moz-inline-box;
display: inline-block;
background: url(star.gif);
height: 16px;
outline: none;
}
.rateit .rateit-range * {
display:block;
}
/* for IE 6 */
* html .rateit, * html .rateit .rateit-range
{
display: inline;
}
/* for IE 7 */
* + html .rateit, * + html .rateit .rateit-range
{
display: inline;
}
.rateit .rateit-hover, .rateit .rateit-selected
{
position: absolute;
left: 0px;
}
.rateit .rateit-hover-rtl, .rateit .rateit-selected-rtl
{
left: auto;
right: 0px;
}
.rateit .rateit-hover
{
background: url(star.gif) left -32px;
}
.rateit .rateit-hover-rtl
{
background-position: right -32px;
}
.rateit .rateit-selected
{
background: url(star.gif) left -48px;
}
.rateit .rateit-selected-rtl
{
background-position: right -48px;
}
.rateit .rateit-preset
{
background: url(star.gif) left -16px;
}
.rateit .rateit-preset-rtl
{
background: url(star.gif) left -16px;
}
.rateit button.rateit-reset
{
background: url(delete.gif) 0 0;
width: 16px;
height: 16px;
display: -moz-inline-box;
display: inline-block;
float: left;
outline: none;
border:none;
padding: 0;
}
.rateit button.rateit-reset:hover, .rateit button.rateit-reset:focus
{
background-position: 0 -16px;
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -24,15 +24,15 @@ border-top: 1px dotted #222;
}
#progressBar {
background-color: #fff;
border: 1px solid #000;
background-color: #fff;
border: 1px solid #000;
}
#progressBartop {
background-color: #fff;
border: 1px solid #ccc;
background-color: #fff;
border: 1px solid #ccc;
}
#percentageBar {
background-color: #4B4545;
background-color: #4B4545;
}
@@ -83,7 +83,7 @@ table{border-spacing:0;}
input, select {
input, select, option {
background-color:#232323;
border-color:#3a3a3a;
color:white;
@@ -110,4 +110,4 @@ span.unselected {
color: white;
background-color:#333;
border: 1px solid #555;
}
}

View File

@@ -0,0 +1,34 @@
<!--#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/>
<!--#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 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

@@ -1,5 +1,6 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>$T('wizard-quickstart')</title>
<link rel="stylesheet" type="text/css" href="static/style.css"/>
<link rel="shortcut icon" href="static/images/favicon.ico" />

View File

@@ -1,34 +1,38 @@
<!--#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/>
<!--#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>
<form action="./four" method="post" autocomplete="off">
<div class="indented bigger">
<h3>Indexer</h3>
<div>$T('explain-rating_enable')</div>
<div>$T('wizard-create-account')<a href="https://www.oznzb.com/register" target="_blank">https://www.oznzb.com/register</a>.</div>
<br class="clear" />
<input type="checkbox" name="rating_enable" id="rating_enable" value="1" <!--#if $rating_enable == 1 then 'checked="checked"' else ''#-->> <label for="rating_enable">$T('opt-rating_enable')</label><br />
<br class="clear" />
<div>
<label class="label">$T('opt-rating_api_key')</label><input type="text" size="35" value="$rating_api_key" name="rating_api_key" id="rating_api_key">
<div class="tips">$T('tip-rating_api_key')</div>
</div>
<br class="clear" />
</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>
<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>
</table>
</div>
<!--#include $webdir + "/inc_bottom.tmpl"#-->
</form>
<!--#include $webdir + "/inc_bottom.tmpl"#-->

View File

@@ -0,0 +1,30 @@
The Backport of OrderedDict() is coming from ActiveState's Python recipe website.
It has been written by Raymond Hettinger.
Home of the module:
http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/
It is covered by the MIT License.
===================
(c) 2009 Raymond Hettinger
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -39,9 +39,14 @@ build_folders = (
)
# Check presense of all builds
sharepath = os.environ.get('SHARE')
if not (sharepath and os.path.exists(sharepath)):
print 'Build share not defined or not found'
exit(1)
build_paths = []
for build in builds:
path = os.path.join(os.environ['HOME'], 'project/osx/%s-%s.cpio' % (prod, build))
path = os.path.join(sharepath,'%s-%s.cpio' % (prod, build))
if os.path.exists(path):
build_paths.append(path)
else:
@@ -100,4 +105,4 @@ print 'Make image internet-enabled'
os.system("hdiutil internet-enable %s" % fileDmg)
print 'Copy GZ file'
os.system('cp ~/project/osx/%s .' % fileOSr)
os.system('cp "%s" .' % os.path.join(sharepath, fileOSr))

View File

@@ -438,8 +438,8 @@ if target == 'app':
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/")
if OSX_ML and os.path.exists(os.path.join(os.environ['HOME'], 'sabnotifier/SABnzbd.app')):
os.system("cp -pR $HOME/sabnotifier/SABnzbd.app dist/SABnzbd.app/Contents/Resources/")
# Add License files
os.mkdir("dist/SABnzbd.app/Contents/Resources/licenses/")
@@ -448,19 +448,20 @@ if target == 'app':
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)
# Archive result to share, if present
dest_path = os.environ.get('SHARE')
if dest_path and os.path.exists(dest_path):
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)
else:
print 'No SHARE variable set, build result not copied'
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" '
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' % os.path.join(dest_path, fileOSr) )
@@ -611,7 +612,7 @@ else:
os.mkdir(root)
# Set data files
data_files.extend(['po/', 'cherrypy/', 'gntp/'])
data_files.extend(['po/', 'cherrypy/', 'gntp/', 'solaris/'])
options['data_files'] = PairList(data_files)
options['data_files'].append(('tools', ['tools/make_mo.py', 'tools/msgfmt.py']))

View File

@@ -1,6 +1,6 @@
#
# SABnzbd Translation Template file EMAIL
# Copyright (C) 2012 by the SABnzbd Team
# Copyright (C) 2011-2012 by the SABnzbd Team
# team@sabnzbd.org
#
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\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-08-04 05:38+0000\n"
"X-Generator: Launchpad (build 15742)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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: 2011-06-26 10:50+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-12-28 10:58+0000\n"
"Last-Translator: Thomas Lucke (Lucky) <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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""
@@ -189,3 +189,24 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"## Translation by Thomas Lucke (Lucky)\n"
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd konnte eine NZB-Datei nicht herunterladen\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hallo,\n"
"\n"
"SABnzbd konnte die NZB-Datei von $url nicht herrunterladen.\n"
"Die Fehlermeldung war: $msg\n"

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-04-03 09:00+0000\n"
"Last-Translator: shypike <Unknown>\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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-03-18 07:02+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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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: 2011-06-26 10:50+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2013-12-10 20:12+0000\n"
"Last-Translator: shypike <Unknown>\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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""
@@ -190,3 +190,25 @@ msgid ""
"\n"
"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"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd ikke klarte å hente en NZB fil\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Etter dette kommer meldingen, den tomme linjen er nødvendig!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd klarte ikke å hente NZB fra $url.\n"
"Feilmeldingen var: $msg\n"
"\n"
"Hade\n"

View File

@@ -7,15 +7,15 @@ msgid ""
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-09 19:11+0000\n"
"Last-Translator: shypike <Unknown>\n"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2013-11-03 22:34+0000\n"
"Last-Translator: markheloking <markheloking@live.nl>\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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""
@@ -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 sjabloon voor SABnzbd\n"
"## Dit is een Cheetah sjabloon\n"
"## Documentatie: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"## Lege regels en spaties zijn belangrijk!\n"
"##\n"
"## These are the email headers\n"
"## Dit zijn de email koppen\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd heeft $amount opdrachten aan de wachtrij toegevoegd\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"## Hierna komt de inhoud, de lege regel is benodigd!\n"
"\n"
"Hallo,\n"
"\n"
@@ -187,13 +187,13 @@ 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"
"## Ongeldige URL Ophaal Email sjabloon voor SABnzbd\n"
"## Dit is een Cheetah sjabloon\n"
"## Documentatie: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"## Lege regels en spaties zijn belangrijk!\n"
"##\n"
"## These are the email headers\n"
"## Dit zijn de email koppen\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-05-02 09:57+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: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-03-10 04:16+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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-04-16 03:32+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-04-29 05:17+0000\n"
"X-Generator: Launchpad (build 15149)\n"
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

View File

@@ -7,15 +7,15 @@ msgid ""
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"
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
"PO-Revision-Date: 2012-05-15 19:28+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: 2013-12-11 06:16+0000\n"
"X-Generator: Launchpad (build 16869)\n"
#: email/email.tmpl:1
msgid ""

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

@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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"
"PO-Revision-Date: 2012-12-28 11:02+0000\n"
"Last-Translator: Steffen Thomsen <urskov@gmail.com>\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-08-15 05:11+0000\n"
"X-Generator: Launchpad (build 15801)\n"
"X-Launchpad-Export-Date: 2012-12-29 05:12+0000\n"
"X-Generator: Launchpad (build 16378)\n"
#: NSIS_Installer.nsi:425
msgid "Go to the SABnzbd Wiki"
@@ -27,11 +27,11 @@ msgstr "Vis udgivelsesbemærkninger"
#: NSIS_Installer.nsi:429
msgid "Support the project, Donate!"
msgstr "Støtte projektet, donere!"
msgstr "Støt projektet, donér!"
#: NSIS_Installer.nsi:431
msgid "Please close \"SABnzbd.exe\" first"
msgstr "Luk 'SABnzbd.exe' først"
msgstr "Luk venligst \"SABnzbd.exe\" først"
#: NSIS_Installer.nsi:433
msgid ""
@@ -52,7 +52,7 @@ msgstr "Kør ved opstart"
#: NSIS_Installer.nsi:439
msgid "Desktop Icon"
msgstr "Skrivebords ikon"
msgstr "Skrivebordsikon"
#: NSIS_Installer.nsi:441
msgid "NZB File association"
@@ -64,19 +64,19 @@ msgstr "Slet program"
#: NSIS_Installer.nsi:445
msgid "Delete Settings"
msgstr "Slet instillinger"
msgstr "Slet indstillinger"
#: 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 ""
"Dette system kræver, at Microsoft runtime biblioteket VC90, der skal "
"installeres først. Ønsker du at gøre det nu?"
"Dette system kræver, at Microsoft runtime biblioteket VC90 skal installeres "
"først. Ønsker du at gøre det nu?"
#: NSIS_Installer.nsi:449
msgid "Downloading Microsoft runtime installer..."
msgstr "Downloading Microsoft runtime installer..."
msgstr "Downloader Microsoft runtime installationsfil..."
#: NSIS_Installer.nsi:451
msgid "Download error, retry?"
@@ -91,12 +91,13 @@ msgid ""
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
"the previous version or `Cancel` to cancel this upgrade."
msgstr ""
"Du kan ikke overskrive en eksisterende installation. Klik `OK` for at fjerne "
"den tidligere version eller `Annuller` for at annullere denne opgradering."
"Du kan ikke overskrive en eksisterende installation. \\n\\nKlik `OK` for at "
"fjerne den tidligere version eller `Annuller` for at annullere denne "
"opgradering."
#: NSIS_Installer.nsi:457
msgid "Your settings and data will be preserved."
msgstr "Dine indstillinger og data vil blive opbevaret."
msgstr "Dine indstillinger og data vil blive bevaret."
#~ msgid "Start SABnzbd (hidden)"
#~ msgstr "Start SABnzbd"

View File

@@ -8,85 +8,93 @@ msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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"
"PO-Revision-Date: 2013-12-04 12:09+0000\n"
"Last-Translator: Daniel Sebastian <trinitytest@hotmail.com>\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-08-15 05:11+0000\n"
"X-Generator: Launchpad (build 15801)\n"
"X-Launchpad-Export-Date: 2013-12-05 05:57+0000\n"
"X-Generator: Launchpad (build 16863)\n"
#: NSIS_Installer.nsi:425
#: NSIS_Installer.nsi:416
msgid "Go to the SABnzbd Wiki"
msgstr ""
msgstr "Gå til SABnzbd Wiki"
#: NSIS_Installer.nsi:427
#: NSIS_Installer.nsi:418
msgid "Show Release Notes"
msgstr ""
msgstr "Vis versjonsmerknader"
#: NSIS_Installer.nsi:429
#: NSIS_Installer.nsi:420
msgid "Support the project, Donate!"
msgstr ""
msgstr "Støtt prosjektet, donèr!"
#: NSIS_Installer.nsi:431
#: NSIS_Installer.nsi:422
msgid "Please close \"SABnzbd.exe\" first"
msgstr ""
msgstr "Vennligst lukk \"SABnzbd.exe\" først"
#: NSIS_Installer.nsi:433
#: NSIS_Installer.nsi:424
msgid ""
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
msgstr ""
" >>>> ADVARSEL <<<<\\r\\n\\r\\nVennligst sjekk "
"versjonsmerknadene først, eller gå til http://wiki.sabnzbd.org/introducing-0-"
"7-0 !"
#: NSIS_Installer.nsi:435
#: NSIS_Installer.nsi:426
msgid "This will uninstall SABnzbd from your system"
msgstr ""
msgstr "Dette vil avinstallere SABnzbd fra ditt system"
#: NSIS_Installer.nsi:437
#: NSIS_Installer.nsi:428
msgid "Run at startup"
msgstr ""
msgstr "Kjør ved oppstart"
#: NSIS_Installer.nsi:439
#: NSIS_Installer.nsi:430
msgid "Desktop Icon"
msgstr ""
msgstr "Skrivebordsikon"
#: NSIS_Installer.nsi:441
#: NSIS_Installer.nsi:432
msgid "NZB File association"
msgstr ""
msgstr "NZB-filassosiering"
#: NSIS_Installer.nsi:443
#: NSIS_Installer.nsi:434
msgid "Delete Program"
msgstr ""
msgstr "Fjern program"
#: NSIS_Installer.nsi:445
#: NSIS_Installer.nsi:436
msgid "Delete Settings"
msgstr ""
msgstr "Slett innstillinger"
#: NSIS_Installer.nsi:447
#: NSIS_Installer.nsi:438
msgid ""
"This system requires the Microsoft runtime library VC90 to be installed "
"first. Do you want to do that now?"
msgstr ""
"Dette sytemet krever at Microsoft runtime library VC90 er installert først. "
"Ønsker du å gjøre dette nå?"
#: NSIS_Installer.nsi:449
#: NSIS_Installer.nsi:440
msgid "Downloading Microsoft runtime installer..."
msgstr ""
msgstr "Laster ned Microsoft runtime installer..."
#: NSIS_Installer.nsi:451
#: NSIS_Installer.nsi:442
msgid "Download error, retry?"
msgstr ""
msgstr "Nedlasting feilet, prøve på nytt?"
#: NSIS_Installer.nsi:453
#: NSIS_Installer.nsi:444
msgid "Cannot install without runtime library, retry?"
msgstr ""
msgstr "Kan ikke installere uten runtime library, prøve på nytt?"
#: NSIS_Installer.nsi:455
#: NSIS_Installer.nsi:446
msgid ""
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
"the previous version or `Cancel` to cancel this upgrade."
msgstr ""
"Du kan ikke overskrive en eksisterende installasjon. \\n\\nTrykk 'OK' for å "
"fjerne tidligere installasjon, eller 'Avbryt' for å avbryte denne "
"oppgraderingen."
#: NSIS_Installer.nsi:457
#: NSIS_Installer.nsi:448
msgid "Your settings and data will be preserved."
msgstr ""
msgstr "Dine innstillinger og data vil bli tatt vare på."

View File

@@ -8,32 +8,32 @@ msgstr ""
"Project-Id-Version: sabnzbd\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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"
"PO-Revision-Date: 2013-11-03 22:38+0000\n"
"Last-Translator: markheloking <markheloking@live.nl>\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-08-15 05:11+0000\n"
"X-Generator: Launchpad (build 15801)\n"
"X-Launchpad-Export-Date: 2013-11-04 05:55+0000\n"
"X-Generator: Launchpad (build 16820)\n"
#: NSIS_Installer.nsi:425
#: NSIS_Installer.nsi:416
msgid "Go to the SABnzbd Wiki"
msgstr "Ga naar de SABnzbd Wiki"
#: NSIS_Installer.nsi:427
#: NSIS_Installer.nsi:418
msgid "Show Release Notes"
msgstr "Toon vrijgave bericht"
#: NSIS_Installer.nsi:429
#: NSIS_Installer.nsi:420
msgid "Support the project, Donate!"
msgstr "Steun het project, Doneer!"
#: NSIS_Installer.nsi:431
#: NSIS_Installer.nsi:422
msgid "Please close \"SABnzbd.exe\" first"
msgstr "Sluit \"SABnzbd.exe\" eerst af"
#: NSIS_Installer.nsi:433
#: NSIS_Installer.nsi:424
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:435
#: NSIS_Installer.nsi:426
msgid "This will uninstall SABnzbd from your system"
msgstr "Dit verwijdert SABnzbd van je systeem"
#: NSIS_Installer.nsi:437
#: NSIS_Installer.nsi:428
msgid "Run at startup"
msgstr "Opstarten bij systeem start"
#: NSIS_Installer.nsi:439
#: NSIS_Installer.nsi:430
msgid "Desktop Icon"
msgstr "Pictogram op bureaublad"
msgstr "Bureaubladpictogram"
#: NSIS_Installer.nsi:441
#: NSIS_Installer.nsi:432
msgid "NZB File association"
msgstr "NZB bestanden koppelen aan SABnzbd"
#: NSIS_Installer.nsi:443
#: NSIS_Installer.nsi:434
msgid "Delete Program"
msgstr "Verwijder programma"
msgstr "Programma verwijderen"
#: NSIS_Installer.nsi:445
#: NSIS_Installer.nsi:436
msgid "Delete Settings"
msgstr "Verwijder instellingen"
#: NSIS_Installer.nsi:447
#: NSIS_Installer.nsi:438
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:449
#: NSIS_Installer.nsi:440
msgid "Downloading Microsoft runtime installer..."
msgstr "Downloaden van de Microsoft bibliotheek"
#: NSIS_Installer.nsi:451
#: NSIS_Installer.nsi:442
msgid "Download error, retry?"
msgstr "Download mislukt, opnieuw?"
msgstr "Download mislukt, opnieuw proberen?"
#: NSIS_Installer.nsi:453
#: NSIS_Installer.nsi:444
msgid "Cannot install without runtime library, retry?"
msgstr "Installeren heeft geen zin zonder de bibliotheek, opnieuw?"
msgstr "Installeren heeft geen zin zonder de bibliotheek, opnieuw proberen?"
#: NSIS_Installer.nsi:455
#: NSIS_Installer.nsi:446
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:457
#: NSIS_Installer.nsi:448
msgid "Your settings and data will be preserved."
msgstr "Je instellingen en bestanden blijven behouden."

View File

@@ -75,6 +75,7 @@ from sabnzbd.postproc import PostProcessor
from sabnzbd.downloader import Downloader
from sabnzbd.assembler import Assembler
from sabnzbd.newzbin import Bookmarks, MSGIDGrabber
from sabnzbd.rating import Rating
import sabnzbd.misc as misc
import sabnzbd.powersup as powersup
from sabnzbd.dirscanner import DirScanner, ProcessArchiveFile, ProcessSingleFile
@@ -220,6 +221,13 @@ def initialize(pause_downloader = False, clean_up = False, evalSched=False, repa
# New admin folder
misc.remove_all(cfg.admin_dir.get_path(), '*.sab')
### Optionally wait for "incomplete" to become online
if cfg.wait_for_dfolder():
wait_for_download_folder()
else:
cfg.download_dir.set(cfg.download_dir(), create=True)
cfg.download_dir.set_create(True)
### Set access rights for "incomplete" base folder
misc.set_permissions(cfg.download_dir.get_path(), recursive=False)
@@ -312,6 +320,8 @@ def initialize(pause_downloader = False, clean_up = False, evalSched=False, repa
DirScanner()
MSGIDGrabber()
Rating()
URLGrabber()
@@ -347,6 +357,8 @@ def start():
MSGIDGrabber.do.start()
Rating.do.start()
logging.debug('Starting urlgrabber')
URLGrabber.do.start()
@@ -377,6 +389,13 @@ def halt():
except:
pass
logging.debug('Stopping rating')
Rating.do.stop()
try:
Rating.do.join()
except:
pass
logging.debug('Stopping dirscanner')
DirScanner.do.stop()
try:
@@ -502,6 +521,7 @@ def save_state(flag=False):
BPSMeter.do.save()
rss.save()
Bookmarks.do.save()
Rating.do.save()
DirScanner.do.save()
PostProcessor.do.save()
#if flag:
@@ -617,7 +637,7 @@ def add_nzbfile(nzbfile, pp=None, script=None, cat=None, priority=NORMAL_PRIORIT
logging.info("Traceback: ", exc_info = True)
if ext.lower() in ('.zip', '.rar'):
return ProcessArchiveFile(filename, path, pp, script, cat, priority=priority)
return ProcessArchiveFile(filename, path, pp, script, cat, priority=priority, nzbname=nzbname)
else:
return ProcessSingleFile(filename, path, pp, script, cat, priority=priority, nzbname=nzbname, keep=keep, reuse=reuse)
@@ -1030,6 +1050,9 @@ def check_all_tasks():
if not MSGIDGrabber.do.isAlive():
logging.info('Restarting crashed newzbin')
MSGIDGrabber.do.__init__()
if not Rating.do.isAlive():
logging.info('Restarting crashed rating')
Rating.do.__init__()
if not sabnzbd.scheduler.sched_check():
logging.info('Restarting crashed scheduler')
sabnzbd.scheduler.init()
@@ -1044,12 +1067,15 @@ def check_all_tasks():
return True
def pid_file(pid_path=None, port=0):
def pid_file(pid_path=None, pid_file= None, port=0):
""" Create or remove pid file
"""
global DIR_PID
if not sabnzbd.WIN32 and pid_path and pid_path.startswith('/'):
DIR_PID = os.path.join(pid_path, 'sabnzbd-%s.pid' % port)
if not sabnzbd.WIN32:
if pid_path and pid_path.startswith('/'):
DIR_PID = os.path.join(pid_path, 'sabnzbd-%s.pid' % port)
elif pid_file and pid_file.startswith('/'):
DIR_PID = pid_file
if DIR_PID:
try:
@@ -1075,6 +1101,13 @@ def check_incomplete_vs_complete():
cfg.download_dir.set('incomplete')
def wait_for_download_folder():
""" Wait for download folder to become available """
while not cfg.download_dir.test_path():
logging.debug('Waiting for "incomplete" folder')
time.sleep(2.0)
# Required wrapper because nzbstuff.py cannot import downloader.py
def active_primaries():
return sabnzbd.downloader.Downloader.do.active_primaries()

View File

@@ -52,12 +52,13 @@ from sabnzbd.utils.json import JsonWriter
from sabnzbd.utils.pathbrowser import folders_at_path
from sabnzbd.misc import loadavg, to_units, diskfree, disktotal, get_ext, \
get_filename, int_conv, globber, time_format, remove_all, \
starts_with_path
starts_with_path, cat_convert
from sabnzbd.encoding import xml_name, unicoder, special_fixer, platform_encode, html_escape
from sabnzbd.postproc import PostProcessor
from sabnzbd.articlecache import ArticleCache
from sabnzbd.utils.servertests import test_nntp_server_dict
from sabnzbd.newzbin import Bookmarks
from sabnzbd.rating import Rating
from sabnzbd.bpsmeter import BPSMeter
from sabnzbd.database import build_history_info, unpack_history_info, get_history_handle
import sabnzbd.growler
@@ -177,10 +178,11 @@ def _api_queue_delete_nzf(output, value, kwargs):
def _api_queue_rename(output, value, kwargs):
""" API: accepts output, value(=old name), value2(=new name) """
""" API: accepts output, value(=old name), value2(=new name), value3(=password) """
value2 = kwargs.get('value2')
value3 = kwargs.get('value3')
if value and value2:
NzbQueue.do.change_name(value, special_fixer(value2))
NzbQueue.do.change_name(value, special_fixer(value2), special_fixer(value3))
return report(output)
else:
return report(output, _MSG_NO_VALUE2)
@@ -269,6 +271,24 @@ def _api_queue_default(output, value, kwargs):
else:
return report(output, _MSG_NOT_IMPLEMENTED)
def _api_queue_rating(output, value, kwargs):
""" API: accepts output, value(=nzo_id), type, setting, detail """
vote_map = {'up': Rating.VOTE_UP, 'down': Rating.VOTE_DOWN}
flag_map = {'spam': Rating.FLAG_SPAM, 'encrypted': Rating.FLAG_ENCRYPTED, 'expired': Rating.FLAG_EXPIRED, 'other': Rating.FLAG_OTHER, 'comment': Rating.FLAG_COMMENT}
type = kwargs.get('type')
setting = kwargs.get('setting')
if value:
try:
video = setting if type == 'video' and setting != "-" else None
audio = setting if type == 'audio' and setting != "-" else None
vote = vote_map[setting] if type == 'vote' else None
flag = flag_map[setting] if type == 'flag' else None
Rating.do.update_user_rating(value, video, audio, vote, flag, kwargs.get('detail'))
return report(output)
except:
return report(output, _MSG_BAD_SERVER_PARMS)
else:
return report(output, _MSG_NO_VALUE)
#------------------------------------------------------------------------------
def _api_options(name, output, kwargs):
@@ -346,7 +366,7 @@ def _api_addlocalfile(name, output, kwargs):
if get_ext(name) in ('.zip', '.rar'):
res = sabnzbd.dirscanner.ProcessArchiveFile(\
fn, name, pp=pp, script=script, cat=cat, priority=priority, keep=True)
fn, name, pp=pp, script=script, cat=cat, priority=priority, keep=True, nzbname=nzbname)
elif get_ext(name) in ('.nzb', '.gz'):
res = sabnzbd.dirscanner.ProcessSingleFile(\
fn, name, pp=pp, script=script, cat=cat, priority=priority, keep=True, nzbname=nzbname)
@@ -632,6 +652,12 @@ def _api_watched_now(name, output, kwargs):
return report(output)
def _api_resume_pp(name, output, kwargs):
""" API: accepts output """
PostProcessor.do.paused = False
return report(output)
def _api_rss_now(name, output, kwargs):
""" API: accepts output """
# Run RSS scan async, because it can take a long time
@@ -650,7 +676,7 @@ def _api_test_email(name, output, kwargs):
pack['unpack'] = ['action 1', 'action 2']
res = sabnzbd.emailer.endjob('I had a d\xe8ja vu', 123, 'unknown', True,
os.path.normpath(os.path.join(cfg.complete_dir.get_path(), '/unknown/I had a d\xe8ja vu')),
123*MEBI, pack, 'my_script', 'Line 1\nLine 2\nLine 3\nd\xe8ja vu\n', 0)
123*MEBI, None, pack, 'my_script', 'Line 1\nLine 2\nLine 3\nd\xe8ja vu\n', 0)
if res == 'Email succeeded':
res = None
return report(output, error=res)
@@ -795,6 +821,7 @@ _api_table = {
'rescan' : _api_rescan,
'eval_sort' : _api_eval_sort,
'watched_now' : _api_watched_now,
'resume_pp' : _api_resume_pp,
'rss_now' : _api_rss_now,
'browse' : _api_browse,
'reset_quota' : _api_reset_quota,
@@ -811,7 +838,8 @@ _api_queue_table = {
'pause' : _api_queue_pause,
'resume' : _api_queue_resume,
'priority' : _api_queue_priority,
'sort' : _api_queue_sort
'sort' : _api_queue_sort,
'rating' : _api_queue_rating
}
_api_config_table = {
@@ -1036,6 +1064,7 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
info['script_list'] = list_scripts()
info['cat_list'] = list_cats(output is None)
info['rating_enable'] = bool(cfg.rating_enable())
n = 0
found_active = False
@@ -1131,7 +1160,8 @@ 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 Downloader.do.paused or Downloader.do.postproc or status not in (Status.DOWNLOADING, Status.QUEUED):
if (Downloader.do.paused or Downloader.do.postproc or status not in (Status.DOWNLOADING, Status.QUEUED)) \
and priority != TOP_PRIORITY:
slot['timeleft'] = '0:00:00'
slot['eta'] = 'unknown'
else:
@@ -1204,8 +1234,13 @@ def build_queue(web_dir=None, root=None, verbose=False, prim=True, webdir='', ve
slot['finished'] = finished
slot['active'] = active
slot['queued'] = queued
rating = Rating.do.get_rating_by_nzo(nzo_id)
slot['has_rating'] = rating is not None
if rating:
slot['rating_avg_video'] = rating.avg_video
slot['rating_avg_audio'] = rating.avg_audio
if (start <= n and n < start + limit) or not limit:
slotinfo.append(slot)
n += 1
@@ -1406,6 +1441,7 @@ def rss_qstatus():
bytes = pnfo[PNFO_BYTES_FIELD] / MEBI
mbleft = (bytesleft / MEBI)
mb = (bytes / MEBI)
nzo_id = pnfo[PNFO_NZO_ID_FIELD]
if mb == mbleft:
@@ -1423,6 +1459,8 @@ def rss_qstatus():
else:
item.link = "http://%s:%s/sabnzbd/history" % ( \
cfg.cherryhost(), cfg.cherryport() )
item.guid = nzo_id
status_line = []
status_line.append('<tr>')
#Total MB/MB left
@@ -1594,6 +1632,7 @@ def build_header(prim, webdir=''):
header['quota'] = to_units(BPSMeter.do.quota)
header['have_quota'] = bool(BPSMeter.do.quota > 0.0)
header['left_quota'] = to_units(BPSMeter.do.left)
header['pp_pause_event'] = sabnzbd.scheduler.pp_pause_event()
status = ''
if Downloader.do.paused or Downloader.do.postproc:
@@ -1749,6 +1788,17 @@ def build_history(start=None, limit=None, verbose=False, verbose_list=None, sear
if item['retry']:
retry_folders.append(path)
rating = Rating.do.get_rating_by_nzo(item['nzo_id'])
item['has_rating'] = rating is not None
if rating:
item['rating_avg_video'] = rating.avg_video
item['rating_avg_audio'] = rating.avg_audio
item['rating_avg_vote_up'] = rating.avg_vote_up
item['rating_avg_vote_down'] = rating.avg_vote_down
item['rating_user_video'] = rating.user_video
item['rating_user_audio'] = rating.user_audio
item['rating_user_vote'] = rating.user_vote
total_items += full_queue_size
fetched_items = len(items)

View File

@@ -24,6 +24,7 @@ import Queue
import binascii
import logging
import struct
import re
from threading import Thread
from time import sleep
try:
@@ -112,8 +113,14 @@ class Assembler(Thread):
logging.debug('Got md5pack for set %s', setname)
if check_encrypted_rar(nzo, filepath):
logging.warning(Ta('WARNING: Paused job "%s" because of encrypted RAR file'), latin1(nzo.final_name))
nzo.pause()
if cfg.pause_on_pwrar() == 1:
logging.warning(Ta('WARNING: Paused job "%s" because of encrypted RAR file'), latin1(nzo.final_name))
nzo.pause()
else:
logging.warning(Ta('WARNING: Aborted job "%s" because of encrypted RAR file'), latin1(nzo.final_name))
nzo.fail_msg = T('Aborted, encryption detected')
import sabnzbd.nzbqueue
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
nzf.completed = True
else:
sabnzbd.nzbqueue.NzbQueue.do.remove(nzo.nzo_id, add_to_history=False, cleanup=False)
@@ -281,12 +288,19 @@ def ParseFilePacket(f, header):
return nothing
RE_SUBS = re.compile(r'\W+subs(?![a-z])', re.I)
def is_cloaked(path, names):
""" Return True if this is likely to be a cloaked encrypted post """
fname = unicoder(os.path.split(path)[1]).lower()
fname = os.path.splitext(fname)[0]
for name in names:
name = unicoder(name.lower())
if fname == name or 'password' in name:
name = os.path.split(name.lower())[1]
name, ext = os.path.splitext(unicoder(name))
if ext == u'.rar' and fname.startswith(name) and (len(fname) - len(name)) < 8 and not RE_SUBS.search(fname):
logging.debug('File %s is probably encrypted due to RAR with same name inside this RAR', fname)
return True
elif 'password' in name:
logging.debug('RAR %s is probably encrypted: "password" in filename %s', fname, name)
return True
return False
@@ -294,11 +308,11 @@ def is_cloaked(path, names):
def check_encrypted_rar(nzo, filepath):
""" Check if file is rar and is encrypted """
encrypted = False
if not nzo.password and cfg.pause_on_pwrar() and is_rarfile(filepath):
if not nzo.password and not nzo.meta.get('password') and cfg.pause_on_pwrar() and is_rarfile(filepath):
try:
zf = RarFile(filepath, all_names=True)
encrypted = zf.encrypted or is_cloaked(filepath, zf.namelist())
if encrypted and int(nzo.encrypted) < 2:
if encrypted and int(nzo.encrypted) < 2 and not nzo.reuse:
nzo.encrypted = 1
else:
encrypted = False

View File

@@ -398,10 +398,20 @@ class BPSMeter(object):
return None, 0, 0
def midnight(self):
""" Midnight action: dummy update for all servers """
for server in self.day_total:
self.update(server)
def quota_handler():
""" To be called from scheduler """
logging.debug('Checking quota')
BPSMeter.do.reset_quota()
def midnight_action():
if BPSMeter.do:
BPSMeter.do.midnight()
BPSMeter()

View File

@@ -80,7 +80,6 @@ email_dir = OptionDir('misc', 'email_dir', create=True)
email_rss = OptionBool('misc', 'email_rss', False)
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')
@@ -88,6 +87,7 @@ start_paused = OptionBool('misc', 'start_paused', False)
enable_unrar = OptionBool('misc', 'enable_unrar', True)
enable_unzip = OptionBool('misc', 'enable_unzip', True)
enable_recursive = OptionBool('misc', 'enable_recursive', True)
enable_filejoin = OptionBool('misc', 'enable_filejoin', True)
enable_tsjoin = OptionBool('misc', 'enable_tsjoin', True)
enable_par_cleanup = OptionBool('misc', 'enable_par_cleanup', True)
@@ -103,6 +103,7 @@ par2_multicore = OptionBool('misc', 'par2_multicore', True)
allow_64bit_tools = OptionBool('misc', 'allow_64bit_tools', True)
allow_streaming = OptionBool('misc', 'allow_streaming', False)
pre_check = OptionBool('misc', 'pre_check', False)
fail_hopeless = OptionBool('misc', 'fail_hopeless', False)
req_completion_rate = OptionNumber('misc', 'req_completion_rate', 100.2, 100, 200)
newzbin_username = OptionStr('newzbin', 'username')
@@ -112,6 +113,11 @@ newzbin_unbookmark = OptionBool('newzbin', 'unbookmark', True)
bookmark_rate = OptionNumber('newzbin', 'bookmark_rate', 60, minval=15, maxval=24*60)
newzbin_url = OptionStr('newzbin', 'url', 'www.newzbin2.es')
rating_enable = OptionBool('misc', 'rating_enable', False)
rating_host = OptionStr('misc', 'rating_host', 'www.oznzb.com')
rating_api_key = OptionStr('misc', 'rating_api_key')
rating_feedback = OptionBool('misc', 'rating_feedback', True)
top_only = OptionBool('misc', 'top_only', False)
autodisconnect = OptionBool('misc', 'auto_disconnect', True)
queue_complete = OptionStr('misc', 'queue_complete')
@@ -127,6 +133,7 @@ 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')
enable_meta = OptionBool('misc', 'enable_meta', True)
safe_postproc = OptionBool('misc', 'safe_postproc', True)
empty_postproc = OptionBool('misc', 'empty_postproc', False)
@@ -162,7 +169,7 @@ xxx_apikey = OptionStr('nzbxxx', 'apikey')
configlock = OptionBool('misc', 'config_lock', 0)
umask = OptionStr('misc', 'permissions', '', validation=validate_octal)
download_dir = OptionDir('misc', 'download_dir', DEF_DOWNLOAD_DIR, validation=validate_safedir)
download_dir = OptionDir('misc', 'download_dir', DEF_DOWNLOAD_DIR, create=False, validation=validate_safedir)
download_free = OptionStr('misc', 'download_free')
complete_dir = OptionDir('misc', 'complete_dir', DEF_COMPLETE_DIR, create=False, \
apply_umask=True, validation=validate_notempty)
@@ -176,6 +183,9 @@ dirscan_speed = OptionNumber('misc', 'dirscan_speed', DEF_SCANRATE, 0, 3600)
size_limit = OptionStr('misc', 'size_limit', '0')
password_file = OptionDir('misc', 'password_file', '', create=False)
fsys_type = OptionNumber('misc', 'fsys_type', 0, 0, 2)
wait_for_dfolder = OptionBool('misc', 'wait_for_dfolder', False)
warn_empty_nzb = OptionBool('misc', 'warn_empty_nzb', True)
sanitize_safe = OptionBool('misc', 'sanitize_safe', False)
cherryhost = OptionStr('misc', 'host', DEF_HOST)
if sabnzbd.WIN32:

View File

@@ -186,6 +186,14 @@ class OptionDir(Option):
res, path = sabnzbd.misc.create_real_path(self.ident()[1], self.__root, value, self.__apply_umask)
return path
def test_path(self):
""" Return True if path exists """
value = self.get()
if value:
return os.path.exists(sabnzbd.misc.real_path(self.__root, value))
else:
return False
def set_root(self, root):
""" Set new root, is assumed to be valid """
self.__root = root
@@ -210,6 +218,10 @@ class OptionDir(Option):
self._Option__set(value)
return error
def set_create(self, value):
""" Set auto-creation value """
self.__create = value
class OptionList(Option):
""" List option class """

View File

@@ -365,7 +365,7 @@ def build_history_info(nzo, storage='', downpath='', postproc_time=0, script_out
downloaded = nzo.bytes_downloaded
completeness = 0
fail_message = decode_factory(nzo.fail_msg)
url_info = nzo_info.get('more_info', '')
url_info = nzo_info.get('details', '') or nzo_info.get('more_info', '')
# Get the dictionary containing the stages and their unpack process
stages = decode_factory(nzo.unpack_info)

View File

@@ -84,6 +84,8 @@ class Decoder(Thread):
article, lines = art_tup
nzf = article.nzf
nzo = nzf.nzo
art_id = article.article
killed = False
data = None
@@ -96,14 +98,16 @@ class Decoder(Thread):
if nzo.precheck:
raise BadYenc
register = True
logging.debug("Decoding %s", article)
logging.debug("Decoding %s", art_id)
data = decode(article, lines)
nzf.article_count += 1
found = True
except IOError, e:
logme = Ta('Decoding %s failed') % article
logging.info(logme)
logme = Ta('Decoding %s failed') % art_id
logging.warning(logme)
logging.info("Traceback: ", exc_info = True)
sabnzbd.downloader.Downloader.do.pause()
article.fetcher = None
@@ -113,7 +117,7 @@ class Decoder(Thread):
register = False
except CrcError, e:
logme = Ta('CRC Error in %s (%s -> %s)') % (article, e.needcrc, e.gotcrc)
logme = Ta('CRC Error in %s (%s -> %s)') % (art_id, e.needcrc, e.gotcrc)
logging.info(logme)
data = e.data
@@ -135,27 +139,32 @@ class Decoder(Thread):
# 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)
lline = line.lower()
if 'message-id:' in lline:
found = True
if not line.startswith('X-') and match_str(lline, ('dmca', 'removed', 'cancel', 'blocked')):
killed = True
break
if killed:
logme = 'Article removed from server (%s)'
logging.info(logme, art_id)
if nzo.precheck:
if found or not killed:
if found and not killed:
# Pre-check, proper article found, just register
logging.debug('Server has article %s', article)
logging.debug('Server has article %s', art_id)
register = True
elif not killed and not found:
logme = Ta('Badly formed yEnc article in %s') % article
logme = Ta('Badly formed yEnc article in %s') % art_id
logging.info(logme)
if not found:
if not found or killed:
new_server_found = self.__search_new_server(article)
if new_server_found:
register = False
logme = None
except:
logme = Ta('Unknown Error while decoding %s') % article
logme = Ta('Unknown Error while decoding %s') % art_id
logging.info(logme)
logging.info("Traceback: ", exc_info = True)
@@ -165,7 +174,10 @@ class Decoder(Thread):
logme = None
if logme:
article.nzf.nzo.inc_log('bad_art_log', logme)
if killed:
article.nzf.nzo.inc_log('killed_art_log', art_id)
else:
article.nzf.nzo.inc_log('bad_art_log', art_id)
else:
new_server_found = self.__search_new_server(article)
@@ -236,7 +248,7 @@ def decode(article, data):
if not ybegin:
found = False
try:
for i in xrange(10):
for i in xrange(min(40, len(data))):
if data[i].startswith('begin '):
nzf.filename = name_fixer(data[i].split(None, 2)[2])
nzf.type = 'uu'
@@ -252,7 +264,10 @@ def decode(article, data):
except IndexError:
raise BadYenc()
decoded_data = '\r\n'.join(data)
if found:
decoded_data = '\r\n'.join(data)
else:
raise BadYenc()
#Deal with yenc encoded posts
elif (ybegin and yend):
@@ -300,7 +315,7 @@ def yCheck(data):
yend = None
## Check head
for i in xrange(40):
for i in xrange(min(40, len(data))):
try:
if data[i].startswith('=ybegin '):
splits = 3

View File

@@ -59,7 +59,8 @@ def CompareStat(tup1, tup2):
return True
def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=None, keep=False, priority=None, url=''):
def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=None, keep=False,
priority=None, url='', nzbname=None):
""" Analyse ZIP file and create job(s).
Accepts ZIP files with ONLY nzb/nfo/folder files in it.
returns (status, nzo_ids)
@@ -88,6 +89,7 @@ def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=No
status = 1
names = zf.namelist()
names.sort()
nzbcount = 0
for name in names:
name = name.lower()
if not (name.endswith('.nzb') or name.endswith('.nfo') or name.endswith('/')):
@@ -95,7 +97,10 @@ def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=No
break
elif name.endswith('.nzb'):
status = 0
nzbcount += 1
if status == 0:
if nzbcount != 1:
nzbname = None
for name in names:
if name.lower().endswith('.nzb'):
try:
@@ -108,11 +113,13 @@ def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=No
name = misc.sanitize_foldername(name)
if data:
try:
nzo = nzbstuff.NzbObject(name, 0, pp, script, data, cat=cat, url=url, priority=priority)
nzo = nzbstuff.NzbObject(name, 0, pp, script, data, cat=cat, url=url,
priority=priority, nzbname=nzbname)
except:
nzo = None
if nzo:
nzo_ids.append(add_nzo(nzo))
nzo.update_rating()
zf.close()
try:
if not keep: os.remove(path)
@@ -171,6 +178,9 @@ def ProcessSingleFile(filename, path, pp=None, script=None, cat=None, catdir=Non
except TypeError:
# Duplicate, ignore
nzo = None
except ValueError:
# Empty, but correct file
return -1, nzo_ids
except:
if data.find("<nzb") >= 0 and data.find("</nzb") < 0:
# Looks like an incomplete file, retry
@@ -180,6 +190,7 @@ def ProcessSingleFile(filename, path, pp=None, script=None, cat=None, catdir=Non
if nzo:
nzo_ids.append(add_nzo(nzo))
nzo.update_rating()
try:
if not keep: os.remove(path)
except:

View File

@@ -47,6 +47,8 @@ _PENALTY_SHARE = 10 # Account sharing detected
_PENALTY_TOOMANY = 10 # Too many connections
_PENALTY_PERM = 10 # Permanent error, like bad username/password
_PENALTY_SHORT = 1 # Minimal penalty when no_penalties is set
_PENALTY_VERYSHORT = 0.1 # Error 400 without cause clues
TIMER_LOCK = RLock()
@@ -77,7 +79,8 @@ class Server(object):
self.warning = ''
self.info = None # Will hold getaddrinfo() list
self.request = False # True if a getaddrinfo() request is pending
self.oddball = 'free.xsusenet.com' in host
self.have_body = 'free.xsusenet.com' not in host
self.have_stat = True # Assume server has "STAT", until proven otherwise
for i in range(threads):
self.idle_threads.append(NewsWrapper(self, i+1))
@@ -274,6 +277,9 @@ class Downloader(Thread):
return True
return False
def nzo_servers(self, nzo):
return filter(nzo.server_in_try_list, self.servers)
def maybe_block_server(self, server):
from sabnzbd.nzbqueue import NzbQueue
if server.optional and server.active and (server.bad_cons/server.threads) > 3:
@@ -502,8 +508,7 @@ class Downloader(Thread):
ecode = msg[:3]
display_msg = ' [%s]' % msg
logging.debug('Server login problem: %s, %s', ecode, msg)
if ((ecode in ('502', '400')) and clues_too_many(msg)) or \
(ecode == '481' and clues_too_many(msg)):
if ecode in ('502', '481', '400') and clues_too_many(msg):
# Too many connections: remove this thread and reduce thread-setting for server
# Plan to go back to the full number after a penalty timeout
if server.active:
@@ -536,6 +541,12 @@ class Downloader(Thread):
else:
penalty = _PENALTY_502
block = True
elif ecode == '400':
# Temp connection problem?
if server.active:
logging.debug('Unspecified error 400 from server %s', server.host)
penalty = _PENALTY_VERYSHORT
block = True
else:
# Unknown error, just keep trying
if server.active:
@@ -599,6 +610,19 @@ class Downloader(Thread):
msg = T('Server %s requires user/password') % ('%s:%s' % (nw.server.host, nw.server.port))
self.__reset_nw(nw, msg, quit=True)
elif code == '500':
if nzo.precheck:
# Assume "STAT" command is not supported
server.have_stat = False
logging.debug('Server %s does not support STAT', server.host)
else:
# Assume "BODY" command is not supported
server.have_body = False
logging.debug('Server %s does not support BODY', server.host)
nw.lines = []
nw.data = ''
self.__request_article(nw)
if done:
server.bad_cons = 0 # Succesful data, clear "bad" counter
if sabnzbd.LOG_ALL:

View File

@@ -187,12 +187,16 @@ def send_with_template(prefix, parm):
return ret
def endjob(filename, msgid, cat, status, path, bytes, stages, script, script_output, script_ret):
def endjob(filename, msgid, cat, status, path, bytes, fail_msg, stages, script, script_output, script_ret):
""" Send end-of-job email """
# Translate the stage names
tr = sabnzbd.api.Ttemplate
xstages = {}
if not status and fail_msg:
xstages = {tr('stage-fail'): (fail_msg,)}
else:
xstages = {}
for stage in stages:
lines = []
for line in stages[stage]:

View File

@@ -119,7 +119,10 @@ def special_fixer(p):
return p
except:
# Now assume it's latin-1
return p.decode('Latin-1').encode('utf-8')
try:
return p.decode('Latin-1').encode('utf-8')
except:
return p
else:
return p

View File

@@ -292,7 +292,8 @@ def send_notification_center(title, msg, gtype):
tool = ncenter_path()
if tool:
try:
command = [tool, '-title', title, '-message', msg, '-group', Tx(NOTIFICATION.get(gtype, 'other'))]
command = [tool, '-title', title, '-message', msg, '-group', Tx(NOTIFICATION.get(gtype, 'other')),
'-sender', 'org.sabnzbd.team']
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
output = proc.stdout.read()
proc.wait()

View File

@@ -39,6 +39,7 @@ from sabnzbd.misc import real_path, to_units, \
from sabnzbd.panic import panic_old_queue
from sabnzbd.newswrapper import GetServerParms
from sabnzbd.newzbin import Bookmarks
from sabnzbd.rating import Rating
from sabnzbd.bpsmeter import BPSMeter
from sabnzbd.encoding import TRANS, xml_name, LatinFilter, unicoder, special_fixer, \
platform_encode, latin1, encode_for_xml
@@ -185,7 +186,7 @@ def check_apikey(kwargs, nokey=False):
Return None when OK, otherwise an error message
"""
def log_warning(txt):
txt = '%s %s' % (txt, cherrypy.request.headers.get('User-Agent', '??'))
txt = '%s %s>%s' % (txt, cherrypy.request.remote.ip, cherrypy.request.headers.get('User-Agent', '??'))
logging.warning('%s', txt)
output = kwargs.get('output')
@@ -461,6 +462,12 @@ class MainPage(object):
retry_job(kwargs.get('job'), kwargs.get('nzbfile'))
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def robots_txt(self):
""" Keep web crawlers out """
cherrypy.response.headers['Content-Type'] = 'text/plain'
return 'User-agent: *\nDisallow: /\n'
#------------------------------------------------------------------------------
class NzoPage(object):
@@ -534,12 +541,19 @@ class NzoPage(object):
cat = pnfo[PNFO_EXTRA_FIELD1]
if not cat:
cat = 'None'
filename = xml_name(nzo.final_name_pw_clean)
filename_pw = xml_name(nzo.final_name_pw_clean)
filename = xml_name(nzo.final_name)
if nzo.password:
password = xml_name(nzo.password)
else:
password = ''
priority = pnfo[PNFO_PRIORITY_FIELD]
slot['nzo_id'] = str(nzo_id)
slot['cat'] = cat
slot['filename'] = filename
slot['filename'] = filename_pw
slot['filename_clean'] = filename
slot['password'] = password or ''
slot['script'] = script
slot['priority'] = str(priority)
slot['unpackopts'] = str(unpackopts)
@@ -587,6 +601,7 @@ class NzoPage(object):
def save_details(self, nzo_id, args, kwargs):
index = kwargs.get('index', None)
name = kwargs.get('name', None)
password = kwargs.get('password', None)
pp = kwargs.get('pp', None)
script = kwargs.get('script', None)
cat = kwargs.get('cat', None)
@@ -596,7 +611,7 @@ class NzoPage(object):
if index != None:
NzbQueue.do.switch(nzo_id, index)
if name != None:
NzbQueue.do.change_name(nzo_id, special_fixer(name))
NzbQueue.do.change_name(nzo_id, special_fixer(name), password)
if cat != None:
NzbQueue.do.change_cat(nzo_id,cat)
if script != None:
@@ -864,7 +879,8 @@ class HistoryPage(object):
self.__verbose_list = []
self.__failed_only = False
self.__prim = prim
self.__edit_rating = None
@cherrypy.expose
def index(self, **kwargs):
if not check_access(): return Protected()
@@ -880,6 +896,8 @@ class HistoryPage(object):
history['isverbose'] = self.__verbose
history['failed_only'] = failed_only
history['rating_enable'] = bool(cfg.rating_enable())
if cfg.newzbin_username() and cfg.newzbin_password():
history['newzbinDetails'] = True
@@ -894,6 +912,12 @@ class HistoryPage(object):
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)
for line in history['lines']:
if self.__edit_rating is not None and line.get('nzo_id') == self.__edit_rating:
line['edit_rating'] = True
else:
line['edit_rating'] = ''
if search:
history['search'] = escape(search)
else:
@@ -1012,6 +1036,29 @@ class HistoryPage(object):
del_hist_job(job, del_files=True)
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def show_edit_rating(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
self.__edit_rating = kwargs.get('job');
raise queueRaiser(self.__root, kwargs)
@cherrypy.expose
def action_edit_rating(self, **kwargs):
flag_map = {'spam': Rating.FLAG_SPAM, 'encrypted': Rating.FLAG_ENCRYPTED, 'expired': Rating.FLAG_EXPIRED}
msg = check_session(kwargs)
if msg: return msg
try:
if kwargs.get('send'):
video = kwargs.get('video') if kwargs.get('video') != "-" else None
audio = kwargs.get('audio') if kwargs.get('audio') != "-" else None
flag = flag_map.get(kwargs.get('rating_flag'))
detail = kwargs.get('expired_host') if kwargs.get('expired_host') != '<Host>' else None
Rating.do.update_user_rating(kwargs.get('job'), video, audio, flag, detail)
except:
pass
self.__edit_rating = None;
raise queueRaiser(self.__root, kwargs)
#------------------------------------------------------------------------------
class ConfigPage(object):
@@ -1044,7 +1091,6 @@ 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)
@@ -1163,7 +1209,8 @@ SWITCH_LIST = \
'ignore_samples', 'pause_on_post_processing', 'quick_check', 'nice', 'ionice',
'ssl_type', 'pre_script', 'pause_on_pwrar', 'ampm', 'sfv_check', 'folder_rename',
'unpack_check', 'quota_size', 'quota_day', 'quota_resume', 'quota_period',
'pre_check', 'max_art_tries', 'max_art_opt'
'pre_check', 'max_art_tries', 'max_art_opt', 'fail_hopeless',
'rating_enable', 'rating_api_key', 'rating_host', 'rating_feedback'
)
#------------------------------------------------------------------------------
@@ -1215,10 +1262,11 @@ 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', 'news_items',
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames',
'osx_menu', 'osx_speed', 'win_menu', 'uniconfig', 'use_pickle', 'allow_incomplete_nzb',
'randomize_server_ip', 'no_ipv6', 'keep_awake', 'overwrite_files', 'empty_postproc',
'web_watchdog'
'web_watchdog', 'wait_for_dfolder', 'warn_empty_nzb', 'enable_recursive', 'sanitize_safe',
'enable_meta'
)
SPECIAL_VALUE_LIST = \
( 'size_limit', 'folder_max_length', 'fsys_type', 'movie_rename_limit', 'nomedia_marker',
@@ -1676,6 +1724,7 @@ class ConfigRss(object):
active_feed = kwargs.get('feed', '')
conf['active_feed'] = active_feed
conf['rss'] = rss
conf['rss_next'] = time.strftime(time_format('%H:%M'),time.localtime(sabnzbd.rss.next_run()))
if active_feed:
readout = bool(self.__refresh_readout)
@@ -2728,6 +2777,7 @@ def rss_history(url, limit=50, search=None):
item.link = history['url_info']
else:
item.link = url
item.guid = history['nzo_id']
stageLine = []
for stage in history['stage_log']:

View File

@@ -31,6 +31,7 @@ import socket
import time
import glob
import stat
import Queue
try:
socket.ssl
_HAVE_SSL = True
@@ -73,8 +74,10 @@ def safe_lower(txt):
#------------------------------------------------------------------------------
def globber(path, pattern='*'):
""" Do a glob.glob(), disabling the [] pattern in 'path' """
return glob.glob(os.path.join(path, pattern).replace('[', '[[]'))
if pattern:
return glob.glob(os.path.join(path, pattern).replace('[', '[[]'))
else:
return glob.glob(path.replace('[', '[[]'))
#------------------------------------------------------------------------------
def cat_to_opts(cat, pp=None, script=None, priority=None):
@@ -236,6 +239,11 @@ def sanitize_foldername(name):
illegal = FL_ILLEGAL
legal = FL_LEGAL
if cfg.sanitize_safe():
# Remove all bad Windows chars too
illegal += r'\/<>?*|"'
legal += r'++{}!@#`'
repl = cfg.replace_illegal()
lst = []
for ch in name.strip():
@@ -1373,3 +1381,223 @@ def set_permissions(path, recursive=True):
set_chmod(path, umask, report)
else:
set_chmod(path, umask_file, report)
#------------------------------------------------------------------------------
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
# Passes Python2.7's test suite and incorporates all the latest updates.
class OrderedDict(dict):
# An inherited dict maps keys to values.
# The inherited dict provides __getitem__, __len__, __contains__, and get.
# The remaining methods are order-aware.
# Big-O running times for all methods are the same as for regular dictionaries.
# The internal self.__map dictionary maps keys to links in a doubly linked list.
# The circular doubly linked list starts and ends with a sentinel element.
# The sentinel element never gets deleted (this simplifies the algorithm).
# Each link is stored as a list of length three: [PREV, NEXT, KEY].
def __init__(self, *args, **kwds):
'''Initialize an ordered dictionary. Signature is the same as for
regular dictionaries, but keyword arguments are not recommended
because their insertion order is arbitrary.
'''
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
self.__root = root = [] # sentinel node
root[:] = [root, root, None]
self.__map = {}
self.__update(*args, **kwds)
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
# Setting a new item creates a new link which goes at the end of the linked
# list, and the inherited dictionary is updated with the new key/value pair.
if key not in self:
root = self.__root
last = root[0]
last[1] = root[0] = self.__map[key] = [last, root, key]
dict_setitem(self, key, value)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
# Deleting an existing item uses self.__map to find the link which is
# then removed by updating the links in the predecessor and successor nodes.
dict_delitem(self, key)
link_prev, link_next, key = self.__map.pop(key)
link_prev[1] = link_next
link_next[0] = link_prev
def __iter__(self):
root = self.__root
curr = root[1]
while curr is not root:
yield curr[2]
curr = curr[1]
def __reversed__(self):
root = self.__root
curr = root[0]
while curr is not root:
yield curr[2]
curr = curr[0]
def clear(self):
try:
for node in self.__map.itervalues():
del node[:]
root = self.__root
root[:] = [root, root, None]
self.__map.clear()
except AttributeError:
pass
dict.clear(self)
def popitem(self, last=True):
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
Pairs are returned in LIFO order if last is true or FIFO order if false.
'''
if not self:
raise KeyError('dictionary is empty')
root = self.__root
if last:
link = root[0]
link_prev = link[0]
link_prev[1] = root
root[0] = link_prev
else:
link = root[1]
link_next = link[1]
root[1] = link_next
link_next[0] = root
key = link[2]
del self.__map[key]
value = dict.pop(self, key)
return key, value
# -- the following methods do not depend on the internal structure --
def keys(self):
return list(self)
def values(self):
return [self[key] for key in self]
def items(self):
return [(key, self[key]) for key in self]
def iterkeys(self):
return iter(self)
def itervalues(self):
for k in self:
yield self[k]
def iteritems(self):
for k in self:
yield (k, self[k])
def update(*args, **kwds):
if len(args) > 2:
raise TypeError('update() takes at most 2 positional '
'arguments (%d given)' % (len(args),))
elif not args:
raise TypeError('update() takes at least 1 argument (0 given)')
self = args[0]
# Make progressively weaker assumptions about "other"
other = ()
if len(args) == 2:
other = args[1]
if isinstance(other, dict):
for key in other:
self[key] = other[key]
elif hasattr(other, 'keys'):
for key in other.keys():
self[key] = other[key]
else:
for key, value in other:
self[key] = value
for key, value in kwds.items():
self[key] = value
__update = update # let subclasses override update without breaking __init__
__marker = object()
def pop(self, key, default=__marker):
if key in self:
result = self[key]
del self[key]
return result
if default is self.__marker:
raise KeyError(key)
return default
def setdefault(self, key, default=None):
if key in self:
return self[key]
self[key] = default
return default
def __repr__(self, _repr_running={}):
call_key = id(self), _get_ident()
if call_key in _repr_running:
return '...'
_repr_running[call_key] = 1
try:
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
finally:
del _repr_running[call_key]
def __reduce__(self):
items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
for k in vars(OrderedDict()):
inst_dict.pop(k, None)
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def copy(self):
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
if isinstance(other, OrderedDict):
return len(self)==len(other) and self.items() == other.items()
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other
# -- the following methods are only used in Python 2.7 --
def viewkeys(self):
return KeysView(self)
def viewvalues(self):
return ValuesView(self)
def viewitems(self):
return ItemsView(self)
#------------------------------------------------------------------------------
# A queue which ignores duplicates but maintains ordering
class OrderedSetQueue(Queue.Queue):
def _init(self, maxsize):
self.maxsize = maxsize
self.queue = OrderedDict()
def _put(self, item):
self.queue[item] = None
def _get(self):
return self.queue.popitem()[0]

View File

@@ -26,13 +26,14 @@ import subprocess
import logging
from time import time
import binascii
import shutil
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, \
flag_file, real_path
flag_file, real_path, globber
from sabnzbd.tvsort import SeriesSorter
import sabnzbd.cfg as cfg
from sabnzbd.constants import Status, QCHECK_FILE, RENAMES_FILE
@@ -128,19 +129,19 @@ def find_programs(curdir):
sabnzbd.newsunpack.RAR_PROBLEM = not unrar_check(sabnzbd.newsunpack.RAR_COMMAND)
#------------------------------------------------------------------------------
def external_processing(extern_proc, complete_dir, filename, msgid, nicename, cat, group, status):
def external_processing(extern_proc, complete_dir, filename, msgid, nicename, cat, group, status, failure_url):
""" Run a user postproc script, return console output and exit value
"""
command = [str(extern_proc), str(complete_dir), str(filename),
str(nicename), str(msgid), str(cat), str(group), str(status)]
str(nicename), str(msgid), str(cat), str(group), str(status), str(failure_url)]
if extern_proc.endswith('.py') and (sabnzbd.WIN32 or not os.access(extern_proc, os.X_OK)):
command.insert(0, 'python')
stup, need_shell, command, creationflags = build_command(command)
env = fix_env()
logging.info('Running external script %s(%s, %s, %s, %s, %s, %s, %s)',
extern_proc, complete_dir, filename, nicename, msgid, cat, group, status)
logging.info('Running external script %s(%s, %s, %s, %s, %s, %s, %s, %s)',
extern_proc, complete_dir, filename, nicename, msgid, cat, group, status, failure_url)
try:
p = subprocess.Popen(command, shell=need_shell, stdin=subprocess.PIPE,
@@ -233,7 +234,7 @@ def unpack_magic(nzo, workdir, workdir_complete, dele, one_folder, joinables, zi
nzo.set_action_line()
if rerun:
if rerun and (cfg.enable_recursive() or new_ts or new_joins):
z, y = unpack_magic(nzo, workdir, workdir_complete, dele, one_folder,
xjoinables, xzips, xrars, xts, depth)
if z:
@@ -289,7 +290,6 @@ def get_seq_number(name):
match, set, num = match_ts(name)
else:
num = tail[1:]
assert isinstance(num, str)
if num.isdigit():
return int(num)
else:
@@ -300,6 +300,7 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
when succesful, delete originals
"""
newfiles = []
bufsize = 24*1024*1024
# Create matching sets from the list of files
joinable_sets = {}
@@ -330,6 +331,11 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
# done, go to next set
continue
# Only join when there is more than one file
size = len(current)
if size < 2:
continue
# Prepare joined file
filename = joinable_set
if workdir_complete:
@@ -338,7 +344,6 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
joined_file = open(filename, 'ab')
# Join the segments
size = len(current)
n = get_seq_number(current[0])
seq_error = n > 1
for joinable in current:
@@ -348,7 +353,7 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
logging.debug("Processing %s", joinable)
nzo.set_action_line(T('Joining'), '%.0f%%' % perc)
f = open(joinable, 'rb')
joined_file.write(f.read())
shutil.copyfileobj(f, joined_file, bufsize)
f.close()
if delete:
logging.debug("Deleting %s", joinable)
@@ -471,13 +476,15 @@ def rar_extract(rarfile, numrars, one_folder, nzo, setname, extraction_path):
passwords = [nzo.password]
else:
passwords = []
# Append meta passwords, to prevent changing the original list
passwords.extend(nzo.meta.get('password', []))
pw_file = cfg.password_file.get_path()
if pw_file:
try:
pwf = open(pw_file, 'r')
passwords = pwf.read().split('\n')
lines = pwf.read().split('\n')
# Remove empty lines and space-only passwords and remove surrounding spaces
passwords = [pw.strip('\r\n ') for pw in passwords if pw.strip('\r\n ')]
passwords.extend([pw.strip('\r\n ') for pw in lines if pw.strip('\r\n ')])
pwf.close()
logging.info('Read the passwords file %s', pw_file)
except IOError:
@@ -609,7 +616,20 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
nzo.fail_msg = T('Unpacking failed, write error or disk is full?')
msg = ('[%s] ' + Ta('Unpacking failed, write error or disk is full?')) % setname
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
logging.warning(Ta('ERROR: write error (%s)'), line[11:])
logging.error(Ta('ERROR: write error (%s)'), line[11:])
fail = 1
elif line.startswith('Cannot create'):
line2 = proc.readline()
if 'must not exceed 260' in line2:
nzo.fail_msg = T('Unpacking failed, path is too long')
msg = '[%s] %s: %s' % (Ta('Unpacking failed, path is too long'), setname, line[13:])
logging.error(Ta('ERROR: path too long (%s)'), line[13:])
else:
nzo.fail_msg = T('Unpacking failed, write error or disk is full?')
msg = '[%s] %s: %s' % (Ta('Unpacking failed, write error or disk is full?'), setname, line[13:])
logging.error(Ta('ERROR: write error (%s)'), line[13:])
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
fail = 1
elif line.startswith('ERROR: '):
@@ -619,8 +639,10 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
fail = 1
elif 'ncrypted file' in line and 'CRC failed' in line:
# unrar 4.x syntax
elif 'ncrypted file' in line and (('CRC failed' in line) or ('Checksum error' in line)):
# unrar 3.x: "Encrypted file: CRC failed in oLKQfrcNVivzdzSG22a2xo7t001.part1.rar (password incorrect ?)"
# unrar 4.x: "CRC failed in the encrypted file oLKQfrcNVivzdzSG22a2xo7t001.part1.rar. Corrupt file or wrong password."
# unrar 5.x: "Checksum error in the encrypted file oLKQfrcNVivzdzSG22a2xo7t001.part1.rar. Corrupt file or wrong password."
m = re.search('encrypted file (.+)\. Corrupt file', line)
if not m:
# unrar 3.x syntax
@@ -634,6 +656,18 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
fail = 2
elif 'is not RAR archive' in line:
# Unrecognizable RAR file
m = re.search('(.+) is not RAR archive', line)
if m:
filename = TRANS(m.group(1)).strip()
else:
filename = '???'
nzo.fail_msg = T('Unusable RAR file')
msg = ('[%s][%s] '+ Ta('Unusable RAR file')) % (setname, latin1(filename))
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
fail = 1
else:
m = re.search(r'^(Extracting|Creating|...)\s+(.*?)\s+OK\s*$', line)
if m:
@@ -774,15 +808,16 @@ def ZIP_Extract(zipfile, extraction_path, one_folder):
# PAR2 Functions
#------------------------------------------------------------------------------
def par2_repair(parfile_nzf, nzo, workdir, setname):
def par2_repair(parfile_nzf, nzo, workdir, setname, single):
""" Try to repair a set, return readd or correctness """
#set the current nzo status to "Repairing". Used in History
parfile = os.path.join(workdir, parfile_nzf.filename)
old_dir_content = os.listdir(workdir)
used_joinables = []
joinables = []
used_joinables = ()
joinables = ()
used_par2 = ()
setpars = pars_of_set(workdir, setname)
result = readd = False
@@ -806,8 +841,8 @@ def par2_repair(parfile_nzf, nzo, workdir, setname):
joinables, zips, rars, ts = build_filelists(workdir, None, check_rar=False)
finished, readd, pars, datafiles, used_joinables = PAR_Verify(parfile, parfile_nzf, nzo,
setname, joinables)
finished, readd, pars, datafiles, used_joinables, used_par2 = PAR_Verify(parfile, parfile_nzf, nzo,
setname, joinables, single=single)
if finished:
result = True
@@ -876,6 +911,7 @@ def par2_repair(parfile_nzf, nzo, workdir, setname):
if f in setpars:
deletables.append(os.path.join(workdir, f))
deletables.extend(used_joinables)
deletables.extend(used_par2)
for filepath in deletables:
if filepath in joinables:
joinables.remove(filepath)
@@ -895,8 +931,10 @@ 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 "([^"]+)"')
_RE_LOADING_PAR2 = re.compile('Loading "([^"]+)"\.')
_RE_LOADED_PAR2 = re.compile('Loaded (\d+) new packets')
def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False, single=False):
""" Run par2 on par-set """
if cfg.never_repair():
cmd = 'v'
@@ -904,6 +942,8 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
cmd = 'r'
retry_classic = False
used_joinables = []
used_par2 = []
extra_par2_name = None
#set the current nzo status to "Verifying...". Used in History
nzo.status = Status.VERIFYING
start = time()
@@ -927,7 +967,15 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
command.append(joinable)
# Append the wildcard for this set
command.append('%s*' % os.path.join(os.path.split(parfile)[0], setname))
wildcard = '%s*' % os.path.join(os.path.split(parfile)[0], setname)
if single or len(globber(wildcard, None)) < 2:
# Support bizarre naming conventions
wildcard = os.path.join(os.path.split(parfile)[0], '*')
if sabnzbd.WIN32 or sabnzbd.DARWIN:
command.append(wildcard)
else:
flist = [item for item in globber(wildcard, None) if os.path.isfile(item)]
command.extend(flist)
stup, need_shell, command, creationflags = build_command(command)
logging.debug('Starting par2: %s', command)
@@ -977,6 +1025,16 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
if 'Repairing:' not in line:
lines.append(line)
if extra_par2_name and line.startswith('Loading:') and line.endswith('%'):
continue
if extra_par2_name and line.startswith('Loaded '):
m = _RE_LOADED_PAR2.search(line)
if m and int(m.group(1)) > 0:
used_par2.append(os.path.join(nzo.downpath, extra_par2_name))
extra_par2_name = None
continue
extra_par2_name = None
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)
@@ -997,6 +1055,12 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
start = time()
verified = 1
elif line.startswith('Loading "'):
# Found an extra par2 file. Only the next line will tell whether it's usable
m = _RE_LOADING_PAR2.search(line)
if m and m.group(1).lower().endswith('.par2'):
extra_par2_name = m.group(1)
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...'))
@@ -1161,6 +1225,13 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
logging.debug('PAR2 will rename "%s" to "%s"', old_name, new_name)
renames[new_name] = old_name
elif 'No details available for recoverable file' in line:
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
elif not verified:
if line.startswith('Verifying source files'):
nzo.set_action_line(T('Verifying'), '01/%02d' % verifytotal)
@@ -1210,9 +1281,9 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
if retry_classic:
logging.debug('Retry PAR2-joining with par2-classic')
return PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=True)
return PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=True, single=single)
else:
return finished, readd, pars, datafiles, used_joinables
return finished, readd, pars, datafiles, used_joinables, used_par2
#-------------------------------------------------------------------------------
@@ -1305,12 +1376,14 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
if workdir_complete:
for root, dirs, files in os.walk(workdir_complete):
for _file in files:
filelist.append(os.path.join(root, _file))
if '.AppleDouble' not in root and '.DS_Store' not in root:
filelist.append(os.path.join(root, _file))
if workdir and not filelist:
for root, dirs, files in os.walk(workdir):
for _file in files:
filelist.append(os.path.join(root, _file))
if '.AppleDouble' not in root and '.DS_Store' not in root:
filelist.append(os.path.join(root, _file))
if check_rar:
joinables = [f for f in filelist if SPLITFILE_RE.search(f) and not is_rarfile(f)]
@@ -1319,10 +1392,7 @@ def build_filelists(workdir, workdir_complete, check_rar=True):
zips = [f for f in filelist if ZIP_RE.search(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)]
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]
@@ -1453,7 +1523,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 = SeriesSorter(None, name, None, None)
job.match(force=True)
if job.is_match():
job.get_values()
@@ -1541,6 +1611,8 @@ def get_from_url(url, timeout=None):
p.wait()
else:
import urllib2
if sys.version_info < (2, 6):
timeout = 0
try:
if timeout:
s = urllib2.urlopen(url, timeout=timeout)

View File

@@ -320,11 +320,14 @@ class NewsWrapper(object):
def body(self, precheck):
self.timeout = time.time() + self.server.timeout
if precheck:
command = 'STAT <%s>\r\n' % (self.article.article)
elif self.server.oddball:
command = 'ARTICLE <%s>\r\n' % (self.article.article)
else:
if self.server.have_stat:
command = 'STAT <%s>\r\n' % (self.article.article)
else:
command = 'HEAD <%s>\r\n' % (self.article.article)
elif self.server.have_body:
command = 'BODY <%s>\r\n' % (self.article.article)
else:
command = 'ARTICLE <%s>\r\n' % (self.article.article)
self.nntp.sock.sendall(command)
def send_group(self, group):
@@ -433,17 +436,17 @@ def test_ipv6():
try:
info = socket.getaddrinfo('www.google.com', 80, socket.AF_INET6, socket.SOCK_STREAM,
socket.IPPROTO_IP, socket.AI_CANONNAME)
except socket.gaierror:
except:
return False
try:
af, socktype, proto, canonname, sa = info[0]
sock = socket.socket(af, socktype, proto)
sock.settimeout(4)
sock.settimeout(6)
sock.connect(sa[0:2])
sock.close()
return True
except socket.error:
except:
return False
_EXTERNAL_IPV6 = test_ipv6()

View File

@@ -220,7 +220,7 @@ class NzbQueue(TryList):
if save_nzo is None or nzo is save_nzo:
sabnzbd.save_data(nzo, nzo.nzo_id, nzo.workpath)
if not nzo.futuretype:
nzo.save_attribs()
nzo.save_to_disk()
sabnzbd.save_admin((QUEUE_VERSION, nzo_ids, []), QUEUE_FILE_NAME)
@@ -308,11 +308,11 @@ class NzbQueue(TryList):
self.set_priority(nzo_id, prio)
@synchronized(NZBQUEUE_LOCK)
def change_name(self, nzo_id, name):
def change_name(self, nzo_id, name, password=None):
if nzo_id in self.__nzo_table:
nzo = self.__nzo_table[nzo_id]
if not nzo.futuretype:
nzo.set_final_name_pw(name)
nzo.set_final_name_pw(name, password)
else:
# Reset url fetch wait time
nzo.wait = None
@@ -595,7 +595,7 @@ class NzbQueue(TryList):
return nzo_id_pos1
nzo.priority = priority
nzo.save_attribs()
nzo.save_to_disk()
if nzo_id_pos1 != -1:
del self.__nzo_list[nzo_id_pos1]
@@ -755,7 +755,7 @@ class NzbQueue(TryList):
if not nzo.deleted:
nzo.deleted = True
if nzo.precheck:
nzo.save_attribs()
nzo.save_to_disk()
# Check result
enough, ratio = nzo.check_quality()
if enough:
@@ -886,7 +886,7 @@ def _nzo_date_cmp(nzo1, nzo2):
return cmp(avg_date1, avg_date2)
def _nzo_name_cmp(nzo1, nzo2):
return cmp(nzo1.filename, nzo2.filename)
return cmp(nzo1.filename.lower(), nzo2.filename.lower())
def _nzo_size_cmp(nzo1, nzo2):
return cmp(nzo1.bytes, nzo2.bytes)

View File

@@ -45,6 +45,7 @@ from sabnzbd.misc import to_units, cat_to_opts, cat_convert, sanitize_foldername
import sabnzbd.cfg as cfg
from sabnzbd.trylist import TryList
from sabnzbd.encoding import unicoder, platform_encode, latin1, name_fixer
from sabnzbd.rating import Rating
__all__ = ['Article', 'NzbFile', 'NzbObject']
@@ -322,6 +323,11 @@ class NzbParser(xml.sax.handler.ContentHandler):
self.in_group = False
self.in_segments = False
self.in_segment = False
self.in_head = False
self.in_meta = False
self.meta_type = ''
self.meta_types = {}
self.meta_content = []
self.filename = ''
self.avg_age = 0
self.valids = 0
@@ -374,6 +380,16 @@ class NzbParser(xml.sax.handler.ContentHandler):
elif name == 'groups' and self.in_nzb and self.in_file:
self.in_groups = True
elif name == 'head' and self.in_nzb:
self.in_head = True
elif name == 'meta' and self.in_nzb and self.in_head:
self.in_meta = True
meta_type = attrs.get('type')
if meta_type:
self.meta_type = meta_type.lower()
self.meta_content = []
elif name == 'nzb':
self.in_nzb = True
@@ -382,6 +398,8 @@ class NzbParser(xml.sax.handler.ContentHandler):
self.group_name.append(content)
elif self.in_segment:
self.article_id.append(content)
elif self.in_meta:
self.meta_content.append(content)
def endElement(self, name):
if name == 'group' and self.in_group:
@@ -436,12 +454,24 @@ class NzbParser(xml.sax.handler.ContentHandler):
sabnzbd.remove_data(nzf.nzf_id, self.nzo.workpath)
self.skipped_files += 1
elif name == 'head':
self.in_head = False
elif name == 'meta':
self.in_meta = False
if self.meta_type:
if self.meta_type not in self.meta_types:
self.meta_types[self.meta_type] = []
self.meta_types[self.meta_type].append(''.join(self.meta_content))
elif name == 'nzb':
self.in_nzb = False
def endDocument(self):
""" End of the file """
self.nzo.groups = self.groups
self.nzo.meta = self.meta_types
logging.debug('META-DATA = %s', self.nzo.meta)
files = max(1, self.valids)
self.nzo.avg_stamp = self.avg_age / files
self.nzo.avg_date = datetime.datetime.fromtimestamp(self.avg_age / files)
@@ -501,7 +531,9 @@ NzbObjectMapper = (
('oversized', 'oversized'), # Was detected as oversized
('create_group_folder', 'create_group_folder'),
('precheck', 'precheck'),
('incomplete', 'incomplete') # Was detected as incomplete
('incomplete', 'incomplete'), # Was detected as incomplete
('reuse', 'reuse'),
('meta', 'meta') # Meta-date from 1.1 type NZB
)
class NzbObject(TryList):
@@ -537,6 +569,7 @@ class NzbObject(TryList):
self.work_name = work_name
self.final_name = work_name
self.meta = {}
self.created = False # dirprefixes + work_name created
self.bytes = 0 # Original bytesize
self.bytes_downloaded = 0 # Downloaded byte
@@ -588,6 +621,7 @@ class NzbObject(TryList):
self.oversized = False
self.precheck = False
self.incomplete = False
self.reuse = reuse
if self.status == Status.QUEUED and not reuse:
self.precheck = cfg.pre_check()
if self.precheck:
@@ -695,18 +729,29 @@ class NzbObject(TryList):
if not self.files and not reuse:
self.purge_data(keep_basic=False)
if self.url:
logging.warning(Ta('Empty NZB file %s') + ' [%s]', filename, self.url)
if cfg.warn_empty_nzb():
mylog = logging.warning
else:
logging.warning(Ta('Empty NZB file %s'), filename)
mylog = logging.info
if self.url:
mylog(Ta('Empty NZB file %s') + ' [%s]', filename, self.url)
else:
mylog(Ta('Empty NZB file %s'), filename)
raise ValueError
if cat is None:
for metacat in self.meta.get('category', ()):
metacat = cat_convert(metacat)
if metacat:
cat = metacat
break
if cat is None:
for grp in self.groups:
cat = cat_convert(grp)
if cat:
break
if cfg.create_group_folders():
self.dirprefix.append(self.group)
@@ -845,8 +890,8 @@ class NzbObject(TryList):
head, vol, block = analyse_par2(fn)
## Is a par2file and repair mode activated
if head and (self.repair or cfg.allow_streaming()):
## Skip if mini-par2 is not complete
if not block and nzf.bytes_left:
## Skip if mini-par2 is not complete and there are more par2 files
if not block and nzf.bytes_left and self.extrapars.get(head):
return
nzf.set_par2(head, vol, block)
## Already got a parfile for this set?
@@ -890,6 +935,14 @@ class NzbObject(TryList):
if file_done:
self.remove_nzf(nzf)
if not self.reuse and cfg.fail_hopeless() and not self.check_quality(99)[0]:
#set the nzo status to return "Queued"
self.status = Status.QUEUED
self.set_download_report()
self.fail_msg = T('Aborted, cannot be completed')
self.set_unpack_info('Download', self.fail_msg, unique=False)
logging.debug('Abort job "%s", due to impossibility to complete it', self.final_name_pw_clean)
return True, True, True
if reset:
self.reset_try_list()
@@ -970,6 +1023,7 @@ class NzbObject(TryList):
def set_pp(self, value):
self.repair, self.unpack, self.delete = sabnzbd.pp_to_opts(value)
self.save_to_disk()
@property
def final_name_pw(self):
@@ -998,11 +1052,15 @@ class NzbObject(TryList):
else:
return self.final_name
def set_final_name_pw(self, name):
def set_final_name_pw(self, name, password=None):
if isinstance(name, str):
name, self.password = scan_password(platform_encode(name))
if password:
name = platform_encode(name)
self.password = platform_encode(password)
else:
name, self.password = scan_password(platform_encode(name))
self.final_name = sanitize_foldername(name)
self.save_attribs()
self.save_to_disk()
def pause(self):
self.status = 'Paused'
@@ -1030,7 +1088,7 @@ class NzbObject(TryList):
self.partable.pop(setname)
__re_quick_par2_check = re.compile('\.par2\W*', re.I)
def check_quality(self):
def check_quality(self, req_ratio=0):
""" Determine amount of articles present on servers
and return (gross available, nett) bytes
"""
@@ -1041,7 +1099,8 @@ class NzbObject(TryList):
for nzf_id in self.files_table:
nzf = self.files_table[nzf_id]
assert isinstance(nzf, NzbFile)
short += nzf.bytes_left
if nzf.deleted:
short += nzf.bytes_left
if self.__re_quick_par2_check.search(nzf.subject):
pars += nzf.bytes
anypars = True
@@ -1050,7 +1109,7 @@ class NzbObject(TryList):
have = need + pars - short
ratio = float(have) / float(max(1, need))
if anypars:
enough = ratio * 100.0 >= float(cfg.req_completion_rate())
enough = ratio * 100.0 >= (req_ratio or float(cfg.req_completion_rate()))
else:
enough = have >= need
logging.debug('Download Quality: enough=%s, have=%s, need=%s, ratio=%s', enough, have, need, ratio)
@@ -1074,15 +1133,18 @@ class NzbObject(TryList):
msg1 = T('Downloaded in %s at an average of %sB/s') % (complete_time, to_units(avg_bps*1024, dec_limit=1))
bad = self.nzo_info.get('bad_art_log', [])
miss = self.nzo_info.get('missing_art_log', [])
killed = self.nzo_info.get('killed_art_log', [])
dups = self.nzo_info.get('dup_art_log', [])
msg2 = msg3 = msg4 = ''
msg2 = msg3 = msg4 = msg5 = ''
if bad:
msg2 = ('<br/>' + T('%s articles were malformed')) % len(bad)
if miss:
msg3 = ('<br/>' + T('%s articles were missing')) % len(miss)
if dups:
msg4 = ('<br/>' + T('%s articles had non-matching duplicates')) % len(dups)
msg = ''.join((msg1, msg2, msg3, msg4,))
if killed:
msg5 = ('<br/>' + T('%s articles were removed')) % len(killed)
msg = ''.join((msg1, msg2, msg3, msg4, msg5, ))
self.set_unpack_info('Download', msg, unique=True)
if self.url:
self.set_unpack_info('Source', format_source_url(self.url), unique=True)
@@ -1201,6 +1263,19 @@ class NzbObject(TryList):
self.files[pos+1] = nzf
self.files[pos] = tmp_nzf
# Determine if rating information (including site identifier so rating can be updated)
# is present in metadata and if so store it
def update_rating(self):
try:
def _get_first_meta(type):
values = self.meta.get('x-rating-' + type, None)
return values[0] if values else None
rating_types = ['id', 'video', 'videocnt', 'audio', 'audiocnt', 'voteup' ,'votedown']
rs = map(_get_first_meta, rating_types)
Rating.do.add_rating(rs[0], self.nzo_id, rs[1], rs[2], rs[3], rs[4], rs[5], rs[6])
except:
pass
## end nzo.Mutators #######################################################
###########################################################################
@property
@@ -1356,6 +1431,12 @@ class NzbObject(TryList):
def repair_opts(self):
return self.repair, self.unpack, self.delete
def save_to_disk(self):
""" Save job's admin to disk """
self.save_attribs()
if self.nzo_id:
sabnzbd.save_data(self, self.nzo_id, self.workpath)
def save_attribs(self):
set_attrib_file(self.workpath, (self.cat, self.pp, self.script, self.priority, self.final_name_pw_clean, self.url))
@@ -1395,6 +1476,8 @@ class NzbObject(TryList):
self.pp_active = False
self.avg_stamp = time.mktime(self.avg_date.timetuple())
self.wait = None
if self.meta is None:
self.meta = {}
TryList.__init__(self)
@@ -1546,24 +1629,36 @@ def format_time_string(seconds, days=0):
return completestr.strip()
RE_PASSWORD1 = re.compile(r'([^/\\]+)[/\\](.+)')
RE_PASSWORD2 = re.compile(r'(.+){{([^{}]+)}}$')
RE_PASSWORD3 = re.compile(r'(.+)\s+password\s*=\s*(.+)$', re.I)
def scan_password(name):
""" Get password (if any) from the title
"""
if 'http://' in name or 'https://' in name:
return name, None
m = RE_PASSWORD1.search(name)
if not m:
m = RE_PASSWORD2.search(name)
if not m:
m = RE_PASSWORD3.search(name)
if m:
return m.group(1).strip('. '), m.group(2).strip()
else:
return name.strip('. '), None
braces = name.find('{{')
if braces < 0:
braces = len(name)
slash = name.find('/')
# Look for name/password, but make sure that '/' comes before any {{
if slash >= 0 and slash < braces and not 'password=' in name:
return name[:slash].strip('. '), name[slash+1:]
# Look for "name password=password"
pw = name.find('password=')
if pw >= 0:
return name[:pw].strip('. '), name[pw+9:]
# Look for name{{password}}
if braces < len(name) and name.endswith('}}'):
return name[:braces].strip('. '), name[braces+2:len(name)-2]
# Look again for name/password
if slash >= 0:
return name[:slash].strip('. '), name[slash+1:]
# No password found
return name, None
def get_attrib_file(path, size):

View File

@@ -39,6 +39,7 @@ from sabnzbd.constants import REPAIR_PRIORITY, TOP_PRIORITY, POSTPROC_QUEUE_FILE
POSTPROC_QUEUE_VERSION, sample_match, JOB_ADMIN, Status, VERIFIED_FILE
from sabnzbd.encoding import TRANS, unicoder
from sabnzbd.newzbin import Bookmarks
from sabnzbd.rating import Rating
import sabnzbd.emailer as emailer
import sabnzbd.dirscanner as dirscanner
import sabnzbd.downloader
@@ -234,6 +235,12 @@ def process_job(nzo):
nzo.save_attribs()
all_ok = False
if nzo.fail_msg: # Special case: aborted due to too many missing data
nzo.status = Status.FAILED
nzo.save_attribs()
all_ok = False
par_error = unpack_error = True
try:
# Get the folder containing the download result
@@ -241,7 +248,7 @@ def process_job(nzo):
tmp_workdir_complete = None
# if no files are present (except __admin__), fail the job
if len(globber(workdir)) < 2:
if all_ok and len(globber(workdir)) < 2:
if nzo.precheck:
enough, ratio = nzo.check_quality()
req_ratio = float(cfg.req_completion_rate()) / 100.0
@@ -272,7 +279,7 @@ def process_job(nzo):
filename, flag_repair, flag_unpack, flag_delete, script, cat)
## Par processing, if enabled
if flag_repair:
if all_ok and flag_repair:
par_error, re_add = parring(nzo, workdir)
if re_add:
# Try to get more par files
@@ -301,7 +308,10 @@ def process_job(nzo):
complete_dir = real_path(cfg.complete_dir.get_path(), catdir)
## TV/Movie/Date Renaming code part 1 - detect and construct paths
file_sorter = Sorter(cat)
if cfg.enable_meta():
file_sorter = Sorter(nzo, cat)
else:
file_sorter = Sorter(None, cat)
complete_dir = file_sorter.detect(dirname, complete_dir)
if file_sorter.sort_file:
one_folder = False
@@ -384,14 +394,17 @@ def process_job(nzo):
if not nzb_list:
## Give destination its final name
if cfg.folder_rename() and tmp_workdir_complete and not one_folder:
if not all_ok:
if all_ok:
try:
newfiles = rename_and_collapse_folder(tmp_workdir_complete, workdir_complete, newfiles)
except:
logging.error(Ta('Error renaming "%s" to "%s"'), tmp_workdir_complete, workdir_complete)
logging.info('Traceback: ', exc_info = True)
# Better disable sorting because filenames are all off now
file_sorter.sort_file = None
else:
workdir_complete = tmp_workdir_complete.replace('_UNPACK_', '_FAILED_')
workdir_complete = get_unique_path(workdir_complete, n=0, create_dir=False)
try:
collapse_folder(tmp_workdir_complete, workdir_complete)
except:
logging.error(Ta('Error renaming "%s" to "%s"'), tmp_workdir_complete, workdir_complete)
logging.info("Traceback: ", exc_info = True)
if empty:
job_result = -1
@@ -414,14 +427,15 @@ def process_job(nzo):
## Run the user script
script_path = make_script_path(script)
if all_ok and (not nzb_list) and script_path:
if (all_ok or not cfg.safe_postproc()) and (not nzb_list) and script_path:
#set the current nzo status to "Ext Script...". Used in History
nzo.status = Status.RUNNING
nzo.set_action_line(T('Running script'), unicoder(script))
nzo.set_unpack_info('Script', T('Running user script %s') % unicoder(script), unique=True)
script_log, script_ret = external_processing(script_path, workdir_complete, nzo.filename,
msgid, dirname, cat, nzo.group, job_result)
script_line = get_last_line(script_log)
msgid, dirname, cat, nzo.group, job_result,
nzo.nzo_info.get('failure', ''))
script_line = get_last_line(script_log)
if script_log:
script_output = nzo.nzo_id
if script_line:
@@ -437,7 +451,7 @@ def process_job(nzo):
if (not nzb_list) and cfg.email_endjob():
if (cfg.email_endjob() == 1) or (cfg.email_endjob() == 2 and (unpack_error or par_error)):
emailer.endjob(dirname, msgid, cat, all_ok, workdir_complete, nzo.bytes_downloaded,
nzo.unpack_info, script, TRANS(script_log), script_ret)
nzo.fail_msg, nzo.unpack_info, script, TRANS(script_log), script_ret)
if script_output:
# Can do this only now, otherwise it would show up in the email
@@ -464,6 +478,18 @@ def process_job(nzo):
elif all_ok and isinstance(nzo.url, str):
sabnzbd.proxy_rm_bookmark(nzo.url)
## Force error for empty result
all_ok = all_ok and not empty
## Update indexer with results
if nzo.encrypted > 0:
Rating.do.update_auto_flag(nzo.nzo_id, Rating.FLAG_ENCRYTPTED)
if empty:
hosts = map(lambda s: s.host, sabnzbd.downloader.Downloader.do.nzo_servers(nzo))
if not hosts: hosts = [None]
for host in hosts:
Rating.do.update_auto_flag(nzo.nzo_id, Rating.FLAG_EXPIRED, host)
## Show final status in history
if all_ok:
growler.send_notification(T('Download Completed'), filename, 'complete')
@@ -482,11 +508,9 @@ def process_job(nzo):
nzo.status = Status.FAILED
par_error = True
all_ok = False
info = nzo.unpack_info.copy()
info['fail'] = [nzo.fail_msg]
if cfg.email_endjob():
emailer.endjob(dirname, msgid, cat, all_ok, workdir_complete, nzo.bytes_downloaded,
info, '', '', 0)
nzo.fail_msg, nzo.unpack_info, '', '', 0)
if all_ok:
@@ -549,6 +573,7 @@ def parring(nzo, workdir):
re_add = False
par_error = False
single = len(repair_sets) == 1
if repair_sets:
for setname in repair_sets:
@@ -557,11 +582,14 @@ def parring(nzo, workdir):
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
if os.path.exists(os.path.join(nzo.downpath, parfile_nzf.filename)) or parfile_nzf.extrapars:
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, setname, single=single)
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
else:
continue
par_error = par_error or not res
else:
logging.info("No par2 sets for %s", filename)
@@ -741,9 +769,10 @@ def remove_samples(path):
#------------------------------------------------------------------------------
def collapse_folder(oldpath, newpath):
def rename_and_collapse_folder(oldpath, newpath, files):
""" Rename folder, collapsing when there's just a single subfolder
oldpath --> newpath OR oldpath/subfolder --> newpath
Modify list of filenames accordingly
"""
orgpath = oldpath
items = globber(oldpath)
@@ -754,11 +783,16 @@ def collapse_folder(oldpath, newpath):
logging.info('Collapsing %s', os.path.join(newpath, folder))
oldpath = folder_path
oldpath = os.path.normpath(oldpath)
newpath = os.path.normpath(newpath)
files = [os.path.normpath(f).replace(oldpath, newpath) for f in files]
renamer(oldpath, newpath)
try:
remove_dir(orgpath)
except:
pass
return files
#------------------------------------------------------------------------------

260
sabnzbd/rating.py Normal file
View File

@@ -0,0 +1,260 @@
#!/usr/bin/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.
"""
sabnzbd.rating - Rating support functions
"""
import httplib
import urllib
import time
import logging
import copy
import socket
try:
socket.ssl
_HAVE_SSL = True
except:
_HAVE_SSL = False
from threading import *
import sabnzbd
from sabnzbd.decorators import synchronized
from sabnzbd.misc import OrderedSetQueue
import sabnzbd.cfg as cfg
RATING_URL = "/releaseRatings/releaseRatings.php"
RATING_LOCK = RLock()
_g_warnings = 0
def _warn(msg):
global _g_warnings
_g_warnings += 1
if _g_warnings < 3:
logging.warning(msg)
def _reset_warn():
global _g_warnings
_g_warnings = 0
class NzbRating(object):
def __init__(self):
self.avg_video = 0
self.avg_video_cnt = 0
self.avg_audio = 0
self.avg_audio_cnt = 0
self.avg_vote_up = 0
self.avg_vote_down = 0
self.user_video = None
self.user_audio = None
self.user_vote = None
self.user_flag = {}
self.auto_flag = {}
self.changed = 0
class Rating(Thread):
VERSION = 1
VOTE_UP = 1
VOTE_DOWN = 2
FLAG_OK = 0
FLAG_SPAM = 1
FLAG_ENCRYPTED = 2
FLAG_EXPIRED = 3
FLAG_OTHER = 4
FLAG_COMMENT = 5
CHANGED_USER_VIDEO = 0x01
CHANGED_USER_AUDIO = 0x02
CHANGED_USER_VOTE = 0x04
CHANGED_USER_FLAG = 0x08
CHANGED_AUTO_FLAG = 0x10
do = None
def __init__(self):
Rating.do = self
self.shutdown = False
self.queue = OrderedSetQueue()
try:
(self.version, self.ratings, self.nzo_indexer_map) = sabnzbd.load_admin("Rating.sab")
if (self.version != Rating.VERSION):
raise Exception()
except:
self.version = Rating.VERSION
self.ratings = {}
self.nzo_indexer_map = {}
Thread.__init__(self)
if not _HAVE_SSL:
logging.warning('Ratings server requires secure connection')
self.stop()
def stop(self):
self.shutdown = True
self.queue.put(None) # Unblock queue
def run(self):
self.shutdown = False
while not self.shutdown:
time.sleep(0.5)
indexer_id = self.queue.get()
try:
if indexer_id and not self._send_rating(indexer_id):
for i in range(0, 60):
if self.shutdown: break
time.sleep(1)
self.queue.put(indexer_id)
except:
pass
logging.debug('Stopping ratings')
@synchronized(RATING_LOCK)
def save(self):
if self.ratings and self.nzo_indexer_map:
sabnzbd.save_admin((self.version, self.ratings, self.nzo_indexer_map), "Rating.sab")
# The same file may be uploaded multiple times creating a new nzo_id each time
@synchronized(RATING_LOCK)
def add_rating(self, indexer_id, nzo_id, video, video_cnt, audio, audio_cnt, vote_up, vote_down):
if indexer_id and nzo_id and (video or audio or vote_up or vote_down):
logging.debug('Add rating (%s, %s: %s, %s, %s, %s)', indexer_id, nzo_id, video, audio, vote_up, vote_down)
try:
rating = self.ratings.get(indexer_id, NzbRating())
if video and video_cnt:
rating.avg_video = int(float(video))
rating.avg_video_cnt = int(float(video_cnt))
if audio and audio_cnt:
rating.avg_audio = int(float(audio))
rating.avg_audio_cnt = int(float(audio_cnt))
if vote_up: rating.avg_vote_up = int(float(vote_up))
if vote_down: rating.avg_vote_down = int(float(vote_down))
self.ratings[indexer_id] = rating
self.nzo_indexer_map[nzo_id] = indexer_id
except:
pass
@synchronized(RATING_LOCK)
def update_user_rating(self, nzo_id, video, audio, vote, flag, flag_detail = None):
logging.debug('Updating user rating (%s: %s, %s, %s, %s)', nzo_id, video, audio, vote, flag)
if nzo_id not in self.nzo_indexer_map:
logging.warning('indexer id (%s) not found for ratings file', nzo_id)
return
indexer_id = self.nzo_indexer_map[nzo_id]
rating = self.ratings[indexer_id]
if video:
rating.user_video = int(video)
rating.avg_video = int((rating.avg_video_cnt * rating.avg_video + rating.user_video) / (rating.avg_video_cnt + 1))
rating.changed = rating.changed | Rating.CHANGED_USER_VIDEO
if audio:
rating.user_audio = int(audio)
rating.avg_audio = int((rating.avg_audio_cnt * rating.avg_audio + rating.user_audio) / (rating.avg_audio_cnt + 1))
rating.changed = rating.changed | Rating.CHANGED_USER_AUDIO
if flag:
rating.user_flag = { 'val': int(flag), 'detail': flag_detail }
rating.changed = rating.changed | Rating.CHANGED_USER_FLAG
if vote and not rating.user_vote:
rating.user_vote = int(vote)
rating.changed = rating.changed | Rating.CHANGED_USER_VOTE
if rating.user_vote == Rating.VOTE_UP:
rating.avg_vote_up += 1
else:
rating.avg_vote_down += 1
self.queue.put(indexer_id)
@synchronized(RATING_LOCK)
def update_auto_flag(self, nzo_id, flag, flag_detail = None):
if not flag or not cfg.rating_feedback():
return
logging.debug('Updating auto flag (%s: %s)', nzo_id, flag)
if nzo_id not in self.nzo_indexer_map:
logging.warning('indexer id (%s) not found for ratings file', nzo_id)
return
indexer_id = self.nzo_indexer_map[nzo_id]
rating = self.ratings[indexer_id]
rating.auto_flag = { 'val': int(flag), 'detail': flag_detail }
rating.changed = rating.changed | Rating.CHANGED_AUTO_FLAG
self.queue.put(indexer_id)
@synchronized(RATING_LOCK)
def get_rating_by_nzo(self, nzo_id):
if nzo_id not in self.nzo_indexer_map:
return None
return copy.copy(self.ratings[self.nzo_indexer_map[nzo_id]])
@synchronized(RATING_LOCK)
def _get_rating_by_indexer(self, indexer_id):
return copy.copy(self.ratings[indexer_id])
def _flag_request(self, val, flag_detail, auto):
if val == Rating.FLAG_SPAM:
return {'m': 'rs', 'auto': auto}
if val == Rating.FLAG_ENCRYPTED:
return {'m': 'rp', 'auto': auto}
if val == Rating.FLAG_EXPIRED:
expired_host = flag_detail if flag_detail and len(flag_detail) > 0 else 'Other'
return {'m': 'rpr', 'pr': expired_host, 'auto': auto}
if (val == Rating.FLAG_OTHER) and flag_detail and len(flag_detail) > 0:
return {'m': 'o', 'r': flag_detail}
if (val == Rating.FLAG_COMMENT) and flag_detail and len(flag_detail) > 0:
return {'m': 'rc', 'r': flag_detail}
def _send_rating(self, indexer_id):
logging.debug('Updating indexer rating (%s)', indexer_id)
api_key = cfg.rating_api_key()
rating_host = cfg.rating_host()
if not api_key or not rating_host:
return False
requests = []
_headers = {'User-agent' : 'SABnzbd+/%s' % sabnzbd.version.__version__, 'Content-type': 'application/x-www-form-urlencoded'}
rating = self._get_rating_by_indexer(indexer_id) # Requesting info here ensures always have latest information even on retry
if rating.changed & Rating.CHANGED_USER_VIDEO:
requests.append({'m': 'r', 'r': 'videoQuality', 'rn': rating.user_video})
if rating.changed & Rating.CHANGED_USER_AUDIO:
requests.append({'m': 'r', 'r': 'audioQuality', 'rn': rating.user_audio})
if rating.changed & Rating.CHANGED_USER_VOTE:
up_down = 'up' if rating.user_vote == Rating.VOTE_UP else 'down'
requests.append({'m': 'v', 'v': up_down, 'r': 'overall'})
if rating.changed & Rating.CHANGED_USER_FLAG:
requests.append(self._flag_request(rating.user_flag.get('val'), rating.user_flag.get('detail'), 0))
if rating.changed & Rating.CHANGED_AUTO_FLAG:
requests.append(self._flag_request(rating.auto_flag.get('val'), rating.auto_flag.get('detail'), 1))
try:
conn = httplib.HTTPSConnection(rating_host)
for request in filter(lambda r: r is not None, requests):
request['apikey'] = api_key
request['i'] = indexer_id
conn.request('POST', RATING_URL, urllib.urlencode(request), headers = _headers)
response = conn.getresponse()
response.read()
if response.status == httplib.UNAUTHORIZED:
_warn('Ratings server unauthorized user')
return False
elif response.status != httplib.OK:
_warn('Ratings server failed to process request (%s, %s)' % (response.status, response.reason))
return False
rating.changed = 0
_reset_warn()
return True
except:
_warn('Problem accessing ratings server: %s' % rating_host)
return False

View File

@@ -84,6 +84,16 @@ def run_method():
else:
return None
def next_run(t=None):
global __RSS
if __RSS:
if t:
__RSS.next_run = t
else:
return __RSS.next_run
else:
return time.time()
def save():
global __RSS
if __RSS: __RSS.save()
@@ -159,6 +169,8 @@ class RSSQueue(object):
return False
self.jobs = {}
self.next_run = time.time()
try:
defined = config.get_rss().keys()
feeds = sabnzbd.load_admin(RSS_FILE_NAME)
@@ -488,6 +500,8 @@ class RSSQueue(object):
"""
if not sabnzbd.PAUSED_ALL:
active = False
if self.next_run < time.time():
self.next_run = time.time() + cfg.rss_rate.get() * 60
feeds = config.get_rss()
for feed in feeds.keys():
try:

View File

@@ -40,7 +40,7 @@ __SCHED = None # Global pointer to Scheduler instance
RSSTASK_MINUTE = random.randint(0, 59)
SCHEDULE_GUARD_FLAG = False
PP_PAUSE_EVENT = False
def schedule_guard():
""" Set flag for scheduler restart """
@@ -53,6 +53,8 @@ def pp_pause():
def pp_resume():
PostProcessor.do.paused = False
def pp_pause_event():
return PP_PAUSE_EVENT
def init():
""" Create the scheduler and set all required events
@@ -136,6 +138,7 @@ def init():
interval = cfg.rss_rate()
delay = random.randint(0, interval-1)
logging.debug("Scheduling RSS interval task every %s min (delay=%s)", interval, delay)
sabnzbd.rss.next_run(time.time() + delay * 60)
__SCHED.add_interval_task(rss.run_method, "RSS", delay*60, interval*60,
kronos.method.sequential, None, None)
__SCHED.add_single_task(rss.run_method, 'RSS', 15, kronos.method.sequential, None, None)
@@ -151,7 +154,7 @@ def init():
kronos.method.sequential, [], None)
if cfg.newzbin_bookmarks():
if False: #cfg.newzbin_bookmarks():
interval = cfg.bookmark_rate()
delay = random.randint(0, interval-1)
logging.debug("Scheduling Bookmark interval task every %s min (delay=%s)", interval, delay)
@@ -166,6 +169,10 @@ def init():
__SCHED.add_daytime_task(action, 'quota_reset', range(1, 8), None, (hour, minute),
kronos.method.sequential, [], None)
logging.info('Setting schedule for midnight BPS reset')
__SCHED.add_daytime_task(sabnzbd.bpsmeter.midnight_action, 'midnight_bps', range(1, 8), None, (0, 0),
kronos.method.sequential, [], None)
# Subscribe to special schedule changes
cfg.newzbin_bookmarks.callback(schedule_guard)
@@ -270,6 +277,8 @@ def sort_schedules(all_events, now=None):
def analyse(was_paused=False):
""" Determine what pause/resume state we would have now.
"""
global PP_PAUSE_EVENT
PP_PAUSE_EVENT = False
paused = None
paused_all = False
pause_post = False
@@ -287,13 +296,16 @@ def analyse(was_paused=False):
paused = True
elif action == 'pause_all':
paused_all = True
PP_PAUSE_EVENT = True
elif action == 'resume':
paused = False
paused_all = False
elif action == 'pause_post':
pause_post = True
PP_PAUSE_EVENT = True
elif action == 'resume_post':
pause_post = False
PP_PAUSE_EVENT = True
elif action == 'speedlimit' and value!=None:
speedlimit = int(ev[2])
elif action == 'enable_server':

View File

@@ -101,6 +101,14 @@ SKIN_TEXT = {
'homePage' : TT('Home page'), #: Home page of the SABnzbd project
'source' : TT('Source'), #: Where to find the SABnzbd sourcecode
'or' : TT('or'), #: Used in "IRC or IRC-Webaccess"
'host' : TT('Host'),
'comment' : TT('Comment'),
'send' : TT('Send'),
'cancel' : TT('Cancel'),
'other' : TT('Other'),
'report' : TT('Report'),
'video' : TT('Video'),
'audio' : TT('Audio'),
# General template elements
'signOn' : TT('The automatic usenet download tool'), #: SABnzbd's theme line
@@ -232,8 +240,11 @@ SKIN_TEXT = {
'purgeCompl' : TT('Purge Completed NZBs'), #: Button to delete all completed jobs in History
'opt-extra-NZB' : TT('Optional Supplemental NZB'), #: Button to add NZB to failed job in History
'msg-path' : TT('Path'), #: Path as displayed in History details
'spam' : TT('Virus/spam'),
'encrypted' : TT('Passworded'),
'expired' : TT('Out of retention'),
'otherProblem' : TT('Other problem'),
# Connections page
'link-forceDisc' : TT('Force Disconnect'), #: Status page button
'askTestEmail' : TT('This will send a test email to your account.'),
@@ -271,6 +282,7 @@ SKIN_TEXT = {
'version' : TT('Version'),
'uptime' : TT('Uptime'),
'backup' : TT('Backup'), #: Indicates that server is Backup server in Status page
'oznzb' : TT('OZnzb'),
# Config->General
'generalConfig' : TT('General configuration'),
@@ -380,13 +392,14 @@ SKIN_TEXT = {
'explain-top_only' : TT('Enable for less memory usage. Disable to prevent slow jobs from blocking the queue.'),
'opt-safe_postproc' : TT('Post-Process Only Verified Jobs'),
'explain-safe_postproc' : TT('Only perform post-processing on jobs that passed all PAR2 checks.'),
'opt-pause_on_pwrar' : TT('Pause job when encrypted RAR is downloaded'),
'explain-pause_on_pwrar' : TT('You\'ll need to set a password and resume the job.'),
'opt-pause_on_pwrar' : TT('Action when encrypted RAR is downloaded'),
'explain-pause_on_pwrar' : TT('In case of "Pause", you\'ll need to set a password and resume the job.'),
'opt-no_dupes' : TT('Detect Duplicate Downloads'),
'explain-no_dupes' : TT('Detect identically named NZB files (requires NZB backup option) and duplicate titles across RSS feeds.'),
'nodupes-off' : TT('Off'), #: Three way switch for duplicates
'nodupes-ignore' : TT('Discard'), #: Three way switch for duplicates
'nodupes-pause' : TT('Pause'), #: Three way switch for duplicates
'abort' : TT('Abort'), #: Three way switch for encrypted posts
'opt-sfv_check' : TT('Enable SFV-based checks'),
'explain-sfv_check' : TT('Do an extra verification based on SFV files.'),
'opt-unpack_check' : TT('Check result of unpacking'),
@@ -444,6 +457,7 @@ SKIN_TEXT = {
'swtag-pp' : TT('Post processing'),
'swtag-naming' : TT('Naming'),
'swtag-quota' : TT('Quota'),
'swtag-indexing' : TT('Indexing'),
'opt-quota_size' : TT('Size'), #: Size of the download quota
'explain-quota_size' : TT('How much can be downloaded this month (K/M/G)'),
'opt-quota_day' : TT('Reset day'), #: Reset day of the download quota
@@ -458,7 +472,15 @@ SKIN_TEXT = {
'explain-max_art_tries' : TT('Maximum number of retries per server'),
'opt-max_art_opt' : TT('Only for optional servers'),
'explain-max_art_opt' : TT('Apply maximum retries only to optional servers'),
'opt-fail_hopeless' : TT('Abort jobs that cannot be completed'),
'explain-fail_hopeless' : TT('When during download it becomes clear that too much data is missing, abort the job'),
'opt-rating_enable' : TT('Enable OZnzb Integration'),
'explain-rating_enable' : TT('Enhanced functionality including ratings and extra status information is available when connected to OZnzb indexer.'),
'opt-rating_api_key' : TT('Site API Key'),
'explain-rating_api_key' : TT('This key provides identity to indexer. Refer to https://www.oznzb.com/profile.'),
'tip-rating_api_key' : TT('Refer to https://www.oznzb.com/profile'),
'opt-rating_feedback' : TT('Automatic Feedback'),
'explain-rating_feedback' : TT('Send automatically calculated validation results for downloads to indexer.'),
# Config->Server
'configServer' : TT('Server configuration'), #: Caption
@@ -868,6 +890,7 @@ SKIN_TEXT = {
'wizard-port-eg' : TT('E.g. 119 or 563 for SSL'), #: Wizard port number examples
'wizard-exit' : TT('Exit SABnzbd'), #: Wizard EXIT button on first page
'wizard-start' : TT('Start Wizard'), #: Wizard START button on first page
'wizard-create-account' : TT('If you do not have an account it can be created at '),
#Special
'yourRights' : TT('''

View File

@@ -31,7 +31,7 @@ from sabnzbd.misc import move_to_path, cleanup_empty_directories, get_unique_pat
get_unique_filename, get_ext, renamer, sanitize_foldername
from sabnzbd.constants import series_match, date_match, year_match, sample_match
import sabnzbd.cfg as cfg
from sabnzbd.encoding import titler
from sabnzbd.encoding import titler, latin1
RE_SAMPLE = re.compile(sample_match, re.I)
# Do not rename .vob files as they are usually DVD's
@@ -61,11 +61,12 @@ COUNTRY_REP = ('(US)', '(UK)', '(EU)', '(CA)', '(YU)', '(VE)', '(TR)', '(CH)', \
'(AW)', '(AR)', '(AL)', '(AF)')
_RE_ENDEXT = re.compile(r'\.%ext[{}]*$', re.I)
_RE_ENDFN = re.compile(r'%fn[{}]*$', re.I)
def endswith_ext(path):
""" Return True when path ends with '.%ext'
def ends_in_file(path):
""" Return True when path ends with '.%ext' or '%fn'
"""
return _RE_ENDEXT.search(path) is not None
return bool(_RE_ENDEXT.search(path) or _RE_ENDFN.search(path))
def move_to_parent_folder(workdir):
@@ -95,31 +96,32 @@ def move_to_parent_folder(workdir):
class Sorter(object):
""" Generic Sorter class
"""
def __init__(self, cat):
def __init__(self, nzo, cat):
self.sorter = None
self.type = None
self.sort_file = False
self.nzo = nzo
self.cat = cat
self.ext = ''
def detect(self, dirname, complete_dir):
""" Detect which kind of sort applies
"""
self.sorter = SeriesSorter(dirname, complete_dir, self.cat)
self.sorter = SeriesSorter(self.nzo, dirname, complete_dir, self.cat)
if self.sorter.matched:
complete_dir = self.sorter.get_final_path()
self.type = 'tv'
self.sort_file = True
return complete_dir
self.sorter = DateSorter(dirname, complete_dir, self.cat)
self.sorter = DateSorter(self.nzo, dirname, complete_dir, self.cat)
if self.sorter.matched:
complete_dir = self.sorter.get_final_path()
self.type = 'date'
self.sort_file = True
return complete_dir
self.sorter = GenericSorter(dirname, complete_dir, self.cat)
self.sorter = GenericSorter(self.nzo, dirname, complete_dir, self.cat)
if self.sorter.matched:
complete_dir = self.sorter.get_final_path()
self.type = 'movie'
@@ -181,11 +183,12 @@ class Sorter(object):
class SeriesSorter(object):
""" Methods for Series Sorting
"""
def __init__(self, dirname, path, cat):
def __init__(self, nzo, dirname, path, cat):
self.matched = False
self.original_dirname = dirname
self.original_path = path
self.nzo = nzo
self.cat = cat
self.sort_string = cfg.tv_sort_string()
self.cats = cfg.tv_categories()
@@ -251,8 +254,8 @@ class SeriesSorter(object):
def get_shownames(self):
''' Get the show name from the match object and format it '''
# Get the formatted title and alternate title formats
self.show_info['show_tname'], self.show_info['show_tname_two'], self.show_info['show_tname_three'] = get_titles(self.match_obj, self.original_dirname, True)
self.show_info['show_name'], self.show_info['show_name_two'], self.show_info['show_name_three'] = get_titles(self.match_obj, self.original_dirname)
self.show_info['show_tname'], self.show_info['show_tname_two'], self.show_info['show_tname_three'] = get_titles(self.nzo, self.match_obj, self.original_dirname, True)
self.show_info['show_name'], self.show_info['show_name_two'], self.show_info['show_name_three'] = get_titles(self.nzo, self.match_obj, self.original_dirname)
def get_seasons(self):
@@ -301,7 +304,7 @@ class SeriesSorter(object):
def get_showdescriptions(self):
''' Get the show descriptions from the match object and format them '''
self.show_info['ep_name'], self.show_info['ep_name_two'], self.show_info['ep_name_three'] = get_descriptions(self.match_obj, self.original_dirname)
self.show_info['ep_name'], self.show_info['ep_name_two'], self.show_info['ep_name_three'] = get_descriptions(self.nzo, self.match_obj, self.original_dirname)
def get_values(self):
@@ -333,7 +336,7 @@ class SeriesSorter(object):
sorter = self.sort_string.replace('\\', '/')
mapping = []
if endswith_ext(sorter):
if ends_in_file(sorter):
extension = True
sorter = sorter.replace('.%ext', '')
else:
@@ -402,9 +405,9 @@ class SeriesSorter(object):
def to_filepath(f, current_path):
if is_full_path(f):
filepath = f.replace('_UNPACK_', '')
filepath = os.path.normpath(f)
else:
filepath = os.path.join(current_path, f)
filepath = os.path.normpath(os.path.join(current_path, f))
return filepath
# Create a generator of filepaths, ignore sample files and excluded files (vobs ect)
@@ -425,24 +428,20 @@ class SeriesSorter(object):
file, filepath, size = largest
# >20MB
if filepath and size > 20971520:
tmp, self.ext = os.path.splitext(file)
self.fname = tmp
self.fname, self.ext = os.path.splitext(os.path.split(file)[1])
newname = "%s%s" % (self.filename_set, self.ext)
# Replace %fn with the original filename
newname = newname.replace('%fn', tmp)
newname = newname.replace('%fn', self.fname)
newpath = os.path.join(current_path, newname)
# Replace %ext with extension
newpath = newpath.replace('%ext', self.ext)
if not os.path.exists(newpath):
try:
logging.debug("Rename: %s to %s", filepath, newpath)
renamer(filepath, newpath)
except:
logging.error("Failed to rename: %s to %s", current_path, newpath)
logging.info("Traceback: ", exc_info = True)
rename_similar(current_path, self.ext, self.filename_set, ())
else:
logging.debug('Current path already exists, skipping rename, %s', newpath)
try:
logging.debug("Rename: %s to %s", filepath, newpath)
renamer(filepath, newpath)
except:
logging.error("Failed to rename: %s to %s", current_path, newpath)
logging.info("Traceback: ", exc_info = True)
rename_similar(current_path, self.ext, self.filename_set, ())
else:
logging.debug('Nothing to rename, %s', files)
@@ -518,7 +517,7 @@ def check_for_sequence(regex, files):
class GenericSorter(object):
""" Methods for Generic Sorting
"""
def __init__(self, dirname, path, cat):
def __init__(self, nzo, dirname, path, cat):
self.matched = False
self.original_dirname = dirname
@@ -527,6 +526,7 @@ class GenericSorter(object):
self.extra = cfg.movie_sort_extra()
self.cats = cfg.movie_categories()
self.cat = cat
self.nzo = nzo
self.filename_set = ''
self.fname = '' # Value for %fn substitution in folders
self.final_path = ''
@@ -567,23 +567,30 @@ class GenericSorter(object):
""" Collect and construct all the values needed for path replacement
"""
## - Get Year
dirname = self.original_dirname.replace('_', ' ')
RE_YEAR = re.compile(year_match, re.I)
year_m = RE_YEAR.search(dirname)
if year_m:
# Find the last matched date
# Keep year_m to use in get_titles
year = RE_YEAR.findall(dirname)[-1][0]
self.movie_info['year'] = year
if self.nzo:
year = self.nzo.nzo_info.get('year') or self.nzo.meta.get('year', (None,))[0]
else:
self.movie_info['year'] = ''
year = ''
if year:
year_m = None
else:
dirname = self.original_dirname.replace('_', ' ')
RE_YEAR = re.compile(year_match, re.I)
year_m = RE_YEAR.search(dirname)
if year_m:
# Find the last matched date
# Keep year_m to use in get_titles
year = RE_YEAR.findall(dirname)[-1][0]
else:
year = ''
self.movie_info['year'] = year
## - Get Decades
self.movie_info['decade'], self.movie_info['decade_two'] = get_decades(self.movie_info['year'])
self.movie_info['decade'], self.movie_info['decade_two'] = get_decades(year)
## - Get Title
self.movie_info['ttitle'], self.movie_info['ttitle_two'], self.movie_info['ttitle_three'] = get_titles(year_m, self.original_dirname, True)
self.movie_info['title'], self.movie_info['title_two'], self.movie_info['title_three'] = get_titles(year_m, self.original_dirname)
self.movie_info['ttitle'], self.movie_info['ttitle_two'], self.movie_info['ttitle_three'] = get_titles(self.nzo, year_m, self.original_dirname, True)
self.movie_info['title'], self.movie_info['title_two'], self.movie_info['title_three'] = get_titles(self.nzo, year_m, self.original_dirname)
return True
@@ -594,7 +601,7 @@ class GenericSorter(object):
sorter = self.sort_string.replace('\\', '/')
mapping = []
if endswith_ext(sorter):
if ends_in_file(sorter):
extension = True
sorter = sorter.replace(".%ext", '')
else:
@@ -657,9 +664,9 @@ class GenericSorter(object):
logging.debug("Renaming Generic file")
def filter_files(_file, current_path):
if is_full_path(_file):
filepath = _file.replace('_UNPACK_', '')
filepath = os.path.normpath(_file)
else:
filepath = os.path.join(current_path, _file)
filepath = os.path.normpath(os.path.join(current_path, _file))
if os.path.exists(filepath):
size = os.stat(filepath).st_size
if size >= cfg.movie_rename_limit.get_int() and not RE_SAMPLE.search(_file) \
@@ -675,14 +682,13 @@ class GenericSorter(object):
if length == 1:
file = files[0]
if is_full_path(file):
filepath = file.replace('_UNPACK_', '')
filepath = os.path.normpath(file)
else:
filepath = os.path.join(current_path, file)
filepath = os.path.normpath(os.path.join(current_path, file))
if os.path.exists(filepath):
tmp, ext = os.path.splitext(file)
self.fname = tmp
self.fname, ext = os.path.splitext(os.path.split(file)[1])
newname = "%s%s" % (self.filename_set, ext)
newname = newname.replace('%fn', tmp)
newname = newname.replace('%fn', self.fname)
newpath = os.path.join(current_path, newname)
try:
logging.debug("Rename: %s to %s", filepath, newpath)
@@ -703,10 +709,9 @@ class GenericSorter(object):
for index, file in matched_files.iteritems():
filepath = os.path.join(current_path, file)
renamed.append(filepath)
tmp, ext = os.path.splitext(file)
self.fname = tmp
self.fname, ext = os.path.splitext(os.path.split(file)[1])
name = '%s%s' % (self.filename_set, self.extra)
name = name.replace('%1', str(index)).replace('%fn', tmp)
name = name.replace('%1', str(index)).replace('%fn', self.fname)
name = name + ext
newpath = os.path.join(current_path, name)
try:
@@ -723,7 +728,7 @@ class GenericSorter(object):
class DateSorter(object):
""" Methods for Date Sorting
"""
def __init__(self, dirname, path, cat):
def __init__(self, nzo, dirname, path, cat):
self.matched = False
self.original_dirname = dirname
@@ -731,6 +736,7 @@ class DateSorter(object):
self.sort_string = cfg.date_sort_string()
self.cats = cfg.date_categories()
self.cat = cat
self.nzo = nzo
self.filename_set = ''
self.fname = '' # Value for %fn substitution in folders
@@ -793,10 +799,10 @@ class DateSorter(object):
self.date_info['decade'], self.date_info['decade_two'] = get_decades(self.date_info['year'])
## - Get Title
self.date_info['ttitle'], self.date_info['ttitle_two'], self.date_info['ttitle_three'] = get_titles(self.match_obj, self.original_dirname, True)
self.date_info['title'], self.date_info['title_two'], self.date_info['title_three'] = get_titles(self.match_obj, self.original_dirname)
self.date_info['ttitle'], self.date_info['ttitle_two'], self.date_info['ttitle_three'] = get_titles(self.nzo, self.match_obj, self.original_dirname, True)
self.date_info['title'], self.date_info['title_two'], self.date_info['title_three'] = get_titles(self.nzo, self.match_obj, self.original_dirname)
self.date_info['ep_name'], self.date_info['ep_name_two'], self.date_info['ep_name_three'] = get_descriptions(self.match_obj, self.original_dirname)
self.date_info['ep_name'], self.date_info['ep_name_two'], self.date_info['ep_name_three'] = get_descriptions(self.nzo, self.match_obj, self.original_dirname)
return True
@@ -807,7 +813,7 @@ class DateSorter(object):
sorter = self.sort_string.replace('\\', '/')
mapping = []
if endswith_ext(sorter):
if ends_in_file(sorter):
extension = True
sorter = sorter.replace(".%ext", '')
else:
@@ -883,18 +889,17 @@ class DateSorter(object):
#find the master file to rename
for file in files:
if is_full_path(file):
filepath = file.replace('_UNPACK_', '')
filepath = os.path.normpath(file)
else:
filepath = os.path.join(current_path, file)
filepath = os.path.normpath(os.path.join(current_path, file))
if os.path.exists(filepath):
size = os.stat(filepath).st_size
if size > cfg.movie_rename_limit.get_int():
if 'sample' not in file:
tmp, ext = os.path.splitext(file)
self.fname = tmp
self.fname, ext = os.path.splitext(os.path.split(file)[1])
newname = "%s%s" % (self.filename_set, ext)
newname = newname.replace('%fn', tmp)
newname = newname.replace('%fn', self.fname)
newpath = os.path.join(current_path, newname)
if not os.path.exists(newpath):
try:
@@ -903,7 +908,7 @@ class DateSorter(object):
except:
logging.error(Ta('Failed to rename: %s to %s'), current_path, newpath)
logging.info("Traceback: ", exc_info = True)
rename_similar(current_path, ext, self.filename_set)
rename_similar(current_path, ext, self.filename_set, ())
break
@@ -930,7 +935,7 @@ def path_subst(path, mapping):
return ''.join(newpath)
def get_titles(match, name, titleing=False):
def get_titles(nzo, match, name, titleing=False):
'''
The title will be the part before the match
Clean it up and title() it
@@ -938,59 +943,64 @@ def get_titles(match, name, titleing=False):
''.title() isn't very good under python so this contains
a lot of little hacks to make it better and for more control
'''
if match:
name = name[:match.start()]
# Replace .US. with (US)
if cfg.tv_sort_countries() == 1:
for rep in COUNTRY_REP:
# (us) > (US)
name = replace_word(name, rep.lower(), rep)
# (Us) > (US)
name = replace_word(name, titler(rep), rep)
# .US. > (US)
dotted_country = '.%s.' % (rep.strip('()'))
name = replace_word(name, dotted_country, rep)
# Remove .US. and (US)
elif cfg.tv_sort_countries() == 2:
for rep in COUNTRY_REP:
# Remove (US)
name = replace_word(name, rep, '')
dotted_country = '.%s.' % (rep.strip('()'))
# Remove .US.
name = replace_word(name, dotted_country, '.')
title = name.replace('.', ' ').replace('_', ' ')
title = title.strip().strip('(').strip('_').strip('-').strip().strip('_')
if titleing:
title = titler(title) # title the show name so it is in a consistant letter case
#title applied uppercase to 's Python bug?
title = title.replace("'S", "'s")
# Replace titled country names, (Us) with (US) and so on
if nzo:
title = latin1(nzo.nzo_info.get('propername') or nzo.meta.get('propername', (None,))[0])
else:
title = ''
if not title:
if match:
name = name[:match.start()]
# Replace .US. with (US)
if cfg.tv_sort_countries() == 1:
for rep in COUNTRY_REP:
title = title.replace(titler(rep), rep)
# Remove country names, ie (Us)
# (us) > (US)
name = replace_word(name, rep.lower(), rep)
# (Us) > (US)
name = replace_word(name, titler(rep), rep)
# .US. > (US)
dotted_country = '.%s.' % (rep.strip('()'))
name = replace_word(name, dotted_country, rep)
# Remove .US. and (US)
elif cfg.tv_sort_countries() == 2:
for rep in COUNTRY_REP:
title = title.replace(titler(rep), '').strip()
# Remove (US)
name = replace_word(name, rep, '')
dotted_country = '.%s.' % (rep.strip('()'))
# Remove .US.
name = replace_word(name, dotted_country, '.')
title = name.replace('.', ' ').replace('_', ' ')
title = title.strip().strip('(').strip('_').strip('-').strip().strip('_')
# Make sure some words such as 'and' or 'of' stay lowercased.
for x in LOWERCASE:
xtitled = titler(x)
title = replace_word(title, xtitled, x)
# Make sure some words such as 'III' or 'IV' stay uppercased.
for x in UPPERCASE:
xtitled = titler(x)
title = replace_word(title, xtitled, x)
# Make sure the first letter of the title is always uppercase
if title:
title = titler(title[0]) + title[1:]
if titleing:
title = titler(title) # title the show name so it is in a consistant letter case
#title applied uppercase to 's Python bug?
title = title.replace("'S", "'s")
# Replace titled country names, (Us) with (US) and so on
if cfg.tv_sort_countries() == 1:
for rep in COUNTRY_REP:
title = title.replace(titler(rep), rep)
# Remove country names, ie (Us)
elif cfg.tv_sort_countries() == 2:
for rep in COUNTRY_REP:
title = title.replace(titler(rep), '').strip()
# Make sure some words such as 'and' or 'of' stay lowercased.
for x in LOWERCASE:
xtitled = titler(x)
title = replace_word(title, xtitled, x)
# Make sure some words such as 'III' or 'IV' stay uppercased.
for x in UPPERCASE:
xtitled = titler(x)
title = replace_word(title, xtitled, x)
# Make sure the first letter of the title is always uppercase
if title:
title = titler(title[0]) + title[1:]
# The title with spaces replaced by dots
dots = title.replace(" - ", "-").replace(' ','.').replace('_','.')
@@ -1010,22 +1020,27 @@ def replace_word(input, one, two):
input = input.replace(one, two)
return input
def get_descriptions(match, name):
def get_descriptions(nzo, match, name):
'''
If present, get a description from the nzb name.
A description has to be after the matched item, seperated either
like ' - Description' or '_-_Description'
'''
if match:
ep_name = name[match.end():] # Need to improve for multi ep support
if nzo:
ep_name = latin1(nzo.nzo_info.get('episodename') or nzo.meta.get('episodename', (None,))[0])
else:
ep_name = name
ep_name = ep_name.strip(' _.')
if ep_name.startswith('-'):
ep_name = ep_name.strip('- _.')
if '.' in ep_name and ' ' not in ep_name:
ep_name = ep_name.replace('.', ' ')
ep_name = ep_name.replace('_', ' ')
ep_name = ''
if not ep_name:
if match:
ep_name = name[match.end():] # Need to improve for multi ep support
else:
ep_name = name
ep_name = ep_name.strip(' _.')
if ep_name.startswith('-'):
ep_name = ep_name.strip('- _.')
if '.' in ep_name and ' ' not in ep_name:
ep_name = ep_name.replace('.', ' ')
ep_name = ep_name.replace('_', ' ')
ep_name2 = ep_name.replace(" - ", "-").replace(" ", ".")
ep_name3 = ep_name.replace(" ", "_")
return ep_name, ep_name2, ep_name3
@@ -1192,13 +1207,13 @@ def eval_sort(sorttype, expression, name=None, multipart=''):
name = sanitize_foldername(name)
if sorttype == 'series':
name = name or ('%s S01E05 - %s [DTS]' % (Ttemplate('show-name'), Ttemplate('ep-name')))
sorter = sabnzbd.tvsort.SeriesSorter(name, path, 'tv')
sorter = sabnzbd.tvsort.SeriesSorter(None, name, path, 'tv')
elif sorttype == 'generic':
name = name or (Ttemplate('movie-sp-name') + ' (2009)')
sorter = sabnzbd.tvsort.GenericSorter(name, path, 'tv')
sorter = sabnzbd.tvsort.GenericSorter(None, name, path, 'tv')
elif sorttype == 'date':
name = name or (Ttemplate('show-name') + ' 2009-01-02')
sorter = sabnzbd.tvsort.DateSorter(name, path, 'tv')
sorter = sabnzbd.tvsort.DateSorter(None, name, path, 'tv')
else:
return None
sorter.sort_string = expression

View File

@@ -147,6 +147,16 @@ class URLGrabber(Thread):
filename = value
if not filename.endswith('.nzb'):
filename += '.nzb'
elif item == 'x-dnzb-propername':
nzo_info['propername'] = value
elif item == 'x-dnzb-episodename':
nzo_info['episodename'] = value
elif item == 'x-dnzb-year':
nzo_info['year'] = value
elif item == 'x-dnzb-failure':
nzo_info['failure'] = value
elif item == 'x-dnzb-details':
nzo_info['details'] = value
elif item in ('content-length',):
length = misc.int_conv(value)
@@ -207,13 +217,18 @@ class URLGrabber(Thread):
if res == -2:
logging.info('Incomplete NZB, retry after 5 min %s', url)
when = 300
elif res == -1:
# Error, but no reason to retry. Warning is already given
NzbQueue.do.remove(future_nzo.nzo_id, add_to_history=False)
continue
else:
logging.info('Unknown error fetching NZB, retry after 2 min %s', url)
when = 120
self.add(url, future_nzo, when)
# Check if a supported archive
else:
if dirscanner.ProcessArchiveFile(filename, fn, pp, script, cat, priority=priority, url=future_nzo.url)[0] == 0:
if dirscanner.ProcessArchiveFile(filename, fn, pp, script, cat, priority=priority,
nzbname=nzbname, url=future_nzo.url)[0] == 0:
NzbQueue.do.remove(future_nzo.nzo_id, add_to_history=False)
else:
# Not a supported filetype, not an nzb (text/html ect)

View File

@@ -41,7 +41,7 @@ class Wizard(object):
self.__web_dir = sabnzbd.WIZARD_DIR
self.__prim = prim
self.info = {'webdir': sabnzbd.WIZARD_DIR,
'steps':3, 'version':sabnzbd.__version__,
'steps':4, 'version':sabnzbd.__version__,
'T': T}
@cherrypy.expose
@@ -151,7 +151,7 @@ class Wizard(object):
@cherrypy.expose
def three(self, **kwargs):
""" Accept webserver parms and show Indexers page """
""" Accept webserver parms and show Indexer page """
if kwargs:
if 'access' in kwargs:
cfg.cherryhost.set(kwargs['access'])
@@ -161,20 +161,39 @@ class Wizard(object):
cfg.password.set(kwargs.get('web_pass', ''))
if not cfg.username() or not cfg.password():
sabnzbd.interface.set_auth(cherrypy.config)
config.save_config()
# Create indexer page
info = self.info.copy()
info['num'] = '&raquo; %s' % T('Step Three')
info['number'] = 3
info['T'] = Ttemplate
info['rating_enable'] = cfg.rating_enable()
info['rating_api_key'] = cfg.rating_api_key()
template = Template(file=os.path.join(self.__web_dir, 'three.html'),
searchList=[info], compilerSettings=sabnzbd.interface.DIRECTIVES)
return template.respond()
@cherrypy.expose
def four(self, **kwargs):
if kwargs:
cfg.rating_enable.set(kwargs.get('rating_enable', 0))
cfg.rating_api_key.set(kwargs.get('rating_api_key', ''))
config.save_config()
# Show Restart screen
info = self.info.copy()
info['num'] = '&raquo; %s' % T('Step Three')
info['number'] = 3
info['num'] = '&raquo; %s' % T('Step Four')
info['number'] = 4
info['helpuri'] = 'http://wiki.sabnzbd.org/'
info['session'] = cfg.api_key()
info['access_url'], info['urls'] = self.get_access_info()
info['T'] = Ttemplate
template = Template(file=os.path.join(self.__web_dir, 'three.html'),
template = Template(file=os.path.join(self.__web_dir, 'four.html'),
searchList=[info], compilerSettings=sabnzbd.interface.DIRECTIVES)
return template.respond()

37
solaris/manifest.xml Normal file
View File

@@ -0,0 +1,37 @@
<?xml version='1.0'?>
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
<service_bundle type='manifest' name='export'>
<service name='network/sabnzbd' type='service' version='0'>
<instance name='default' enabled='true'>
<dependency name='multi-user' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/milestone/multi-user'/>
</dependency>
<dependency name='network' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/milestone/network:default'/>
</dependency>
<dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/system/filesystem/local:default'/>
</dependency>
<method_context>
<method_credential group='other' user='root'/>
</method_context>
<exec_method name='start' type='method' exec='/opt/sabnzbd/venv_sabnzbd/bin/python /opt/sabnzbd/SABnzbd.py -f /data/sabnzbd/.sabnzbd/sabnzbd.ini -d' timeout_seconds='30'/>
<exec_method name='stop' type='method' exec=':kill' timeout_seconds='2'/>
<property_group name='startd' type='framework'>
<propval name='ignore_error' type='astring' value='core,signal'/>
</property_group>
<property_group name='general' type='framework'>
<property name='action_authorization' type='astring'/>
</property_group>
<template>
<common_name>
<loctext xml:lang='C'>SABnzbd</loctext>
</common_name>
<documentation>
<doc_link name='sabnzbd' uri='http://sabnzbd.org/'/>
</documentation>
</template>
</instance>
<stability value='Evolving'/>
</service>
</service_bundle>