You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3088 lines
109 KiB
3088 lines
109 KiB
#!/usr/bin/python -OO
|
|
# Copyright 2008-2015 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.interface - webinterface
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
import datetime
|
|
import cherrypy
|
|
import logging
|
|
import urllib
|
|
import json
|
|
import re
|
|
import hashlib
|
|
from random import randint
|
|
from xml.sax.saxutils import escape
|
|
|
|
from sabnzbd.utils.rsslib import RSS, Item
|
|
import sabnzbd
|
|
import sabnzbd.rss
|
|
import sabnzbd.scheduler as scheduler
|
|
|
|
from Cheetah.Template import Template
|
|
from sabnzbd.misc import real_path, to_units, from_units, \
|
|
time_format, long_path, calc_age, \
|
|
cat_to_opts, int_conv, globber, globber_full, remove_all, get_base_url
|
|
from sabnzbd.panic import panic_old_queue
|
|
from sabnzbd.newswrapper import GetServerParms
|
|
from sabnzbd.rating import Rating
|
|
from sabnzbd.bpsmeter import BPSMeter
|
|
from sabnzbd.encoding import TRANS, xml_name, LatinFilter, unicoder, special_fixer, \
|
|
platform_encode
|
|
import sabnzbd.config as config
|
|
import sabnzbd.cfg as cfg
|
|
import sabnzbd.notifier as notifier
|
|
import sabnzbd.newsunpack
|
|
from sabnzbd.downloader import Downloader
|
|
from sabnzbd.nzbqueue import NzbQueue
|
|
import sabnzbd.wizard
|
|
from sabnzbd.utils.servertests import test_nntp_server_dict
|
|
from sabnzbd.decoder import HAVE_YENC
|
|
from sabnzbd.utils.sslinfo import ssl_version, ssl_protocols_labels
|
|
|
|
from sabnzbd.constants import \
|
|
REC_RAR_VERSION, NORMAL_PRIORITY, \
|
|
MEBI, DEF_SKIN_COLORS, DEF_STDINTF, DEF_STDCONFIG, DEF_MAIN_TMPL, \
|
|
DEFAULT_PRIORITY
|
|
|
|
from sabnzbd.lang import list_languages, set_language
|
|
|
|
from sabnzbd.api import list_scripts, list_cats, del_from_section, \
|
|
api_handler, build_queue, remove_callable, rss_qstatus, build_status, \
|
|
retry_job, retry_all_jobs, build_header, build_history, del_job_files, \
|
|
format_bytes, std_time, report, del_hist_job, Ttemplate, \
|
|
build_queue_header, _api_test_email, _api_test_notif
|
|
|
|
##############################################################################
|
|
# Global constants
|
|
##############################################################################
|
|
DIRECTIVES = {
|
|
'directiveStartToken': '<!--#',
|
|
'directiveEndToken': '#-->',
|
|
'prioritizeSearchListOverSelf': True
|
|
}
|
|
FILTER = LatinFilter
|
|
|
|
|
|
def check_server(host, port, ajax):
|
|
""" Check if server address resolves properly """
|
|
|
|
if host.lower() == 'localhost' and sabnzbd.AMBI_LOCALHOST:
|
|
return badParameterResponse(T('Warning: LOCALHOST is ambiguous, use numerical IP-address.'), ajax)
|
|
|
|
if GetServerParms(host, int_conv(port)):
|
|
return ""
|
|
else:
|
|
return badParameterResponse(T('Server address "%s:%s" is not valid.') % (host, port), ajax)
|
|
|
|
|
|
def check_access(access_type=4):
|
|
""" Check if external address is allowed given `access_type`
|
|
`access_type`: 1=nzb, 2=api, 3=full_api, 4=webui, 5=webui with login for external
|
|
"""
|
|
referrer = cherrypy.request.remote.ip
|
|
|
|
# CherryPy will report ::ffff:192.168.0.10 on dual-stack situation
|
|
# It will always contain that ::ffff: prefix
|
|
range_ok = not cfg.local_ranges() or bool([1 for r in cfg.local_ranges() if (referrer.startswith(r) or referrer.replace('::ffff:', '').startswith(r))])
|
|
allowed = referrer in ('127.0.0.1', '::ffff:127.0.0.1', '::1') or range_ok or access_type <= cfg.inet_exposure()
|
|
if not allowed:
|
|
logging.debug('Refused connection to %s', referrer)
|
|
return allowed
|
|
|
|
|
|
def ConvertSpecials(p):
|
|
""" Convert None to 'None' and 'Default' to '' """
|
|
if p is None:
|
|
p = 'None'
|
|
elif p.lower() == T('Default').lower():
|
|
p = ''
|
|
return p
|
|
|
|
|
|
def Raiser(root, **kwargs):
|
|
args = {}
|
|
for key in kwargs:
|
|
val = kwargs.get(key)
|
|
if val:
|
|
args[key] = val
|
|
root = '%s?%s' % (root, urllib.urlencode(args))
|
|
return cherrypy.HTTPRedirect(root)
|
|
|
|
|
|
def queueRaiser(root, kwargs):
|
|
return Raiser(root, start=kwargs.get('start'),
|
|
limit=kwargs.get('limit'),
|
|
search=kwargs.get('search'),
|
|
_dc=kwargs.get('_dc'))
|
|
|
|
|
|
def dcRaiser(root, kwargs):
|
|
return Raiser(root, _dc=kwargs.get('_dc'))
|
|
|
|
|
|
def rssRaiser(root, kwargs):
|
|
return Raiser(root, feed=kwargs.get('feed'))
|
|
|
|
|
|
def IsNone(value):
|
|
""" Return True if either None, 'None' or '' """
|
|
return value is None or value == "" or value.lower() == 'none'
|
|
|
|
|
|
def Strip(txt):
|
|
""" Return stripped string, can handle None """
|
|
try:
|
|
return txt.strip()
|
|
except:
|
|
return None
|
|
|
|
|
|
##############################################################################
|
|
# Web login support
|
|
##############################################################################
|
|
def get_users():
|
|
users = {}
|
|
users[cfg.username()] = cfg.password()
|
|
return users
|
|
|
|
def encrypt_pwd(pwd):
|
|
return pwd
|
|
|
|
# Create a more unique ID for each instance
|
|
COOKIE_SECRET = str(randint(1000,100000)*os.getpid())
|
|
|
|
def set_login_cookie(remove=False, remember_me=False):
|
|
""" We try to set a cookie as unique as possible
|
|
to the current user. Based on it's IP and the
|
|
current process ID of the SAB instance and a random
|
|
number, so cookies cannot be re-used
|
|
"""
|
|
salt = randint(1,1000)
|
|
cherrypy.response.cookie['login_cookie'] = hashlib.sha1(str(salt) + cherrypy.request.remote.ip + COOKIE_SECRET).hexdigest()
|
|
cherrypy.response.cookie['login_cookie']['path'] = '/'
|
|
cherrypy.response.cookie['login_salt'] = salt
|
|
cherrypy.response.cookie['login_salt']['path'] = '/'
|
|
|
|
# If we want to be remembered
|
|
if remember_me:
|
|
cherrypy.response.cookie['login_cookie']['max-age'] = 3600*24*14
|
|
cherrypy.response.cookie['login_salt']['max-age'] = 3600*24*14
|
|
|
|
# To remove
|
|
if remove:
|
|
cherrypy.response.cookie['login_cookie']['expires'] = 0
|
|
cherrypy.response.cookie['login_salt']['expires'] = 0
|
|
else:
|
|
# Notify about new login
|
|
notifier.send_notification(T('User logged in'), T('User logged in to the web interface'), 'new_login')
|
|
|
|
def check_login_cookie():
|
|
# Do we have everything?
|
|
if 'login_cookie' not in cherrypy.request.cookie or 'login_salt' not in cherrypy.request.cookie:
|
|
return False
|
|
|
|
return cherrypy.request.cookie['login_cookie'].value == hashlib.sha1(str(cherrypy.request.cookie['login_salt'].value) + cherrypy.request.remote.ip + COOKIE_SECRET).hexdigest()
|
|
|
|
def check_login():
|
|
# Not when no authentication required or basic-auth is on
|
|
if not cfg.html_login() or not cfg.username() or not cfg.password():
|
|
return True
|
|
|
|
# If we show login for external IP, by using access_type=6 we can check if IP match
|
|
if cfg.inet_exposure() == 5 and check_access(access_type=6):
|
|
return True
|
|
|
|
# Check the cookie
|
|
return check_login_cookie()
|
|
|
|
def set_auth(conf):
|
|
""" Set the authentication for CherryPy """
|
|
if cfg.username() and cfg.password() and not cfg.html_login():
|
|
conf.update({'tools.basic_auth.on': True, 'tools.basic_auth.realm': cfg.login_realm(),
|
|
'tools.basic_auth.users': get_users, 'tools.basic_auth.encrypt': encrypt_pwd})
|
|
conf.update({'/api': {'tools.basic_auth.on': False},
|
|
'/m/api': {'tools.basic_auth.on': False},
|
|
'/sabnzbd/api': {'tools.basic_auth.on': False},
|
|
'/sabnzbd/m/api': {'tools.basic_auth.on': False},
|
|
})
|
|
else:
|
|
conf.update({'tools.basic_auth.on': False})
|
|
|
|
|
|
def check_session(kwargs):
|
|
""" Check session key """
|
|
if not check_access():
|
|
return u'Access denied'
|
|
key = kwargs.get('session')
|
|
if not key:
|
|
key = kwargs.get('apikey')
|
|
msg = None
|
|
if not key:
|
|
logging.warning(T('Missing Session key'))
|
|
msg = T('Error: Session Key Required')
|
|
elif key != cfg.api_key():
|
|
logging.warning(T('Error: Session Key Incorrect'))
|
|
msg = T('Error: Session Key Incorrect')
|
|
return msg
|
|
|
|
|
|
def check_apikey(kwargs, nokey=False):
|
|
""" Check api key or nzbkey
|
|
Return None when OK, otherwise an error message
|
|
"""
|
|
def log_warning(txt):
|
|
txt = '%s %s>%s' % (txt, cherrypy.request.remote.ip, cherrypy.request.headers.get('User-Agent', '??'))
|
|
logging.warning('%s', txt)
|
|
|
|
output = kwargs.get('output')
|
|
mode = kwargs.get('mode', '')
|
|
name = kwargs.get('name', '')
|
|
callback = kwargs.get('callback')
|
|
|
|
# Don't give a visible warning: these commands are used by some
|
|
# external utilities to detect if username/password is required
|
|
# The cfg item can suppress all visible warnings
|
|
special = mode in ('get_scripts', 'qstatus') or not cfg.api_warnings.get()
|
|
|
|
# Lookup required access level
|
|
req_access = sabnzbd.api.api_level(mode, name)
|
|
|
|
if req_access == 1 and check_access(1):
|
|
# NZB-only actions
|
|
pass
|
|
elif not check_access(req_access):
|
|
return report(output, 'Access denied')
|
|
|
|
# First check APIKEY, if OK that's sufficient
|
|
if not (cfg.disable_key() or nokey):
|
|
key = kwargs.get('apikey')
|
|
if not key:
|
|
key = kwargs.get('session')
|
|
if not key:
|
|
if not special:
|
|
log_warning(T('API Key missing, please enter the api key from Config->General into your 3rd party program:'))
|
|
return report(output, 'API Key Required', callback=callback)
|
|
elif req_access == 1 and key == cfg.nzb_key():
|
|
return None
|
|
elif key == cfg.api_key():
|
|
return None
|
|
else:
|
|
log_warning(T('API Key incorrect, Use the api key from Config->General in your 3rd party program:'))
|
|
return report(output, 'API Key Incorrect', callback=callback)
|
|
|
|
# No active APIKEY, check web credentials instead
|
|
if cfg.username() and cfg.password():
|
|
if check_login() or (kwargs.get('ma_username') == cfg.username() and kwargs.get('ma_password') == cfg.password()):
|
|
pass
|
|
else:
|
|
if not special:
|
|
log_warning(T('Authentication missing, please enter username/password from Config->General into your 3rd party program:'))
|
|
return report(output, 'Missing authentication', callback=callback)
|
|
return None
|
|
|
|
|
|
class NoPage(object):
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
return badParameterResponse(T('Error: No secondary interface defined.'))
|
|
|
|
|
|
##############################################################################
|
|
class MainPage(object):
|
|
|
|
def __init__(self, web_dir, root, web_dir2=None, root2=None, web_dirc=None, prim=True, first=0):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
if first >= 1 and web_dir2:
|
|
# Setup addresses for secondary skin
|
|
self.m = MainPage(web_dir2, root2, web_dirc=web_dirc, prim=False)
|
|
if first == 2:
|
|
# Setup addresses with /sabnzbd prefix for primary and secondary skin
|
|
self.sabnzbd = MainPage(web_dir, '/sabnzbd/', web_dir2, '/sabnzbd/m/', web_dirc=web_dirc, prim=True, first=1)
|
|
|
|
self.login = LoginPage(web_dirc, root + 'login/', prim)
|
|
self.queue = QueuePage(web_dir, root + 'queue/', prim)
|
|
self.history = HistoryPage(web_dir, root + 'history/', prim)
|
|
self.status = Status(web_dir, root + 'status/', prim)
|
|
self.config = ConfigPage(web_dirc, root + 'config/', prim)
|
|
self.nzb = NzoPage(web_dir, root + 'nzb/', prim)
|
|
self.wizard = sabnzbd.wizard.Wizard(web_dir, root + 'wizard/', prim)
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if not check_access():
|
|
return Protected()
|
|
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
if sabnzbd.OLD_QUEUE and not cfg.warned_old_queue():
|
|
cfg.warned_old_queue.set(True)
|
|
config.save_config()
|
|
return panic_old_queue()
|
|
|
|
if not cfg.notified_new_skin() and cfg.web_dir() != 'Glitter':
|
|
logging.warning(T('Try our new skin Glitter! Fresh new design that is optimized for desktop and mobile devices. Go to Config -> General to change your skin.'))
|
|
if not cfg.notified_new_skin():
|
|
cfg.notified_new_skin.set(1)
|
|
config.save_config()
|
|
|
|
if kwargs.get('skip_wizard') or config.get_servers():
|
|
info = build_header(self.__prim, self.__web_dir)
|
|
|
|
info['script_list'] = list_scripts(default=True)
|
|
info['script'] = 'Default'
|
|
|
|
info['cat'] = 'Default'
|
|
info['cat_list'] = list_cats(True)
|
|
info['have_rss_defined'] = bool(config.get_rss())
|
|
info['have_watched_dir'] = bool(cfg.dirscan_dir())
|
|
|
|
# Have logout only with HTML and if inet=5, only when we are external
|
|
info['have_logout'] = cfg.username() and cfg.password() and (cfg.html_login() and (cfg.inet_exposure() < 5 or (cfg.inet_exposure() == 5 and not check_access(access_type=6))))
|
|
|
|
bytespersec_list = BPSMeter.do.get_bps_list()
|
|
info['bytespersec_list'] = ','.join([str(bps) for bps in bytespersec_list])
|
|
|
|
info['warning'] = ''
|
|
if cfg.enable_unrar():
|
|
version = sabnzbd.newsunpack.RAR_VERSION
|
|
if version and version < REC_RAR_VERSION and not cfg.ignore_wrong_unrar():
|
|
have_str = '%.2f' % (float(version) / 100)
|
|
want_str = '%.2f' % (float(REC_RAR_VERSION) / 100)
|
|
info['warning'] = T('Your UNRAR version is %s, we recommend version %s or higher.<br />') % \
|
|
(have_str, want_str)
|
|
if not sabnzbd.newsunpack.RAR_COMMAND:
|
|
info['warning'] = T('No UNRAR program found, unpacking RAR files is not possible<br />')
|
|
if not sabnzbd.newsunpack.PAR2_COMMAND:
|
|
info['warning'] = T('No PAR2 program found, repairs not possible<br />')
|
|
|
|
# For Glitter we pre-load the JSON output
|
|
if 'Glitter' in self.__web_dir:
|
|
# Queue
|
|
queue = build_queue(limit=cfg.queue_limit(), output='json')[0]
|
|
queue['categories'] = info.pop('cat_list')
|
|
queue['scripts'] = info.pop('script_list')
|
|
|
|
# History
|
|
history = {}
|
|
grand, month, week, day = BPSMeter.do.get_sums()
|
|
history['total_size'], history['month_size'], history['week_size'], history['day_size'] = \
|
|
to_units(grand), to_units(month), to_units(week), to_units(day)
|
|
history['slots'], fetched_items, history['noofslots'] = build_history(limit=cfg.history_limit(), output='json')
|
|
|
|
# Make sure the JSON works, otherwise leave empty
|
|
try:
|
|
info['preload_queue'] = json.dumps({'queue': remove_callable(queue)})
|
|
info['preload_history'] = json.dumps({'history': history})
|
|
except UnicodeDecodeError:
|
|
# We use the javascript recognized 'false'
|
|
info['preload_queue'] = 'false'
|
|
info['preload_history'] = 'false'
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'main.tmpl'),
|
|
filter=FILTER, searchList=[info], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
else:
|
|
# Redirect to the setup wizard
|
|
raise cherrypy.HTTPRedirect('/sabnzbd/wizard/')
|
|
|
|
#@cherrypy.expose
|
|
# def reset_lang(self, **kwargs):
|
|
# msg = check_session(kwargs)
|
|
# if msg: return msg
|
|
# set_language(cfg.language())
|
|
# raise dcRaiser(self.__root, kwargs)
|
|
|
|
def add_handler(self, kwargs):
|
|
if not check_access():
|
|
return Protected()
|
|
url = kwargs.get('url', '')
|
|
pp = kwargs.get('pp')
|
|
script = kwargs.get('script')
|
|
cat = kwargs.get('cat')
|
|
priority = kwargs.get('priority')
|
|
redirect = kwargs.get('redirect')
|
|
nzbname = kwargs.get('nzbname')
|
|
|
|
url = Strip(url)
|
|
if url:
|
|
sabnzbd.add_url(url, pp, script, cat, priority, nzbname)
|
|
if not redirect:
|
|
redirect = self.__root
|
|
raise cherrypy.HTTPRedirect(redirect)
|
|
|
|
@cherrypy.expose
|
|
def addURL(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
raise self.add_handler(kwargs)
|
|
|
|
@cherrypy.expose
|
|
def addFile(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
nzbfile = kwargs.get('nzbfile')
|
|
if nzbfile is not None and nzbfile.filename:
|
|
if nzbfile.value or nzbfile.file:
|
|
sabnzbd.add_nzbfile(nzbfile, kwargs.get('pp'), kwargs.get('script'),
|
|
kwargs.get('cat'), kwargs.get('priority', NORMAL_PRIORITY))
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def shutdown(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
|
|
# Check for PID
|
|
pid_in = kwargs.get('pid')
|
|
if pid_in and int(pid_in) != os.getpid():
|
|
msg = "Incorrect PID for this instance, remove PID from URL to initiate shutdown."
|
|
|
|
if msg:
|
|
yield msg
|
|
else:
|
|
yield "Initiating shutdown..."
|
|
sabnzbd.halt()
|
|
yield "<br>SABnzbd-%s shutdown finished" % sabnzbd.__version__
|
|
cherrypy.engine.exit()
|
|
sabnzbd.SABSTOP = True
|
|
|
|
@cherrypy.expose
|
|
def pause(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
scheduler.plan_resume(0)
|
|
Downloader.do.pause()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def resume(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
scheduler.plan_resume(0)
|
|
sabnzbd.unpause_all()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def rss(self, **kwargs):
|
|
msg = check_apikey(kwargs, nokey=True)
|
|
if msg:
|
|
return msg
|
|
|
|
if kwargs.get('mode') == 'history':
|
|
return rss_history(cherrypy.url(), limit=kwargs.get('limit', 50), search=kwargs.get('search'))
|
|
elif kwargs.get('mode') == 'queue':
|
|
return rss_qstatus()
|
|
elif kwargs.get('mode') == 'warnings':
|
|
return rss_warnings()
|
|
|
|
@cherrypy.expose
|
|
def tapi(self, **kwargs):
|
|
""" Handler for API over http, for template use """
|
|
msg = check_apikey(kwargs)
|
|
if msg:
|
|
return msg
|
|
return api_handler(kwargs)
|
|
|
|
@cherrypy.expose
|
|
def api(self, **kwargs):
|
|
""" Handler for API over http, with explicit authentication parameters """
|
|
if cfg.api_logging():
|
|
logging.debug('API-call from %s [%s] %s', cherrypy.request.remote.ip,
|
|
cherrypy.request.headers.get('User-Agent', '??'), kwargs)
|
|
mode = kwargs.get('mode', '')
|
|
if isinstance(mode, list):
|
|
mode = mode[0]
|
|
kwargs['mode'] = mode
|
|
name = kwargs.get('name', '')
|
|
if isinstance(name, list):
|
|
name = name[0]
|
|
kwargs['name'] = name
|
|
if mode not in ('version', 'auth'):
|
|
msg = check_apikey(kwargs)
|
|
if msg:
|
|
return msg
|
|
return api_handler(kwargs)
|
|
|
|
@cherrypy.expose
|
|
def scriptlog(self, **kwargs):
|
|
""" Duplicate of scriptlog of History, needed for some skins """
|
|
# No session key check, due to fixed URLs
|
|
if not check_access():
|
|
return Protected()
|
|
|
|
name = kwargs.get('name')
|
|
if name:
|
|
history_db = sabnzbd.connect_db()
|
|
return ShowString(history_db.get_name(name), history_db.get_script_log(name))
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def retry(self, **kwargs):
|
|
""" Duplicate of retry of History, needed for some skins """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
job = kwargs.get('job', '')
|
|
url = kwargs.get('url', '').strip()
|
|
pp = kwargs.get('pp')
|
|
cat = kwargs.get('cat')
|
|
script = kwargs.get('script')
|
|
if url:
|
|
sabnzbd.add_url(url, pp, script, cat, nzbname=kwargs.get('nzbname'))
|
|
del_hist_job(job, del_files=True)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def retry_pp(self, **kwargs):
|
|
# Duplicate of History/retry_pp to please the SMPL skin :(
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
retry_job(kwargs.get('job'), kwargs.get('nzbfile'), kwargs.get('password'))
|
|
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 LoginPage(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
# Base output var
|
|
info = build_header(self.__prim, self.__web_dir)
|
|
info['error'] = ''
|
|
|
|
# Logout?
|
|
if kwargs.get('logout'):
|
|
set_login_cookie(remove=True)
|
|
raise dcRaiser('.', kwargs)
|
|
|
|
# Check if there's even a username/password set
|
|
#if check_login():
|
|
# raise dcRaiser('../', kwargs)
|
|
|
|
# Check login info
|
|
if kwargs.get('username') == cfg.username() and kwargs.get('password') == cfg.password():
|
|
# Save login cookie
|
|
set_login_cookie(remember_me=kwargs.get('remember_me', False))
|
|
# Redirect
|
|
raise dcRaiser('../', kwargs)
|
|
elif kwargs.get('username') or kwargs.get('password'):
|
|
info['error'] = T('Authentication failed, check username/password.')
|
|
|
|
# Show login
|
|
template = Template(file=os.path.join(self.__web_dir, 'login', 'main.tmpl'),
|
|
filter=FILTER, searchList=[info], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
|
|
##############################################################################
|
|
class NzoPage(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
self.__cached_selection = {} # None
|
|
|
|
@cherrypy.expose
|
|
def default(self, *args, **kwargs):
|
|
# Allowed URL's
|
|
# /nzb/SABnzbd_nzo_xxxxx/
|
|
# /nzb/SABnzbd_nzo_xxxxx/details
|
|
# /nzb/SABnzbd_nzo_xxxxx/files
|
|
# /nzb/SABnzbd_nzo_xxxxx/bulk_operation
|
|
# /nzb/SABnzbd_nzo_xxxxx/save
|
|
if not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
nzo_id = None
|
|
for a in args:
|
|
if a.startswith('SABnzbd_nzo'):
|
|
nzo_id = a
|
|
break
|
|
|
|
nzo = NzbQueue.do.get_nzo(nzo_id)
|
|
if nzo_id and nzo:
|
|
info, pnfo_list, bytespersec, q_size, bytes_left_previous_page = build_queue_header(self.__prim, self.__web_dir)
|
|
|
|
# /SABnzbd_nzo_xxxxx/bulk_operation
|
|
if 'bulk_operation' in args:
|
|
return self.bulk_operation(nzo_id, kwargs)
|
|
|
|
# /SABnzbd_nzo_xxxxx/details
|
|
elif 'details' in args:
|
|
info = self.nzo_details(info, pnfo_list, nzo_id)
|
|
|
|
# /SABnzbd_nzo_xxxxx/files
|
|
elif 'files' in args:
|
|
info = self.nzo_files(info, pnfo_list, nzo_id)
|
|
|
|
# /SABnzbd_nzo_xxxxx/save
|
|
elif 'save' in args:
|
|
self.save_details(nzo_id, args, kwargs)
|
|
return # never reached
|
|
|
|
# /SABnzbd_nzo_xxxxx/
|
|
else:
|
|
info = self.nzo_details(info, pnfo_list, nzo_id)
|
|
info = self.nzo_files(info, pnfo_list, nzo_id)
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'nzo.tmpl'),
|
|
filter=FILTER, searchList=[info], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
else:
|
|
# Job no longer exists, go to main page
|
|
raise dcRaiser(cherrypy.lib.httputil.urljoin(self.__root, '../queue/'), {})
|
|
|
|
def nzo_details(self, info, pnfo_list, nzo_id):
|
|
slot = {}
|
|
n = 0
|
|
for pnfo in pnfo_list:
|
|
if pnfo.nzo_id == nzo_id:
|
|
nzo = sabnzbd.nzbqueue.get_nzo(nzo_id)
|
|
repair = pnfo.repair
|
|
unpack = pnfo.unpack
|
|
delete = pnfo.delete
|
|
unpackopts = sabnzbd.opts_to_pp(repair, unpack, delete)
|
|
script = pnfo.script
|
|
if script is None:
|
|
script = 'None'
|
|
cat = pnfo.category
|
|
if not cat:
|
|
cat = 'None'
|
|
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.priority
|
|
|
|
slot['nzo_id'] = str(nzo_id)
|
|
slot['cat'] = cat
|
|
slot['filename'] = filename_pw
|
|
slot['filename_clean'] = filename
|
|
slot['password'] = password or ''
|
|
slot['script'] = script
|
|
slot['priority'] = str(priority)
|
|
slot['unpackopts'] = str(unpackopts)
|
|
info['index'] = n
|
|
break
|
|
n += 1
|
|
|
|
info['slot'] = slot
|
|
info['script_list'] = list_scripts()
|
|
info['cat_list'] = list_cats()
|
|
info['noofslots'] = len(pnfo_list)
|
|
|
|
return info
|
|
|
|
def nzo_files(self, info, pnfo_list, nzo_id):
|
|
active = []
|
|
nzo = NzbQueue.do.get_nzo(nzo_id)
|
|
if nzo:
|
|
pnfo = nzo.gather_info(full=True)
|
|
info['nzo_id'] = pnfo.nzo_id
|
|
info['filename'] = xml_name(pnfo.filename)
|
|
|
|
for nzf in pnfo.active_files:
|
|
checked = False
|
|
if nzf.nzf_id in self.__cached_selection and \
|
|
self.__cached_selection[nzf.nzf_id] == 'on':
|
|
checked = True
|
|
active.append({'filename': xml_name(nzf.filename if nzf.filename else nzf.subject),
|
|
'mbleft': "%.2f" % (nzf.bytes_left / MEBI),
|
|
'mb': "%.2f" % (nzf.bytes / MEBI),
|
|
'size': format_bytes(nzf.bytes),
|
|
'sizeleft': format_bytes(nzf.bytes_left),
|
|
'nzf_id': nzf.nzf_id,
|
|
'age': calc_age(nzf.date),
|
|
'checked': checked})
|
|
|
|
info['active_files'] = active
|
|
return info
|
|
|
|
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)
|
|
priority = kwargs.get('priority', None)
|
|
nzo = sabnzbd.nzbqueue.get_nzo(nzo_id)
|
|
|
|
if index is not None:
|
|
NzbQueue.do.switch(nzo_id, index)
|
|
if name is not None:
|
|
NzbQueue.do.change_name(nzo_id, special_fixer(name), password)
|
|
|
|
if cat is not None and nzo.cat is not cat and not (nzo.cat == '*' and cat == 'Default'):
|
|
NzbQueue.do.change_cat(nzo_id, cat, priority)
|
|
# Category changed, so make sure "Default" attributes aren't set again
|
|
if script == 'Default':
|
|
script = None
|
|
if priority == 'Default':
|
|
priority = None
|
|
if pp == 'Default':
|
|
pp = None
|
|
|
|
if script is not None and nzo.script != script:
|
|
NzbQueue.do.change_script(nzo_id, script)
|
|
if pp is not None and nzo.pp != pp:
|
|
NzbQueue.do.change_opts(nzo_id, pp)
|
|
if priority is not None and nzo.priority != int(priority):
|
|
NzbQueue.do.set_priority(nzo_id, priority)
|
|
|
|
raise dcRaiser(cherrypy.lib.httputil.urljoin(self.__root, '../queue/'), {})
|
|
|
|
def bulk_operation(self, nzo_id, kwargs):
|
|
self.__cached_selection = kwargs
|
|
if kwargs['action_key'] == 'Delete':
|
|
for key in kwargs:
|
|
if kwargs[key] == 'on':
|
|
NzbQueue.do.remove_nzf(nzo_id, key)
|
|
|
|
elif kwargs['action_key'] in ('Top', 'Up', 'Down', 'Bottom'):
|
|
nzf_ids = []
|
|
for key in kwargs:
|
|
if kwargs[key] == 'on':
|
|
nzf_ids.append(key)
|
|
size = int_conv(kwargs.get('action_size', 1))
|
|
if kwargs['action_key'] == 'Top':
|
|
NzbQueue.do.move_top_bulk(nzo_id, nzf_ids)
|
|
elif kwargs['action_key'] == 'Up':
|
|
NzbQueue.do.move_up_bulk(nzo_id, nzf_ids, size)
|
|
elif kwargs['action_key'] == 'Down':
|
|
NzbQueue.do.move_down_bulk(nzo_id, nzf_ids, size)
|
|
elif kwargs['action_key'] == 'Bottom':
|
|
NzbQueue.do.move_bottom_bulk(nzo_id, nzf_ids)
|
|
|
|
if sabnzbd.nzbqueue.get_nzo(nzo_id):
|
|
url = cherrypy.lib.httputil.urljoin(self.__root, nzo_id)
|
|
else:
|
|
url = cherrypy.lib.httputil.urljoin(self.__root, '../queue')
|
|
if url and not url.endswith('/'):
|
|
url += '/'
|
|
raise dcRaiser(url, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
class QueuePage(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
start = int_conv(kwargs.get('start'))
|
|
limit = int_conv(kwargs.get('limit'))
|
|
search = kwargs.get('search')
|
|
info, _pnfo_list, _bytespersec = build_queue(self.__web_dir, self.__root, self.__prim, self.__web_dir,
|
|
start=start, limit=limit, trans=True, search=search)
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'queue.tmpl'),
|
|
filter=FILTER, searchList=[info], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def delete(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
uid = kwargs.get('uid')
|
|
del_files = int_conv(kwargs.get('del_files'))
|
|
if uid:
|
|
NzbQueue.do.remove(uid, False, keep_basic=not del_files, del_files=del_files)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def purge(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
NzbQueue.do.remove_all(kwargs.get('search'))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def removeNzf(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
nzo_id = kwargs.get('nzo_id')
|
|
nzf_id = kwargs.get('nzf_id')
|
|
if nzo_id and nzf_id:
|
|
NzbQueue.do.remove_nzf(nzo_id, nzf_id)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def change_queue_complete_action(self, **kwargs):
|
|
""" Action or script to be performed once the queue has been completed
|
|
Scripts are prefixed with 'script_'
|
|
"""
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
action = kwargs.get('action')
|
|
sabnzbd.change_queue_complete_action(action)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def switch(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
uid1 = kwargs.get('uid1')
|
|
uid2 = kwargs.get('uid2')
|
|
if uid1 and uid2:
|
|
NzbQueue.do.switch(uid1, uid2)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def change_opts(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
nzo_id = kwargs.get('nzo_id')
|
|
pp = kwargs.get('pp', '')
|
|
if nzo_id and pp and pp.isdigit():
|
|
NzbQueue.do.change_opts(nzo_id, int(pp))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def change_script(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
nzo_id = kwargs.get('nzo_id')
|
|
script = kwargs.get('script', '')
|
|
if nzo_id and script:
|
|
if script == 'None':
|
|
script = None
|
|
NzbQueue.do.change_script(nzo_id, script)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def change_cat(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
nzo_id = kwargs.get('nzo_id')
|
|
cat = kwargs.get('cat', '')
|
|
if nzo_id and cat:
|
|
if cat == 'None':
|
|
cat = None
|
|
NzbQueue.do.change_cat(nzo_id, cat)
|
|
cat, pp, script, priority = cat_to_opts(cat)
|
|
NzbQueue.do.change_script(nzo_id, script)
|
|
NzbQueue.do.change_opts(nzo_id, pp)
|
|
NzbQueue.do.set_priority(nzo_id, priority)
|
|
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def shutdown(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
yield msg
|
|
else:
|
|
yield "Initiating shutdown..."
|
|
sabnzbd.halt()
|
|
cherrypy.engine.exit()
|
|
yield "<br>SABnzbd-%s shutdown finished" % sabnzbd.__version__
|
|
sabnzbd.SABSTOP = True
|
|
|
|
@cherrypy.expose
|
|
def pause(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
scheduler.plan_resume(0)
|
|
Downloader.do.pause()
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def resume(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
scheduler.plan_resume(0)
|
|
sabnzbd.unpause_all()
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def pause_nzo(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
uid = kwargs.get('uid', '')
|
|
NzbQueue.do.pause_multiple_nzo(uid.split(','))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def resume_nzo(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
uid = kwargs.get('uid', '')
|
|
NzbQueue.do.resume_multiple_nzo(uid.split(','))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def set_priority(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
sabnzbd.nzbqueue.set_priority(kwargs.get('nzo_id'), kwargs.get('priority'))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def sort_by_avg_age(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
sabnzbd.nzbqueue.sort_queue('avg_age', kwargs.get('dir'))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def sort_by_name(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
sabnzbd.nzbqueue.sort_queue('name', kwargs.get('dir'))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def sort_by_size(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
sabnzbd.nzbqueue.sort_queue('size', kwargs.get('dir'))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def set_speedlimit(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
Downloader.do.limit_speed(int_conv(kwargs.get('value')))
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def set_pause(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
scheduler.plan_resume(int_conv(kwargs.get('value')))
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
class HistoryPage(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__verbose = False
|
|
self.__verbose_list = []
|
|
self.__failed_only = False
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
start = int_conv(kwargs.get('start'))
|
|
limit = int_conv(kwargs.get('limit'))
|
|
search = kwargs.get('search')
|
|
failed_only = kwargs.get('failed_only')
|
|
if failed_only is None:
|
|
failed_only = self.__failed_only
|
|
|
|
history = build_header(self.__prim, self.__web_dir)
|
|
|
|
history['isverbose'] = self.__verbose
|
|
history['failed_only'] = failed_only
|
|
|
|
history['rating_enable'] = bool(cfg.rating_enable())
|
|
|
|
postfix = T('B') # : Abbreviation for bytes, as in GB
|
|
grand, month, week, day = BPSMeter.do.get_sums()
|
|
history['total_size'], history['month_size'], history['week_size'], history['day_size'] = \
|
|
to_units(grand, postfix=postfix), to_units(month, postfix=postfix), \
|
|
to_units(week, postfix=postfix), to_units(day, postfix=postfix)
|
|
|
|
history['lines'], history['fetched'], history['noofslots'] = build_history(limit=limit, start=start, verbose=self.__verbose, verbose_list=self.__verbose_list, search=search, failed_only=failed_only)
|
|
|
|
if search:
|
|
history['search'] = escape(search)
|
|
else:
|
|
history['search'] = ''
|
|
|
|
history['start'] = int_conv(start)
|
|
history['limit'] = int_conv(limit)
|
|
history['finish'] = history['start'] + history['limit']
|
|
if history['finish'] > history['noofslots']:
|
|
history['finish'] = history['noofslots']
|
|
if not history['finish']:
|
|
history['finish'] = history['fetched']
|
|
history['time_format'] = time_format
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'history.tmpl'),
|
|
filter=FILTER, searchList=[history], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def purge(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
history_db = sabnzbd.connect_db()
|
|
history_db.remove_history()
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def delete(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
job = kwargs.get('job')
|
|
del_files = int_conv(kwargs.get('del_files'))
|
|
if job:
|
|
jobs = job.split(',')
|
|
for job in jobs:
|
|
del_hist_job(job, del_files=del_files)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def retry_pp(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
retry_job(kwargs.get('job'), kwargs.get('nzbfile'), kwargs.get('password'))
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def retry_all(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
retry_all_jobs()
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def purge_failed(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
del_files = bool(int_conv(kwargs.get('del_files')))
|
|
history_db = sabnzbd.connect_db()
|
|
if del_files:
|
|
del_job_files(history_db.get_failed_paths())
|
|
history_db.remove_failed()
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def reset(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
# sabnzbd.reset_byte_counter()
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def tog_verbose(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
jobs = kwargs.get('jobs')
|
|
if not jobs:
|
|
self.__verbose = not self.__verbose
|
|
self.__verbose_list = []
|
|
else:
|
|
if self.__verbose:
|
|
self.__verbose = False
|
|
else:
|
|
jobs = jobs.split(',')
|
|
for job in jobs:
|
|
if job in self.__verbose_list:
|
|
self.__verbose_list.remove(job)
|
|
else:
|
|
self.__verbose_list.append(job)
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def tog_failed_only(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
self.__failed_only = not self.__failed_only
|
|
raise queueRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def scriptlog(self, **kwargs):
|
|
""" Duplicate of scriptlog of History, needed for some skins """
|
|
# No session key check, due to fixed URLs
|
|
if not check_access():
|
|
return Protected()
|
|
name = kwargs.get('name')
|
|
if name:
|
|
history_db = sabnzbd.connect_db()
|
|
return ShowString(history_db.get_name(name), history_db.get_script_log(name))
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def retry(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
job = kwargs.get('job', '')
|
|
url = kwargs.get('url', '').strip()
|
|
pp = kwargs.get('pp')
|
|
cat = kwargs.get('cat')
|
|
script = kwargs.get('script')
|
|
if url:
|
|
sabnzbd.add_url(url, pp, script, cat, nzbname=kwargs.get('nzbname'))
|
|
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
|
|
if cfg.rating_enable():
|
|
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):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
self.folders = ConfigFolders(web_dir, root + 'folders/', prim)
|
|
self.notify = ConfigNotify(web_dir, root + 'notify/', prim)
|
|
self.general = ConfigGeneral(web_dir, root + 'general/', prim)
|
|
self.rss = ConfigRss(web_dir, root + 'rss/', prim)
|
|
self.scheduling = ConfigScheduling(web_dir, root + 'scheduling/', prim)
|
|
self.server = ConfigServer(web_dir, root + 'server/', prim)
|
|
self.switches = ConfigSwitches(web_dir, root + 'switches/', prim)
|
|
self.categories = ConfigCats(web_dir, root + 'categories/', prim)
|
|
self.sorting = ConfigSorting(web_dir, root + 'sorting/', prim)
|
|
self.special = ConfigSpecial(web_dir, root + 'special/', prim)
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['configfn'] = config.get_filename()
|
|
conf['cmdline'] = sabnzbd.CMDLINE
|
|
conf['build'] = sabnzbd.version.__baseline__[:7]
|
|
|
|
conf['have_unzip'] = bool(sabnzbd.newsunpack.ZIP_COMMAND)
|
|
conf['have_7zip'] = bool(sabnzbd.newsunpack.SEVEN_COMMAND)
|
|
conf['have_yenc'] = HAVE_YENC
|
|
|
|
if sabnzbd.HAVE_SSL:
|
|
conf['have_ssl'] = 1
|
|
conf['have_ssl_context'] = sabnzbd.HAVE_SSL_CONTEXT
|
|
conf['ssl_version'] = ssl_version()
|
|
conf['ssl_protocols'] = ', '.join(ssl_protocols_labels())
|
|
else:
|
|
conf['have_ssl'] = 0
|
|
conf['have_ssl_context'] = 0
|
|
|
|
new = {}
|
|
for svr in config.get_servers():
|
|
new[svr] = {}
|
|
conf['servers'] = new
|
|
|
|
conf['folders'] = sabnzbd.nzbqueue.scan_jobs(all=False, action=False)
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def restart(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
yield msg
|
|
else:
|
|
yield T('Initiating restart...<br />')
|
|
sabnzbd.halt()
|
|
yield T(' <br />SABnzbd shutdown finished.<br />Wait for about 5 second and then click the button below.<br /><br /><strong><a href="..">Refresh</a></strong><br />')
|
|
cherrypy.engine.restart()
|
|
|
|
@cherrypy.expose
|
|
def repair(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
yield msg
|
|
else:
|
|
sabnzbd.request_repair()
|
|
yield T('Initiating restart...<br />')
|
|
sabnzbd.halt()
|
|
yield T(' <br />SABnzbd shutdown finished.<br />Wait for about 5 second and then click the button below.<br /><br /><strong><a href="..">Refresh</a></strong><br />')
|
|
cherrypy.engine.restart()
|
|
|
|
@cherrypy.expose
|
|
def delete(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
orphan_delete(kwargs)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def add(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
orphan_add(kwargs)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
def orphan_delete(kwargs):
|
|
path = kwargs.get('name')
|
|
if path:
|
|
path = platform_encode(path)
|
|
path = os.path.join(long_path(cfg.download_dir.get_path()), path)
|
|
remove_all(path, recursive=True)
|
|
|
|
def orphan_delete_all():
|
|
paths = sabnzbd.nzbqueue.scan_jobs(all=False, action=False)
|
|
for path in paths:
|
|
kwargs = {'name': path}
|
|
orphan_delete(kwargs)
|
|
|
|
def orphan_add(kwargs):
|
|
path = kwargs.get('name')
|
|
if path:
|
|
path = platform_encode(path)
|
|
path = os.path.join(long_path(cfg.download_dir.get_path()), path)
|
|
sabnzbd.nzbqueue.repair_job(path, None, None)
|
|
|
|
def orphan_add_all():
|
|
paths = sabnzbd.nzbqueue.scan_jobs(all=False, action=False)
|
|
for path in paths:
|
|
kwargs = {'name': path}
|
|
orphan_add(kwargs)
|
|
|
|
|
|
##############################################################################
|
|
LIST_DIRPAGE = (
|
|
'download_dir', 'download_free', 'complete_dir', 'admin_dir',
|
|
'nzb_backup_dir', 'dirscan_dir', 'dirscan_speed', 'script_dir',
|
|
'email_dir', 'permissions', 'log_dir', 'password_file'
|
|
)
|
|
|
|
|
|
class ConfigFolders(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
for kw in LIST_DIRPAGE:
|
|
conf[kw] = config.get_config('misc', kw)()
|
|
|
|
# Temporary fix, problem with build_header
|
|
conf['restart_req'] = sabnzbd.RESTART_REQ
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_folders.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def saveDirectories(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
for kw in LIST_DIRPAGE:
|
|
value = kwargs.get(kw)
|
|
if value is not None:
|
|
value = platform_encode(value)
|
|
if kw in ('complete_dir', 'dirscan_dir'):
|
|
msg = config.get_config('misc', kw).set(value, create=True)
|
|
else:
|
|
msg = config.get_config('misc', kw).set(value)
|
|
if msg:
|
|
# return sabnzbd.api.report('json', error=msg)
|
|
return badParameterResponse(msg, kwargs.get('ajax'))
|
|
|
|
sabnzbd.check_incomplete_vs_complete()
|
|
config.save_config()
|
|
if kwargs.get('ajax'):
|
|
return sabnzbd.api.report('json')
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
SWITCH_LIST = \
|
|
('par2_multicore', 'par_option', 'overwrite_files', 'top_only', 'ssl_ciphers',
|
|
'auto_sort', 'propagation_delay', 'check_new_rel', 'auto_disconnect', 'flat_unpack',
|
|
'safe_postproc', 'no_dupes', 'replace_spaces', 'replace_dots', 'replace_illegal', 'auto_browser',
|
|
'ignore_samples', 'pause_on_post_processing', 'quick_check', 'nice', 'ionice',
|
|
'pre_script', 'pause_on_pwrar', 'sfv_check', 'folder_rename',
|
|
'unpack_check', 'quota_size', 'quota_day', 'quota_resume', 'quota_period',
|
|
'pre_check', 'max_art_tries', 'max_art_opt', 'fail_hopeless_jobs', 'enable_all_par',
|
|
'enable_recursive', 'no_series_dupes', 'script_can_fail', 'new_nzb_on_failure',
|
|
'unwanted_extensions', 'action_on_unwanted_extensions', 'enable_meta', 'sanitize_safe',
|
|
'rating_enable', 'rating_host', 'rating_api_key', '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',
|
|
'load_balancing', 'enable_https_verification'
|
|
)
|
|
|
|
|
|
class ConfigSwitches(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['have_ssl'] = sabnzbd.HAVE_SSL
|
|
conf['have_ssl_context'] = sabnzbd.HAVE_SSL_CONTEXT
|
|
conf['have_multicore'] = sabnzbd.WIN32 or sabnzbd.DARWIN_INTEL
|
|
conf['have_nice'] = bool(sabnzbd.newsunpack.NICE_COMMAND)
|
|
conf['have_ionice'] = bool(sabnzbd.newsunpack.IONICE_COMMAND)
|
|
conf['cleanup_list'] = cfg.cleanup_list.get_string()
|
|
|
|
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']
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_switches.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def saveSwitches(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
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)
|
|
|
|
cleanup_list = kwargs.get('cleanup_list')
|
|
if cleanup_list and sabnzbd.WIN32:
|
|
cleanup_list = cleanup_list.lower()
|
|
cfg.cleanup_list.set(cleanup_list)
|
|
|
|
config.save_config()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
SPECIAL_BOOL_LIST = \
|
|
('start_paused', 'no_penalties', 'ignore_wrong_unrar', 'create_group_folders',
|
|
'queue_complete_pers', 'api_warnings', 'allow_64bit_tools', 'ampm',
|
|
'enable_unrar', 'enable_unzip', 'enable_7zip', 'enable_filejoin', 'enable_tsjoin',
|
|
'prospective_par_download', 'never_repair', 'allow_streaming', 'ignore_unrar_dates',
|
|
'osx_menu', 'osx_speed', 'win_menu', 'use_pickle', 'allow_incomplete_nzb',
|
|
'rss_filenames', 'ipv6_hosting', 'keep_awake', 'empty_postproc', 'html_login',
|
|
'wait_for_dfolder', 'warn_empty_nzb', 'enable_bonjour','allow_duplicate_files',
|
|
'warn_dupl_jobs', 'backup_for_duplicates', 'enable_par_cleanup', 'api_logging',
|
|
'fixed_ports'
|
|
)
|
|
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', 'selftest_host'
|
|
)
|
|
SPECIAL_LIST_LIST = \
|
|
('rss_odd_titles', 'prio_sort_list'
|
|
)
|
|
|
|
|
|
class ConfigSpecial(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['nt'] = sabnzbd.WIN32
|
|
|
|
conf['switches'] = [(kw, config.get_config('misc', kw)(), config.get_config('misc', kw).default()) for kw in SPECIAL_BOOL_LIST]
|
|
conf['entries'] = [(kw, config.get_config('misc', kw)(), config.get_config('misc', kw).default()) for kw in SPECIAL_VALUE_LIST]
|
|
conf['entries'].extend([(kw, config.get_config('misc', kw).get_string(), '') for kw in SPECIAL_LIST_LIST])
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_special.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def saveSpecial(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
for kw in SPECIAL_BOOL_LIST + SPECIAL_VALUE_LIST + SPECIAL_LIST_LIST:
|
|
item = config.get_config('misc', kw)
|
|
value = kwargs.get(kw)
|
|
msg = item.set(value)
|
|
if msg:
|
|
return badParameterResponse(msg)
|
|
|
|
config.save_config()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
GENERAL_LIST = (
|
|
'host', 'port', 'username', 'password', 'disable_api_key',
|
|
'refresh_rate', 'cache_limit', 'local_ranges', 'inet_exposure',
|
|
'enable_https', 'https_port', 'https_cert', 'https_key', 'https_chain'
|
|
)
|
|
|
|
|
|
class ConfigGeneral(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
def ListColors(web_dir):
|
|
lst = []
|
|
web_dir = os.path.join(sabnzbd.DIR_INTERFACES, web_dir)
|
|
dd = os.path.abspath(web_dir + '/templates/static/stylesheets/colorschemes')
|
|
if (not dd) or (not os.access(dd, os.R_OK)):
|
|
return lst
|
|
for color in globber(dd):
|
|
col = color.replace('.css', '')
|
|
lst.append(col)
|
|
return lst
|
|
|
|
def add_color(skin_dir, color):
|
|
if skin_dir:
|
|
if not color:
|
|
try:
|
|
color = DEF_SKIN_COLORS[skin_dir.lower()]
|
|
except KeyError:
|
|
return skin_dir
|
|
return '%s - %s' % (skin_dir, color)
|
|
else:
|
|
return ''
|
|
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['configfn'] = config.get_filename()
|
|
|
|
# Temporary fix, problem with build_header
|
|
conf['restart_req'] = sabnzbd.RESTART_REQ
|
|
|
|
conf['have_ssl'] = sabnzbd.HAVE_SSL
|
|
conf['have_cryptography'] = sabnzbd.HAVE_CRYPTOGRAPHY
|
|
|
|
wlist = []
|
|
interfaces = globber_full(sabnzbd.DIR_INTERFACES)
|
|
for k in interfaces:
|
|
if k.endswith(DEF_STDINTF):
|
|
interfaces.remove(k)
|
|
interfaces.insert(0, k)
|
|
break
|
|
for k in interfaces:
|
|
if k.endswith(DEF_STDCONFIG):
|
|
interfaces.remove(k)
|
|
break
|
|
for web in interfaces:
|
|
rweb = os.path.basename(web)
|
|
if os.access(web + '/' + DEF_MAIN_TMPL, os.R_OK):
|
|
cols = ListColors(rweb)
|
|
if cols:
|
|
for col in cols:
|
|
wlist.append(add_color(rweb, col))
|
|
else:
|
|
wlist.append(rweb)
|
|
conf['web_list'] = wlist
|
|
conf['web_dir'] = add_color(cfg.web_dir(), cfg.web_color())
|
|
conf['web_dir2'] = add_color(cfg.web_dir2(), cfg.web_color2())
|
|
|
|
conf['language'] = cfg.language()
|
|
lang_list = list_languages()
|
|
if len(lang_list) < 2:
|
|
lang_list = []
|
|
conf['lang_list'] = lang_list
|
|
|
|
conf['disable_api_key'] = cfg.disable_key()
|
|
conf['host'] = cfg.cherryhost()
|
|
conf['port'] = cfg.cherryport()
|
|
conf['https_port'] = cfg.https_port()
|
|
conf['https_cert'] = cfg.https_cert()
|
|
conf['https_key'] = cfg.https_key()
|
|
conf['https_chain'] = cfg.https_chain()
|
|
conf['enable_https'] = cfg.enable_https()
|
|
conf['username'] = cfg.username()
|
|
conf['password'] = cfg.password.get_stars()
|
|
conf['html_login'] = cfg.html_login()
|
|
conf['bandwidth_max'] = cfg.bandwidth_max()
|
|
conf['bandwidth_perc'] = cfg.bandwidth_perc()
|
|
conf['refresh_rate'] = cfg.refresh_rate()
|
|
conf['cache_limit'] = cfg.cache_limit()
|
|
conf['cleanup_list'] = cfg.cleanup_list.get_string()
|
|
conf['nzb_key'] = cfg.nzb_key()
|
|
conf['local_ranges'] = cfg.local_ranges.get_string()
|
|
conf['inet_exposure'] = cfg.inet_exposure()
|
|
conf['my_lcldata'] = cfg.admin_dir.get_path()
|
|
conf['caller_url1'] = cherrypy.request.base + '/sabnzbd/'
|
|
conf['caller_url2'] = cherrypy.request.base + '/sabnzbd/m/'
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_general.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def saveGeneral(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
# Special handling for cache_limitstr
|
|
# kwargs['cache_limit'] = kwargs.get('cache_limitstr')
|
|
|
|
# Handle general options
|
|
for kw in GENERAL_LIST:
|
|
item = config.get_config('misc', kw)
|
|
value = platform_encode(kwargs.get(kw))
|
|
msg = item.set(value)
|
|
if msg:
|
|
return badParameterResponse(msg)
|
|
|
|
# Handle special options
|
|
language = kwargs.get('language')
|
|
if language and language != cfg.language():
|
|
cfg.language.set(language)
|
|
set_language(language)
|
|
sabnzbd.api.clear_trans_cache()
|
|
|
|
cleanup_list = kwargs.get('cleanup_list')
|
|
if cleanup_list and sabnzbd.WIN32:
|
|
cleanup_list = cleanup_list.lower()
|
|
cfg.cleanup_list.set(cleanup_list)
|
|
|
|
web_dir = kwargs.get('web_dir')
|
|
web_dir2 = kwargs.get('web_dir2')
|
|
change_web_dir(web_dir)
|
|
try:
|
|
web_dir2, web_color2 = web_dir2.split(' - ')
|
|
except:
|
|
web_color2 = ''
|
|
web_dir2_path = real_path(sabnzbd.DIR_INTERFACES, web_dir2)
|
|
|
|
if web_dir2 == 'None':
|
|
cfg.web_dir2.set('')
|
|
elif os.path.exists(web_dir2_path):
|
|
cfg.web_dir2.set(web_dir2)
|
|
cfg.web_color2.set(web_color2)
|
|
|
|
bandwidth_max = kwargs.get('bandwidth_max')
|
|
if bandwidth_max is not None:
|
|
cfg.bandwidth_max.set(bandwidth_max)
|
|
bandwidth_perc = kwargs.get('bandwidth_perc')
|
|
if bandwidth_perc is not None:
|
|
cfg.bandwidth_perc.set(bandwidth_perc)
|
|
bandwidth_perc = cfg.bandwidth_perc()
|
|
if bandwidth_perc and not bandwidth_max:
|
|
logging.warning(T('You must set a maximum bandwidth before you can set a bandwidth limit'))
|
|
|
|
config.save_config()
|
|
|
|
# Update CherryPy authentication
|
|
set_auth(cherrypy.config)
|
|
if kwargs.get('ajax'):
|
|
return sabnzbd.api.report('json', data={'success': True, 'restart_req': sabnzbd.RESTART_REQ})
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def generateAPIKey(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
logging.debug('API Key Changed')
|
|
cfg.api_key.set(config.create_api_key())
|
|
config.save_config()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def generateNzbKey(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
logging.debug('NZB Key Changed')
|
|
cfg.nzb_key.set(config.create_api_key())
|
|
config.save_config()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
def change_web_dir(web_dir):
|
|
try:
|
|
web_dir, web_color = web_dir.split(' - ')
|
|
except:
|
|
try:
|
|
web_color = DEF_SKIN_COLORS[web_dir.lower()]
|
|
except:
|
|
web_color = ''
|
|
|
|
web_dir_path = real_path(sabnzbd.DIR_INTERFACES, web_dir)
|
|
|
|
if not os.path.exists(web_dir_path):
|
|
return badParameterResponse('Cannot find web template: %s' % unicoder(web_dir_path))
|
|
else:
|
|
cfg.web_dir.set(web_dir)
|
|
cfg.web_color.set(web_color)
|
|
|
|
|
|
##############################################################################
|
|
class ConfigServer(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
new = []
|
|
servers = config.get_servers()
|
|
server_names = sorted(servers.keys(), key=lambda svr: '%d%02d%s' % (int(not servers[svr].enable()), servers[svr].priority(), servers[svr].displayname().lower()))
|
|
for svr in server_names:
|
|
new.append(servers[svr].get_dict(safe=True))
|
|
t, m, w, d = BPSMeter.do.amounts(svr)
|
|
if t:
|
|
new[-1]['amounts'] = to_units(t), to_units(m), to_units(w), to_units(d)
|
|
conf['servers'] = new
|
|
conf['cats'] = list_cats(default=True)
|
|
conf['have_ssl'] = sabnzbd.HAVE_SSL
|
|
conf['have_ssl_context'] = sabnzbd.HAVE_SSL_CONTEXT
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_server.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def addServer(self, **kwargs):
|
|
return handle_server(kwargs, self.__root, True)
|
|
|
|
@cherrypy.expose
|
|
def saveServer(self, **kwargs):
|
|
return handle_server(kwargs, self.__root)
|
|
|
|
@cherrypy.expose
|
|
def testServer(self, **kwargs):
|
|
return handle_server_test(kwargs, self.__root)
|
|
|
|
@cherrypy.expose
|
|
def delServer(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
kwargs['section'] = 'servers'
|
|
kwargs['keyword'] = kwargs.get('server')
|
|
del_from_section(kwargs)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def clrServer(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
server = kwargs.get('server')
|
|
if server:
|
|
BPSMeter.do.clear_server(server)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def toggleServer(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
server = kwargs.get('server')
|
|
if server:
|
|
svr = config.get_config('servers', server)
|
|
if svr:
|
|
svr.enable.set(not svr.enable())
|
|
config.save_config()
|
|
Downloader.do.update_server(server, server)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
def unique_svr_name(server):
|
|
""" Return a unique variant on given server name """
|
|
num = 0
|
|
svr = 1
|
|
new_name = server
|
|
while svr:
|
|
if num:
|
|
new_name = '%s@%d' % (server, num)
|
|
else:
|
|
new_name = '%s' % server
|
|
svr = config.get_config('servers', new_name)
|
|
num += 1
|
|
return new_name
|
|
|
|
|
|
def handle_server(kwargs, root=None, new_svr=False):
|
|
""" Internal server handler """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
ajax = kwargs.get('ajax')
|
|
host = kwargs.get('host', '').strip()
|
|
if not host:
|
|
return badParameterResponse(T('Server address required'), ajax)
|
|
|
|
port = kwargs.get('port', '').strip()
|
|
if not port:
|
|
if not kwargs.get('ssl', '').strip():
|
|
port = '119'
|
|
else:
|
|
port = '563'
|
|
kwargs['port'] = port
|
|
|
|
if kwargs.get('connections', '').strip() == '':
|
|
kwargs['connections'] = '1'
|
|
|
|
if kwargs.get('enable') == '1':
|
|
msg = check_server(host, port, ajax)
|
|
if msg:
|
|
return msg
|
|
|
|
# Default server name is just the host name
|
|
server = host
|
|
|
|
svr = None
|
|
old_server = kwargs.get('server')
|
|
if old_server:
|
|
svr = config.get_config('servers', old_server)
|
|
if svr:
|
|
server = old_server
|
|
else:
|
|
svr = config.get_config('servers', server)
|
|
|
|
if new_svr:
|
|
server = unique_svr_name(server)
|
|
|
|
for kw in ('ssl', 'send_group', 'enable', 'optional'):
|
|
if kw not in kwargs.keys():
|
|
kwargs[kw] = None
|
|
if svr and not new_svr:
|
|
svr.set_dict(kwargs)
|
|
else:
|
|
old_server = None
|
|
config.ConfigServer(server, kwargs)
|
|
|
|
config.save_config()
|
|
Downloader.do.update_server(old_server, server)
|
|
if root:
|
|
if ajax:
|
|
return sabnzbd.api.report('json')
|
|
else:
|
|
raise dcRaiser(root, kwargs)
|
|
|
|
|
|
def handle_server_test(kwargs, root):
|
|
_result, msg = test_nntp_server_dict(kwargs)
|
|
return msg
|
|
|
|
|
|
##############################################################################
|
|
class ConfigRss(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
self.__refresh_readout = None # Set to URL when new readout is needed
|
|
self.__refresh_download = False # True when feed needs to be read
|
|
self.__refresh_force = False # True if forced download of all matches is required
|
|
self.__refresh_ignore = False # True if first batch of new feed must be ignored
|
|
self.__evaluate = False # True if feed needs to be re-filtered
|
|
self.__show_eval_button = True # True if the "Apply filers" button should be shown
|
|
self.__last_msg = '' # Last error message from RSS reader
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['script_list'] = list_scripts(default=True)
|
|
pick_script = conf['script_list'] != []
|
|
|
|
conf['cat_list'] = list_cats(default=True)
|
|
pick_cat = conf['cat_list'] != []
|
|
|
|
conf['rss_rate'] = cfg.rss_rate()
|
|
|
|
rss = {}
|
|
feeds = config.get_rss()
|
|
for feed in feeds:
|
|
rss[feed] = feeds[feed].get_dict()
|
|
filters = feeds[feed].filters()
|
|
rss[feed]['filters'] = filters
|
|
rss[feed]['filter_states'] = [bool(sabnzbd.rss.convert_filter(f[4])) for f in filters]
|
|
rss[feed]['filtercount'] = len(filters)
|
|
|
|
rss[feed]['pick_cat'] = pick_cat
|
|
rss[feed]['pick_script'] = pick_script
|
|
rss[feed]['link'] = urllib.quote_plus(feed.encode('utf-8'))
|
|
rss[feed]['baselink'] = get_base_url(rss[feed]['uri'])
|
|
|
|
active_feed = kwargs.get('feed', '')
|
|
conf['active_feed'] = active_feed
|
|
conf['rss'] = rss
|
|
conf['rss_next'] = time.strftime(time_format('%H:%M'), time.localtime(sabnzbd.rss.next_run())).decode(codepage)
|
|
|
|
if active_feed:
|
|
readout = bool(self.__refresh_readout)
|
|
logging.debug('RSS READOUT = %s', readout)
|
|
if not readout:
|
|
self.__refresh_download = False
|
|
self.__refresh_force = False
|
|
self.__refresh_ignore = False
|
|
if self.__evaluate:
|
|
msg = sabnzbd.rss.run_feed(active_feed, download=self.__refresh_download, force=self.__refresh_force,
|
|
ignoreFirst=self.__refresh_ignore, readout=readout)
|
|
else:
|
|
msg = ''
|
|
self.__evaluate = False
|
|
if readout:
|
|
sabnzbd.rss.save()
|
|
self.__last_msg = msg
|
|
else:
|
|
msg = self.__last_msg
|
|
self.__refresh_readout = None
|
|
conf['evalButton'] = self.__show_eval_button
|
|
conf['error'] = msg
|
|
|
|
conf['downloaded'], conf['matched'], conf['unmatched'] = GetRssLog(active_feed)
|
|
else:
|
|
self.__last_msg = ''
|
|
|
|
# Find a unique new Feed name
|
|
unum = 1
|
|
txt = T('Feed') # : Used as default Feed name in Config->RSS
|
|
while txt + str(unum) in feeds:
|
|
unum += 1
|
|
conf['feed'] = txt + str(unum)
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_rss.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def save_rss_rate(self, **kwargs):
|
|
""" Save changed RSS automatic readout rate """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
cfg.rss_rate.set(kwargs.get('rss_rate'))
|
|
config.save_config()
|
|
scheduler.restart()
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def upd_rss_feed(self, **kwargs):
|
|
""" Update Feed level attributes,
|
|
legacy version: ignores 'enable' parameter
|
|
"""
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
if kwargs.get('enable') is not None:
|
|
del kwargs['enable']
|
|
try:
|
|
cf = config.get_rss()[kwargs.get('feed')]
|
|
except KeyError:
|
|
cf = None
|
|
uri = Strip(kwargs.get('uri'))
|
|
if cf and uri:
|
|
kwargs['uri'] = uri
|
|
cf.set_dict(kwargs)
|
|
config.save_config()
|
|
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def save_rss_feed(self, **kwargs):
|
|
""" Update Feed level attributes """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
try:
|
|
cf = config.get_rss()[kwargs.get('feed')]
|
|
except KeyError:
|
|
cf = None
|
|
if 'enable' not in kwargs:
|
|
kwargs['enable'] = 0
|
|
uri = Strip(kwargs.get('uri'))
|
|
if cf and uri:
|
|
kwargs['uri'] = uri
|
|
cf.set_dict(kwargs)
|
|
config.save_config()
|
|
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def toggle_rss_feed(self, **kwargs):
|
|
""" Toggle automatic read-out flag of Feed """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
try:
|
|
item = config.get_rss()[kwargs.get('feed')]
|
|
except KeyError:
|
|
item = None
|
|
if cfg:
|
|
item.enable.set(not item.enable())
|
|
config.save_config()
|
|
if kwargs.get('table'):
|
|
raise dcRaiser(self.__root, kwargs)
|
|
else:
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def add_rss_feed(self, **kwargs):
|
|
""" Add one new RSS feed definition """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
feed = Strip(kwargs.get('feed')).strip('[]')
|
|
uri = Strip(kwargs.get('uri'))
|
|
if feed and uri:
|
|
try:
|
|
cfg = config.get_rss()[feed]
|
|
except KeyError:
|
|
cfg = None
|
|
if (not cfg) and uri:
|
|
kwargs['feed'] = feed
|
|
kwargs['uri'] = uri
|
|
config.ConfigRSS(feed, kwargs)
|
|
# Clear out any existing reference to this feed name
|
|
# Otherwise first-run detection can fail
|
|
sabnzbd.rss.clear_feed(feed)
|
|
config.save_config()
|
|
self.__refresh_readout = feed
|
|
self.__refresh_download = False
|
|
self.__refresh_force = False
|
|
self.__refresh_ignore = True
|
|
self.__evaluate = True
|
|
raise rssRaiser(self.__root, kwargs)
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def upd_rss_filter(self, **kwargs):
|
|
""" Save updated filter definition """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
try:
|
|
cfg = config.get_rss()[kwargs.get('feed')]
|
|
except KeyError:
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
pp = kwargs.get('pp')
|
|
if IsNone(pp):
|
|
pp = ''
|
|
script = ConvertSpecials(kwargs.get('script'))
|
|
cat = ConvertSpecials(kwargs.get('cat'))
|
|
prio = ConvertSpecials(kwargs.get('priority'))
|
|
filt = kwargs.get('filter_text')
|
|
enabled = kwargs.get('enabled', '0')
|
|
|
|
if filt:
|
|
cfg.filters.update(int(kwargs.get('index', 0)), (cat, pp, script, kwargs.get('filter_type'),
|
|
platform_encode(filt), prio, enabled))
|
|
|
|
# Move filter if requested
|
|
index = int_conv(kwargs.get('index', ''))
|
|
new_index = kwargs.get('new_index', '')
|
|
if new_index and int_conv(new_index) != index:
|
|
cfg.filters.move(int(index), int_conv(new_index))
|
|
|
|
config.save_config()
|
|
self.__evaluate = False
|
|
self.__show_eval_button = True
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def del_rss_feed(self, *args, **kwargs):
|
|
""" Remove complete RSS feed """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
kwargs['section'] = 'rss'
|
|
kwargs['keyword'] = kwargs.get('feed')
|
|
del_from_section(kwargs)
|
|
sabnzbd.rss.clear_feed(kwargs.get('feed'))
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def del_rss_filter(self, **kwargs):
|
|
""" Remove one RSS filter """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
try:
|
|
cfg = config.get_rss()[kwargs.get('feed')]
|
|
except KeyError:
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
cfg.filters.delete(int(kwargs.get('index', 0)))
|
|
config.save_config()
|
|
self.__evaluate = False
|
|
self.__show_eval_button = True
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def download_rss_feed(self, *args, **kwargs):
|
|
""" Force download of all matching jobs in a feed """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
if 'feed' in kwargs:
|
|
feed = kwargs['feed']
|
|
self.__refresh_readout = feed
|
|
self.__refresh_download = True
|
|
self.__refresh_force = True
|
|
self.__refresh_ignore = False
|
|
self.__evaluate = True
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def clean_rss_jobs(self, *args, **kwargs):
|
|
""" Remove processed RSS jobs from UI """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
sabnzbd.rss.clear_downloaded(kwargs['feed'])
|
|
self.__evaluate = True
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def test_rss_feed(self, *args, **kwargs):
|
|
""" Read the feed content again and show results """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
if 'feed' in kwargs:
|
|
feed = kwargs['feed']
|
|
self.__refresh_readout = feed
|
|
self.__refresh_download = False
|
|
self.__refresh_force = False
|
|
self.__refresh_ignore = True
|
|
self.__evaluate = True
|
|
self.__show_eval_button = False
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def eval_rss_feed(self, *args, **kwargs):
|
|
""" Re-apply the filters to the feed """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
if 'feed' in kwargs:
|
|
feed = kwargs['feed']
|
|
self.__refresh_readout = feed
|
|
self.__refresh_download = False
|
|
self.__refresh_force = False
|
|
self.__refresh_ignore = False
|
|
self.__show_eval_button = False
|
|
self.__evaluate = True
|
|
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def download(self, **kwargs):
|
|
""" Download NZB from provider (Download button) """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
feed = kwargs.get('feed')
|
|
url = kwargs.get('url')
|
|
nzbname = kwargs.get('nzbname')
|
|
att = sabnzbd.rss.lookup_url(feed, url)
|
|
if att:
|
|
pp = att.get('pp')
|
|
cat = att.get('cat')
|
|
script = att.get('script')
|
|
prio = att.get('prio')
|
|
|
|
if url:
|
|
sabnzbd.add_url(url, pp, script, cat, prio, nzbname)
|
|
# Need to pass the title instead
|
|
sabnzbd.rss.flag_downloaded(feed, url)
|
|
self.__evaluate = True
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def rss_now(self, *args, **kwargs):
|
|
""" Run an automatic RSS run now """
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
scheduler.force_rss()
|
|
raise rssRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
_SCHED_ACTIONS = ('resume', 'pause', 'pause_all', 'shutdown', 'restart', 'speedlimit',
|
|
'pause_post', 'resume_post', 'scan_folder', 'rss_scan', 'remove_failed',
|
|
'remove_completed', 'pause_all_low', 'pause_all_normal', 'pause_all_high',
|
|
'resume_all_low', 'resume_all_normal', 'resume_all_high',
|
|
'enable_quota', 'disable_quota'
|
|
)
|
|
|
|
|
|
class ConfigScheduling(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
def get_days():
|
|
days = {}
|
|
days["*"] = T('Daily')
|
|
days["1"] = T('Monday')
|
|
days["2"] = T('Tuesday')
|
|
days["3"] = T('Wednesday')
|
|
days["4"] = T('Thursday')
|
|
days["5"] = T('Friday')
|
|
days["6"] = T('Saturday')
|
|
days["7"] = T('Sunday')
|
|
return days
|
|
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
actions = []
|
|
actions.extend(_SCHED_ACTIONS)
|
|
day_names = get_days()
|
|
conf['schedlines'] = []
|
|
snum = 1
|
|
conf['taskinfo'] = []
|
|
for ev in scheduler.sort_schedules(all_events=False):
|
|
line = ev[3]
|
|
conf['schedlines'].append(line)
|
|
try:
|
|
m, h, day_numbers, action = line.split(' ', 3)
|
|
except:
|
|
continue
|
|
action = action.strip()
|
|
try:
|
|
action, value = action.split(' ', 1)
|
|
except:
|
|
value = ''
|
|
value = value.strip()
|
|
if value and not value.lower().strip('0123456789kmgtp%.'):
|
|
if '%' not in value and from_units(value) < 1.0:
|
|
value = T('off') # : "Off" value for speedlimit in scheduler
|
|
else:
|
|
if '%' not in value and int_conv(value) > 1 and int_conv(value) < 101:
|
|
value += '%'
|
|
value = value.upper()
|
|
if action in actions:
|
|
action = Ttemplate("sch-" + action)
|
|
else:
|
|
if action in ('enable_server', 'disable_server'):
|
|
try:
|
|
value = '"%s"' % config.get_servers()[value].displayname()
|
|
except KeyError:
|
|
value = '"%s" <<< %s' % (value, T('Undefined server!'))
|
|
action = Ttemplate("sch-" + action)
|
|
|
|
if day_numbers == "1234567":
|
|
days_of_week = "Daily"
|
|
elif day_numbers == "12345":
|
|
days_of_week = "Weekdays"
|
|
elif day_numbers == "67":
|
|
days_of_week = "Weekends"
|
|
else:
|
|
days_of_week = ", ".join([day_names.get(i, "**") for i in day_numbers])
|
|
item = (snum, '%02d' % int(h), '%02d' % int(m), days_of_week, '%s %s' % (action, value))
|
|
|
|
conf['taskinfo'].append(item)
|
|
snum += 1
|
|
|
|
actions_lng = {}
|
|
for action in actions:
|
|
actions_lng[action] = Ttemplate("sch-" + action)
|
|
|
|
actions_servers = {}
|
|
servers = config.get_servers()
|
|
for srv in servers:
|
|
actions_servers[srv] = servers[srv].displayname()
|
|
|
|
conf['actions_servers'] = actions_servers
|
|
conf['actions'] = actions
|
|
conf['actions_lng'] = actions_lng
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_scheduling.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def addSchedule(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
servers = config.get_servers()
|
|
minute = kwargs.get('minute')
|
|
hour = kwargs.get('hour')
|
|
days_of_week = ''.join([str(x) for x in kwargs.get('daysofweek', '')])
|
|
if not days_of_week:
|
|
days_of_week = '1234567'
|
|
action = kwargs.get('action')
|
|
arguments = kwargs.get('arguments')
|
|
|
|
arguments = arguments.strip().lower()
|
|
if arguments in ('on', 'enable'):
|
|
arguments = '1'
|
|
elif arguments in ('off', 'disable'):
|
|
arguments = '0'
|
|
|
|
if minute and hour and days_of_week and action:
|
|
if action == 'speedlimit':
|
|
if not arguments or arguments.strip('0123456789kmgtp%.'):
|
|
arguments = 0
|
|
elif action in _SCHED_ACTIONS:
|
|
arguments = ''
|
|
elif action in servers:
|
|
if arguments == '1':
|
|
arguments = action
|
|
action = 'enable_server'
|
|
else:
|
|
arguments = action
|
|
action = 'disable_server'
|
|
else:
|
|
action = None
|
|
|
|
if action:
|
|
sched = cfg.schedules()
|
|
sched.append('%s %s %s %s %s' %
|
|
(minute, hour, days_of_week, action, arguments))
|
|
cfg.schedules.set(sched)
|
|
|
|
config.save_config()
|
|
scheduler.restart(force=True)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def delSchedule(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
schedules = cfg.schedules()
|
|
line = kwargs.get('line')
|
|
if line and line in schedules:
|
|
schedules.remove(line)
|
|
cfg.schedules.set(schedules)
|
|
config.save_config()
|
|
scheduler.restart(force=True)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
class ConfigCats(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['script_list'] = list_scripts(default=True)
|
|
conf['defdir'] = cfg.complete_dir.get_path()
|
|
|
|
categories = config.get_ordered_categories()
|
|
conf['have_cats'] = len(categories) > 1
|
|
|
|
slotinfo = []
|
|
for cat in categories:
|
|
cat['newzbin'] = cat['newzbin'].replace('"', '"')
|
|
slotinfo.append(cat)
|
|
|
|
# Add empty line
|
|
empty = {'name': '', 'order': '0', 'pp': '-1', 'script': '', 'dir': '', 'newzbin': '', 'priority': DEFAULT_PRIORITY}
|
|
slotinfo.insert(1, empty)
|
|
conf['slotinfo'] = slotinfo
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_cat.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def delete(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
kwargs['section'] = 'categories'
|
|
kwargs['keyword'] = kwargs.get('name')
|
|
del_from_section(kwargs)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def save(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
name = kwargs.get('name', '*')
|
|
if name == '*':
|
|
newname = name
|
|
else:
|
|
newname = re.sub('"', '', kwargs.get('newname', ''))
|
|
if newname:
|
|
if name:
|
|
config.delete('categories', name)
|
|
name = newname.lower()
|
|
if kwargs.get('dir'):
|
|
kwargs['dir'] = platform_encode(kwargs['dir'])
|
|
config.ConfigCat(name, kwargs)
|
|
|
|
config.save_config()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
SORT_LIST = (
|
|
'enable_tv_sorting', 'tv_sort_string', 'tv_categories',
|
|
'enable_movie_sorting', 'movie_sort_string', 'movie_sort_extra', 'movie_extra_folder',
|
|
'enable_date_sorting', 'date_sort_string', 'movie_categories', 'date_categories'
|
|
)
|
|
|
|
|
|
class ConfigSorting(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
conf['complete_dir'] = cfg.complete_dir.get_path()
|
|
|
|
for kw in SORT_LIST:
|
|
conf[kw] = config.get_config('misc', kw)()
|
|
conf['cat_list'] = list_cats(False)
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_sorting.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def saveSorting(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
try:
|
|
kwargs['movie_categories'] = kwargs['movie_cat']
|
|
except:
|
|
pass
|
|
try:
|
|
kwargs['date_categories'] = kwargs['date_cat']
|
|
except:
|
|
pass
|
|
try:
|
|
kwargs['tv_categories'] = kwargs['tv_cat']
|
|
except:
|
|
pass
|
|
|
|
for kw in SORT_LIST:
|
|
item = config.get_config('misc', kw)
|
|
value = kwargs.get(kw)
|
|
msg = item.set(value)
|
|
if msg:
|
|
return badParameterResponse(msg)
|
|
|
|
config.save_config()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
##############################################################################
|
|
LOG_API_RE = re.compile(r"(apikey|api)(=|:)[\w]+", re.I)
|
|
LOG_API_JSON_RE = re.compile(r"u'(apikey|api)': u'[\w]+'", re.I)
|
|
LOG_USER_RE = re.compile(r"(user|username)\s?=\s?[\w]+", re.I)
|
|
LOG_PASS_RE = re.compile(r"(password)\s?=\s?[\w]+", re.I)
|
|
LOG_INI_HIDE_RE = re.compile(r"(email_pwd|rating_api_key|pushover_token|pushover_userkey|pushbullet_apikey|prowl_apikey|growl_password|growl_server)\s?=\s?[\w]+", re.I)
|
|
LOG_HASH_RE = re.compile(r"([a-fA-F\d]{25})", re.I)
|
|
|
|
class Status(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
header = build_status(web_dir=self.__web_dir, prim=self.__prim, skip_dashboard=kwargs.get('skip_dashboard'))
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'status.tmpl'),
|
|
filter=FILTER, searchList=[header], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def reset_quota(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
BPSMeter.do.reset_quota(force=True)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def disconnect(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
Downloader.do.disconnect()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def refresh_conn(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
# No real action, just reload the page
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def showlog(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
try:
|
|
sabnzbd.LOGHANDLER.flush()
|
|
except:
|
|
pass
|
|
|
|
# Fetch the INI and the log-data and add a message at the top
|
|
log_data = '--------------------------------\n\n'
|
|
log_data += 'The log includes a copy of your sabnzbd.ini with\nall usernames, passwords and API-keys removed.'
|
|
log_data += '\n\n--------------------------------\n'
|
|
log_data += open(sabnzbd.LOGFILE, "r").read()
|
|
log_data += open(config.get_filename(), 'r').read()
|
|
|
|
# We need to remove all passwords/usernames/api-keys
|
|
log_data = LOG_API_RE.sub("apikey=<APIKEY>", log_data)
|
|
log_data = LOG_API_JSON_RE.sub("'apikey':<APIKEY>'", log_data)
|
|
log_data = LOG_USER_RE.sub(r'\g<1>=<USER>', log_data)
|
|
log_data = LOG_PASS_RE.sub("password=<PASSWORD>", log_data)
|
|
log_data = LOG_INI_HIDE_RE.sub(r"\1 = <REMOVED>", log_data)
|
|
log_data = LOG_HASH_RE.sub("<HASH>", log_data)
|
|
|
|
# Try to replace the username
|
|
try:
|
|
import getpass
|
|
cur_user = getpass.getuser()
|
|
if cur_user:
|
|
log_data = log_data.replace(cur_user, '<USERNAME>')
|
|
except:
|
|
pass
|
|
# Set headers
|
|
cherrypy.response.headers['Content-Type'] = 'application/x-download;charset=utf-8'
|
|
cherrypy.response.headers['Content-Disposition'] = 'attachment;filename="sabnzbd.log"'
|
|
return log_data
|
|
|
|
@cherrypy.expose
|
|
def showweb(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
if sabnzbd.WEBLOGFILE:
|
|
return cherrypy.lib.static.serve_file(sabnzbd.WEBLOGFILE, "application/x-download", "attachment")
|
|
else:
|
|
return "Web logging is off!"
|
|
|
|
@cherrypy.expose
|
|
def clearwarnings(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
sabnzbd.GUIHANDLER.clear()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def change_loglevel(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
cfg.log_level.set(kwargs.get('loglevel'))
|
|
config.save_config()
|
|
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def unblock_server(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
Downloader.do.unblock(kwargs.get('server'))
|
|
# Short sleep so that UI shows new server status
|
|
time.sleep(1.0)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def delete(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
orphan_delete(kwargs)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def delete_all(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
orphan_delete_all()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def add(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
orphan_add(kwargs)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def add_all(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
orphan_add_all()
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def dashrefresh(self, **kwargs):
|
|
# This function is run when Refresh button on Dashboard is clicked
|
|
# Put the time consuming dashboard functions here; they only get executed when the user clicks the Refresh button
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
|
|
from sabnzbd.utils.diskspeed import diskspeedmeasure
|
|
sabnzbd.downloaddirspeed = round(diskspeedmeasure(sabnzbd.cfg.download_dir.get_path()), 1)
|
|
time.sleep(1.0)
|
|
sabnzbd.completedirspeed = round(diskspeedmeasure(sabnzbd.cfg.complete_dir.get_path()), 1)
|
|
|
|
raise dcRaiser(self.__root, kwargs) # Refresh screen
|
|
|
|
|
|
def Protected():
|
|
cherrypy.response.status = 403
|
|
return 'Access denied'
|
|
|
|
def NeedLogin(url, kwargs):
|
|
raise dcRaiser(url + 'login/', kwargs)
|
|
|
|
def badParameterResponse(msg, ajax=None):
|
|
""" Return a html page with error message and a 'back' button """
|
|
if ajax:
|
|
return sabnzbd.api.report('json', error=msg)
|
|
else:
|
|
return '''
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
|
<html>
|
|
<head>
|
|
<title>SABnzbd %s - %s</title>
|
|
</head>
|
|
<body>
|
|
<h3>%s</h3>
|
|
%s
|
|
<br><br>
|
|
<FORM><INPUT TYPE="BUTTON" VALUE="%s" ONCLICK="history.go(-1)"></FORM>
|
|
</body>
|
|
</html>
|
|
''' % (sabnzbd.__version__, T('ERROR:'), T('Incorrect parameter'), unicoder(msg), T('Back'))
|
|
|
|
|
|
def ShowString(name, string):
|
|
""" Return a html page listing a file and a 'back' button """
|
|
try:
|
|
msg = TRANS(string)
|
|
except:
|
|
msg = "Encoding Error\n"
|
|
|
|
return '''
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
|
<html>
|
|
<head>
|
|
<title>%s</title>
|
|
</head>
|
|
<body>
|
|
<FORM><INPUT TYPE="BUTTON" VALUE="%s" ONCLICK="history.go(-1)"></FORM>
|
|
<h3>%s</h3>
|
|
<code><pre>%s</pre></code>
|
|
</body>
|
|
</html>
|
|
''' % (xml_name(name), T('Back'), xml_name(name), escape(unicoder(msg)))
|
|
|
|
|
|
def GetRssLog(feed):
|
|
def make_item(job):
|
|
url = job.get('url', '')
|
|
title = xml_name(job.get('title', ''))
|
|
size = job.get('size')
|
|
age = job.get('age', 0)
|
|
age_ms = job.get('age', 0)
|
|
time_downloaded = job.get('time_downloaded')
|
|
time_downloaded_ms = job.get('time_downloaded')
|
|
|
|
if sabnzbd.rss.special_rss_site(url):
|
|
nzbname = ""
|
|
else:
|
|
nzbname = xml_name(job.get('title', ''))
|
|
|
|
if size:
|
|
size = to_units(size)
|
|
|
|
if age:
|
|
age = calc_age(age, True)
|
|
age_ms = time.mktime(age_ms.timetuple())
|
|
|
|
if time_downloaded:
|
|
time_downloaded = time.strftime(time_format('%H:%M %a %d %b'), time_downloaded).decode(codepage)
|
|
time_downloaded_ms = time.mktime(time_downloaded_ms)
|
|
|
|
# Also return extra fields for sorting
|
|
return url, \
|
|
title, \
|
|
'*' * int(job.get('status', '').endswith('*')), \
|
|
job.get('rule', 0), \
|
|
nzbname, \
|
|
size, \
|
|
job.get('size'), \
|
|
age, \
|
|
age_ms, \
|
|
time_downloaded, \
|
|
time_downloaded_ms,\
|
|
job.get('cat')
|
|
|
|
jobs = sabnzbd.rss.show_result(feed)
|
|
names = jobs.keys()
|
|
# Sort in the order the jobs came from the feed
|
|
names.sort(lambda x, y: jobs[x].get('order', 0) - jobs[y].get('order', 0))
|
|
|
|
good = [make_item(jobs[job]) for job in names if jobs[job]['status'][0] == 'G']
|
|
bad = [make_item(jobs[job]) for job in names if jobs[job]['status'][0] == 'B']
|
|
|
|
# Sort in reverse order of time stamp for 'Done'
|
|
names.sort(lambda x, y: int(jobs[y].get('time', 0) - jobs[x].get('time', 0)))
|
|
done = [make_item(jobs[job]) for job in names if jobs[job]['status'] == 'D']
|
|
|
|
return done, good, bad
|
|
|
|
|
|
##############################################################################
|
|
LIST_EMAIL = (
|
|
'email_endjob', 'email_full',
|
|
'email_server', 'email_to', 'email_from',
|
|
'email_account', 'email_pwd', 'email_dir', 'email_rss'
|
|
)
|
|
LIST_GROWL = ('growl_enable', 'growl_server', 'growl_password',
|
|
'growl_prio_startup', 'growl_prio_download', 'growl_prio_pp', 'growl_prio_complete', 'growl_prio_failed',
|
|
'growl_prio_disk_full', 'growl_prio_warning', 'growl_prio_error', 'growl_prio_queue_done', 'growl_prio_other',
|
|
'growl_prio_new_login')
|
|
LIST_NCENTER = ('ncenter_enable',
|
|
'ncenter_prio_startup', 'ncenter_prio_download', 'ncenter_prio_pp', 'ncenter_prio_complete', 'ncenter_prio_failed',
|
|
'ncenter_prio_disk_full', 'ncenter_prio_warning', 'ncenter_prio_error', 'ncenter_prio_queue_done', 'ncenter_prio_other',
|
|
'ncenter_prio_new_login')
|
|
LIST_ACENTER = ('acenter_enable',
|
|
'acenter_prio_startup', 'acenter_prio_download', 'acenter_prio_pp', 'acenter_prio_complete', 'acenter_prio_failed',
|
|
'acenter_prio_disk_full', 'acenter_prio_warning', 'acenter_prio_error', 'acenter_prio_queue_done', 'acenter_prio_other',
|
|
'acenter_prio_new_login')
|
|
LIST_NTFOSD = ('ntfosd_enable',
|
|
'ntfosd_prio_startup', 'ntfosd_prio_download', 'ntfosd_prio_pp', 'ntfosd_prio_complete', 'ntfosd_prio_failed',
|
|
'ntfosd_prio_disk_full', 'ntfosd_prio_warning', 'ntfosd_prio_error', 'ntfosd_prio_queue_done', 'ntfosd_prio_other',
|
|
'ntfosd_prio_new_login')
|
|
LIST_PROWL = ('prowl_enable', 'prowl_apikey',
|
|
'prowl_prio_startup', 'prowl_prio_download', 'prowl_prio_pp', 'prowl_prio_complete', 'prowl_prio_failed',
|
|
'prowl_prio_disk_full', 'prowl_prio_warning', 'prowl_prio_error', 'prowl_prio_queue_done', 'prowl_prio_other',
|
|
'prowl_prio_new_login')
|
|
LIST_PUSHOVER = ('pushover_enable', 'pushover_token', 'pushover_userkey', 'pushover_device',
|
|
'pushover_prio_startup', 'pushover_prio_download', 'pushover_prio_pp', 'pushover_prio_complete', 'pushover_prio_failed',
|
|
'pushover_prio_disk_full', 'pushover_prio_warning', 'pushover_prio_error', 'pushover_prio_queue_done', 'pushover_prio_other',
|
|
'pushover_prio_new_login')
|
|
LIST_PUSHBULLET = ('pushbullet_enable', 'pushbullet_apikey', 'pushbullet_device',
|
|
'pushbullet_prio_startup', 'pushbullet_prio_download', 'pushbullet_prio_pp', 'pushbullet_prio_complete', 'pushbullet_prio_failed',
|
|
'pushbullet_prio_disk_full', 'pushbullet_prio_warning', 'pushbullet_prio_error', 'pushbullet_prio_queue_done', 'pushbullet_prio_other',
|
|
'pushbullet_prio_new_login')
|
|
LIST_NSCRIPT = ('nscript_enable', 'nscript_script', 'nscript_parameters',
|
|
'nscript_prio_startup', 'nscript_prio_download', 'nscript_prio_pp', 'nscript_prio_complete', 'nscript_prio_failed',
|
|
'nscript_prio_disk_full', 'nscript_prio_warning', 'nscript_prio_error', 'nscript_prio_queue_done', 'nscript_prio_other',
|
|
'nscript_prio_new_login')
|
|
|
|
|
|
class ConfigNotify(object):
|
|
|
|
def __init__(self, web_dir, root, prim):
|
|
self.__root = root
|
|
self.__web_dir = web_dir
|
|
self.__prim = prim
|
|
self.__lastmail = None
|
|
|
|
@cherrypy.expose
|
|
def index(self, **kwargs):
|
|
if cfg.configlock() or not check_access():
|
|
return Protected()
|
|
if not check_login():
|
|
raise NeedLogin(self.__root, kwargs)
|
|
|
|
conf = build_header(self.__prim, self.__web_dir)
|
|
|
|
conf['my_home'] = sabnzbd.DIR_HOME
|
|
conf['lastmail'] = self.__lastmail
|
|
conf['have_growl'] = True
|
|
conf['have_ntfosd'] = sabnzbd.notifier.have_ntfosd()
|
|
conf['have_ncenter'] = sabnzbd.DARWIN_VERSION > 7 and bool(sabnzbd.notifier.ncenter_path())
|
|
conf['script_list'] = list_scripts(default=False, none=True)
|
|
|
|
for kw in LIST_EMAIL:
|
|
conf[kw] = config.get_config('misc', kw).get_string()
|
|
for kw in LIST_GROWL:
|
|
try:
|
|
conf[kw] = config.get_config('growl', kw)()
|
|
except:
|
|
logging.debug('MISSING KW=%s', kw)
|
|
for kw in LIST_PROWL:
|
|
conf[kw] = config.get_config('prowl', kw)()
|
|
for kw in LIST_PUSHOVER:
|
|
conf[kw] = config.get_config('pushover', kw)()
|
|
for kw in LIST_PUSHBULLET:
|
|
conf[kw] = config.get_config('pushbullet', kw)()
|
|
for kw in LIST_NCENTER:
|
|
conf[kw] = config.get_config('ncenter', kw)()
|
|
for kw in LIST_ACENTER:
|
|
conf[kw] = config.get_config('acenter', kw)()
|
|
for kw in LIST_NTFOSD:
|
|
conf[kw] = config.get_config('ntfosd', kw)()
|
|
for kw in LIST_NSCRIPT:
|
|
conf[kw] = config.get_config('nscript', kw)()
|
|
conf['notify_keys'] = sabnzbd.constants.NOTIFY_KEYS
|
|
conf['notify_texts'] = sabnzbd.notifier.NOTIFICATION
|
|
|
|
template = Template(file=os.path.join(self.__web_dir, 'config_notify.tmpl'),
|
|
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
|
|
return template.respond()
|
|
|
|
@cherrypy.expose
|
|
def saveEmail(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
ajax = kwargs.get('ajax')
|
|
|
|
for kw in LIST_EMAIL:
|
|
msg = config.get_config('misc', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_GROWL:
|
|
msg = config.get_config('growl', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_NCENTER:
|
|
msg = config.get_config('ncenter', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_ACENTER:
|
|
msg = config.get_config('acenter', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_NTFOSD:
|
|
msg = config.get_config('ntfosd', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_PROWL:
|
|
msg = config.get_config('prowl', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_PUSHOVER:
|
|
msg = config.get_config('pushover', kw).set(platform_encode(kwargs.get(kw)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_PUSHBULLET:
|
|
msg = config.get_config('pushbullet', kw).set(platform_encode(kwargs.get(kw, 0)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
for kw in LIST_NSCRIPT:
|
|
msg = config.get_config('nscript', kw).set(platform_encode(kwargs.get(kw, 0)))
|
|
if msg:
|
|
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)), ajax)
|
|
|
|
config.save_config()
|
|
self.__lastmail = None
|
|
if ajax:
|
|
return sabnzbd.api.report('json')
|
|
else:
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def testmail(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
self.__lastmail = _api_test_email(name=None, output=None, kwargs=None)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
@cherrypy.expose
|
|
def testnotification(self, **kwargs):
|
|
msg = check_session(kwargs)
|
|
if msg:
|
|
return msg
|
|
_api_test_notif(name=None, output=None, kwargs=None)
|
|
raise dcRaiser(self.__root, kwargs)
|
|
|
|
|
|
def rss_history(url, limit=50, search=None):
|
|
url = url.replace('rss', '')
|
|
|
|
youngest = None
|
|
|
|
rss = RSS()
|
|
rss.channel.title = "SABnzbd History"
|
|
rss.channel.description = "Overview of completed downloads"
|
|
rss.channel.link = "http://sabnzbd.org/"
|
|
rss.channel.language = "en"
|
|
|
|
items, _fetched_items, _max_items = build_history(limit=limit, search=search)
|
|
|
|
for history in items:
|
|
item = Item()
|
|
|
|
item.pubDate = std_time(history['completed'])
|
|
item.title = history['name']
|
|
|
|
if not youngest:
|
|
youngest = history['completed']
|
|
elif history['completed'] < youngest:
|
|
youngest = history['completed']
|
|
|
|
if history['url_info']:
|
|
item.link = history['url_info']
|
|
else:
|
|
item.link = url
|
|
item.guid = history['nzo_id']
|
|
|
|
stageLine = []
|
|
for stage in history['stage_log']:
|
|
stageLine.append("<tr><dt>Stage %s</dt>" % stage['name'])
|
|
actions = []
|
|
for action in stage['actions']:
|
|
actions.append("<dd>%s</dd>" % (action))
|
|
actions.sort()
|
|
actions.reverse()
|
|
for act in actions:
|
|
stageLine.append(act)
|
|
stageLine.append("</tr>")
|
|
item.description = ''.join(stageLine)
|
|
rss.addItem(item)
|
|
|
|
rss.channel.lastBuildDate = std_time(youngest)
|
|
rss.channel.pubDate = std_time(time.time())
|
|
|
|
return rss.write()
|
|
|
|
|
|
def rss_warnings():
|
|
""" Return an RSS feed with last warnings/errors """
|
|
rss = RSS()
|
|
rss.channel.title = "SABnzbd Warnings"
|
|
rss.channel.description = "Overview of warnings/errors"
|
|
rss.channel.link = "http://sabnzbd.org/"
|
|
rss.channel.language = "en"
|
|
|
|
for warn in sabnzbd.GUIHANDLER.content():
|
|
item = Item()
|
|
item.title = warn
|
|
rss.addItem(item)
|
|
|
|
rss.channel.lastBuildDate = std_time(time.time())
|
|
rss.channel.pubDate = rss.channel.lastBuildDate
|
|
return rss.write()
|
|
|
|
|