mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-24 08:08:37 -05:00
Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c2eda4e4b | ||
|
|
5591127a0e | ||
|
|
823ec4fad8 | ||
|
|
661dc33912 | ||
|
|
28370c826a | ||
|
|
12086a247f | ||
|
|
f53c25ccc5 | ||
|
|
04d31bd807 | ||
|
|
433711bdc6 | ||
|
|
376428d12d | ||
|
|
8969b7c87e | ||
|
|
92bc48c241 | ||
|
|
d85708c5f5 | ||
|
|
fa4eaf3e42 | ||
|
|
8b788551bb | ||
|
|
db58025e96 | ||
|
|
9ec397d6a0 | ||
|
|
dc3fe36d8a | ||
|
|
d809b13936 | ||
|
|
c1607c6bf2 | ||
|
|
0ff3c5978c | ||
|
|
c1d283f299 | ||
|
|
04637c2c74 | ||
|
|
3a3eeba429 | ||
|
|
1404175ac8 | ||
|
|
081010d50b | ||
|
|
1591954472 | ||
|
|
c2a2b61e15 | ||
|
|
4b6c469831 | ||
|
|
2d10f879da | ||
|
|
c2043c6b83 | ||
|
|
b3a637d29c | ||
|
|
67395f0c19 | ||
|
|
55f0eccb88 | ||
|
|
e293a669d2 | ||
|
|
26b79513c6 | ||
|
|
fa1ef47341 | ||
|
|
746be02899 | ||
|
|
9a4fdd4582 | ||
|
|
c76f01b54b | ||
|
|
0849039bb4 | ||
|
|
a79d1173de | ||
|
|
2eeb6cd9c5 | ||
|
|
7571f146fa | ||
|
|
6753ed031d | ||
|
|
f4dd7d0df4 | ||
|
|
e5e9b37bf8 | ||
|
|
460841ae36 | ||
|
|
565d8bec8a | ||
|
|
4bf057b0a9 | ||
|
|
081da0a0fc | ||
|
|
7fe125fd7e | ||
|
|
9669580ea7 | ||
|
|
27cc3f9be5 | ||
|
|
57084ab6d1 | ||
|
|
56d0e2957b | ||
|
|
08ffaedaca | ||
|
|
3c3d6dbb4b | ||
|
|
a66ffc61fd | ||
|
|
7e04cb019d | ||
|
|
802c9cfab6 | ||
|
|
987a64d3a4 | ||
|
|
ea90e9e153 | ||
|
|
ace1ef5498 | ||
|
|
d2926dcbac | ||
|
|
5d4f8c2459 | ||
|
|
e2790cbaa9 | ||
|
|
1441eea5ae | ||
|
|
db4ecfc59c | ||
|
|
4a69dea144 | ||
|
|
3f790a5048 | ||
|
|
470c1fc13e | ||
|
|
6faeb578a7 | ||
|
|
44383deb33 | ||
|
|
80fccd92ec | ||
|
|
666e115032 | ||
|
|
048c36c107 | ||
|
|
de86ba7496 | ||
|
|
a58d070aaf | ||
|
|
30ed8a2d30 | ||
|
|
c503e3078f | ||
|
|
3d74d879a9 | ||
|
|
ae0268eb3c | ||
|
|
d6eb1ffbe0 | ||
|
|
d88b3a76a5 | ||
|
|
a6a68b9b67 | ||
|
|
182bce40a7 | ||
|
|
5072f55d6e | ||
|
|
dc2d4ffb85 | ||
|
|
22f2e0881e | ||
|
|
614510c00b | ||
|
|
ee32bb5419 | ||
|
|
99eb16b504 | ||
|
|
2da71a9fa2 | ||
|
|
9cc9e0eaea | ||
|
|
2d92069a3c | ||
|
|
a0cfc19682 | ||
|
|
d87cd33523 | ||
|
|
63186c671b | ||
|
|
3d3d8f1535 | ||
|
|
a90754149b | ||
|
|
ad6a896e46 | ||
|
|
f4e0559894 | ||
|
|
e517b67faf | ||
|
|
bbb2b27157 | ||
|
|
0b1f9dfe14 | ||
|
|
e7bd7ae0d3 | ||
|
|
8a4579d2ec | ||
|
|
0adf5ef93d | ||
|
|
fdacbeaa4d | ||
|
|
f5ecf3df05 | ||
|
|
eae644d8f6 | ||
|
|
406f18d6bb | ||
|
|
bf04f950c3 | ||
|
|
e90ff0eeb7 | ||
|
|
35381555a9 | ||
|
|
4bf956f4ff | ||
|
|
ddfe8653f4 | ||
|
|
0066b9f11e | ||
|
|
c897b2252d | ||
|
|
2aadcd032d | ||
|
|
23e9bf112e | ||
|
|
9b577df408 | ||
|
|
e6d75b45ae | ||
|
|
f68de5df4c | ||
|
|
8bda8efa2f | ||
|
|
62792f859b | ||
|
|
79fa42f90f | ||
|
|
acb3ed2c77 | ||
|
|
50f411fb0b | ||
|
|
38c13bc4f0 | ||
|
|
7a7bf0f4e4 | ||
|
|
4d1b02fa64 | ||
|
|
612e68b5e6 | ||
|
|
9645f947b8 | ||
|
|
c211969a81 | ||
|
|
86916cdf90 | ||
|
|
fa3e0f941b | ||
|
|
32c524c18d | ||
|
|
7ba1a4c20f | ||
|
|
3b3759e81e | ||
|
|
ba3aaab3dc | ||
|
|
30ec3c430d | ||
|
|
dac568fc35 | ||
|
|
1ee00e12ce | ||
|
|
15ae1ae5fd | ||
|
|
14f39a21e3 | ||
|
|
97ea4ee2eb | ||
|
|
c68fa9f0c5 | ||
|
|
06576baf5c | ||
|
|
7b5fcbe0af | ||
|
|
003ee07dee | ||
|
|
654b5e9a24 | ||
|
|
1b05bc9ed2 | ||
|
|
dc328c545b | ||
|
|
823816ddc4 | ||
|
|
8979598f23 | ||
|
|
f26bf9b21f | ||
|
|
5d3a0cc593 | ||
|
|
21d445b7a6 | ||
|
|
9c0df30d34 | ||
|
|
bc9be3f92b | ||
|
|
2dc5c329c9 | ||
|
|
67817978f4 | ||
|
|
e2ab8c6ce4 | ||
|
|
f33a952536 | ||
|
|
cc582b5321 | ||
|
|
bdc526c91b | ||
|
|
52039c29b4 | ||
|
|
1dc4175f82 | ||
|
|
92f70fc177 | ||
|
|
fd573208bd | ||
|
|
ca9f10c12f | ||
|
|
49a72d0902 | ||
|
|
6aafe3c531 | ||
|
|
9e84696f96 | ||
|
|
120c133d7a | ||
|
|
cf9713a4b0 | ||
|
|
d12e9889e7 | ||
|
|
711a546989 | ||
|
|
7f78e6fac1 | ||
|
|
72533eefa4 | ||
|
|
d9643d9ea8 | ||
|
|
2de71bb96c |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -26,3 +26,7 @@ SABnzbd*.dmg
|
||||
*.keep
|
||||
*.bak
|
||||
*.log
|
||||
|
||||
# Some people use Emacs as an editor
|
||||
\#*
|
||||
.\#*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd 0.7.11 ***
|
||||
*** This is SABnzbd 0.7.19 ***
|
||||
*******************************************
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
|
||||
129
CHANGELOG.txt
129
CHANGELOG.txt
@@ -1,3 +1,132 @@
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.19RC2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Make matching of SFV file against RAR-sets case-insensitive
|
||||
- Make sure final destination path is always sanitized and trimmed
|
||||
- Improve "check for unwanted extensions"
|
||||
- Upgrade unrar to version 5.11 (OSX and Windows)
|
||||
- Limit article cache to 1G to prevent a memory size bug in the _yenc module
|
||||
- Fix a number of problems with embedded passwords and folder size trimming
|
||||
- Allow "float" timestamps in RSS feeds
|
||||
- Expose 'rating_host' to Config->Special
|
||||
- Add Finnish translation
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.19RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- More oznzb filtering
|
||||
- Fix OSX notification center problem
|
||||
- Fix sort order of RSS feeds
|
||||
- Prevent multiple pauses in "unwanted extensions" option
|
||||
- Change renaming scheme for duplicate files
|
||||
- Fix sorting of the queue
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.18 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Update translations
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.18RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Support for X-Failure header
|
||||
- Support for detecting unwanted extensions inside RAR files
|
||||
- Using priority Force will override duplicate detection
|
||||
- Notification: Respect NotifyOSD-preference and allow testing of values from UI
|
||||
- Prevent pseudo error message when testing "Notification Center"
|
||||
- Testing email based on values in UI instead of stored config
|
||||
- Don't trim file names when renaming them (so revert to old behavior)
|
||||
- Add "pause_pp" to the API
|
||||
- Pause/abort on encryption failed when pre-check was active
|
||||
- Also remove colons ":" with option sanitize_safe
|
||||
- Update DMG template
|
||||
- Fix potential crash when unpacking due to unset variable
|
||||
- Fix problem of cookie interference with other apps
|
||||
- Add API function server_stats
|
||||
- Support password embedding in file detail page and AddNZB dialog
|
||||
- Pause/Remove posts when unwanted extensions are detected (like .exe)
|
||||
- Fix issue with some RAR file sets leading to Windows "87" error
|
||||
- Handle 5xx RSS feed error messages
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.17Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Implement "retry-after" header to support rate-limiting
|
||||
- Update OSX image to show Mavericks support
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.17RC2 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Support UNC paths in Sort expressions (Windows)
|
||||
- URL in the queue should not show up "sanitized"
|
||||
- Fix shutdown issue in PP queue
|
||||
- Allow "Force" to be set as priority in files overview
|
||||
- Special option "warn_dupl_jobs" to suppress/enable warnings for duplicate jobs
|
||||
- Fix problem with "sanitize" in "renamer"
|
||||
- Add (partial) RAR5 support
|
||||
- Fix some more password-in-filename issues
|
||||
- Prevent unwanted change of queue order after editing job details
|
||||
- Add password entry boxes in smpl and Classic skins
|
||||
- Prevent unrar zombies on some systems
|
||||
-------------------------------------------------------------------------------
|
||||
0.7.17RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Fix bug in rating system
|
||||
- Fix multiple encryption password issues
|
||||
- Allow Default category to be picked in Multi-Ops
|
||||
- Allow "force" prio to be picked in NZO page
|
||||
- Prevent PP queue timeout construction from keeping the CPU awake.
|
||||
- Special option "flat_unpack"
|
||||
-------------------------------------------------------------------------------
|
||||
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
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
(c) Copyright 2007-2013 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||
(c) Copyright 2007-2014 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>
|
||||
|
||||
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
MAINTAINER Johannes 'fish' Ziemke <docker@freigeist.org> @discordianfish
|
||||
|
||||
RUN echo deb http://archive.ubuntu.com/ubuntu/ trusty multiverse >> \
|
||||
/etc/apt/sources.list
|
||||
RUN apt-get -qy update && apt-get -qy install python python-cheetah unrar \
|
||||
unzip python-yenc par2
|
||||
|
||||
RUN useradd sabnzbd -d /sab -m && chown -R sabnzbd:sabnzbd /sab
|
||||
VOLUME /sab
|
||||
ADD . /sabnzbd
|
||||
|
||||
EXPOSE 8080
|
||||
USER sabnzbd
|
||||
ENV HOME /sab
|
||||
|
||||
ENTRYPOINT [ "python", "/sabnzbd/SABnzbd.py", "-s", "0.0.0.0:8080" ]
|
||||
@@ -1,10 +1,10 @@
|
||||
SABnzbd 0.7.11
|
||||
SABnzbd 0.7.19
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
(c) Copyright 2007-2013 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||
(c) Copyright 2007-2014 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(c) Copyright 2007-2013 by "The SABnzbd-team" <team@sabnzbd.org>
|
||||
(c) Copyright 2007-2014 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
|
||||
|
||||
4
PKG-INFO
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 0.7.11
|
||||
Summary: SABnzbd-0.7.11
|
||||
Version: 0.7.19RC2
|
||||
Summary: SABnzbd-0.7.19RC2
|
||||
Home-page: http://sabnzbd.org
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
@@ -51,4 +51,4 @@ Our many other commandline options are explained in depth [here](http://wiki.sab
|
||||
|
||||
## About Our Repo
|
||||
|
||||
We're going to be attempting to follow the [gitflow model](http://nvie.com/posts/a-successful-git-branching-model/), so you can consider "master" to be whatever our present stable release build is (presently 0.6.x) and "develop" to be whatever our next build will be (presently 0.7.x). Once we transition from unstable to stable dev builds we'll create release branches, and encourage you to follow along and help us test.
|
||||
We're going to be attempting to follow the [gitflow model](http://nvie.com/posts/a-successful-git-branching-model/), so you can consider "master" to be whatever our present stable release build is (presently 0.7.x) and "develop" to be whatever our next build will be (presently 0.8.x). Once we transition from unstable to stable dev builds we'll create release branches, and encourage you to follow along and help us test.
|
||||
|
||||
18
README.mkd
18
README.mkd
@@ -1,11 +1,19 @@
|
||||
Release Notes - SABnzbd 0.7.11
|
||||
================================
|
||||
Release Notes - SABnzbd 0.7.19RC2
|
||||
===================================
|
||||
|
||||
## Features
|
||||
|
||||
- Extended filtering for oznzb.com
|
||||
- Upgrade to unrar 5.11 (OSX and Windows)
|
||||
- Add Finnish translation
|
||||
|
||||
## Bug fixes
|
||||
- Obfuscated file name support causes regular multi-set NZBs to verify (much) slower
|
||||
- Bad articles from some servers are accepted as valid data
|
||||
- Generic Sort fails to rename files when an extra folder level is present in the RAR files
|
||||
- Fix OSX notification center problem
|
||||
- Fix sort order of RSS feeds
|
||||
- Prevent multiple pauses in "unwanted extensions" option
|
||||
- Change renaming scheme for duplicate files
|
||||
- Fix sorting of the queue
|
||||
- Prevent removal of embedded passwords in filenames by trimming option
|
||||
|
||||
## What's new in 0.7.0
|
||||
|
||||
|
||||
35
SABnzbd.py
35
SABnzbd.py
@@ -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"
|
||||
@@ -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',):
|
||||
@@ -1270,6 +1276,25 @@ def main():
|
||||
logging.info('Python-version = %s', sys.version)
|
||||
logging.info('Arguments = %s', sabnzbd.CMDLINE)
|
||||
|
||||
if sabnzbd.cfg.log_level() > 1:
|
||||
try:
|
||||
s_ipv4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s_ipv4.connect(('google.com', 80))
|
||||
logging.debug('My IPv4 address = %s', s_ipv4.getsockname()[0])
|
||||
s_ipv4.close()
|
||||
except:
|
||||
logging.debug('Could not determine my IPv4 address')
|
||||
pass
|
||||
|
||||
try:
|
||||
s_ipv6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
s_ipv6.connect(('ipv6.google.com', 80))
|
||||
logging.debug('My IPv6 address = %s', s_ipv6.getsockname()[0])
|
||||
s_ipv6.close()
|
||||
except:
|
||||
logging.debug('Could not determine my IPv6 address')
|
||||
pass
|
||||
|
||||
# OSX 10.5 I/O priority setting
|
||||
if sabnzbd.DARWIN:
|
||||
logging.info('[osx] IO priority setting')
|
||||
@@ -1318,6 +1343,8 @@ def main():
|
||||
sabnzbd.WEB_COLOR2 = CheckColor(sabnzbd.cfg.web_color2(), web_dir2)
|
||||
sabnzbd.cfg.web_color2.set(sabnzbd.WEB_COLOR2)
|
||||
|
||||
logging.debug('Unwanted extensions are ... %s',sabnzbd.cfg.unwanted_extensions())
|
||||
|
||||
if fork and not sabnzbd.WIN32:
|
||||
daemonize()
|
||||
|
||||
@@ -1545,8 +1572,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__)
|
||||
|
||||
@@ -11,6 +11,7 @@ echo The fourth parameter (newzbin #) = %4
|
||||
echo The fifth parameter (category) = %5
|
||||
echo The sixth parameter (group) = %6
|
||||
echo The seventh parameter (status) = %7
|
||||
echo The eigth parameter (failure_url)= %8
|
||||
echo.
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ echo "The fourth parameter (newzbin-id) =" "$4"
|
||||
echo "The fifth parameter (category) =" "$5"
|
||||
echo "The sixth parameter (group) =" "$6"
|
||||
echo "The seventh parameter (status) =" "$7"
|
||||
echo "The eigth parameter (failure_url) =" "$8"
|
||||
echo
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,15 @@ $T('thisWeek'): $week_size | $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') $line.rating_avg_video $T('audio') $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')
|
||||
<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')
|
||||
<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"> $T('spam')
|
||||
<input type="radio" name="rating_flag" value="encrypted"> $T('encrypted')
|
||||
<input type="radio" name="rating_flag" value="expired"> $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#-->
|
||||
|
||||
@@ -73,7 +73,10 @@
|
||||
<h3>$T('nzoDetails')</h3>
|
||||
<form action="save" method="post">
|
||||
<label class="label">$T('nzoName'):</label><br />
|
||||
<input type="text" name="name" style="width:400px" size="80" value="$slot.filename">
|
||||
<input type="text" name="name" style="width:400px" size="80" value="$slot.filename_clean">
|
||||
<br />
|
||||
<label class="label">$T('srv-password'):</label><br />
|
||||
<input type="text" name="password" style="width:200px" size="100" value="$slot.password">
|
||||
<br />
|
||||
<label class="label">$T('pp'):</label><br />
|
||||
<select name="pp">
|
||||
@@ -88,6 +91,7 @@
|
||||
<label class="label">$T('priority'):</label><br />
|
||||
<select name="priority">
|
||||
<option value="-100" <!--#if $slot.priority == "-100" then "selected" else ""#-->>$T('default')</option>
|
||||
<option value="2" <!--#if $slot.priority == "2" then "selected" else ""#-->>$T('pr-force')</option>
|
||||
<option value="1" <!--#if $slot.priority == "1" then "selected" else ""#-->>$T('pr-high')</option>
|
||||
<option value="0" <!--#if $slot.priority == "0" then "selected" else ""#-->>$T('pr-normal')</option>
|
||||
<option value="-1" <!--#if $slot.priority == "-1" then "selected" else ""#-->>$T('pr-low')</option>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
@@ -99,6 +99,26 @@
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
* takes the inputs-elements found in the current selector
|
||||
* and extracts the values into the provided data-object.
|
||||
*/
|
||||
\$.fn.extractFormDataTo = function(target)
|
||||
{
|
||||
var inputs = \$("input[type != 'submit'][type != 'button']", this);
|
||||
|
||||
// could use .serializeArray() but that omits unchecked items
|
||||
inputs.each(function (i,elem) {
|
||||
if (elem.type == "checkbox") {
|
||||
target[elem.name] = elem.checked;
|
||||
} else {
|
||||
target[elem.name] = elem.value;
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<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>
|
||||
@@ -24,11 +25,6 @@
|
||||
<h5 class="copyright">Copyright © 2008-2013 The SABnzbd Team <<span style="color: #0000ff;">team@sabnzbd.org</span>></h5>
|
||||
<p class="copyright"><small>$T('yourRights')</small></p>
|
||||
</div>
|
||||
<!--#if $news_items#-->
|
||||
<div class="padding">
|
||||
<iframe frameborder=0 width=100% src="http://sabnzbdplus.sourceforge.net/version/news.html"></iframe>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<div class="section">
|
||||
<div class="section" id="email">
|
||||
<div class="col2">
|
||||
<h3>$T('emailAccount')</h3>
|
||||
</div><!-- /col2 -->
|
||||
@@ -79,7 +79,7 @@
|
||||
</fieldset>
|
||||
</div><!-- /col1 -->
|
||||
</div><!-- /section -->
|
||||
<div class="section">
|
||||
<div class="section" id="growl">
|
||||
<div class="col2">
|
||||
<h3>$T('growlSettings')</h3>
|
||||
</div><!-- /col2 -->
|
||||
@@ -139,10 +139,12 @@
|
||||
\$('#email_dir').fileBrowser({ title: 'Select $T('opt-email_dir')' });
|
||||
\$('#test_email').click(function () {
|
||||
if (confirm(\$('#test_email').attr('rel'))) {
|
||||
var data = { mode: 'test_email', apikey: '$session', output: 'json' };
|
||||
\$("#email").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: {mode: 'test_email', apikey: '$session', output: 'json' },
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_email').attr("disabled", "disabled");
|
||||
\$('#testmail-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
@@ -162,10 +164,12 @@
|
||||
}
|
||||
});
|
||||
\$('#test_notification').click(function () {
|
||||
var data = { mode: 'test_notif', apikey: '$session', output: 'json' };
|
||||
\$("#growl").extractFormDataTo(data);
|
||||
\$.ajax({
|
||||
type: "GET",
|
||||
url: "../../tapi",
|
||||
data: {mode: 'test_notif', apikey: '$session', output: 'json' },
|
||||
data: data,
|
||||
beforeSend: function () {
|
||||
\$('#test_notification').attr("disabled", "disabled");
|
||||
\$('#testnotice-result').removeClass("success failure").addClass("loading").html('$T('post-Verifying')');
|
||||
|
||||
@@ -116,6 +116,20 @@
|
||||
</select>
|
||||
<span class="desc">$T('explain-pause_on_pwrar')</span>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="action_on_unwanted_extensions">$T('opt-action_on_unwanted_extensions')</label>
|
||||
<select name="action_on_unwanted_extensions" id="action_on_unwanted_extensions">
|
||||
<option value="0" <!--#if int($action_on_unwanted_extensions) == 0 then 'selected="selected" class="selected"' else ""#--> >$T('nodupes-off')</option>
|
||||
<option value="1" <!--#if int($action_on_unwanted_extensions) == 1 then 'selected="selected" class="selected"' else ""#--> >$T('nodupes-pause')</option>
|
||||
<option value="2" <!--#if int($action_on_unwanted_extensions) == 2 then 'selected="selected" class="selected"' else ""#--> >$T('abort')</option>
|
||||
</select>
|
||||
<span class="desc">$T('explain-action_on_unwanted_extensions')</span>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="config" for="unwanted_extensions">$T('opt-unwanted_extensions')</label>
|
||||
<input type="text" name="unwanted_extensions" id="unwanted_extensions" value="$unwanted_extensions" size="50"/>
|
||||
<span class="desc">$T('explain-unwanted_extensions')</span>
|
||||
</div>
|
||||
<div class="field-pair alt">
|
||||
<label class="config" for="top_only">$T('opt-top_only')</label>
|
||||
<input type="checkbox" name="top_only" id="top_only" value="1" <!--#if int($top_only) > 0 then 'checked="checked"' else ""#--> />
|
||||
@@ -305,6 +319,134 @@
|
||||
</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 alt">
|
||||
<label class="config" for="rating_filter_enable">$T('opt-rating_filter_enable')</label>
|
||||
<input type="checkbox" name="rating_filter_enable" id="rating_filter_enable" value="1" <!--#if int($rating_filter_enable) > 0 then 'checked="checked"' else ""#--> />
|
||||
<span class="desc">$T('explain-rating_filter_enable')</span>
|
||||
</div>
|
||||
<div class="field-pair" id="rating_filter_abort">
|
||||
<label class="config" for="rating_filter_abort">$T('opt-rating_filter_abort_if')</label>
|
||||
<div class="rating-filter">
|
||||
<p>
|
||||
<label for="rating_filter_abort_video">$T('opt-rating_filter_video')</label>
|
||||
<select name="rating_filter_abort_video" id="rating_filter_abort_video">
|
||||
<option value="0" <!--#if $rating_filter_abort_video == 0 then 'selected="selected" class="selected"' else ""#--> >$T('notUsed')</option>
|
||||
<!--#for $val in $range(1, 10)#--><option value="$val" <!--#if $rating_filter_abort_video == $val then 'selected="selected" class="selected"' else ""#--> >$val $T('orLess')</option><!--#end for#-->
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="rating_filter_abort_audio">$T('opt-rating_filter_audio')</label>
|
||||
<select name="rating_filter_abort_audio" id="rating_filter_abort_audio">
|
||||
<option value="0" <!--#if $rating_filter_abort_audio == 0 then 'selected="selected" class="selected"' else ""#--> >$T('notUsed')</option>
|
||||
<!--#for $val in $range(1, 10)#--><option value="$val" <!--#if $rating_filter_abort_audio == $val then 'selected="selected" class="selected"' else ""#--> >$val $T('orLess')</option><!--#end for#-->
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_abort_encrypted" name="rating_filter_abort_encrypted" <!--#if int($rating_filter_abort_encrypted) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_abort_encrypted">$T('opt-rating_filter_passworded')</label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_abort_encrypted_confirm" name="rating_filter_abort_encrypted_confirm" <!--#if int($rating_filter_abort_encrypted_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_abort_encrypted_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_abort_spam" name="rating_filter_abort_spam" <!--#if int($rating_filter_abort_spam) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_abort_spam">$T('opt-rating_filter_spam')</label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_abort_spam_confirm" name="rating_filter_abort_spam_confirm" <!--#if int($rating_filter_abort_spam_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_abort_spam_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" value="1" id="rating_filter_abort_downvoted" name="rating_filter_abort_downvoted" <!--#if int($rating_filter_abort_downvoted) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_abort_downvoted">$T('opt-rating_filter_downvoted')</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="rating_filter_abort_keywords">$T('opt-rating_filter_keywords')</label>
|
||||
<input type="text" name="rating_filter_abort_keywords" id="rating_filter_abort_keywords" value="$rating_filter_abort_keywords" size="35"/>
|
||||
<span class="desc">$T('explain-rating_filter_keywords')</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-pair alt" id="rating_filter_pause">
|
||||
<label class="config" for="rating_filter_pause">$T('opt-rating_filter_pause_if')</label>
|
||||
<div class="rating-filter">
|
||||
<p>
|
||||
<label for="rating_filter_pause_video">$T('opt-rating_filter_video')</label>
|
||||
<select name="rating_filter_pause_video" id="rating_filter_pause_video">
|
||||
<option value="0" <!--#if $rating_filter_pause_video == 0 then 'selected="selected" class="selected"' else ""#--> >$T('notUsed')</option>
|
||||
<!--#for $val in $range(1, 10)#--><option value="$val" <!--#if $rating_filter_pause_video == $val then 'selected="selected" class="selected"' else ""#--> >$val $T('orLess')</option><!--#end for#-->
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="rating_filter_pause_audio">$T('opt-rating_filter_audio')</label>
|
||||
<select name="rating_filter_pause_audio" id="rating_filter_pause_audio">
|
||||
<option value="0" <!--#if $rating_filter_pause_audio == 0 then 'selected="selected" class="selected"' else ""#--> >$T('notUsed')</option>
|
||||
<!--#for $val in $range(1, 10)#--><option value="$val" <!--#if $rating_filter_pause_audio == $val then 'selected="selected" class="selected"' else ""#--> >$val $T('orLess') </option><!--#end for#-->
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_pause_encrypted" name="rating_filter_pause_encrypted" <!--#if int($rating_filter_pause_encrypted) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_pause_encrypted">$T('opt-rating_filter_passworded')</label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_pause_encrypted_confirm" name="rating_filter_pause_encrypted_confirm" <!--#if int($rating_filter_pause_encrypted_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_pause_encrypted_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_pause_spam" name="rating_filter_pause_spam" <!--#if int($rating_filter_pause_spam) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_pause_spam">$T('opt-rating_filter_spam')</label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="checkbox" value="1" id="rating_filter_pause_spam_confirm" name="rating_filter_pause_spam_confirm" <!--#if int($rating_filter_pause_spam_confirm) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_pause_spam_confirm">$T('opt-rating_filter_confirmed')</label>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" value="1" id="rating_filter_pause_downvoted" name="rating_filter_pause_downvoted" <!--#if int($rating_filter_pause_downvoted) > 0 then 'checked="checked"' else ""#--> />
|
||||
<label for="rating_filter_pause_downvoted">$T('opt-rating_filter_downvoted')</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="rating_filter_pause_keywords">$T('opt-rating_filter_keywords')</label>
|
||||
<input type="text" name="rating_filter_pause_keywords" id="rating_filter_pause_keywords" value="$rating_filter_pause_keywords" size="35"/>
|
||||
<span class="desc">$T('explain-rating_filter_keywords')</span>
|
||||
</p>
|
||||
</div>
|
||||
</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" />
|
||||
@@ -312,4 +454,24 @@
|
||||
</form>
|
||||
</div><!-- /colmask -->
|
||||
|
||||
<script>
|
||||
\$(document).ready(function() {
|
||||
if (!\$('#rating_filter_enable').is(":checked")) {
|
||||
\$("#rating_filter_abort").hide();
|
||||
\$("#rating_filter_pause").hide();
|
||||
}
|
||||
\$('#rating_filter_enable').change(function () {
|
||||
if (\$(this).is(":checked")) {
|
||||
\$("#rating_filter_abort").show();
|
||||
\$("#rating_filter_pause").show();
|
||||
}
|
||||
else {
|
||||
\$("#rating_filter_abort").hide();
|
||||
\$("#rating_filter_pause").hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->
|
||||
|
||||
@@ -13,3 +13,12 @@
|
||||
.checkbox-days p{margin: 0 0 5px 0}
|
||||
.checkbox-days input {vertical-align:middle; margin-top: -1px;}
|
||||
.checkbox-days label{padding-left: 20px}
|
||||
.rating-filter {float: left}
|
||||
.rating-filter p {margin: 0 0 5px 0}
|
||||
.rating-filter select {vertical-align:middle}
|
||||
.rating-filter input {vertical-align:middle; margin-top:-1px}
|
||||
.rating-filter label {display:inline-block; padding-left:0px; width:100px}
|
||||
.rating-filter input[type="checkbox"] {display:inline}
|
||||
.rating-filter input[type="checkbox"] + label {padding-left:20px; width:auto}
|
||||
.rating-filter p > span:first-child {float:left; width:130px}
|
||||
.rating-filter .desc {display:block; margin:0px; padding-left:103px}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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'>
|
||||
@@ -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"> $T('spam')</div>
|
||||
<div class="rating_flag_radio"><input type="radio" name="rating_flag" value="encrypted"> $T('encrypted')</div>
|
||||
<div class="rating_flag_radio">
|
||||
<input type="radio" name="rating_flag" value="expired"> $T('expired')
|
||||
<div class="rating_modal_extra">
|
||||
<div class="rating_modal_expired">$T('host') <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"> $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"> $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>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<td class="nzb_status_col">
|
||||
<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#-->"> </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'])) %>
|
||||
|
||||
@@ -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>
|
||||
@@ -128,7 +129,7 @@
|
||||
<select id="multi_cat"><optgroup label="$T('category')">
|
||||
<option value="">$T('category')</option>
|
||||
<!--#for $ct in $cat_list#-->
|
||||
<!--#if $ct != "Default"#--><option value="$ct">$Tspec($ct)</option><!--#end if#-->
|
||||
<option value="$ct">$Tspec($ct)</option>
|
||||
<!--#end for#-->
|
||||
</optgroup></select>
|
||||
<!--#end if#-->
|
||||
|
||||
@@ -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" style="width:200px" size="100" value="$slot.password" placeholder="$T('srv-password')"/>
|
||||
|
||||
<div>
|
||||
<select name="index"><optgroup label="$T('order')">
|
||||
@@ -23,6 +24,7 @@
|
||||
<!--#end if#-->
|
||||
<select name="priority"><optgroup label="$T('priority')">
|
||||
<option value="-100" <!--#if $slot.priority == "-100" then "selected" else ""#-->>$T('default')</option>
|
||||
<option value="2" <!--#if $slot.priority == "2" then "selected" else ""#-->>$T('pr-force')</option>
|
||||
<option value="1" <!--#if $slot.priority == "1" then "selected" else ""#-->>$T('pr-high')</option>
|
||||
<option value="0" <!--#if $slot.priority == "0" then "selected" else ""#-->>$T('pr-normal')</option>
|
||||
<option value="-1" <!--#if $slot.priority == "-1" then "selected" else ""#-->>$T('pr-low')</option>
|
||||
|
||||
@@ -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('.', '.​').replace('_', '_​')</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 $T('Plush-left')</span>
|
||||
<!--#else#-->
|
||||
$T('post-'+$slot.status)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -169,33 +169,33 @@ jQuery(function($){
|
||||
// Refresh rate
|
||||
$("#refreshRate-option").val($.plush.refreshRate).change( function() {
|
||||
$.plush.refreshRate = $("#refreshRate-option").val();
|
||||
$.cookie('plushRefreshRate', $.plush.refreshRate, { expires: 365, path: '/' });
|
||||
$.cookie('plushRefreshRate', $.plush.refreshRate, { expires: 365 });
|
||||
$.plush.Refresh();
|
||||
});
|
||||
|
||||
// Container width
|
||||
$("#containerWidth-option").val($.plush.containerWidth).change( function() {
|
||||
$.plush.containerWidth = $("#containerWidth-option").val();
|
||||
$.cookie('plushContainerWidth', $.plush.containerWidth, { expires: 365, path: '/' });
|
||||
$.cookie('plushContainerWidth', $.plush.containerWidth, { expires: 365 });
|
||||
$('#master-width').css('width',$.plush.containerWidth);
|
||||
}).trigger('change');
|
||||
|
||||
// Confirm Queue Deletions toggle
|
||||
$("#confirmDeleteQueue").prop('checked', $.plush.confirmDeleteQueue ).change( function() {
|
||||
$.plush.confirmDeleteQueue = $("#confirmDeleteQueue").prop('checked');
|
||||
$.cookie('plushConfirmDeleteQueue', $.plush.confirmDeleteQueue ? 1 : 0, { expires: 365, path: '/' });
|
||||
$.cookie('plushConfirmDeleteQueue', $.plush.confirmDeleteQueue ? 1 : 0, { expires: 365 });
|
||||
});
|
||||
|
||||
// Confirm History Deletions toggle
|
||||
$("#confirmDeleteHistory").prop('checked', $.plush.confirmDeleteHistory ).change( function() {
|
||||
$.plush.confirmDeleteHistory = $("#confirmDeleteHistory").prop('checked');
|
||||
$.cookie('plushConfirmDeleteHistory', $.plush.confirmDeleteHistory ? 1 : 0, { expires: 365, path: '/' });
|
||||
$.cookie('plushConfirmDeleteHistory', $.plush.confirmDeleteHistory ? 1 : 0, { expires: 365 });
|
||||
});
|
||||
|
||||
// Block Refreshes on Hover toggle
|
||||
$("#blockRefresh").prop('checked', $.plush.blockRefresh ).change( function() {
|
||||
$.plush.blockRefresh = $("#blockRefresh").prop('checked');
|
||||
$.cookie('plushBlockRefresh', $.plush.blockRefresh ? 1 : 0, { expires: 365, path: '/' });
|
||||
$.cookie('plushBlockRefresh', $.plush.blockRefresh ? 1 : 0, { expires: 365 });
|
||||
});
|
||||
|
||||
// Sabnzbd restart
|
||||
@@ -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();
|
||||
@@ -341,7 +352,7 @@ jQuery(function($){
|
||||
$.plush.multiOpsChecks = new Array();
|
||||
$('<input type="checkbox" class="multiops" />').appendTo('#queue tr td.nzb_status_col');
|
||||
}
|
||||
$.cookie('plushMultiOps', $.plush.multiOps ? 1 : 0, { expires: 365, path: '/' });
|
||||
$.cookie('plushMultiOps', $.plush.multiOps ? 1 : 0, { expires: 365 });
|
||||
});
|
||||
if ($.plush.multiOps)
|
||||
$('#multiops_toggle').trigger('click');
|
||||
@@ -354,7 +365,7 @@ jQuery(function($){
|
||||
$('#topmenu_bar').show();
|
||||
$.plush.noTopMenu = false;
|
||||
}
|
||||
$.cookie('plushNoTopMenu', $.plush.noTopMenu ? 1 : 0, { expires: 365, path: '/' });
|
||||
$.cookie('plushNoTopMenu', $.plush.noTopMenu ? 1 : 0, { expires: 365 });
|
||||
});
|
||||
if ($.plush.noTopMenu)
|
||||
$('#topmenu_toggle').trigger('click');
|
||||
@@ -510,7 +521,7 @@ jQuery(function($){
|
||||
$("#queue-pagination-perpage").change(function(event){
|
||||
$.plush.queuecurpage = Math.floor($.plush.queuecurpage * $.plush.queuePerPage / $(event.target).val() );
|
||||
$.plush.queuePerPage = $(event.target).val();
|
||||
$.cookie('plushQueuePerPage', $.plush.queuePerPage, { expires: 365, path: '/' });
|
||||
$.cookie('plushQueuePerPage', $.plush.queuePerPage, { expires: 365 });
|
||||
$.plush.queueforcerepagination = true;
|
||||
$.plush.RefreshQueue();
|
||||
});
|
||||
@@ -739,9 +750,9 @@ $.plush.queueprevslots = $.plush.queuenoofslots; // for the next refresh
|
||||
});
|
||||
var last1, last2;
|
||||
$("#multiops_select_range").click(function(){
|
||||
if (last1 && last2 && last1 < last2)
|
||||
if (last1 >= 0 && last2 >= 0 && last1 < last2)
|
||||
$("INPUT[type='checkbox']","#queueTable").slice(last1,last2).prop('checked', true).trigger('change');
|
||||
else if (last1 && last2)
|
||||
else if (last1 >= 0 && last2 >= 0)
|
||||
$("INPUT[type='checkbox']","#queueTable").slice(last2,last1).prop('checked', true).trigger('change');
|
||||
});
|
||||
$("#multiops_select_invert").click(function(){
|
||||
@@ -754,7 +765,7 @@ $.plush.queueprevslots = $.plush.queuenoofslots; // for the next refresh
|
||||
});
|
||||
$("#queue").delegate('.multiops','change',function(event) {
|
||||
// range event interaction
|
||||
if (last1) last2 = last1;
|
||||
if (last1 >= 0) last2 = last1;
|
||||
last1 = $(event.target).parent()[0].rowIndex ? $(event.target).parent()[0].rowIndex : $(event.target).parent().parent()[0].rowIndex;
|
||||
|
||||
// checkbox state persistence
|
||||
@@ -937,7 +948,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
$("#history-pagination-perpage").change(function(event){
|
||||
$.plush.histcurpage = Math.floor($.plush.histcurpage * $.plush.histPerPage / $(event.target).val() );
|
||||
$.plush.histPerPage = $(event.target).val();
|
||||
$.cookie('plushHistPerPage', $.plush.histPerPage, { expires: 365, path: '/' });
|
||||
$.cookie('plushHistPerPage', $.plush.histPerPage, { expires: 365 });
|
||||
$.plush.histforcerepagination = true;
|
||||
if ($.plush.histPerPage=="1")
|
||||
$("#history-pagination").html(''); // pagination rebuild not triggered on blank history (disabled)
|
||||
@@ -1022,7 +1033,7 @@ $("a","#multiops_inputs").click(function(e){
|
||||
// show all / show failed
|
||||
$('#failed_only').change(function(){
|
||||
$.plush.failedOnly = $("#failed_only").val();
|
||||
$.cookie('plushFailedOnly', $.plush.failedOnly, { expires: 365, path: '/' });
|
||||
$.cookie('plushFailedOnly', $.plush.failedOnly, { expires: 365 });
|
||||
$.plush.RefreshHistory();
|
||||
}).val($.plush.failedOnly);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 319 B |
Binary file not shown.
|
After Width: | Height: | Size: 286 B |
Binary file not shown.
|
After Width: | Height: | Size: 347 B |
Binary file not shown.
|
After Width: | Height: | Size: 341 B |
BIN
interfaces/Plush/templates/static/stylesheets/rateit/delete.gif
Normal file
BIN
interfaces/Plush/templates/static/stylesheets/rateit/delete.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 752 B |
@@ -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;
|
||||
}
|
||||
BIN
interfaces/Plush/templates/static/stylesheets/rateit/star.gif
Normal file
BIN
interfaces/Plush/templates/static/stylesheets/rateit/star.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@@ -1129,7 +1129,7 @@ function loadingJSON(){
|
||||
<li><a class="config" href="$prefix/config/general/" onclick="lr('config/general/','', 0, 0);">$T('cmenu-general')</a></li>
|
||||
<li><a class="config" href="$prefix/config/folders/" onclick="lr('config/folders/','', 0, 0);">$T('cmenu-folders')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/switches/" onclick="lr('config/switches/','', 0, 0);">$T('cmenu-switches')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/server" onclick="lr('config/server/','', 0, 0);">$T('cmenu-servers')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/server/" onclick="lr('config/server/','', 0, 0);">$T('cmenu-servers')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/scheduling/" onclick="lr('config/scheduling/','', 0, 0);">$T('cmenu-scheduling')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/rss/" onclick="lr('config/rss/','', 0, 0);">$T('cmenu-rss')</a> </li>
|
||||
<li><a class="config" href="$prefix/config/notify/" onclick="lr('config/notify/','', 0, 0);">$T('cmenu-notif')</a></li>
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
<h3>$T('nzoDetails')</h3>
|
||||
<form id="nzbEditForm" class="cmxform">
|
||||
<label class="label">$T('nzoName'):</label>
|
||||
<input type="text" name="name" style="width:400px" size="80" value="$slot.filename">
|
||||
<input type="text" name="name" style="width:400px" size="80" value="$slot.filename_clean">
|
||||
<br class="clear" />
|
||||
<label class="label">$T('srv-password'):</label>
|
||||
<input type="text" name="password" style="width:200px" size="100" value="$slot.password">
|
||||
<br class="clear" />
|
||||
<label class="label">$T('pp'):</label>
|
||||
<select name="pp">
|
||||
@@ -19,6 +22,7 @@
|
||||
<select name="priority">
|
||||
<optgroup label="$T('priority')">
|
||||
<option value="-100" <!--#if $slot.priority == "-100" then "selected" else ""#-->>$T('default')</option>
|
||||
<option value="2" <!--#if $slot.priority == "2" then "selected" else ""#-->>$T('pr-force')</option>
|
||||
<option value="1" <!--#if $slot.priority == "1" then "selected" else ""#-->>$T('pr-high')</option>
|
||||
<option value="0" <!--#if $slot.priority == "0" then "selected" else ""#-->>$T('pr-normal')</option>
|
||||
<option value="-1" <!--#if $slot.priority == "-1" then "selected" else ""#-->>$T('pr-low')</option>
|
||||
|
||||
34
interfaces/wizard/four.html
Normal file
34
interfaces/wizard/four.html
Normal 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"#-->
|
||||
@@ -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" />
|
||||
|
||||
@@ -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="‹ $T('wizard-previous')" /></td>
|
||||
<td>
|
||||
<div class="align-center">
|
||||
<!--#for $step in xrange($steps)#-->
|
||||
<!--#set $step = $step + 1#-->
|
||||
<span class="<!--#if $step == $number then 'selected' else 'unselected'#-->">$step</span>
|
||||
<!--#end for#-->
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-right"><input class="bigbutton" type="submit" value="$T('wizard-next') »" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--#include $webdir + "/inc_bottom.tmpl"#-->
|
||||
</form>
|
||||
<!--#include $webdir + "/inc_bottom.tmpl"#-->
|
||||
|
||||
30
licenses/License-OrderedDict.txt
Normal file
30
licenses/License-OrderedDict.txt
Normal 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.
|
||||
|
||||
23
make_dmg.py
23
make_dmg.py
@@ -20,6 +20,15 @@
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import platform
|
||||
|
||||
OSX_MAV = [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 9, 0]
|
||||
|
||||
# Check if signing is possible
|
||||
authority = os.environ.get('SIGNING_AUTH')
|
||||
if authority and not OSX_MAV:
|
||||
print 'Signing is only possible on OSX Mavericks (10.9.x) or higher'
|
||||
exit(1)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print 'Usage: %s <release>' % os.path.split(sys.argv[0])[1]
|
||||
@@ -33,15 +42,15 @@ fileOSr = prod + '-osx-src.tar.gz'
|
||||
fileImg = prod + '.sparseimage'
|
||||
builds = ('sl', 'lion', 'ml')
|
||||
build_folders = (
|
||||
'OS X 10.5 and 10.6 (Leopards)',
|
||||
'OS X 10.7 (Lion)',
|
||||
'OS X 10.8 (Mountain Lion)'
|
||||
'10.5 (Leopard) 10.6 (S-Leopard)',
|
||||
'10.7 (Lion)',
|
||||
'10.8 (M-Lion) 10.9 (Mavericks)'
|
||||
)
|
||||
|
||||
# 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'
|
||||
print 'Build share not defined or not found. Path expected in env variable SHARE'
|
||||
exit(1)
|
||||
|
||||
build_paths = []
|
||||
@@ -69,8 +78,6 @@ m = re.search(r'/dev/(\w+)\s+', data)
|
||||
volume = 'SABnzbd-' + str(release)
|
||||
os.system('diskutil rename %s %s' % (m.group(1), volume))
|
||||
|
||||
authority = os.environ.get('SIGNING_AUTH')
|
||||
|
||||
# Unpack build into image and sign if possible
|
||||
for build in xrange(len(builds)):
|
||||
vol_path = '/Volumes/%s/%s/' % (volume, build_folders[build])
|
||||
@@ -106,3 +113,7 @@ os.system("hdiutil internet-enable %s" % fileDmg)
|
||||
|
||||
print 'Copy GZ file'
|
||||
os.system('cp "%s" .' % os.path.join(sharepath, fileOSr))
|
||||
|
||||
if not authority:
|
||||
print "Images are not signed!"
|
||||
print
|
||||
|
||||
Binary file not shown.
@@ -612,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']))
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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-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-12-29 05:11+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
"X-Launchpad-Export-Date: 2013-12-11 06:16+0000\n"
|
||||
"X-Generator: Launchpad (build 16869)\n"
|
||||
|
||||
#: email/email.tmpl:1
|
||||
msgid ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
212
po/email/fi.po
Normal file
212
po/email/fi.po
Normal file
@@ -0,0 +1,212 @@
|
||||
# Finnish translation for sabnzbd
|
||||
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
|
||||
# This file is distributed under the same license as the sabnzbd package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sabnzbd\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2013-12-10 20:31+0000\n"
|
||||
"PO-Revision-Date: 2013-02-19 15:28+0000\n"
|
||||
"Last-Translator: Matti Ylönen <Unknown>\n"
|
||||
"Language-Team: Finnish <fi@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: 2013-12-11 06:16+0000\n"
|
||||
"X-Generator: Launchpad (build 16869)\n"
|
||||
|
||||
#: email/email.tmpl:1
|
||||
msgid ""
|
||||
"##\n"
|
||||
"## Default 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 has <!--#if $status then \"completed\" else \"failed\" #--> "
|
||||
"job $name\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"SABnzbd has downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else "
|
||||
"\"(newzbin #\" + $msgid + \")\"#-->\n"
|
||||
"<!--#else#-->\n"
|
||||
"SABnzbd has failed to download \"$name\" <!--#if $msgid==\"\" then \"\" else "
|
||||
"\"(newzbin #\" + $msgid + \")\"#-->\n"
|
||||
"<!--#end if#-->\n"
|
||||
"Finished at $end_time\n"
|
||||
"Downloaded $size\n"
|
||||
"\n"
|
||||
"Results of the job:\n"
|
||||
"<!--#for $stage in $stages #-->\n"
|
||||
"Stage $stage <!--#slurp#-->\n"
|
||||
"<!--#for $result in $stages[$stage]#-->\n"
|
||||
" $result <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#if $script!=\"\" #-->\n"
|
||||
"Output from user script \"$script\" (Exit code = $script_ret):\n"
|
||||
"$script_output\n"
|
||||
"<!--#end if#-->\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"Enjoy!\n"
|
||||
"<!--#else#-->\n"
|
||||
"Sorry!\n"
|
||||
"<!--#end if#-->\n"
|
||||
msgstr ""
|
||||
"##\n"
|
||||
"## Oletus sähköpostipohja SABnzbd:lle\n"
|
||||
"## Tämä on Cheetah pohja\n"
|
||||
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
|
||||
"##\n"
|
||||
"## Nämä ovat otsaketiedot. Rivien ensimmäisiä sanoja ei saa vaihtaa!\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd on <!--#if $status then \"valmistunut\" else "
|
||||
"\"epäonnistunut\" #--> työssä $name\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## Tämän jälkeen tulee viestin runko, ensimmäinen rivinvaihto on "
|
||||
"pakollinen!\n"
|
||||
"\n"
|
||||
"Hei,\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"SABnzbd on ladannut \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin "
|
||||
"#\" + $msgid + \")\"#-->\n"
|
||||
"<!--#else#-->\n"
|
||||
"SABnzbd on epäonnistunut \"$name\" <!--#if $msgid==\"\" then \"\" else "
|
||||
"\"(newzbin #\" + $msgid + \")\"#--> latauksessa\n"
|
||||
"<!--#end if#-->\n"
|
||||
"Valmistui $end_time\n"
|
||||
"Ladattu $size\n"
|
||||
"\n"
|
||||
"Työn lopputulos:\n"
|
||||
"<!--#for $stage in $stages #-->\n"
|
||||
"Tila $stage <!--#slurp#-->\n"
|
||||
"<!--#for $result in $stages[$stage]#-->\n"
|
||||
" $result <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"<!--#if $script!=\"\" #-->\n"
|
||||
"Käyttäjän skriptin tuloste \"$script\" (Exit code = $script_ret):\n"
|
||||
"$script_output\n"
|
||||
"<!--#end if#-->\n"
|
||||
"<!--#if $status #-->\n"
|
||||
"Nauti!\n"
|
||||
"<!--#else#-->\n"
|
||||
"Pahoittelut!\n"
|
||||
"<!--#end if#-->\n"
|
||||
|
||||
#: email/rss.tmpl:1
|
||||
msgid ""
|
||||
"##\n"
|
||||
"## RSS 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 has added $amount jobs to the queue\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"\n"
|
||||
"SABnzbd has added $amount job(s) to the queue.\n"
|
||||
"They are from RSS feed \"$feed\".\n"
|
||||
"<!--#for $job in $jobs#-->\n"
|
||||
" $job <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"\n"
|
||||
"Bye\n"
|
||||
msgstr ""
|
||||
"##\n"
|
||||
"## RSS sähköpostipohja SABnzbd:lle\n"
|
||||
"## Tämä on Cheetah pohja\n"
|
||||
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
|
||||
"##\n"
|
||||
"## Nämä ovat otsaketiedot. Rivien ensimmäisiä sanoja ei saa vaihtaa!\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd on lisännyt $amount työtä jonoon\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## Tämän jälkeen tulee viestin runko, ensimmäinen rivinvaihto on "
|
||||
"pakollinen!\n"
|
||||
"\n"
|
||||
"Hei,\n"
|
||||
"\n"
|
||||
"SABnzbd on lisännyt $amount työtä jonoon.\n"
|
||||
"Ne ovat RSS syötteestä \"$feed\".\n"
|
||||
"<!--#for $job in $jobs#-->\n"
|
||||
" $job <!--#slurp#-->\n"
|
||||
"<!--#end for#-->\n"
|
||||
"\n"
|
||||
"Heippa\n"
|
||||
|
||||
#: email/badfetch.tmpl:1
|
||||
msgid ""
|
||||
"##\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 failed to fetch an NZB\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## After this comes the body, the empty line is required!\n"
|
||||
"\n"
|
||||
"Hi,\n"
|
||||
"\n"
|
||||
"SABnzbd has failed to retrieve the NZB from $url.\n"
|
||||
"The error message was: $msg\n"
|
||||
"\n"
|
||||
"Bye\n"
|
||||
msgstr ""
|
||||
"##\n"
|
||||
"## Virheellisen URL-noudon sähköpostin pohja SABnzbd ohjelmalle\n"
|
||||
"## Tämä on Cheetah pohja\n"
|
||||
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
|
||||
"##\n"
|
||||
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
|
||||
"##\n"
|
||||
"## Tässä on sähköpostin otsikkotiedot\n"
|
||||
"To: $to\n"
|
||||
"From: $from\n"
|
||||
"Date: $date\n"
|
||||
"Subject: SABnzbd ei voinut hakea NZB tiedostoa\n"
|
||||
"X-priority: 5\n"
|
||||
"X-MS-priority: 5\n"
|
||||
"## Tämän jälkeen tulee viestin sisältö, tyhjä rivi on pakollinen!\n"
|
||||
"\n"
|
||||
"Hei,\n"
|
||||
"\n"
|
||||
"SABnzbd ei voinut hakea NZB tiedostoa osoitteesta $url.\n"
|
||||
"Virheviesti: $msg\n"
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
3752
po/main/SABnzbd.pot
3752
po/main/SABnzbd.pot
File diff suppressed because it is too large
Load Diff
1909
po/main/da.po
1909
po/main/da.po
File diff suppressed because it is too large
Load Diff
1938
po/main/de.po
1938
po/main/de.po
File diff suppressed because it is too large
Load Diff
1899
po/main/es.po
1899
po/main/es.po
File diff suppressed because it is too large
Load Diff
4536
po/main/fi.po
Normal file
4536
po/main/fi.po
Normal file
File diff suppressed because it is too large
Load Diff
1915
po/main/fr.po
1915
po/main/fr.po
File diff suppressed because it is too large
Load Diff
2330
po/main/nb.po
2330
po/main/nb.po
File diff suppressed because it is too large
Load Diff
1919
po/main/nl.po
1919
po/main/nl.po
File diff suppressed because it is too large
Load Diff
1915
po/main/pl.px
1915
po/main/pl.px
File diff suppressed because it is too large
Load Diff
1923
po/main/pt_BR.po
1923
po/main/pt_BR.po
File diff suppressed because it is too large
Load Diff
1900
po/main/ro.px
1900
po/main/ro.px
File diff suppressed because it is too large
Load Diff
1987
po/main/sv.po
1987
po/main/sv.po
File diff suppressed because it is too large
Load Diff
116
po/nsis/fi.po
Normal file
116
po/nsis/fi.po
Normal file
@@ -0,0 +1,116 @@
|
||||
# Finnish translation for sabnzbd
|
||||
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
|
||||
# This file is distributed under the same license as the sabnzbd package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
|
||||
#
|
||||
msgid ""
|
||||
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: 2013-02-19 15:24+0000\n"
|
||||
"Last-Translator: Matti Ylönen <Unknown>\n"
|
||||
"Language-Team: Finnish <fi@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: 2013-02-20 05:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16491)\n"
|
||||
|
||||
#: NSIS_Installer.nsi:425
|
||||
msgid "Go to the SABnzbd Wiki"
|
||||
msgstr "Siirry SABnzbd wikiin"
|
||||
|
||||
#: NSIS_Installer.nsi:427
|
||||
msgid "Show Release Notes"
|
||||
msgstr "Näytä julkaisutiedot"
|
||||
|
||||
#: NSIS_Installer.nsi:429
|
||||
msgid "Support the project, Donate!"
|
||||
msgstr "Tue projektia, lahjoita!"
|
||||
|
||||
#: NSIS_Installer.nsi:431
|
||||
msgid "Please close \"SABnzbd.exe\" first"
|
||||
msgstr "Ole hyvä ja sulje \"SABnzbd.exe\" ensin"
|
||||
|
||||
#: NSIS_Installer.nsi:433
|
||||
msgid ""
|
||||
" >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
"release notes or go to http://wiki.sabnzbd.org/introducing-0-7-0 !"
|
||||
msgstr ""
|
||||
" >>>> VAROITUS <<<<\\r\\n\\r\\nOle hyvä ja tarkista "
|
||||
"julkaisutiedot tai käy osoitteessa http://wiki.sabnzbd.org/introducing-0-7-0 "
|
||||
"!"
|
||||
|
||||
#: NSIS_Installer.nsi:435
|
||||
msgid "This will uninstall SABnzbd from your system"
|
||||
msgstr "Tämä poistaa SABnzbd:n tietokoneestasi"
|
||||
|
||||
#: NSIS_Installer.nsi:437
|
||||
msgid "Run at startup"
|
||||
msgstr "Suorita käynnistyksen yhteydessä"
|
||||
|
||||
#: NSIS_Installer.nsi:439
|
||||
msgid "Desktop Icon"
|
||||
msgstr "Työpöydän kuvake"
|
||||
|
||||
#: NSIS_Installer.nsi:441
|
||||
msgid "NZB File association"
|
||||
msgstr "NZB tiedostosidos"
|
||||
|
||||
#: NSIS_Installer.nsi:443
|
||||
msgid "Delete Program"
|
||||
msgstr "Poista sovellus"
|
||||
|
||||
#: NSIS_Installer.nsi:445
|
||||
msgid "Delete Settings"
|
||||
msgstr "Poista asetukset"
|
||||
|
||||
#: 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 ""
|
||||
"Tämä järjestelmä vaatii, että Microsoft runtime kirjasto VC90 täytyy asentaa "
|
||||
"ensin. Haluatko asentaa sen nyt?"
|
||||
|
||||
#: NSIS_Installer.nsi:449
|
||||
msgid "Downloading Microsoft runtime installer..."
|
||||
msgstr "Ladataan Microsoft runtime asennusta..."
|
||||
|
||||
#: NSIS_Installer.nsi:451
|
||||
msgid "Download error, retry?"
|
||||
msgstr "Latausvirhe, yritä uudelleen?"
|
||||
|
||||
#: NSIS_Installer.nsi:453
|
||||
msgid "Cannot install without runtime library, retry?"
|
||||
msgstr "Ei voida asentaa ilman runtime kirjastoa, yritä uudelleen?"
|
||||
|
||||
#: NSIS_Installer.nsi:455
|
||||
msgid ""
|
||||
"You cannot overwrite an existing installation. \\n\\nClick `OK` to remove "
|
||||
"the previous version or `Cancel` to cancel this upgrade."
|
||||
msgstr ""
|
||||
"Et voi asentaa tätä vanhan asennuksen päälle. \\n\\nPaina `OK` poistaaksesi "
|
||||
"edellisen version tai paina `Peruuta` peruuttaaksesi tämän päivityksen."
|
||||
|
||||
#: NSIS_Installer.nsi:457
|
||||
msgid "Your settings and data will be preserved."
|
||||
msgstr "Asetuksiasi ja tietojasi ei poisteta."
|
||||
|
||||
#~ msgid ""
|
||||
#~ " >>>> WARNING <<<<\\r\\n\\r\\nPlease, first check the "
|
||||
#~ "release notes or go to http://wiki.sabnzbd.org/introducing-0-6-0 !"
|
||||
#~ msgstr ""
|
||||
#~ " >>>> VAROITUS <<<<\\r\\n\\r\\nOle hyvä ja tarkista "
|
||||
#~ "julkaisutiedot tai mene osoitteeseen http://wiki.sabnzbd.org/introducing-0-6-"
|
||||
#~ "0 !"
|
||||
|
||||
#~ msgid "Start SABnzbd (hidden)"
|
||||
#~ msgstr "Aloita SABnzbd (piilotettuna)"
|
||||
|
||||
#~ msgid "Delete Cache"
|
||||
#~ msgstr "Poista välimuisti"
|
||||
|
||||
#~ msgid "Delete Logs"
|
||||
#~ msgstr "Poista lokit"
|
||||
@@ -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å."
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import gzip
|
||||
import subprocess
|
||||
import time
|
||||
import cherrypy
|
||||
import sys
|
||||
from threading import RLock, Lock, Condition, Thread
|
||||
try:
|
||||
import sleepless
|
||||
@@ -75,6 +76,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
|
||||
@@ -319,6 +321,8 @@ def initialize(pause_downloader = False, clean_up = False, evalSched=False, repa
|
||||
DirScanner()
|
||||
|
||||
MSGIDGrabber()
|
||||
|
||||
Rating()
|
||||
|
||||
URLGrabber()
|
||||
|
||||
@@ -354,6 +358,8 @@ def start():
|
||||
|
||||
MSGIDGrabber.do.start()
|
||||
|
||||
Rating.do.start()
|
||||
|
||||
logging.debug('Starting urlgrabber')
|
||||
URLGrabber.do.start()
|
||||
|
||||
@@ -384,6 +390,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:
|
||||
@@ -509,6 +522,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:
|
||||
@@ -946,7 +960,8 @@ def load_admin(_id, remove=False, do_pickle=True):
|
||||
if remove:
|
||||
os.remove(path)
|
||||
except:
|
||||
logging.error(Ta('Loading %s failed'), path)
|
||||
excepterror = str(sys.exc_info()[0])
|
||||
logging.error(Ta('Loading %s failed with error %s'), path, excepterror)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
return None
|
||||
|
||||
@@ -1037,6 +1052,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()
|
||||
@@ -1051,12 +1069,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:
|
||||
|
||||
@@ -58,6 +58,7 @@ 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):
|
||||
@@ -632,6 +652,18 @@ 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_pause_pp(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
PostProcessor.do.paused = True
|
||||
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 +682,8 @@ 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, None, 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,
|
||||
test=kwargs)
|
||||
if res == 'Email succeeded':
|
||||
res = None
|
||||
return report(output, error=res)
|
||||
@@ -658,7 +691,7 @@ def _api_test_email(name, output, kwargs):
|
||||
def _api_test_notif(name, output, kwargs):
|
||||
""" API: send a test notification, return result """
|
||||
logging.info("Sending test notification")
|
||||
res = sabnzbd.growler.send_notification('SABnzbd', T('Test Notification'), 'other', wait=True)
|
||||
res = sabnzbd.growler.send_notification('SABnzbd', T('Test Notification'), 'other', wait=True, test=kwargs)
|
||||
return report(output, error=res)
|
||||
|
||||
def _api_undefined(name, output, kwargs):
|
||||
@@ -757,8 +790,22 @@ def _api_config_undefined(output, kwargs):
|
||||
return report(output, _MSG_NOT_IMPLEMENTED)
|
||||
|
||||
|
||||
def _api_server_stats(name, output, kwargs):
|
||||
""" API: accepts output """
|
||||
sum_t, sum_m, sum_w, sum_d = BPSMeter.do.get_sums()
|
||||
stats = {'total': sum_t, 'month': sum_m, 'week': sum_w, 'day': sum_d}
|
||||
|
||||
stats['servers'] = {}
|
||||
for svr in config.get_servers():
|
||||
t, m, w, d = BPSMeter.do.amounts(svr)
|
||||
stats['servers'][svr] = {'total': t or 0, 'month': m or 0, 'week': w or 0, 'day': d or 0}
|
||||
|
||||
return report(output, keyword='', data=stats)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
_api_table = {
|
||||
'server_stats' : _api_server_stats,
|
||||
'get_config' : _api_get_config,
|
||||
'set_config' : _api_set_config,
|
||||
'del_config' : _api_del_config,
|
||||
@@ -795,6 +842,8 @@ _api_table = {
|
||||
'rescan' : _api_rescan,
|
||||
'eval_sort' : _api_eval_sort,
|
||||
'watched_now' : _api_watched_now,
|
||||
'resume_pp' : _api_resume_pp,
|
||||
'pause_pp' : _api_pause_pp,
|
||||
'rss_now' : _api_rss_now,
|
||||
'browse' : _api_browse,
|
||||
'reset_quota' : _api_reset_quota,
|
||||
@@ -811,7 +860,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 +1086,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 +1182,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 +1256,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 +1463,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 +1481,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 +1654,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 +1810,20 @@ def build_history(start=None, limit=None, verbose=False, verbose_list=None, sear
|
||||
if item['retry']:
|
||||
retry_folders.append(path)
|
||||
|
||||
if Rating.do:
|
||||
rating = Rating.do.get_rating_by_nzo(item['nzo_id'])
|
||||
else:
|
||||
rating = None
|
||||
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)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import threading
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized
|
||||
from sabnzbd.constants import GIGI
|
||||
|
||||
|
||||
ARTICLE_LOCK = threading.Lock()
|
||||
@@ -31,6 +32,7 @@ class ArticleCache(object):
|
||||
do = None
|
||||
|
||||
def __init__(self):
|
||||
self.__cache_limit_org = 0
|
||||
self.__cache_limit = 0
|
||||
self.__cache_size = 0
|
||||
|
||||
@@ -40,12 +42,16 @@ class ArticleCache(object):
|
||||
|
||||
@synchronized(ARTICLE_LOCK)
|
||||
def cache_info(self):
|
||||
return (len(self.__article_list), self.__cache_size, self.__cache_limit)
|
||||
return (len(self.__article_list), self.__cache_size, self.__cache_limit_org)
|
||||
|
||||
@synchronized(ARTICLE_LOCK)
|
||||
def new_limit(self, limit):
|
||||
""" Called when cache limit changes """
|
||||
self.__cache_limit = limit
|
||||
self.__cache_limit_org = limit
|
||||
if limit < 0:
|
||||
self.__cache_limit = GIGI
|
||||
else:
|
||||
self.__cache_limit = min(limit, GIGI)
|
||||
|
||||
|
||||
@synchronized(ARTICLE_LOCK)
|
||||
|
||||
@@ -24,6 +24,7 @@ import Queue
|
||||
import binascii
|
||||
import logging
|
||||
import struct
|
||||
import re
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
try:
|
||||
@@ -43,13 +44,14 @@ from sabnzbd.postproc import PostProcessor
|
||||
import sabnzbd.downloader
|
||||
from sabnzbd.utils.rarfile import RarFile, is_rarfile
|
||||
from sabnzbd.encoding import latin1, unicoder, is_utf8
|
||||
from sabnzbd.rating import Rating
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
class Assembler(Thread):
|
||||
do = None # Link to the instance of this method
|
||||
|
||||
def __init__ (self, queue = None):
|
||||
def __init__(self, queue=None):
|
||||
Thread.__init__(self)
|
||||
|
||||
if queue:
|
||||
@@ -100,7 +102,7 @@ class Assembler(Thread):
|
||||
# Pause without saving
|
||||
sabnzbd.downloader.Downloader.do.pause(save=False)
|
||||
except:
|
||||
logging.error('Fatal error in Assembler', exc_info = True)
|
||||
logging.error('Fatal error in Assembler', exc_info=True)
|
||||
break
|
||||
|
||||
nzf.remove_admin()
|
||||
@@ -120,6 +122,31 @@ class Assembler(Thread):
|
||||
nzo.fail_msg = T('Aborted, encryption detected')
|
||||
import sabnzbd.nzbqueue
|
||||
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
|
||||
|
||||
unwanted = rar_contains_unwanted_file(filepath)
|
||||
if unwanted:
|
||||
logging.warning(Ta('WARNING: In "%s" unwanted extension in RAR file. Unwanted file is %s '), latin1(nzo.final_name), unwanted)
|
||||
logging.debug(Ta('Unwanted extension is in rar file %s'), filepath)
|
||||
if cfg.action_on_unwanted_extensions() == 1 and nzo.unwanted_ext == 0:
|
||||
logging.debug('Unwanted extension ... pausing')
|
||||
nzo.unwanted_ext = 1
|
||||
nzo.pause()
|
||||
if cfg.action_on_unwanted_extensions() == 2:
|
||||
logging.debug('Unwanted extension ... aborting')
|
||||
nzo.fail_msg = T('Aborted, unwanted extension detected')
|
||||
import sabnzbd.nzbqueue
|
||||
sabnzbd.nzbqueue.NzbQueue.do.end_job(nzo)
|
||||
|
||||
filter, reason = nzo_filtered_by_rating(nzo)
|
||||
if filter == 1:
|
||||
logging.warning(Ta('WARNING: Paused job "%s" because of rating (%s)'), latin1(nzo.final_name), reason)
|
||||
nzo.pause()
|
||||
elif filter == 2:
|
||||
logging.warning(Ta('WARNING: Aborted job "%s" because of rating (%s)'), latin1(nzo.final_name), reason)
|
||||
nzo.fail_msg = T('Aborted, rating filter matched (%s)') % reason
|
||||
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)
|
||||
@@ -128,7 +155,7 @@ class Assembler(Thread):
|
||||
|
||||
def _assemble(nzf, path, dupe):
|
||||
if os.path.exists(path):
|
||||
unique_path = get_unique_path(path, create_dir = False)
|
||||
unique_path = get_unique_filename(path)
|
||||
if dupe:
|
||||
path = unique_path
|
||||
else:
|
||||
@@ -239,7 +266,7 @@ def GetMD5Hashes(fname, force=False):
|
||||
table = {}
|
||||
except:
|
||||
logging.debug('QuickCheck parser crashed in file %s', fname)
|
||||
logging.info('Traceback: ', exc_info = True)
|
||||
logging.info('Traceback: ', exc_info=True)
|
||||
table = {}
|
||||
|
||||
f.close()
|
||||
@@ -287,12 +314,19 @@ def ParseFilePacket(f, header):
|
||||
return nothing
|
||||
|
||||
|
||||
RE_SUBS = re.compile(r'\W+sub|subs|subpack|subtitle|subtitles(?![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 len(names) < 3 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
|
||||
|
||||
@@ -314,3 +348,62 @@ def check_encrypted_rar(nzo, filepath):
|
||||
logging.debug('RAR file %s cannot be inspected', filepath)
|
||||
return encrypted
|
||||
|
||||
|
||||
def rar_contains_unwanted_file(filepath):
|
||||
# checks for unwanted extensions in the rar file 'filepath'
|
||||
# ... unwanted extensions are defined in global variable cfg.unwanted_extensions()
|
||||
# returns False if no unwanted extensions are found in the rar file
|
||||
# returns name of file if unwanted extension is found in the rar file
|
||||
unwanted = None
|
||||
if is_rarfile(filepath):
|
||||
#logging.debug('rar file to check: %s',filepath)
|
||||
#logging.debug('unwanted extensions are: %s', cfg.unwanted_extensions())
|
||||
try:
|
||||
zf = RarFile(filepath, all_names=True)
|
||||
#logging.debug('files in rar file: %s', zf.namelist())
|
||||
for somefile in zf.namelist() :
|
||||
logging.debug('file in rar file: %s', somefile)
|
||||
if os.path.splitext(somefile)[1].replace('.', '').lower() in cfg.unwanted_extensions():
|
||||
logging.debug('Unwanted file %s', somefile)
|
||||
unwanted = somefile
|
||||
zf.close()
|
||||
except:
|
||||
logging.debug('RAR file %s cannot be inspected.', filepath)
|
||||
return unwanted
|
||||
|
||||
def nzo_filtered_by_rating(nzo):
|
||||
if Rating.do and cfg.rating_enable() and cfg.rating_filter_enable() and (nzo.rating_filtered < 2):
|
||||
rating = Rating.do.get_rating_by_nzo(nzo.nzo_id)
|
||||
if rating is not None:
|
||||
nzo.rating_filtered = 1
|
||||
reason = rating_filtered(rating, nzo.filename.lower(), True)
|
||||
if reason is not None: return (2, reason)
|
||||
reason = rating_filtered(rating, nzo.filename.lower(), False)
|
||||
if reason is not None: return (1, reason)
|
||||
return (0, "")
|
||||
|
||||
def rating_filtered(rating, filename, abort):
|
||||
def check_keyword(keyword):
|
||||
clean_keyword = keyword.strip().lower()
|
||||
return (len(clean_keyword) > 0) and (clean_keyword in filename)
|
||||
audio = cfg.rating_filter_abort_audio() if abort else cfg.rating_filter_pause_audio()
|
||||
video = cfg.rating_filter_abort_video() if abort else cfg.rating_filter_pause_video()
|
||||
spam = cfg.rating_filter_abort_spam() if abort else cfg.rating_filter_pause_spam()
|
||||
spam_confirm = cfg.rating_filter_abort_spam_confirm() if abort else cfg.rating_filter_pause_spam_confirm()
|
||||
encrypted = cfg.rating_filter_abort_encrypted() if abort else cfg.rating_filter_pause_encrypted()
|
||||
encrypted_confirm = cfg.rating_filter_abort_encrypted_confirm() if abort else cfg.rating_filter_pause_encrypted_confirm()
|
||||
downvoted = cfg.rating_filter_abort_downvoted() if abort else cfg.rating_filter_pause_downvoted()
|
||||
keywords = cfg.rating_filter_abort_keywords() if abort else cfg.rating_filter_pause_keywords()
|
||||
if (video > 0) and (rating.avg_video > 0) and (rating.avg_video <= video):
|
||||
return T('video')
|
||||
if (audio > 0) and (rating.avg_audio > 0) and (rating.avg_audio <= audio):
|
||||
return T('audio')
|
||||
if (spam and ((rating.avg_spam_cnt > 0) or rating.avg_encrypted_confirm)) or (spam_confirm and rating.avg_spam_confirm):
|
||||
return T('spam')
|
||||
if (encrypted and ((rating.avg_encrypted_cnt > 0) or rating.avg_encrypted_confirm)) or (encrypted_confirm and rating.avg_encrypted_confirm):
|
||||
return T('passworded')
|
||||
if downvoted and (rating.avg_vote_up < rating.avg_vote_down):
|
||||
return T('downvoted')
|
||||
if any(check_keyword(k) for k in keywords.split(',')):
|
||||
return T('keywords')
|
||||
return None
|
||||
|
||||
@@ -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,12 +87,14 @@ 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)
|
||||
never_repair = OptionBool('misc', 'never_repair', False)
|
||||
ignore_unrar_dates = OptionBool('misc', 'ignore_unrar_dates', False)
|
||||
overwrite_files = OptionBool('misc', 'overwrite_files', False)
|
||||
flat_unpack = OptionBool('misc', 'flat_unpack', False)
|
||||
|
||||
par_option = OptionStr('misc', 'par_option', '', validation=no_nonsense)
|
||||
nice = OptionStr('misc', 'nice', '', validation=no_nonsense)
|
||||
@@ -113,6 +114,28 @@ 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', 'api.oznzb.com')
|
||||
rating_api_key = OptionStr('misc', 'rating_api_key')
|
||||
rating_feedback = OptionBool('misc', 'rating_feedback', True)
|
||||
rating_filter_enable = OptionBool('misc', 'rating_filter_enable', False)
|
||||
rating_filter_abort_audio = OptionNumber('misc', 'rating_filter_abort_audio', 0)
|
||||
rating_filter_abort_video = OptionNumber('misc', 'rating_filter_abort_video', 0)
|
||||
rating_filter_abort_encrypted = OptionBool('misc', 'rating_filter_abort_encrypted', False)
|
||||
rating_filter_abort_encrypted_confirm = OptionBool('misc', 'rating_filter_abort_encrypted_confirm', False)
|
||||
rating_filter_abort_spam = OptionBool('misc', 'rating_filter_abort_spam', False)
|
||||
rating_filter_abort_spam_confirm = OptionBool('misc', 'rating_filter_abort_spam_confirm', False)
|
||||
rating_filter_abort_downvoted = OptionBool('misc', 'rating_filter_abort_downvoted', False)
|
||||
rating_filter_abort_keywords = OptionStr('misc', 'rating_filter_abort_keywords')
|
||||
rating_filter_pause_audio = OptionNumber('misc', 'rating_filter_pause_audio', 0)
|
||||
rating_filter_pause_video = OptionNumber('misc', 'rating_filter_pause_video', 0)
|
||||
rating_filter_pause_encrypted = OptionBool('misc', 'rating_filter_pause_encrypted', False)
|
||||
rating_filter_pause_encrypted_confirm = OptionBool('misc', 'rating_filter_pause_encrypted_confirm', False)
|
||||
rating_filter_pause_spam = OptionBool('misc', 'rating_filter_pause_spam', False)
|
||||
rating_filter_pause_spam_confirm = OptionBool('misc', 'rating_filter_pause_spam_confirm', False)
|
||||
rating_filter_pause_downvoted = OptionBool('misc', 'rating_filter_pause_downvoted', False)
|
||||
rating_filter_pause_keywords = OptionStr('misc', 'rating_filter_pause_keywords')
|
||||
|
||||
top_only = OptionBool('misc', 'top_only', False)
|
||||
autodisconnect = OptionBool('misc', 'auto_disconnect', True)
|
||||
queue_complete = OptionStr('misc', 'queue_complete')
|
||||
@@ -128,6 +151,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)
|
||||
@@ -179,6 +203,7 @@ 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:
|
||||
@@ -204,6 +229,9 @@ web_color2 = OptionStr('misc', 'web_color2')
|
||||
cleanup_list = OptionList('misc', 'cleanup_list')
|
||||
warned_old_queue = OptionBool('misc', 'warned_old_queue', False)
|
||||
|
||||
unwanted_extensions = OptionList('misc', 'unwanted_extensions')
|
||||
action_on_unwanted_extensions = OptionNumber('misc', 'action_on_unwanted_extensions', 0)
|
||||
|
||||
log_web = OptionBool('logging', 'enable_cherrypy_logging', False)
|
||||
log_dir = OptionDir('misc', 'log_dir', 'logs', validation=validate_notempty)
|
||||
log_level = OptionNumber('logging', 'log_level', 1, -1, 2)
|
||||
@@ -259,6 +287,9 @@ wait_ext_drive = OptionNumber('misc', 'wait_ext_drive', 5, 1, 60)
|
||||
history_limit = OptionNumber('misc', 'history_limit', 50, 0)
|
||||
show_sysload = OptionNumber('misc', 'show_sysload', 2, 0, 2)
|
||||
web_watchdog = OptionBool('misc', 'web_watchdog', False)
|
||||
warn_dupl_jobs = OptionBool('misc', 'warn_dupl_jobs', True)
|
||||
new_nzb_on_failure = OptionBool('misc', 'new_nzb_on_failure', False)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Set root folders for Folder config-items
|
||||
@@ -278,4 +309,4 @@ def set_root_folders(home, lcldata):
|
||||
def set_root_folders2():
|
||||
https_cert.set_root(admin_dir.get_path())
|
||||
https_key.set_root(admin_dir.get_path())
|
||||
https_chain.set_root(admin_dir.get_path())
|
||||
https_chain.set_root(admin_dir.get_path())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -110,7 +110,6 @@ def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=No
|
||||
return -1, []
|
||||
name = re.sub(r'\[.*nzbmatrix.com\]', '', name)
|
||||
name = os.path.basename(name)
|
||||
name = misc.sanitize_foldername(name)
|
||||
if data:
|
||||
try:
|
||||
nzo = nzbstuff.NzbObject(name, 0, pp, script, data, cat=cat, url=url,
|
||||
@@ -119,6 +118,7 @@ def ProcessArchiveFile(filename, path, pp=None, script=None, cat=None, catdir=No
|
||||
nzo = None
|
||||
if nzo:
|
||||
nzo_ids.append(add_nzo(nzo))
|
||||
nzo.update_rating()
|
||||
zf.close()
|
||||
try:
|
||||
if not keep: os.remove(path)
|
||||
@@ -169,7 +169,9 @@ def ProcessSingleFile(filename, path, pp=None, script=None, cat=None, catdir=Non
|
||||
if name:
|
||||
name, cat = name_to_cat(name, catdir)
|
||||
# The name is used as the name of the folder, so sanitize it using folder specific santization
|
||||
name = misc.sanitize_foldername(name)
|
||||
if not nzbname:
|
||||
# Prevent embedded password from being damaged by sanitize and trimming
|
||||
nzbname = os.path.split(name)[1]
|
||||
|
||||
try:
|
||||
nzo = nzbstuff.NzbObject(name, 0, pp, script, data, cat=cat, priority=priority, nzbname=nzbname,
|
||||
@@ -189,6 +191,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:
|
||||
|
||||
@@ -277,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:
|
||||
|
||||
@@ -37,22 +37,40 @@ def errormsg(msg):
|
||||
logging.error(latin1(msg))
|
||||
return msg
|
||||
|
||||
|
||||
|
||||
################################################################################
|
||||
# EMAIL_SEND
|
||||
#
|
||||
#
|
||||
################################################################################
|
||||
def send(message, recipient):
|
||||
def send(message, email_to, test=None):
|
||||
""" Send message if message non-empty and email-parms are set """
|
||||
|
||||
# we should not use CFG if we are testing. we should use values
|
||||
# from UI instead.
|
||||
|
||||
if test:
|
||||
email_server = test.get('email_server')
|
||||
email_from = test.get('email_from')
|
||||
email_account = test.get('email_account')
|
||||
email_pwd = test.get('email_pwd')
|
||||
else:
|
||||
email_server = cfg.email_server()
|
||||
email_from = cfg.email_from()
|
||||
email_account = cfg.email_account()
|
||||
email_pwd = cfg.email_pwd()
|
||||
|
||||
# email_to is replaced at send_with_template, since it can be an array
|
||||
|
||||
if not message.strip('\n\r\t '):
|
||||
return "Skipped empty message"
|
||||
|
||||
if cfg.email_server() and recipient and cfg.email_from():
|
||||
if email_server and email_to and email_from:
|
||||
|
||||
message = _prepare_message(message)
|
||||
|
||||
server, port = split_host(cfg.email_server())
|
||||
server, port = split_host(email_server)
|
||||
if not port:
|
||||
port = 25
|
||||
|
||||
@@ -92,14 +110,14 @@ def send(message, recipient):
|
||||
return errormsg(T('Failed to initiate TLS connection'))
|
||||
|
||||
# Authentication
|
||||
if (cfg.email_account() != "") and (cfg.email_pwd() != ""):
|
||||
if (email_account != "") and (email_pwd != ""):
|
||||
try:
|
||||
mailconn.login(cfg.email_account(), cfg.email_pwd())
|
||||
mailconn.login(email_account, email_pwd)
|
||||
except:
|
||||
return errormsg(T('Failed to authenticate to mail server'))
|
||||
|
||||
try:
|
||||
mailconn.sendmail(cfg.email_from(), recipient, message)
|
||||
mailconn.sendmail(email_from, email_to, message)
|
||||
msg = None
|
||||
except smtplib.SMTPHeloError:
|
||||
msg = errormsg('The server didn\'t reply properly to the helo greeting.')
|
||||
@@ -139,7 +157,7 @@ def get_email_date():
|
||||
################################################################################
|
||||
from Cheetah.Template import Template
|
||||
|
||||
def send_with_template(prefix, parm):
|
||||
def send_with_template(prefix, parm, test=None):
|
||||
""" Send an email using template """
|
||||
|
||||
parm['from'] = cfg.email_from()
|
||||
@@ -166,15 +184,20 @@ def send_with_template(prefix, parm):
|
||||
source = _decode_file(temp)
|
||||
if source:
|
||||
sent = True
|
||||
if len(cfg.email_to()):
|
||||
for recipient in cfg.email_to():
|
||||
if test:
|
||||
recipients = [ test.get('email_to') ]
|
||||
else:
|
||||
recipients = cfg.email_to()
|
||||
|
||||
if len(recipients):
|
||||
for recipient in recipients:
|
||||
parm['to'] = recipient
|
||||
message = Template(source=source,
|
||||
searchList=[parm],
|
||||
filter=EmailFilter,
|
||||
compilerSettings={'directiveStartToken': '<!--#',
|
||||
'directiveEndToken': '#-->'})
|
||||
ret = send(message.respond(), recipient)
|
||||
ret = send(message.respond(), recipient, test)
|
||||
del message
|
||||
else:
|
||||
ret = T('No recipients given, no email sent')
|
||||
@@ -187,7 +210,7 @@ def send_with_template(prefix, parm):
|
||||
return ret
|
||||
|
||||
|
||||
def endjob(filename, msgid, cat, status, path, bytes, fail_msg, stages, script, script_output, script_ret):
|
||||
def endjob(filename, msgid, cat, status, path, bytes, fail_msg, stages, script, script_output, script_ret, test=None):
|
||||
""" Send end-of-job email """
|
||||
|
||||
# Translate the stage names
|
||||
@@ -219,7 +242,7 @@ def endjob(filename, msgid, cat, status, path, bytes, fail_msg, stages, script,
|
||||
parm['size'] = "%sB" % to_units(bytes)
|
||||
parm['end_time'] = time.strftime(time_format('%Y-%m-%d %H:%M:%S'), time.localtime(time.time()))
|
||||
|
||||
return send_with_template('email', parm)
|
||||
return send_with_template('email', parm, test)
|
||||
|
||||
|
||||
def rss_mail(feed, jobs):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -102,25 +102,39 @@ def have_ntfosd():
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def send_notification(title , msg, gtype, wait=False):
|
||||
def send_notification(title , msg, gtype, wait=False, test=None):
|
||||
""" Send Notification message
|
||||
Return '' when OK, otherwise an error string
|
||||
"""
|
||||
res = []
|
||||
if gtype in sabnzbd.cfg.notify_classes() or wait:
|
||||
if sabnzbd.DARWIN_ML and sabnzbd.cfg.ncenter_enable():
|
||||
|
||||
# support testing values from UI
|
||||
if (test):
|
||||
ncenter_enable = test.get('ncenter_enable') == 'true'
|
||||
ntfosd_enable = test.get('ntfosd_enable') == 'true'
|
||||
growl_enable = test.get('growl_enable') == 'true'
|
||||
growl_server = test.get('growl_server') or None
|
||||
else:
|
||||
ncenter_enable = sabnzbd.cfg.ncenter_enable()
|
||||
ntfosd_enable = sabnzbd.cfg.ntfosd_enable()
|
||||
growl_enable = sabnzbd.cfg.growl_enable()
|
||||
growl_server = sabnzbd.cfg.growl_server()
|
||||
|
||||
if sabnzbd.DARWIN_ML and ncenter_enable:
|
||||
res.append(send_notification_center(title, msg, gtype))
|
||||
if sabnzbd.cfg.growl_enable():
|
||||
if _HAVE_CLASSIC_GROWL and not sabnzbd.cfg.growl_server():
|
||||
if growl_enable:
|
||||
if _HAVE_CLASSIC_GROWL and not growl_server:
|
||||
return send_local_growl(title, msg, gtype)
|
||||
else:
|
||||
if wait:
|
||||
res.append(send_growl(title, msg, gtype))
|
||||
# we only test with wait=True
|
||||
res.append(send_growl(title, msg, gtype, test))
|
||||
else:
|
||||
res.append('ok')
|
||||
Thread(target=send_growl, args=(title, msg, gtype)).start()
|
||||
time.sleep(0.5)
|
||||
if have_ntfosd():
|
||||
if ntfosd_enable and have_ntfosd():
|
||||
res.append(send_notify_osd(title, msg))
|
||||
|
||||
return ' / '.join([r for r in res if r])
|
||||
@@ -135,11 +149,19 @@ def reset_growl():
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def register_growl():
|
||||
def register_growl(test=None):
|
||||
""" Register this app with Growl
|
||||
"""
|
||||
error = None
|
||||
host, port = sabnzbd.misc.split_host(sabnzbd.cfg.growl_server())
|
||||
|
||||
if (test):
|
||||
growl_server = test.get('growl_server')
|
||||
growl_password = test.get('growl_password')
|
||||
else:
|
||||
growl_server = sabnzbd.cfg.growl_server()
|
||||
growl_password = sabnzbd.cfg.growl_password()
|
||||
|
||||
host, port = sabnzbd.misc.split_host(growl_server)
|
||||
|
||||
sys_name = hostname(host)
|
||||
|
||||
@@ -153,7 +175,7 @@ def register_growl():
|
||||
notifications = [Tx(NOTIFICATION[key]) for key in NOTIFY_KEYS],
|
||||
hostname = host or 'localhost',
|
||||
port = port or 23053,
|
||||
password = sabnzbd.cfg.growl_password() or None
|
||||
password = growl_password or None
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -182,15 +204,19 @@ def register_growl():
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def send_growl(title , msg, gtype):
|
||||
def send_growl(title , msg, gtype, test=None):
|
||||
""" Send Growl message
|
||||
"""
|
||||
global _GROWL, _GROWL_REG
|
||||
|
||||
for n in (0, 1):
|
||||
if not _GROWL_REG: _GROWL = None
|
||||
|
||||
if test:
|
||||
reset_growl()
|
||||
|
||||
if not _GROWL:
|
||||
_GROWL, error = register_growl()
|
||||
_GROWL, error = register_growl(test)
|
||||
if _GROWL:
|
||||
assert isinstance(_GROWL, GrowlNotifier)
|
||||
_GROWL_REG = True
|
||||
@@ -204,22 +230,27 @@ def send_growl(title , msg, gtype):
|
||||
description = unicoder(msg),
|
||||
)
|
||||
if ret is None or isinstance(ret, bool):
|
||||
return None
|
||||
result = None
|
||||
elif ret[0] == '401':
|
||||
_GROWL = False
|
||||
else:
|
||||
logging.debug('Growl error %s', ret)
|
||||
return 'Growl error %s', ret
|
||||
result = 'Growl error %s', ret
|
||||
except socket.error, err:
|
||||
error = 'Growl error %s' % err
|
||||
logging.debug(error)
|
||||
return error
|
||||
result = error
|
||||
except:
|
||||
error = 'Growl error (unknown)'
|
||||
logging.debug(error)
|
||||
return error
|
||||
result = error
|
||||
else:
|
||||
return error
|
||||
result = error
|
||||
|
||||
if test:
|
||||
reset_growl()
|
||||
|
||||
return result
|
||||
return None
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
@@ -292,11 +323,12 @@ 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()
|
||||
if 'Notification delivered' in output:
|
||||
if 'Notification delivered' in output or 'Removing previously' in output:
|
||||
output = ''
|
||||
except:
|
||||
logging.info('Cannot run notifier "%s"', tool)
|
||||
|
||||
@@ -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
|
||||
@@ -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).replace('"', '"')
|
||||
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,9 @@ 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)
|
||||
if password == "":
|
||||
password = None
|
||||
pp = kwargs.get('pp', None)
|
||||
script = kwargs.get('script', None)
|
||||
cat = kwargs.get('cat', None)
|
||||
@@ -596,9 +613,9 @@ 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)
|
||||
NzbQueue.do.change_cat(nzo_id, cat, priority)
|
||||
if script != None:
|
||||
NzbQueue.do.change_script(nzo_id,script)
|
||||
if pp != None:
|
||||
@@ -864,7 +881,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 +898,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 +914,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 +1038,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 +1093,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 +1211,15 @@ 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', 'fail_hopeless'
|
||||
'pre_check', 'max_art_tries', 'max_art_opt', 'fail_hopeless',
|
||||
'rating_enable', 'rating_api_key', 'rating_host', 'rating_feedback', 'rating_filter_enable',
|
||||
'rating_filter_abort_audio', 'rating_filter_abort_video', 'rating_filter_abort_encrypted',
|
||||
'rating_filter_abort_encrypted_confirm', 'rating_filter_abort_spam', 'rating_filter_abort_spam_confirm',
|
||||
'rating_filter_abort_downvoted', 'rating_filter_abort_keywords',
|
||||
'rating_filter_pause_audio', 'rating_filter_pause_video', 'rating_filter_pause_encrypted',
|
||||
'rating_filter_pause_encrypted_confirm', 'rating_filter_pause_spam', 'rating_filter_pause_spam_confirm',
|
||||
'rating_filter_pause_downvoted', 'rating_filter_pause_keywords',
|
||||
'unwanted_extensions', 'action_on_unwanted_extensions'
|
||||
)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
@@ -1186,6 +1242,7 @@ class ConfigSwitches(object):
|
||||
|
||||
for kw in SWITCH_LIST:
|
||||
conf[kw] = config.get_config('misc', kw)()
|
||||
conf['unwanted_extensions'] = cfg.unwanted_extensions.get_string()
|
||||
|
||||
conf['script_list'] = list_scripts() or ['None']
|
||||
conf['have_ampm'] = HAVE_AMPM
|
||||
@@ -1202,6 +1259,8 @@ class ConfigSwitches(object):
|
||||
for kw in SWITCH_LIST:
|
||||
item = config.get_config('misc', kw)
|
||||
value = platform_encode(kwargs.get(kw))
|
||||
if kw == 'unwanted_extensions' and value:
|
||||
value = value.lower().replace('.', '')
|
||||
msg = item.set(value)
|
||||
if msg:
|
||||
return badParameterResponse(msg)
|
||||
@@ -1215,17 +1274,18 @@ class ConfigSwitches(object):
|
||||
SPECIAL_BOOL_LIST = \
|
||||
( 'start_paused', 'no_penalties', 'ignore_wrong_unrar', 'create_group_folders',
|
||||
'queue_complete_pers', 'api_warnings', 'allow_64bit_tools', 'par2_multicore',
|
||||
'never_repair', 'allow_streaming', 'ignore_unrar_dates', 'rss_filenames', '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', 'wait_for_dfolder', 'warn_empty_nzb'
|
||||
'web_watchdog', 'wait_for_dfolder', 'warn_empty_nzb', 'enable_recursive', 'sanitize_safe',
|
||||
'enable_meta', 'flat_unpack', 'warn_dupl_jobs', 'new_nzb_on_failure'
|
||||
)
|
||||
SPECIAL_VALUE_LIST = \
|
||||
( 'size_limit', 'folder_max_length', 'fsys_type', 'movie_rename_limit', 'nomedia_marker',
|
||||
'req_completion_rate', 'wait_ext_drive', 'history_limit', 'show_sysload', 'ipv6_servers'
|
||||
)
|
||||
SPECIAL_LIST_LIST = \
|
||||
( 'rss_odd_titles', 'prio_sort_list'
|
||||
( 'rss_odd_titles', 'prio_sort_list', 'rating_host'
|
||||
)
|
||||
|
||||
class ConfigSpecial(object):
|
||||
@@ -2527,7 +2587,7 @@ def GetRssLog(feed):
|
||||
if sabnzbd.rss.special_rss_site(url):
|
||||
nzbname = ""
|
||||
else:
|
||||
nzbname = xml_name(sanitize_foldername(job.get('title', '')))
|
||||
nzbname = xml_name(job.get('title', ''))
|
||||
return url, \
|
||||
title, \
|
||||
'*' * int(job.get('status', '').endswith('*')), \
|
||||
@@ -2544,7 +2604,7 @@ def GetRssLog(feed):
|
||||
|
||||
# Sort in reverse order of time stamp for 'Done'
|
||||
dnames = [job for job in jobs.keys() if jobs[job]['status'] == 'D']
|
||||
dnames.sort(lambda x, y: jobs[y].get('timestamp', 0) - jobs[x].get('timestamp', 0))
|
||||
dnames.sort(lambda x, y: int(jobs[y].get('time', 0) - jobs[x].get('time', 0)))
|
||||
done = [xml_name(jobs[job]['title']) for job in dnames]
|
||||
|
||||
|
||||
@@ -2729,6 +2789,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']:
|
||||
|
||||
255
sabnzbd/misc.py
255
sabnzbd/misc.py
@@ -31,6 +31,7 @@ import socket
|
||||
import time
|
||||
import glob
|
||||
import stat
|
||||
import Queue
|
||||
try:
|
||||
socket.ssl
|
||||
_HAVE_SSL = True
|
||||
@@ -225,7 +226,7 @@ FL_LEGAL = CH_LEGAL + "-''"
|
||||
uFL_ILLEGAL = FL_ILLEGAL.decode('latin-1')
|
||||
uFL_LEGAL = FL_LEGAL.decode('latin-1')
|
||||
|
||||
def sanitize_foldername(name):
|
||||
def sanitize_foldername(name, limit=True):
|
||||
""" Return foldername with dodgy chars converted to safe ones
|
||||
Remove any leading and trailing dot and space characters
|
||||
"""
|
||||
@@ -238,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():
|
||||
@@ -254,11 +260,28 @@ def sanitize_foldername(name):
|
||||
name = 'unknown'
|
||||
|
||||
maxlen = cfg.folder_max_length()
|
||||
if len(name) > maxlen:
|
||||
if limit and len(name) > maxlen:
|
||||
name = name[:maxlen]
|
||||
|
||||
return name
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def sanitize_and_trim_path(path):
|
||||
""" Remove illegal characters and trim element size
|
||||
"""
|
||||
path = path.replace('\\', '/')
|
||||
parts = path.split('/')
|
||||
if sabnzbd.WIN32 and len(parts[0]) == 2 and ':' in parts[0]:
|
||||
new_path = parts[0]
|
||||
parts.pop(0)
|
||||
elif path.startswith('/'):
|
||||
new_path = '/'
|
||||
else:
|
||||
new_path = ''
|
||||
for part in parts:
|
||||
new_path = os.path.join(new_path, sanitize_foldername(part))
|
||||
return new_path
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def flag_file(path, flag, create=False):
|
||||
@@ -1229,6 +1252,13 @@ def ip_extract():
|
||||
|
||||
def renamer(old, new):
|
||||
""" Rename file/folder with retries for Win32 """
|
||||
# Sanitize last part of new name
|
||||
path, name = os.path.split(new)
|
||||
# Use the more stringent folder rename to end up with a nicer name,
|
||||
# but do not trim size
|
||||
new = os.path.join(path, sanitize_foldername(name, False))
|
||||
|
||||
logging.debug('Renaming "%s" to "%s"', old, new)
|
||||
if sabnzbd.WIN32:
|
||||
retries = 15
|
||||
while retries > 0:
|
||||
@@ -1236,6 +1266,7 @@ def renamer(old, new):
|
||||
os.rename(old, new)
|
||||
return
|
||||
except WindowsError, err:
|
||||
logging.debug('Error renaming "%s" to "%s" <%s>', old, new, err)
|
||||
if err[0] == 32:
|
||||
logging.debug('Retry rename %s to %s', old, new)
|
||||
retries -= 1
|
||||
@@ -1375,3 +1406,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]
|
||||
|
||||
@@ -26,6 +26,7 @@ import subprocess
|
||||
import logging
|
||||
from time import time
|
||||
import binascii
|
||||
import shutil
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.encoding import TRANS, UNTRANS, unicode2local, name_fixer, \
|
||||
@@ -93,7 +94,7 @@ def find_programs(curdir):
|
||||
|
||||
if sabnzbd.DARWIN:
|
||||
try:
|
||||
os_version = subprocess.Popen("sw_vers -productVersion", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.read()
|
||||
os_version = run_simple('sw_vers -productVersion')
|
||||
#par2-sl from Macpar Deluxe 4.1 is only 10.6 and later
|
||||
if int(os_version.split('.')[1]) >= 6:
|
||||
sabnzbd.newsunpack.PAR2_COMMAND = check(curdir, 'osx/par2/par2-sl')
|
||||
@@ -128,19 +129,22 @@ 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)]
|
||||
|
||||
if failure_url:
|
||||
command.extend(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,
|
||||
@@ -187,7 +191,8 @@ def unpack_magic(nzo, workdir, workdir_complete, dele, one_folder, joinables, zi
|
||||
|
||||
rerun = False
|
||||
newfiles = []
|
||||
error = False
|
||||
error = 0
|
||||
new_joins = new_rars = new_zips = new_ts = None
|
||||
|
||||
if cfg.enable_filejoin():
|
||||
new_joins = [jn for jn in xjoinables if jn not in joinables]
|
||||
@@ -217,7 +222,7 @@ def unpack_magic(nzo, workdir, workdir_complete, dele, one_folder, joinables, zi
|
||||
rerun = True
|
||||
logging.info('Unzip starting on %s', workdir)
|
||||
if unzip(nzo, workdir, workdir_complete, dele, one_folder, new_zips):
|
||||
error = True
|
||||
error = 1
|
||||
logging.info('Unzip finished on %s', workdir)
|
||||
nzo.set_action_line()
|
||||
|
||||
@@ -233,7 +238,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 +294,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,20 +304,18 @@ 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 = {}
|
||||
joinable_set = None
|
||||
set = num = None
|
||||
for joinable in joinables:
|
||||
head, tail = os.path.splitext(joinable)
|
||||
if tail == '.ts':
|
||||
match, set, num = match_ts(joinable)
|
||||
if not set:
|
||||
set = head
|
||||
if set not in joinable_sets:
|
||||
joinable_sets[set] = []
|
||||
joinable_sets[set].append(joinable)
|
||||
head = match_ts(joinable)[1]
|
||||
if head not in joinable_sets:
|
||||
joinable_sets[head] = []
|
||||
joinable_sets[head].append(joinable)
|
||||
logging.debug("joinable_sets: %s", joinable_sets)
|
||||
|
||||
try:
|
||||
@@ -330,6 +332,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 +345,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 +354,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)
|
||||
@@ -455,7 +461,7 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, one_folder, rars):
|
||||
except OSError:
|
||||
logging.warning(Ta('Deleting %s failed!'), latin1(brokenrar))
|
||||
|
||||
return not success, extracted_files
|
||||
return fail, extracted_files
|
||||
|
||||
|
||||
def rar_extract(rarfile, numrars, one_folder, nzo, setname, extraction_path):
|
||||
@@ -468,20 +474,24 @@ def rar_extract(rarfile, numrars, one_folder, nzo, setname, extraction_path):
|
||||
new_files = None
|
||||
rars = []
|
||||
if nzo.password:
|
||||
passwords = [nzo.password]
|
||||
logging.info('Got a password set by user')
|
||||
passwords = [nzo.password.strip()]
|
||||
else:
|
||||
passwords = []
|
||||
# Append meta passwords, to prevent changing the original list
|
||||
passwords.extend(nzo.meta.get('password', []))
|
||||
if passwords:
|
||||
logging.info('Read %s passwords from meta data in NZB', len(passwords))
|
||||
pw_file = cfg.password_file.get_path()
|
||||
if pw_file:
|
||||
try:
|
||||
pwf = open(pw_file, 'r')
|
||||
lines = pwf.read().split('\n')
|
||||
# Remove empty lines and space-only passwords and remove surrounding spaces
|
||||
passwords.extend([pw.strip('\r\n ') for pw in lines if pw.strip('\r\n ')])
|
||||
pws = [pw.strip('\r\n ') for pw in lines if pw.strip('\r\n ')]
|
||||
passwords.extend(pws)
|
||||
pwf.close()
|
||||
logging.info('Read the passwords file %s', pw_file)
|
||||
logging.info('Read %s passwords from file %s', len(pws), pw_file)
|
||||
except IOError:
|
||||
logging.info('Failed to read the passwords file %s', pw_file)
|
||||
|
||||
@@ -510,7 +520,7 @@ def rar_extract(rarfile, numrars, one_folder, nzo, setname, extraction_path):
|
||||
|
||||
def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path, password):
|
||||
""" Unpack single rar set 'rarfile' to 'extraction_path'
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password), new_files, rars
|
||||
Return fail==0(ok)/fail==1(error)/fail==2(wrong password)/fail==3(crc-error), new_files, rars
|
||||
"""
|
||||
start = time()
|
||||
|
||||
@@ -532,7 +542,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
|
||||
############################################################################
|
||||
|
||||
if one_folder:
|
||||
if one_folder or cfg.flat_unpack():
|
||||
action = 'e'
|
||||
else:
|
||||
action = 'x'
|
||||
@@ -605,7 +615,7 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
msg = ('[%s] '+Ta('ERROR: CRC failed in "%s"')) % (setname, latin1(filename))
|
||||
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
|
||||
logging.warning(Ta('ERROR: CRC failed in "%s"'), latin1(setname))
|
||||
fail = 1
|
||||
fail = 3
|
||||
|
||||
elif line.startswith('Write error'):
|
||||
nzo.fail_msg = T('Unpacking failed, write error or disk is full?')
|
||||
@@ -634,8 +644,12 @@ 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 'The specified password is incorrect' in line or \
|
||||
('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."
|
||||
# unrar 5.01 : "The specified password is incorrect."
|
||||
m = re.search('encrypted file (.+)\. Corrupt file', line)
|
||||
if not m:
|
||||
# unrar 3.x syntax
|
||||
@@ -643,12 +657,24 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
if m:
|
||||
filename = TRANS(m.group(1)).strip()
|
||||
else:
|
||||
filename = '???'
|
||||
filename = os.path.split(rarfile)[1]
|
||||
nzo.fail_msg = T('Unpacking failed, archive requires a password')
|
||||
msg = ('[%s][%s] '+Ta('Unpacking failed, archive requires a password')) % (setname, latin1(filename))
|
||||
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 = 3
|
||||
|
||||
else:
|
||||
m = re.search(r'^(Extracting|Creating|...)\s+(.*?)\s+OK\s*$', line)
|
||||
if m:
|
||||
@@ -766,7 +792,7 @@ def unzip(nzo, workdir, workdir_complete, delete, one_folder, zips):
|
||||
def ZIP_Extract(zipfile, extraction_path, one_folder):
|
||||
""" Unzip single zip set 'zipfile' to 'extraction_path'
|
||||
"""
|
||||
if one_folder:
|
||||
if one_folder or cfg.flat_unpack():
|
||||
option = '-j' # Unpack without folders
|
||||
else:
|
||||
option = '-qq' # Dummy option
|
||||
@@ -789,7 +815,7 @@ 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
|
||||
|
||||
@@ -823,7 +849,7 @@ 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, used_par2 = PAR_Verify(parfile, parfile_nzf, nzo,
|
||||
setname, joinables)
|
||||
setname, joinables, single=single)
|
||||
|
||||
if finished:
|
||||
result = True
|
||||
@@ -915,7 +941,7 @@ _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'
|
||||
@@ -943,16 +969,16 @@ def PAR_Verify(parfile, parfile_nzf, nzo, setname, joinables, classic=False):
|
||||
command = [str(PAR2C_COMMAND), cmd, parfile]
|
||||
classic = True
|
||||
|
||||
for joinable in joinables:
|
||||
if setname in joinable:
|
||||
command.append(joinable)
|
||||
|
||||
# Append the wildcard for this set
|
||||
wildcard = '%s*' % os.path.join(os.path.split(parfile)[0], setname)
|
||||
if len(globber(wildcard, None)) < 2:
|
||||
if single or len(globber(wildcard, None)) < 2:
|
||||
# Support bizarre naming conventions
|
||||
wildcard = os.path.join(os.path.split(parfile)[0], '*')
|
||||
command.append(wildcard)
|
||||
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)
|
||||
@@ -1258,7 +1284,7 @@ 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, used_par2
|
||||
|
||||
@@ -1353,12 +1379,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)]
|
||||
@@ -1367,10 +1395,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]
|
||||
|
||||
@@ -1436,7 +1461,7 @@ def unrar_check(rar):
|
||||
""" Return True if correct version of unrar is found """
|
||||
if rar:
|
||||
try:
|
||||
version = subprocess.Popen(rar, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.read()
|
||||
version = run_simple(rar)
|
||||
except:
|
||||
return False
|
||||
m = re.search("RAR\s(\d+)\.(\d+)\s+.*Alexander Roshal", version)
|
||||
@@ -1501,7 +1526,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()
|
||||
@@ -1600,3 +1625,13 @@ def get_from_url(url, timeout=None):
|
||||
except:
|
||||
output = None
|
||||
return output
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def run_simple(cmd):
|
||||
""" Run simple external command and return output
|
||||
"""
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
txt = p.stdout.read()
|
||||
p.wait()
|
||||
return txt
|
||||
|
||||
@@ -186,7 +186,9 @@ class NzbQueue(TryList):
|
||||
from sabnzbd.dirscanner import ProcessSingleFile
|
||||
res, nzo_ids = ProcessSingleFile(nzo.work_name + '.nzb', nzb_path, reuse=True)
|
||||
if res == 0 and nzo_ids:
|
||||
self.replace_in_q(nzo, nzo_ids[0])
|
||||
nzo = self.replace_in_q(nzo, nzo_ids[0])
|
||||
# Reset reuse flag to make pause/abort on encryption possible
|
||||
nzo.reuse = False
|
||||
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
@@ -200,10 +202,11 @@ class NzbQueue(TryList):
|
||||
self.__nzo_list.pop(pos)
|
||||
del self.__nzo_table[nzo.nzo_id]
|
||||
del nzo
|
||||
return new_nzo
|
||||
except:
|
||||
logging.error('Failed to restart NZB after pre-check (%s)', nzo.nzo_id)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
return
|
||||
return nzo
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
def save(self, save_nzo=None):
|
||||
@@ -220,7 +223,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)
|
||||
|
||||
@@ -299,20 +302,21 @@ class NzbQueue(TryList):
|
||||
self.__nzo_table[nzo_id].script = script
|
||||
|
||||
@synchronized(NZBQUEUE_LOCK)
|
||||
def change_cat(self, nzo_ids, cat):
|
||||
def change_cat(self, nzo_ids, cat, explicit_priority=None):
|
||||
for nzo_id in [item.strip() for item in nzo_ids.split(',')]:
|
||||
if nzo_id in self.__nzo_table:
|
||||
nzo = self.__nzo_table[nzo_id]
|
||||
nzo.cat, pp, nzo.script, prio = cat_to_opts(cat)
|
||||
nzo.set_pp(pp)
|
||||
self.set_priority(nzo_id, prio)
|
||||
if explicit_priority is None:
|
||||
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 +599,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 +759,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 +890,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.final_name.lower(), nzo2.final_name.lower())
|
||||
|
||||
def _nzo_size_cmp(nzo1, nzo2):
|
||||
return cmp(nzo1.bytes, nzo2.bytes)
|
||||
|
||||
@@ -36,7 +36,7 @@ except ImportError:
|
||||
import sabnzbd
|
||||
from sabnzbd.constants import sample_match, GIGI, ATTRIB_FILE, JOB_ADMIN, \
|
||||
DEFAULT_PRIORITY, LOW_PRIORITY, NORMAL_PRIORITY, \
|
||||
HIGH_PRIORITY, PAUSED_PRIORITY, TOP_PRIORITY, DUP_PRIORITY, \
|
||||
HIGH_PRIORITY, PAUSED_PRIORITY, TOP_PRIORITY, DUP_PRIORITY, REPAIR_PRIORITY, \
|
||||
RENAMES_FILE, Status
|
||||
from sabnzbd.misc import to_units, cat_to_opts, cat_convert, sanitize_foldername, \
|
||||
get_unique_path, get_admin_path, remove_all, format_source_url, \
|
||||
@@ -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']
|
||||
|
||||
@@ -532,7 +533,9 @@ NzbObjectMapper = (
|
||||
('precheck', 'precheck'),
|
||||
('incomplete', 'incomplete'), # Was detected as incomplete
|
||||
('reuse', 'reuse'),
|
||||
('meta', 'meta') # Meta-date from 1.1 type NZB
|
||||
('meta', 'meta'), # Meta-date from 1.1 type NZB
|
||||
('unwanted_ext', 'unwanted_ext'),
|
||||
('rating_filtered', 'rating_filtered') # Has passed through ratings filter
|
||||
)
|
||||
|
||||
class NzbObject(TryList):
|
||||
@@ -544,7 +547,6 @@ class NzbObject(TryList):
|
||||
|
||||
filename = platform_encode(filename)
|
||||
nzbname = platform_encode(nzbname)
|
||||
nzbname = sanitize_foldername(nzbname)
|
||||
|
||||
if pp is None:
|
||||
r = u = d = None
|
||||
@@ -562,8 +564,11 @@ class NzbObject(TryList):
|
||||
dname, ext = os.path.splitext(work_name) # Used for folder name for final unpack
|
||||
if ext.lower() == '.nzb':
|
||||
work_name = dname
|
||||
work_name = sanitize_foldername(work_name)
|
||||
work_name, password = scan_password(work_name)
|
||||
if not work_name:
|
||||
work_name = filename
|
||||
if nzb and work_name and not reuse:
|
||||
work_name = sanitize_foldername(work_name)
|
||||
|
||||
self.work_name = work_name
|
||||
self.final_name = work_name
|
||||
@@ -620,6 +625,8 @@ class NzbObject(TryList):
|
||||
self.oversized = False
|
||||
self.precheck = False
|
||||
self.incomplete = False
|
||||
self.unwanted_ext = 0
|
||||
self.rating_filtered = 0
|
||||
self.reuse = reuse
|
||||
if self.status == Status.QUEUED and not reuse:
|
||||
self.precheck = cfg.pre_check()
|
||||
@@ -673,7 +680,8 @@ class NzbObject(TryList):
|
||||
adir = os.path.join(wdir, JOB_ADMIN)
|
||||
|
||||
# Duplicate checking, needs to be done before the backup
|
||||
duplicate = (not reuse) and nzb and dup_check and sabnzbd.backup_exists(filename)
|
||||
duplicate = (not reuse) and nzb and dup_check and sabnzbd.backup_exists(filename) \
|
||||
and priority != REPAIR_PRIORITY
|
||||
|
||||
if reuse:
|
||||
remove_all(adir, 'SABnzbd_nz?_*')
|
||||
@@ -750,7 +758,7 @@ class NzbObject(TryList):
|
||||
cat = cat_convert(grp)
|
||||
if cat:
|
||||
break
|
||||
|
||||
|
||||
if cfg.create_group_folders():
|
||||
self.dirprefix.append(self.group)
|
||||
|
||||
@@ -801,12 +809,12 @@ class NzbObject(TryList):
|
||||
self.priority = LOW_PRIORITY
|
||||
|
||||
if duplicate and cfg.no_dupes() == 1:
|
||||
logging.warning(Ta('Ignoring duplicate NZB "%s"'), filename)
|
||||
if cfg.warn_dupl_jobs(): logging.warning(Ta('Ignoring duplicate NZB "%s"'), filename)
|
||||
self.purge_data(keep_basic=False)
|
||||
raise TypeError
|
||||
|
||||
if duplicate or self.priority == DUP_PRIORITY:
|
||||
logging.warning(Ta('Pausing duplicate NZB "%s"'), filename)
|
||||
if cfg.warn_dupl_jobs(): logging.warning(Ta('Pausing duplicate NZB "%s"'), filename)
|
||||
self.duplicate = True
|
||||
self.pause()
|
||||
self.priority = NORMAL_PRIORITY
|
||||
@@ -823,6 +831,33 @@ class NzbObject(TryList):
|
||||
else:
|
||||
self.files.sort(cmp=nzf_cmp_name)
|
||||
|
||||
# In the hunt for Unwanted Extensions:
|
||||
# The file with the unwanted extension often is in the first or the last rar file
|
||||
# So put the last rar immediatly after the first rar file so that it gets detected early
|
||||
if cfg.unwanted_extensions() and not cfg.auto_sort():
|
||||
# ... only useful if there are unwanted extensions defined and there is no sorting on date
|
||||
logging.debug('Unwanted Extension: putting last rar after first rar')
|
||||
nzfposcounter = firstrarpos = lastrarpos = 0
|
||||
for nzf in self.files:
|
||||
nzfposcounter += 1
|
||||
if '.rar' in str(nzf):
|
||||
# a NZF found with '.rar' in the name
|
||||
if firstrarpos == 0:
|
||||
# this is the first .rar found, so remember this position
|
||||
firstrarpos = nzfposcounter
|
||||
lastrarpos = nzfposcounter
|
||||
lastrarnzf = nzf # The NZF itself
|
||||
|
||||
if firstrarpos != lastrarpos:
|
||||
# at least two different .rar's found
|
||||
logging.debug('Unwanted Extension: First rar at %s, Last rar at %s',firstrarpos, lastrarpos)
|
||||
logging.debug('Unwanted Extension: Last rar is %s', str(lastrarnzf))
|
||||
try:
|
||||
self.files.remove(lastrarnzf) # first remove. NB: remove() does searches for lastrarnzf
|
||||
self.files.insert(firstrarpos,lastrarnzf) # ... and only then add after position firstrarpos
|
||||
except:
|
||||
logging.debug('The lastrar swap did not go well')
|
||||
|
||||
# Set nzo save-delay to 6 sec per GB with a max of 5 min
|
||||
self.save_timeout = min(6.0 * float(self.bytes) / GIGI, 300.0)
|
||||
|
||||
@@ -889,8 +924,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?
|
||||
@@ -934,7 +969,7 @@ class NzbObject(TryList):
|
||||
|
||||
if file_done:
|
||||
self.remove_nzf(nzf)
|
||||
if not self.reuse and not self.precheck and cfg.fail_hopeless() and not self.check_quality(99)[0]:
|
||||
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()
|
||||
@@ -1022,6 +1057,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):
|
||||
@@ -1034,27 +1070,35 @@ class NzbObject(TryList):
|
||||
prefix += Ta('TOO LARGE') + ' / ' #: Queue indicator for oversized job
|
||||
if self.incomplete and self.status == 'Paused':
|
||||
prefix += Ta('INCOMPLETE') + ' / ' #: Queue indicator for incomplete NZB
|
||||
if self.unwanted_ext and self.status == 'Paused':
|
||||
prefix += Ta('UNWANTED') + ' / ' #: Queue indicator for unwanted extensions
|
||||
if self.rating_filtered and self.status == 'Paused':
|
||||
prefix += Ta('FILTERED') + ' / ' #: Queue indicator for filtered
|
||||
if isinstance(self.wait, float):
|
||||
dif = int(self.wait - time.time() + 0.5)
|
||||
if dif > 0:
|
||||
prefix += Ta('WAIT %s sec') % dif + ' / ' #: Queue indicator for waiting URL fetch
|
||||
if self.password:
|
||||
return '%s%s / %s' % (name_fixer(prefix), self.final_name, self.password)
|
||||
return '%s%s/%s' % (name_fixer(prefix), self.final_name, self.password)
|
||||
else:
|
||||
return '%s%s' % (name_fixer(prefix), self.final_name)
|
||||
|
||||
@property
|
||||
def final_name_pw_clean(self):
|
||||
if self.password:
|
||||
return '%s / %s' % (self.final_name, self.password)
|
||||
return '%s/%s' % (self.final_name, self.password)
|
||||
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 is not None:
|
||||
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'
|
||||
@@ -1067,10 +1111,16 @@ class NzbObject(TryList):
|
||||
if self.encrypted:
|
||||
# If user resumes after encryption warning, no more auto-pauses
|
||||
self.encrypted = 2
|
||||
# If user resumes after warning, reset duplicate/oversized/incomplete indicators
|
||||
if self.rating_filtered:
|
||||
# If user resumes after filtered warning, no more auto-pauses
|
||||
self.rating_filtered = 2
|
||||
# If user resumes after warning, reset duplicate/oversized/incomplete/unwanted indicators
|
||||
self.duplicate = False
|
||||
self.oversized = False
|
||||
self.incomplete = False
|
||||
if self.unwanted_ext:
|
||||
# If user resumes after "unwanted" warning, no more auto-pauses
|
||||
self.unwanted_ext = 2
|
||||
|
||||
def add_parfile(self, parfile):
|
||||
if parfile not in self.files:
|
||||
@@ -1257,6 +1307,22 @@ 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 = ['video', 'videocnt', 'audio', 'audiocnt', 'voteup' ,'votedown', \
|
||||
'spam', 'confirmed-spam', 'passworded', 'confirmed-passworded']
|
||||
fields = {}
|
||||
for k in rating_types:
|
||||
fields[k] = _get_first_meta(k)
|
||||
Rating.do.add_rating(_get_first_meta('id'), self.nzo_id, fields)
|
||||
except:
|
||||
pass
|
||||
|
||||
## end nzo.Mutators #######################################################
|
||||
###########################################################################
|
||||
@property
|
||||
@@ -1412,6 +1478,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))
|
||||
|
||||
@@ -1604,24 +1676,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):
|
||||
|
||||
@@ -31,7 +31,7 @@ import re
|
||||
from sabnzbd.newsunpack import unpack_magic, par2_repair, external_processing, sfv_check
|
||||
from threading import Thread
|
||||
from sabnzbd.misc import real_path, get_unique_path, create_dirs, move_to_path, \
|
||||
make_script_path, \
|
||||
make_script_path, sanitize_and_trim_path, \
|
||||
on_cleanup_list, renamer, remove_dir, remove_all, globber, \
|
||||
set_permissions, cleanup_empty_directories
|
||||
from sabnzbd.tvsort import Sorter
|
||||
@@ -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
|
||||
@@ -125,13 +126,14 @@ class PostProcessor(Thread):
|
||||
except:
|
||||
nzo_id = getattr(nzo, 'nzo_id', 'unknown id')
|
||||
logging.error(Ta('Failed to remove nzo from postproc queue (id)') + ' ' + nzo_id)
|
||||
logging.info('Traceback: ', exc_info = True)
|
||||
self.save()
|
||||
|
||||
def stop(self):
|
||||
""" Stop thread after finishing running job """
|
||||
self.__stop = True
|
||||
self.queue.put(None)
|
||||
self.save()
|
||||
self.__stop = True
|
||||
|
||||
def empty(self):
|
||||
""" Return True if pp queue is empty """
|
||||
@@ -160,12 +162,14 @@ class PostProcessor(Thread):
|
||||
continue
|
||||
|
||||
try:
|
||||
nzo = self.queue.get(timeout=3)
|
||||
nzo = self.queue.get(timeout=1)
|
||||
except Queue.Empty:
|
||||
if check_eoq:
|
||||
check_eoq = False
|
||||
handle_empty_queue()
|
||||
continue
|
||||
continue
|
||||
else:
|
||||
nzo = self.queue.get()
|
||||
|
||||
## Stop job
|
||||
if not nzo:
|
||||
@@ -238,7 +242,8 @@ def process_job(nzo):
|
||||
nzo.status = Status.FAILED
|
||||
nzo.save_attribs()
|
||||
all_ok = False
|
||||
par_error = unpack_error = True
|
||||
par_error = True
|
||||
unpack_error = 1
|
||||
|
||||
try:
|
||||
|
||||
@@ -268,7 +273,8 @@ def process_job(nzo):
|
||||
flag_repair = flag_unpack = False
|
||||
all_ok = cfg.empty_postproc() and empty
|
||||
if not all_ok:
|
||||
par_error = unpack_error = True
|
||||
par_error = True
|
||||
unpack_error = 1
|
||||
|
||||
script = nzo.script
|
||||
cat = nzo.cat
|
||||
@@ -307,11 +313,16 @@ 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
|
||||
|
||||
complete_dir = sanitize_and_trim_path(complete_dir)
|
||||
|
||||
if one_folder:
|
||||
workdir_complete = create_dirs(complete_dir)
|
||||
else:
|
||||
@@ -405,7 +416,7 @@ def process_job(nzo):
|
||||
if empty:
|
||||
job_result = -1
|
||||
else:
|
||||
job_result = int(par_error) + int(unpack_error)*2
|
||||
job_result = int(par_error) + int(bool(unpack_error))*2
|
||||
|
||||
if cfg.ignore_samples() > 0:
|
||||
remove_samples(workdir_complete)
|
||||
@@ -429,7 +440,8 @@ def process_job(nzo):
|
||||
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)
|
||||
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
|
||||
@@ -476,6 +488,15 @@ def process_job(nzo):
|
||||
## 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_ENCRYPTED)
|
||||
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')
|
||||
@@ -534,6 +555,10 @@ def process_job(nzo):
|
||||
logging.error(Ta('Error removing workdir (%s)'), workdir)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
|
||||
# Use automatic retry link on par2 errors and encrypted/bad RARs
|
||||
if par_error or unpack_error in (2, 3):
|
||||
try_alt_nzb(nzo)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -559,6 +584,7 @@ def parring(nzo, workdir):
|
||||
|
||||
re_add = False
|
||||
par_error = False
|
||||
single = len(repair_sets) == 1
|
||||
|
||||
if repair_sets:
|
||||
for setname in repair_sets:
|
||||
@@ -567,13 +593,14 @@ def parring(nzo, workdir):
|
||||
if not verified.get(setname, False):
|
||||
logging.info("Running repair on set %s", setname)
|
||||
parfile_nzf = par_table[setname]
|
||||
if not os.path.exists(os.path.join(nzo.downpath, parfile_nzf.filename)):
|
||||
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
|
||||
need_re_add, res = par2_repair(parfile_nzf, nzo, workdir, setname)
|
||||
re_add = re_add or need_re_add
|
||||
if not res and not need_re_add and cfg.sfv_check():
|
||||
res = try_sfv_check(nzo, workdir, setname)
|
||||
verified[setname] = res
|
||||
par_error = par_error or not res
|
||||
else:
|
||||
logging.info("No par2 sets for %s", filename)
|
||||
@@ -607,7 +634,7 @@ def try_sfv_check(nzo, workdir, setname):
|
||||
par_error = False
|
||||
found = False
|
||||
for sfv in sfvs:
|
||||
if setname in os.path.basename(sfv):
|
||||
if setname.lower() in os.path.basename(sfv).lower():
|
||||
found = True
|
||||
nzo.set_unpack_info('Repair', T('Trying SFV verification'))
|
||||
failed = sfv_check(sfv)
|
||||
@@ -814,3 +841,9 @@ def remove_from_list(name, lst):
|
||||
logging.debug('Popping %s', lst[n])
|
||||
lst.pop(n)
|
||||
return
|
||||
|
||||
def try_alt_nzb(nzo):
|
||||
""" Try to get a new NZB if available """
|
||||
url = nzo.nzo_info.get('failure')
|
||||
if url and cfg.new_nzb_on_failure():
|
||||
sabnzbd.add_url(url, nzo.pp, nzo.script, nzo.cat, nzo.priority)
|
||||
|
||||
280
sabnzbd/rating.py
Normal file
280
sabnzbd/rating.py
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/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 NzbRatingV2(NzbRating):
|
||||
def __init__(self):
|
||||
super(NzbRatingV2, self).__init__()
|
||||
self.avg_spam_cnt = 0
|
||||
self.avg_spam_confirm = False
|
||||
self.avg_encrypted_cnt = 0
|
||||
self.avg_encrypted_confirm = False
|
||||
|
||||
def to_v2(self, rating):
|
||||
self.__dict__.update(rating.__dict__)
|
||||
return self
|
||||
|
||||
class Rating(Thread):
|
||||
VERSION = 2
|
||||
|
||||
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 == 1:
|
||||
ratings = {}
|
||||
for k, v in self.ratings.iteritems():
|
||||
ratings[k] = NzbRatingV2().to_v2(v)
|
||||
self.ratings = ratings
|
||||
self.version = 2
|
||||
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(1)
|
||||
if not cfg.rating_enable(): continue
|
||||
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, fields):
|
||||
if indexer_id and nzo_id and (len(fields) == 10):
|
||||
logging.debug('Add rating (%s, %s: %s, %s, %s, %s)', indexer_id, nzo_id, fields['video'], fields['audio'], fields['voteup'], fields['votedown'])
|
||||
try:
|
||||
rating = self.ratings.get(indexer_id, NzbRatingV2())
|
||||
if fields['video'] and fields['videocnt']:
|
||||
rating.avg_video = int(float(fields['video']))
|
||||
rating.avg_video_cnt = int(float(fields['videocnt']))
|
||||
if fields['audio'] and fields['audiocnt']:
|
||||
rating.avg_audio = int(float(fields['audio']))
|
||||
rating.avg_audio_cnt = int(float(fields['audiocnt']))
|
||||
if fields['voteup']: rating.avg_vote_up = int(float(fields['voteup']))
|
||||
if fields['votedown']: rating.avg_vote_down = int(float(fields['votedown']))
|
||||
if fields['spam']: rating.avg_spam_cnt = int(float(fields['spam']))
|
||||
if fields['confirmed-spam']: rating.avg_spam_confirm = (fields['confirmed-spam'].lower() == 'yes')
|
||||
if fields['passworded']: rating.avg_encrypted_cnt = int(float(fields['passworded']))
|
||||
if fields['confirmed-passworded']: rating.avg_encrypted_confirm = (fields['confirmed-passworded'].lower() == 'yes')
|
||||
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_enable() or not cfg.rating_feedback() or (nzo_id not in self.nzo_indexer_map):
|
||||
return
|
||||
logging.debug('Updating auto flag (%s: %s)', nzo_id, flag)
|
||||
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
|
||||
|
||||
self.ratings[indexer_id].changed = self.ratings[indexer_id].changed & ~rating.changed
|
||||
_reset_warn()
|
||||
return True
|
||||
except:
|
||||
_warn('Problem accessing ratings server: %s' % rating_host)
|
||||
return False
|
||||
@@ -342,6 +342,10 @@ class RSSQueue(object):
|
||||
msg = Ta('Do not have valid authentication for feed %s') % feed
|
||||
logging.info(msg)
|
||||
return unicoder(msg)
|
||||
if status >= 500 and status <=599:
|
||||
msg = Ta('Server side error (server code %s); could not get %s on %s') % (status, feed, uri)
|
||||
logging.info(msg)
|
||||
return unicoder(msg)
|
||||
|
||||
entries = d.get('entries')
|
||||
if 'bozo_exception' in d and not entries:
|
||||
@@ -392,11 +396,7 @@ class RSSQueue(object):
|
||||
|
||||
if link:
|
||||
# Make sure spaces are quoted in the URL
|
||||
if 'nzbclub.com' in link:
|
||||
link, path = os.path.split(link.strip())
|
||||
link = '%s/%s' % (link, urllib.quote(path))
|
||||
else:
|
||||
link = link.strip().replace(' ','%20')
|
||||
link = link.strip().replace(' ','%20')
|
||||
|
||||
newlinks.append(link)
|
||||
|
||||
@@ -588,7 +588,7 @@ def _HandleLink(jobs, link, title, flag, orgcat, cat, pp, script, download, star
|
||||
if special_rss_site(link):
|
||||
nzbname = None
|
||||
else:
|
||||
nzbname = sanitize_foldername(title)
|
||||
nzbname = title
|
||||
m = RE_NEWZBIN.search(link)
|
||||
if m and m.group(1).lower() == 'newz' and m.group(2) and m.group(3):
|
||||
if download:
|
||||
|
||||
@@ -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
|
||||
@@ -275,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
|
||||
@@ -292,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':
|
||||
|
||||
@@ -101,6 +101,16 @@ 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'),
|
||||
'notUsed' : TT('Not used'),
|
||||
'orLess' : TT('or less'),
|
||||
|
||||
# General template elements
|
||||
'signOn' : TT('The automatic usenet download tool'), #: SABnzbd's theme line
|
||||
@@ -232,7 +242,10 @@ 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
|
||||
@@ -271,6 +284,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'),
|
||||
@@ -384,6 +398,10 @@ SKIN_TEXT = {
|
||||
'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.'),
|
||||
'opt-action_on_unwanted_extensions' : TT('Action when unwanted extension detected'),
|
||||
'explain-action_on_unwanted_extensions' : TT('Action when an unwanted extension is detected in RAR files'),
|
||||
'opt-unwanted_extensions' : TT('Unwanted extensions'),
|
||||
'explain-unwanted_extensions' : TT('List all unwanted extensions. For example: <b>exe</b> or <b>exe, com</b>'),
|
||||
'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
|
||||
@@ -445,6 +463,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
|
||||
@@ -461,7 +480,25 @@ SKIN_TEXT = {
|
||||
'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.'),
|
||||
'opt-rating_filter_enable' : TT('Enable Filtering'),
|
||||
'explain-rating_filter_enable' : TT('Action downloads according to filtering rules.'),
|
||||
'opt-rating_filter_abort_if' : TT('Abort If'),
|
||||
'opt-rating_filter_pause_if' : TT('Else Pause If'),
|
||||
'opt-rating_filter_video' : TT('Video rating'),
|
||||
'opt-rating_filter_audio' : TT('Audio rating'),
|
||||
'opt-rating_filter_passworded' : TT('Passworded'),
|
||||
'opt-rating_filter_spam' : TT('Spam'),
|
||||
'opt-rating_filter_confirmed' : TT('Confirmed'),
|
||||
'opt-rating_filter_downvoted' : TT('More thumbs down than up'),
|
||||
'opt-rating_filter_keywords' : TT('Title keywords'),
|
||||
'explain-rating_filter_keywords' : TT('Comma separated list'),
|
||||
|
||||
# Config->Server
|
||||
'configServer' : TT('Server configuration'), #: Caption
|
||||
@@ -871,6 +908,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('''
|
||||
|
||||
@@ -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
|
||||
@@ -46,7 +46,6 @@ REPLACE_AFTER = {
|
||||
'..': '.',
|
||||
'__': '_',
|
||||
' ': ' ',
|
||||
'//': '/',
|
||||
' .%ext': '.%ext'
|
||||
}
|
||||
|
||||
@@ -96,31 +95,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'
|
||||
@@ -182,11 +182,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()
|
||||
@@ -252,8 +253,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):
|
||||
@@ -302,7 +303,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):
|
||||
@@ -433,16 +434,13 @@ class SeriesSorter(object):
|
||||
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 +516,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 +525,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 +566,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
|
||||
|
||||
@@ -721,7 +727,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
|
||||
@@ -729,6 +735,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
|
||||
|
||||
@@ -791,10 +798,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
|
||||
|
||||
@@ -927,7 +934,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
|
||||
@@ -935,59 +942,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()]
|
||||
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:
|
||||
# (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
|
||||
# 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, '.')
|
||||
|
||||
# Make sure some words such as 'and' or 'of' stay lowercased.
|
||||
for x in LOWERCASE:
|
||||
xtitled = titler(x)
|
||||
title = replace_word(title, xtitled, x)
|
||||
title = name.replace('.', ' ').replace('_', ' ')
|
||||
title = title.strip().strip('(').strip('_').strip('-').strip().strip('_')
|
||||
|
||||
# Make sure some words such as 'III' or 'IV' stay uppercased.
|
||||
for x in UPPERCASE:
|
||||
xtitled = titler(x)
|
||||
title = replace_word(title, xtitled, x)
|
||||
if titleing:
|
||||
title = titler(title) # title the show name so it is in a consistant letter case
|
||||
|
||||
# Make sure the first letter of the title is always uppercase
|
||||
if title:
|
||||
title = titler(title[0]) + title[1:]
|
||||
#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('_','.')
|
||||
@@ -1007,22 +1019,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
|
||||
@@ -1072,6 +1089,7 @@ def strip_folders(path):
|
||||
""" Return 'path' without leading and trailing spaces and underscores in each element
|
||||
For Windows, also remove leading and trailing dots
|
||||
"""
|
||||
unc = sabnzbd.WIN32 and (path.startswith('//') or path.startswith('\\\\'))
|
||||
f = path.strip('/').split('/')
|
||||
|
||||
# For path beginning with a slash, insert empty element to prevent loss
|
||||
@@ -1090,7 +1108,11 @@ def strip_folders(path):
|
||||
x = x.strip()
|
||||
return x
|
||||
|
||||
return os.path.normpath('/'.join([strip_all(x) for x in f]))
|
||||
path = os.path.normpath('/'.join([strip_all(x) for x in f]))
|
||||
if unc:
|
||||
return '\\' + path
|
||||
else:
|
||||
return path
|
||||
|
||||
|
||||
def rename_similar(folder, skip_ext, name, skipped_files):
|
||||
@@ -1189,13 +1211,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
|
||||
|
||||
@@ -127,6 +127,7 @@ class URLGrabber(Thread):
|
||||
category = None
|
||||
length = 0
|
||||
nzo_info = {}
|
||||
wait = 0
|
||||
try:
|
||||
fn, header = opener.retrieve(url)
|
||||
except:
|
||||
@@ -147,8 +148,21 @@ 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)
|
||||
elif item == 'retry-after':
|
||||
# For NZBFinder
|
||||
wait = misc.int_conv(value)
|
||||
|
||||
if not filename:
|
||||
for item in tup:
|
||||
@@ -173,7 +187,13 @@ class URLGrabber(Thread):
|
||||
continue
|
||||
|
||||
else:
|
||||
fn, msg, retry, wait = _analyse_others(fn, url)
|
||||
if wait:
|
||||
# For sites that have a rate-limiting attribute
|
||||
msg = ''
|
||||
retry = True
|
||||
fn = None
|
||||
else:
|
||||
fn, msg, retry, wait = _analyse_others(fn, url)
|
||||
if not fn:
|
||||
if retry:
|
||||
logging.info('Retry URL %s', url)
|
||||
@@ -184,10 +204,6 @@ class URLGrabber(Thread):
|
||||
|
||||
if not filename:
|
||||
filename = os.path.basename(url) + '.nzb'
|
||||
# Sanitize and trim name, preserving the extension
|
||||
filename, ext = os.path.splitext(filename)
|
||||
filename = misc.sanitize_foldername(filename)
|
||||
filename += '.' + misc.sanitize_foldername(ext)
|
||||
|
||||
pp = future_nzo.pp
|
||||
script = future_nzo.script
|
||||
@@ -217,7 +233,8 @@ class URLGrabber(Thread):
|
||||
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)
|
||||
|
||||
@@ -42,6 +42,7 @@ _use_extract_hack = 0
|
||||
#
|
||||
|
||||
RAR_ID = "Rar!\x1a\x07\x00"
|
||||
RAR5_ID = "Rar!\x1a\x07\x01\x00"
|
||||
|
||||
# block types
|
||||
RAR_BLOCK_MARK = 0x72 # r
|
||||
@@ -108,8 +109,8 @@ RAR_OS_UNIX = 3
|
||||
def is_rarfile(fn):
|
||||
'''Check quickly whether file is rar archive.'''
|
||||
try:
|
||||
buf = open(fn, "rb").read(len(RAR_ID))
|
||||
return buf == RAR_ID
|
||||
buf = open(fn, "rb").read(50)
|
||||
return buf.startswith(RAR_ID) or buf.startswith(RAR5_ID)
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32con
|
||||
import win32gui_struct
|
||||
@@ -173,17 +174,21 @@ class SysTrayIconThread(Thread):
|
||||
self.create_menu(menu, self.menu_options)
|
||||
#win32gui.SetMenuDefaultItem(menu, 1000, 0)
|
||||
|
||||
pos = win32gui.GetCursorPos()
|
||||
# See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asp
|
||||
win32gui.SetForegroundWindow(self.hwnd)
|
||||
win32gui.TrackPopupMenu(menu,
|
||||
win32con.TPM_LEFTALIGN,
|
||||
pos[0],
|
||||
pos[1],
|
||||
0,
|
||||
self.hwnd,
|
||||
None)
|
||||
win32gui.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0)
|
||||
try:
|
||||
pos = win32gui.GetCursorPos()
|
||||
# See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asp
|
||||
win32gui.SetForegroundWindow(self.hwnd)
|
||||
win32gui.TrackPopupMenu(menu,
|
||||
win32con.TPM_LEFTALIGN,
|
||||
pos[0],
|
||||
pos[1],
|
||||
0,
|
||||
self.hwnd,
|
||||
None)
|
||||
win32gui.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0)
|
||||
except pywintypes.error:
|
||||
# Weird PyWin/win32gui bug, just ignore it for now
|
||||
logging.debug('win32gui problem, cannot show SysTray menu')
|
||||
|
||||
def create_menu(self, menu, menu_options):
|
||||
for option_text, option_icon, option_action, option_id in menu_options[::-1]:
|
||||
|
||||
@@ -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'] = '» %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'] = '» %s' % T('Step Three')
|
||||
info['number'] = 3
|
||||
info['num'] = '» %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
37
solaris/manifest.xml
Normal 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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user