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.
 
 
 
 
 

3695 lines
120 KiB

#!/usr/bin/python -OO
# Copyright 2008-2010 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 datetime
import time
import cherrypy
import logging
import re
import glob
import urllib
from xml.sax.saxutils import escape
from sabnzbd.utils.rsslib import RSS, Item
from sabnzbd.utils.json import JsonWriter
import sabnzbd
import sabnzbd.rss
import sabnzbd.scheduler as scheduler
from Cheetah.Template import Template
import sabnzbd.emailer as emailer
from sabnzbd.misc import real_path, loadavg, \
to_units, diskfree, disktotal, get_ext, sanitize_foldername, \
get_filename, cat_to_opts, IntConv, panic_old_queue
from sabnzbd.newswrapper import GetServerParms
from sabnzbd.newzbin import Bookmarks, MSGIDGrabber
from sabnzbd.codecs import TRANS, xml_name, LatinFilter, unicoder, special_fixer
import sabnzbd.config as config
import sabnzbd.cfg as cfg
from sabnzbd.articlecache import ArticleCache
import sabnzbd.newsunpack
from sabnzbd.postproc import PostProcessor
import sabnzbd.downloader as downloader
from sabnzbd.bpsmeter import BPSMeter
import sabnzbd.nzbqueue as nzbqueue
from sabnzbd.database import build_history_info, unpack_history_info
import sabnzbd.wizard
from sabnzbd.utils.servertests import test_nntp_server_dict
from sabnzbd.constants import *
from sabnzbd.lang import T, Ta, Tspec, list_languages, reset_language
#------------------------------------------------------------------------------
# Global constants
DIRECTIVES = {
'directiveStartToken': '<!--#',
'directiveEndToken': '#-->',
'prioritizeSearchListOverSelf' : True
}
FILTER = LatinFilter
#------------------------------------------------------------------------------
#
def check_server(host, port):
""" Check if server address resolves properly """
if host.lower() == 'localhost' and sabnzbd.AMBI_LOCALHOST:
return badParameterResponse(T('msg-warning-ambiLocalhost'))
if GetServerParms(host, IntConv(port)):
return ""
else:
return badParameterResponse(T('msg-invalidServer@2') % (host, port))
def ListScripts(default=False):
""" Return a list of script names """
lst = []
dd = cfg.script_dir.get_path()
if dd and os.access(dd, os.R_OK):
if default:
lst = ['Default', 'None']
else:
lst = ['None']
for script in glob.glob(dd + '/*'):
if os.path.isfile(script):
sc= os.path.basename(script)
if sc != "_svn" and sc != ".svn":
lst.append(sc)
return lst
def ListCats(default=False):
""" Return list of categories """
lst = sorted(config.get_categories().keys())
if lst:
lst.insert(0, 'None')
if default:
lst.insert(0, 'Default')
return lst
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 IsNone(value):
""" Return True if either None, 'None' or '' """
return value==None or value=="" or value.lower()=='none'
def List2String(lst):
""" Return list as a comma-separated string """
if type(lst) == type(""):
return lst
return ', '.join(lst)
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
def set_auth(conf):
""" Set the authentication for CherryPy
"""
if cfg.username() and cfg.password():
conf.update({'tools.basic_auth.on' : True, 'tools.basic_auth.realm' : 'SABnzbd',
'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 """
key = kwargs.get('session')
if not key:
key = kwargs.get('apikey')
msg = None
if not key:
logging.warning(Ta('warn-missingKey'))
msg = T('error-missingKey')
elif key != cfg.api_key():
logging.warning(Ta('error-badKey'))
msg = T('error-badKey')
return msg
def check_apikey(kwargs, nokey=False):
""" Check api key """
output = kwargs.get('output')
mode = kwargs.get('mode', '')
# Don't give a visible warning: these commands are used by some
# external utilities to detect if username/password is required
special = mode in ('get_scripts', 'qstatus')
# First check APIKEY, if OK that's sufficient
if not (cfg.disable_key() or nokey):
key = kwargs.get('apikey')
if not key:
if not special:
logging.warning(Ta('warn-apikeyNone'))
return report(output, 'API Key Required')
elif key != cfg.api_key():
logging.warning(Ta('warn-apikeyBad'))
return report(output, 'API Key Incorrect')
else:
return None
# No active APIKEY, check web credentials instead
if cfg.username() and cfg.password():
if kwargs.get('ma_username') == cfg.username() and kwargs.get('ma_password') == cfg.password():
pass
else:
if not special:
logging.warning(Ta('warn-authMissing'))
return report(output, 'Missing authentication')
return None
def del_from_section(kwargs):
""" Remove keyword in section """
section = kwargs.get('section', '')
if section in ('servers', 'rss', 'categories'):
keyword = kwargs.get('keyword')
if keyword:
item = config.get_config(section, keyword)
if item:
item.delete()
del item
config.save_config()
if section == 'servers':
downloader.update_server(keyword, None)
return True
else:
return False
#------------------------------------------------------------------------------
class NoPage:
def __init__(self):
pass
@cherrypy.expose
def index(self, **kwargs):
return badParameterResponse(T('error-noSecUI'))
#------------------------------------------------------------------------------
_MSG_NO_VALUE = 'expect one parameter'
_MSG_NO_VALUE2 = 'expect two parameters'
_MSG_INT_VALUE = 'expect integer value'
_MSG_NO_ITEM = 'item does not exist'
_MSG_NOT_IMPLEMENTED = 'not implemented'
_MSG_NO_FILE = 'no file given'
_MSG_NO_PATH = 'file does not exist'
_MSG_OUTPUT_FORMAT = 'Format not supported'
_MSG_NO_SUCH_CONFIG = 'Config item does not exist'
_MSG_BAD_SERVER_PARMS = 'Incorrect server settings'
def remove_callable(dic):
""" Remove all callable items from dictionary """
for key, value in dic.items():
if callable(value):
del dic[key]
return dic
_PLURAL_TO_SINGLE = {
'categories' : 'category',
'servers' : 'server',
'rss' : 'feed',
'scripts' : 'script',
'warnings' : 'warning',
'files' : 'file',
'jobs' : 'job'
}
def plural_to_single(kw, def_kw=''):
try:
return _PLURAL_TO_SINGLE[kw]
except KeyError:
return def_kw
def report(output, error=None, keyword='value', data=None):
""" Report message in json, xml or plain text
If error is set, only an status/error report is made.
If no error and no data, only a status report is made.
Else, a data report is made (optional 'keyword' for outer XML section).
"""
if output == 'json':
content = "application/json;charset=UTF-8"
if error:
info = {'status':False, 'error':error}
elif data is None:
info = {'status':True}
else:
if hasattr(data,'__iter__') and not keyword:
info = data
else:
info = {keyword:data}
response = JsonWriter().write(info)
elif output == 'xml':
content = "text/xml"
xmlmaker = xml_factory()
if error:
status_str = xmlmaker.run('result', {'status':False, 'error':error})
elif data is None:
status_str = xmlmaker.run('result', {'status':True})
else:
status_str = xmlmaker.run(keyword, data)
response = '<?xml version="1.0" encoding="UTF-8" ?>\n%s\n' % status_str
else:
content = "text/plain"
if error:
response = "error: %s\n" % error
elif data is None:
response = 'ok\n'
else:
if type(data) in (list, tuple):
# Special handling for list/tuple (backward compatibility)
data = [str(val) for val in data]
response = '%s\n' % ' '.join(data)
else:
response = '%s\n' % str(data)
cherrypy.response.headers['Content-Type'] = content
cherrypy.response.headers['Pragma'] = 'no-cache'
return response
class MainPage:
def __init__(self, web_dir, root, web_dir2=None, root2=None, prim=True, first=0):
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
if first >= 1:
self.m = MainPage(web_dir2, root2, prim=False)
if first == 2:
self.sabnzbd = MainPage(web_dir, '/sabnzbd/', web_dir2, '/sabnzbd/m/', prim=True, first=1)
self.queue = QueuePage(web_dir, root+'queue/', prim)
self.history = HistoryPage(web_dir, root+'history/', prim)
self.connections = ConnectionInfo(web_dir, root+'connections/', prim)
self.config = ConfigPage(web_dir, 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 sabnzbd.OLD_QUEUE and not cfg.warned_old_queue():
cfg.warned_old_queue.set(True)
config.save_config()
return panic_old_queue()
if kwargs.get('skip_wizard') or config.get_servers():
info, pnfo_list, bytespersec = build_header(self.__prim)
if cfg.newzbin_username() and cfg.newzbin_password.get_stars():
info['newzbinDetails'] = True
info['script_list'] = ListScripts(default=True)
info['script'] = cfg.dirscan_script()
info['cat'] = 'Default'
info['cat_list'] = ListCats(True)
info['warning'] = ''
if cfg.enable_unrar():
if sabnzbd.newsunpack.RAR_PROBLEM:
info['warning'] = T('warn-badUnrar')
if not sabnzbd.newsunpack.RAR_COMMAND:
info['warning'] = T('warn-noUnpack')
if not sabnzbd.newsunpack.PAR2_COMMAND:
info['warning'] = T('warn-noRepair')
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('/wizard/')
#@cherrypy.expose
#def reset_lang(self, **kwargs):
# msg = check_session(kwargs)
# if msg: return msg
# reset_language(cfg.language())
# raise dcRaiser(self.__root, kwargs)
def add_handler(self, kwargs):
id = kwargs.get('id', '')
if not id:
id = 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')
RE_NEWZBIN_URL = re.compile(r'/browse/post/(\d+)')
newzbin_url = RE_NEWZBIN_URL.search(id.lower())
id = Strip(id)
if id and (id.isdigit() or len(id)==5):
sabnzbd.add_msgid(id, pp, script, cat, priority, nzbname)
elif newzbin_url:
sabnzbd.add_msgid(Strip(newzbin_url.group(1)), pp, script, cat, priority, nzbname)
elif id:
sabnzbd.add_url(id, pp, script, cat, priority, nzbname)
if not redirect:
redirect = self.__root
raise cherrypy.HTTPRedirect(redirect)
@cherrypy.expose
def addID(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
raise self.add_handler(kwargs)
@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 != None and nzbfile.filename and nzbfile.value:
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)
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.pause_downloader()
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_session(kwargs)
if msg: return msg
return self.api_handler(kwargs)
@cherrypy.expose
def api(self, **kwargs):
"""Handler for API over http, with explicit authentication parameters
"""
if kwargs.get('mode', '') not in ('version', 'auth'):
msg = check_apikey(kwargs)
if msg: return msg
return self.api_handler(kwargs)
def api_handler(self, kwargs):
""" Actual API handler, not exposed to Web-ui
"""
mode = kwargs.get('mode')
output = kwargs.get('output')
if mode == 'set_config':
if kwargs.get('section') == 'servers':
handle_server_api(output, kwargs)
else:
res = config.set_config(kwargs)
if not res:
return report(output, _MSG_NO_SUCH_CONFIG)
config.save_config()
if mode == 'del_config':
if del_from_section(kwargs):
return report(output)
else:
return report(output, _MSG_NOT_IMPLEMENTED)
if mode in ('get_config', 'set_config'):
res, data = config.get_dconfig(kwargs.get('section'), kwargs.get('keyword'))
return report(output, keyword='config', data=data)
if mode == 'qstatus':
if output == 'json':
# Compatibility Fix:
# Old qstatus did not have a keyword, so do not use one now.
keyword = ''
else:
keyword = 'queue'
return report(output, keyword=keyword, data=qstatus_data())
if mode == 'queue':
name = kwargs.get('name')
sort = kwargs.get('sort')
dir = kwargs.get('dir')
value = kwargs.get('value')
value2 = kwargs.get('value2')
start = kwargs.get('start')
limit = kwargs.get('limit')
if name == 'delete':
if value.lower()=='all':
nzbqueue.remove_all_nzo()
return report(output)
elif value:
items = value.split(',')
nzbqueue.remove_multiple_nzos(items)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
elif name == 'delete_nzf':
# Value = nzo_id Value2 = nzf_id
if value and value2:
nzbqueue.remove_nzf(value, value2)
return report(output)
else:
return report(output, _MSG_NO_VALUE2)
elif name == 'rename':
if value and value2:
nzbqueue.rename_nzo(value, special_fixer(value2))
return report(output)
else:
return report(output, _MSG_NO_VALUE2)
elif name == 'change_complete_action':
# http://localhost:8080/sabnzbd/api?mode=queue&name=change_complete_action&value=hibernate_pc
sabnzbd.change_queue_complete_action(value)
return report(output)
elif name == 'purge':
nzbqueue.remove_all_nzo()
return report(output)
elif name == 'pause':
if value:
items = value.split(',')
nzbqueue.pause_multiple_nzo(items)
return report(output)
elif name == 'resume':
if value:
items = value.split(',')
nzbqueue.resume_multiple_nzo(items)
return report(output)
elif name == 'priority':
if value and value2:
try:
try:
priority = int(value2)
except:
return report(output, _MSG_INT_VALUE)
items = value.split(',')
if len(items) > 1:
pos = nzbqueue.set_priority_multiple(items, priority)
else:
pos = nzbqueue.set_priority(value, priority)
# Returns the position in the queue, -1 is incorrect job-id
return report(output, keyword='position', data=pos)
except:
return report(output, _MSG_NO_VALUE2)
else:
return report(output, _MSG_NO_VALUE2)
elif name == 'sort':
if sort:
nzbqueue.sort_queue(sort,dir)
return report(output)
else:
return report(output, _MSG_NO_VALUE2)
elif output in ('xml', 'json'):
if sort and sort != 'index':
reverse = dir.lower() == 'desc'
nzbqueue.sort_queue(sort,reverse)
# &history=1 will show unprocessed items in the history
history = bool(kwargs.get('history'))
info, pnfo_list, bytespersec, verboseList, dictn = \
build_queue(history=history, start=start, limit=limit, output=output)
info['categories'] = info.pop('cat_list')
info['scripts'] = info.pop('script_list')
return report(output, keyword='queue', data=remove_callable(info))
elif output == 'rss':
return rss_qstatus()
else:
return report(output, _MSG_NOT_IMPLEMENTED)
if mode == 'options':
return options_list(output)
if mode == 'translate':
return report(output, keyword='value', data=T(kwargs.get('value')))
name = kwargs.get('name', '')
pp = kwargs.get('pp')
script = kwargs.get('script')
cat = kwargs.get('cat')
priority = kwargs.get('priority')
value = kwargs.get('value')
value2 = kwargs.get('value2')
start = kwargs.get('start')
limit = kwargs.get('limit')
nzbname = kwargs.get('nzbname')
if mode == 'addfile':
# When uploading via flash it will send the nzb in a kw arg called Filedata
if name is None or isinstance(name, str) or isinstance(name, unicode):
name = kwargs.get('Filedata')
# Normal upload will send the nzb in a kw arg called nzbfile
if name is None or isinstance(name, str) or isinstance(name, unicode):
name = kwargs.get('nzbfile')
if name is not None and name.filename and name.value:
sabnzbd.add_nzbfile(name, pp, script, cat, priority, nzbname)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
if mode == 'addlocalfile':
if name:
if os.path.exists(name):
fn = get_filename(name)
if fn:
if get_ext(name) in ('.zip','.rar', '.gz'):
sabnzbd.dirscanner.ProcessArchiveFile(\
fn, name, pp=pp, script=script, cat=cat, priority=priority, keep=True)
elif get_ext(name) in ('.nzb'):
sabnzbd.dirscanner.ProcessSingleFile(\
fn, name, pp=pp, script=script, cat=cat, priority=priority, keep=True, nzbname=nzbname)
else:
return report(output, _MSG_NO_FILE)
else:
return report(output, _MSG_NO_PATH)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
if mode == 'switch':
if value and value2:
pos, prio = nzbqueue.switch(value, value2)
# Returns the new position and new priority (if different)
if output not in ('xml', 'json'):
return report(output, data=(pos, prio))
else:
return report(output, keyword='result', data={'position':pos, 'priority':prio})
else:
return report(output, _MSG_NO_VALUE2)
if mode == 'change_cat':
if value and value2:
nzo_id = value
cat = value2
if cat == 'None':
cat = None
nzbqueue.change_cat(nzo_id, cat)
cat, pp, script, cat_priority = cat_to_opts(cat)
nzbqueue.change_script(nzo_id, script)
nzbqueue.change_opts(nzo_id, pp)
nzbqueue.set_priority(nzo_id, cat_priority)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
if mode == 'change_script':
if value and value2:
nzo_id = value
script = value2
if script.lower() == 'none':
script = None
nzbqueue.change_script(nzo_id, script)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
if mode == 'change_opts':
if value and value2 and value2.isdigit():
nzbqueue.change_opts(value, int(value2))
return report(output)
if mode == 'fullstatus':
return report(output, _MSG_NOT_IMPLEMENTED + ' YET') #xml_full()
if mode == 'history':
if name == 'delete':
if value.lower()=='all':
history_db = cherrypy.thread_data.history_db
history_db.remove_history()
return report(output)
elif value:
jobs = value.split(',')
history_db = cherrypy.thread_data.history_db
history_db.remove_history(jobs)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
elif not name:
history, pnfo_list, bytespersec = build_header(True)
history['total_size'], history['month_size'], history['week_size'] = get_history_size()
history['slots'], fetched_items, history['noofslots'] = build_history(start=start, limit=limit, verbose=True)
return report(output, keyword='history', data=remove_callable(history))
else:
return report(output, _MSG_NOT_IMPLEMENTED)
if mode == 'get_files':
if value:
return report(output, keyword='files', data=build_file_list(value))
else:
return report(output, _MSG_NO_VALUE)
if mode == 'addurl':
if name:
sabnzbd.add_url(name, pp, script, cat, priority, nzbname)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
if mode == 'addid':
RE_NEWZBIN_URL = re.compile(r'/browse/post/(\d+)')
newzbin_url = RE_NEWZBIN_URL.search(name.lower())
if name: name = name.strip()
if name and (name.isdigit() or len(name)==5):
sabnzbd.add_msgid(name, pp, script, cat, priority, nzbname)
return report(output)
elif newzbin_url:
sabnzbd.add_msgid(newzbin_url.group(1), pp, script, cat, priority, nzbname)
return report(output)
elif name:
sabnzbd.add_url(name, pp, script, cat, priority, nzbname)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
if mode == 'pause':
scheduler.plan_resume(0)
downloader.pause_downloader()
return report(output)
if mode == 'resume':
scheduler.plan_resume(0)
sabnzbd.unpause_all()
return report(output)
if mode == 'shutdown':
sabnzbd.halt()
cherrypy.engine.exit()
sabnzbd.SABSTOP = True
return report(output)
if mode == 'warnings':
if name == 'clear':
return report(output, keyword="warnings", data=sabnzbd.GUIHANDLER.clear())
elif name == 'show':
return report(output, keyword="warnings", data=sabnzbd.GUIHANDLER.content())
elif name:
return report(output, _MSG_NOT_IMPLEMENTED)
return report(output, keyword="warnings", data=sabnzbd.GUIHANDLER.content())
if mode == 'config':
if name == 'speedlimit' or name == 'set_speedlimit': # http://localhost:8080/sabnzbd/api?mode=config&name=speedlimit&value=400
if not value: value = '0'
if value.isdigit():
try:
value = int(value)
except:
return report(output, _MSG_NO_VALUE)
downloader.limit_speed(value)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
elif name == 'get_speedlimit':
return report(output, keyword='speedlimit', data=int(downloader.get_limit()))
elif name == 'set_colorscheme':
if value:
if self.__prim:
cfg.web_color.set(value)
else:
cfg.web_color2.set(value)
return report(output)
else:
return report(output, _MSG_NO_VALUE)
elif name == 'set_pause':
scheduler.plan_resume(IntConv(value))
return report(output)
elif name == 'set_apikey':
cfg.api_key.set(config.create_api_key())
config.save_config()
return report(output, keyword='apikey', data=cfg.api_key())
elif name == 'test_server':
result, msg = test_nntp_server_dict(kwargs)
response = {'result': result, 'message': msg}
if output:
return report(output, data=response)
else:
return msg
else:
return report(output, _MSG_NOT_IMPLEMENTED)
if mode == 'get_cats':
return report(output, keyword="categories", data=ListCats())
if mode == 'get_scripts':
return report(output, keyword="scripts", data=ListScripts())
if mode == 'version':
return report(output, keyword='version', data=sabnzbd.__version__)
if mode == 'auth':
auth = 'None'
if cfg.username.get() and cfg.password.get():
auth = 'login'
if not cfg.disable_key.get():
auth = 'apikey'
return report(output, keyword='auth', data=auth)
if mode == 'newzbin':
if name == 'get_bookmarks':
Bookmarks.do.run()
return report(output)
return report(output, _MSG_NOT_IMPLEMENTED)
if mode == 'restart':
sabnzbd.halt()
cherrypy.engine.restart()
return report(output)
if mode == 'disconnect':
downloader.disconnect()
return report(output)
if mode == 'osx_icon':
sabnzbd.OSX_ICON = int(value != '0')
return report(output)
return report(output, _MSG_NOT_IMPLEMENTED)
@cherrypy.expose
def scriptlog(self, **kwargs):
""" Duplicate of scriptlog of History, needed for some skins """
# No session key check, due to fixed URLs
name = kwargs.get('name')
if name:
history_db = cherrypy.thread_data.history_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
url = kwargs.get('url', '')
pp = kwargs.get('pp')
cat = kwargs.get('cat')
script = kwargs.get('script')
url = url.strip()
if url and (url.isdigit() or len(url)==5):
sabnzbd.add_msgid(url, pp, script, cat)
elif url:
sabnzbd.add_url(url, pp, script, cat)
if url:
return ShowOK(url)
else:
raise dcRaiser(self.__root, kwargs)
#------------------------------------------------------------------------------
class NzoPage:
def __init__(self, web_dir, root, prim):
self.__root = root
self.__web_dir = web_dir
self.__verbose = False
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
info, pnfo_list, bytespersec = build_header(self.__prim)
nzo_id = None
for a in args:
if a.startswith('SABnzbd_nzo'):
nzo_id = a
break
if nzo_id:
# /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
# /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()
def nzo_details(self, info, pnfo_list, nzo_id):
slot = {}
n = 0
for pnfo in pnfo_list:
if pnfo[PNFO_NZO_ID_FIELD] == nzo_id:
repair = pnfo[PNFO_REPAIR_FIELD]
unpack = pnfo[PNFO_UNPACK_FIELD]
delete = pnfo[PNFO_DELETE_FIELD]
unpackopts = sabnzbd.opts_to_pp(repair, unpack, delete)
script = pnfo[PNFO_SCRIPT_FIELD]
if script is None:
script = 'None'
cat = pnfo[PNFO_EXTRA_FIELD1]
if not cat:
cat = 'None'
filename = xml_name(pnfo[PNFO_FILENAME_FIELD])
priority = pnfo[PNFO_PRIORITY_FIELD]
slot['nzo_id'] = str(nzo_id)
slot['cat'] = cat
slot['filename'] = filename
slot['script'] = script
slot['priority'] = str(priority)
slot['unpackopts'] = str(unpackopts)
info['index'] = n
break
n += 1
info['slot'] = slot
info['script_list'] = ListScripts()
info['cat_list'] = ListCats()
info['noofslots'] = len(pnfo_list)
return info
def nzo_files(self, info, pnfo_list, nzo_id):
active = []
for pnfo in pnfo_list:
if pnfo[PNFO_NZO_ID_FIELD] == nzo_id:
info['nzo_id'] = nzo_id
info['filename'] = xml_name(pnfo[PNFO_FILENAME_FIELD])
for tup in pnfo[PNFO_ACTIVE_FILES_FIELD]:
bytes_left, bytes, fn, date, nzf_id = tup
checked = False
if nzf_id in self.__cached_selection and \
self.__cached_selection[nzf_id] == 'on':
checked = True
line = {'filename':xml_name(fn),
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'size': format_bytes(bytes),
'sizeleft':format_bytes(bytes_left),
'nzf_id':nzf_id,
'age':calc_age(date),
'checked':checked}
active.append(line)
break
info['active_files'] = active
return info
def save_details(self, nzo_id, args, kwargs):
index = kwargs.get('index', None)
name = kwargs.get('name',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 != None:
nzbqueue.switch(nzo_id, index)
if name != None:
sabnzbd.nzbqueue.change_name(nzo_id, special_fixer(name))
if cat != None:
sabnzbd.nzbqueue.change_cat(nzo_id,cat)
if script != None:
sabnzbd.nzbqueue.change_script(nzo_id,script)
if pp != None:
sabnzbd.nzbqueue.change_opts(nzo_id,pp)
if priority != None and nzo and nzo.get_priority() != int(priority):
sabnzbd.nzbqueue.set_priority(nzo_id, priority)
args = [arg for arg in args if arg != 'save']
extra = '/'.join(args)
url = cherrypy._urljoin(self.__root,extra)
if url and not url.endswith('/'):
url += '/'
raise dcRaiser(url, {})
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.remove_nzf(nzo_id, key)
elif kwargs['action_key'] == 'Top' or kwargs['action_key'] == 'Up' or \
kwargs['action_key'] == 'Down' or kwargs['action_key'] == 'Bottom':
nzf_ids = []
for key in kwargs:
if kwargs[key] == 'on':
nzf_ids.append(key)
if kwargs['action_key'] == 'Top':
nzbqueue.move_top_bulk(nzo_id, nzf_ids)
elif kwargs['action_key'] == 'Up':
nzbqueue.move_up_bulk(nzo_id, nzf_ids)
elif kwargs['action_key'] == 'Down':
nzbqueue.move_down_bulk(nzo_id, nzf_ids)
elif kwargs['action_key'] == 'Bottom':
nzbqueue.move_bottom_bulk(nzo_id, nzf_ids)
if nzbqueue.get_nzo(nzo_id):
url = cherrypy._urljoin(self.__root, nzo_id)
else:
url = cherrypy._urljoin(self.__root, '../queue')
if url and not url.endswith('/'):
url += '/'
raise dcRaiser(url, kwargs)
#------------------------------------------------------------------------------
class QueuePage:
def __init__(self, web_dir, root, prim):
self.__root = root
self.__web_dir = web_dir
self.__verbose = False
self.__verboseList = []
self.__prim = prim
@cherrypy.expose
def index(self, **kwargs):
start = kwargs.get('start')
limit = kwargs.get('limit')
dummy2 = kwargs.get('dummy2')
info, pnfo_list, bytespersec, self.__verboseList, self.__dict__ = build_queue(self.__web_dir, self.__root, self.__verbose, self.__prim, self.__verboseList, self.__dict__, start=start, limit=limit, dummy2=dummy2)
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')
if uid:
nzbqueue.remove_nzo(uid, False)
raise queueRaiser(self.__root, kwargs)
@cherrypy.expose
def purge(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
nzbqueue.remove_all_nzo()
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.remove_nzf(nzo_id, nzf_id)
raise queueRaiser(self.__root, kwargs)
@cherrypy.expose
def tog_verbose(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
self.__verbose = not self.__verbose
raise queueRaiser(self.__root, kwargs)
@cherrypy.expose
def tog_uid_verbose(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
uid = kwargs.get('uid')
if self.__verboseList.count(uid):
self.__verboseList.remove(uid)
else:
self.__verboseList.append(uid)
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
sabnzbd.change_queue_complete_action(kwargs.get('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.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.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.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.change_cat(nzo_id, cat)
item = config.get_config('categories', cat)
if item:
cat, pp, script, priority = cat_to_opts(cat)
else:
script = cfg.dirscan_script()
pp = cfg.dirscan_pp()
priority = cfg.dirscan_priority()
nzbqueue.change_script(nzo_id, script)
nzbqueue.change_opts(nzo_id, pp)
nzbqueue.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.pause_downloader()
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.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.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
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
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
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
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.limit_speed(IntConv(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(IntConv(kwargs.get('value')))
raise dcRaiser(self.__root, kwargs)
class HistoryPage:
def __init__(self, web_dir, root, prim):
self.__root = root
self.__web_dir = web_dir
self.__verbose = False
self.__verbose_list = []
self.__prim = prim
@cherrypy.expose
def index(self, **kwargs):
start = kwargs.get('start')
limit = kwargs.get('limit')
search = kwargs.get('search')
history, pnfo_list, bytespersec = build_header(self.__prim)
history['isverbose'] = self.__verbose
if cfg.newzbin_username() and cfg.newzbin_password():
history['newzbinDetails'] = True
#history_items, total_bytes, bytes_beginning = sabnzbd.history_info()
#history['bytes_beginning'] = "%.2f" % (bytes_beginning / GIGI)
history['total_size'], history['month_size'], history['week_size'] = get_history_size()
history['lines'], history['fetched'], history['noofslots'] = build_history(limit=limit, start=start, verbose=self.__verbose, verbose_list=self.__verbose_list, search=search)
if search:
history['search'] = escape(search)
else:
history['search'] = ''
history['start'] = IntConv(start)
history['limit'] = IntConv(limit)
history['finish'] = history['start'] + history['limit']
if history['finish'] > history['noofslots']:
history['finish'] = history['noofslots']
if not history['finish']:
history['finish'] = history['fetched']
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 = cherrypy.thread_data.history_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')
if job:
jobs = job.split(',')
history_db = cherrypy.thread_data.history_db
history_db.remove_history(jobs)
raise queueRaiser(self.__root, kwargs)
@cherrypy.expose
def purge_failed(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
history_db = cherrypy.thread_data.history_db
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 scriptlog(self, **kwargs):
""" Duplicate of scriptlog of History, needed for some skins """
# No session key check, due to fixed URLs
name = kwargs.get('name')
if name:
history_db = cherrypy.thread_data.history_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
url = kwargs.get('url', '').strip()
pp = kwargs.get('pp')
cat = kwargs.get('cat')
script = kwargs.get('script')
if url and (url.isdigit() or len(url)==5):
sabnzbd.add_msgid(url, pp, script, cat)
elif url:
sabnzbd.add_url(url, pp, script, cat, nzbname=kwargs.get('nzbname'))
if url:
return ShowOK(url)
else:
raise dcRaiser(self.__root, kwargs)
#------------------------------------------------------------------------------
class ConfigPage:
def __init__(self, web_dir, root, prim):
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
self.directories = ConfigDirectories(web_dir, root+'directories/', prim)
self.email = ConfigEmail(web_dir, root+'email/', prim)
self.general = ConfigGeneral(web_dir, root+'general/', prim)
self.newzbin = ConfigNewzbin(web_dir, root+'newzbin/', 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)
@cherrypy.expose
def index(self, **kwargs):
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['configfn'] = config.get_filename()
new = {}
for svr in config.get_servers():
new[svr] = {}
conf['servers'] = new
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('restart1')
sabnzbd.halt()
yield T('restart2')
cherrypy.engine.restart()
#------------------------------------------------------------------------------
LIST_DIRPAGE = ( \
'download_dir', 'download_free', 'complete_dir', 'cache_dir',
'nzb_backup_dir', 'dirscan_dir', 'dirscan_speed', 'script_dir',
'email_dir', 'permissions', 'log_dir'
)
class ConfigDirectories:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
for kw in LIST_DIRPAGE:
conf[kw] = config.get_config('misc', kw)()
conf['my_home'] = sabnzbd.DIR_HOME
conf['my_lcldata'] = sabnzbd.DIR_LCLDATA
# Temporary fix, problem with build_header
conf['restart_req'] = sabnzbd.RESTART_REQ
template = Template(file=os.path.join(self.__web_dir, 'config_directories.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 != None:
msg = config.get_config('misc', kw).set(value)
if msg:
return badParameterResponse(msg)
config.save_config()
raise dcRaiser(self.__root, kwargs)
SWITCH_LIST = \
('par2_multicore', 'par_option', 'enable_unrar', 'enable_unzip', 'enable_filejoin',
'enable_tsjoin', 'send_group', 'fail_on_crc', 'top_only',
'dirscan_opts', 'enable_par_cleanup', 'auto_sort', 'check_new_rel', 'auto_disconnect',
'safe_postproc', 'no_dupes', 'replace_spaces', 'replace_illegal', 'auto_browser',
'ignore_samples', 'pause_on_post_processing', 'quick_check', 'dirscan_script', 'nice', 'ionice',
'dirscan_priority', 'ssl_type'
)
#------------------------------------------------------------------------------
class ConfigSwitches:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['nt'] = sabnzbd.WIN32
conf['have_nice'] = bool(sabnzbd.newsunpack.NICE_COMMAND)
conf['have_ionice'] = bool(sabnzbd.newsunpack.IONICE_COMMAND)
for kw in SWITCH_LIST:
conf[kw] = config.get_config('misc', kw)()
conf['script_list'] = ListScripts()
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 = 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', 'rss_rate',
'cache_limit',
'enable_https', 'https_port', 'https_cert', 'https_key'
)
class ConfigGeneral:
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 glob.glob(dd + '/*'):
col= os.path.basename(color).replace('.css','')
if col != "_svn" and col != ".svn":
lst.append(col)
return lst
def add_color(dir, color):
if dir:
if not color:
try:
color = DEF_SKIN_COLORS[dir.lower()]
except KeyError:
return dir
return '%s - %s' % (dir, color)
else:
return ''
if cfg.configlock():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['configfn'] = config.get_filename()
# Temporary fix, problem with build_header
conf['restart_req'] = sabnzbd.RESTART_REQ
if sabnzbd.newswrapper.HAVE_SSL:
conf['have_ssl'] = 1
else:
conf['have_ssl'] = 0
wlist = []
wlist2 = ['None']
interfaces = glob.glob(sabnzbd.DIR_INTERFACES + "/*")
for k in interfaces:
if k.endswith(DEF_STDINTF):
interfaces.remove(k)
interfaces.insert(0, k)
break
for web in interfaces:
rweb = os.path.basename(web)
if rweb != '.svn' and rweb != '_svn' and os.access(web + '/' + DEF_MAIN_TMPL, os.R_OK):
cols = ListColors(rweb)
if cols:
for col in cols:
if rweb != 'Mobile':
wlist.append(add_color(rweb, col))
wlist2.append(add_color(rweb, col))
else:
if rweb != 'Mobile':
wlist.append(rweb)
wlist2.append(rweb)
conf['web_list'] = wlist
conf['web_list2'] = wlist2
# Obsolete template variables, must exist and have a value
conf['web_colors'] = ['None']
conf['web_color'] = 'None'
conf['web_colors2'] = ['None']
conf['web_color2'] = 'None'
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()
list = list_languages(sabnzbd.DIR_LANGUAGE)
if len(list) < 2:
list = []
conf['lang_list'] = 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['enable_https'] = cfg.enable_https()
conf['username'] = cfg.username()
conf['password'] = cfg.password.get_stars()
conf['bandwidth_limit'] = cfg.bandwidth_limit()
conf['refresh_rate'] = cfg.refresh_rate()
conf['rss_rate'] = cfg.rss_rate()
conf['cache_limit'] = cfg.cache_limit()
conf['cleanup_list'] = List2String(cfg.cleanup_list())
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 = 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)
reset_language(language)
cleanup_list = kwargs.get('cleanup_list')
if cleanup_list and sabnzbd.WIN32:
cleanup_list = cleanup_list.lower()
cfg.cleanup_list.set_string(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_limit = kwargs.get('bandwidth_limit')
if bandwidth_limit != None:
bandwidth_limit = IntConv(bandwidth_limit)
cfg.bandwidth_limit.set(bandwidth_limit)
config.save_config()
# Update CherryPy authentication
set_auth(cherrypy.config)
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)
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' % web_dir_path)
else:
cfg.web_dir.set(web_dir)
cfg.web_color.set(web_color)
#------------------------------------------------------------------------------
class ConfigServer:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
new = {}
servers = config.get_servers()
for svr in servers:
new[svr] = servers[svr].get_dict(safe=True)
conf['servers'] = new
if sabnzbd.newswrapper.HAVE_SSL:
conf['have_ssl'] = 1
else:
conf['have_ssl'] = 0
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)
@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)
def handle_server(kwargs, root=None):
""" Internal server handler """
msg = check_session(kwargs)
if msg: return msg
host = kwargs.get('host', '').strip()
if not host:
return badParameterResponse(T('error-needServer'))
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'
msg = check_server(host, port)
if msg:
return msg
server = '%s:%s' % (host, port)
svr = None
old_server = kwargs.get('server')
if old_server:
svr = config.get_config('servers', old_server)
if not svr:
svr = config.get_config('servers', server)
if svr:
for kw in ('fillserver', 'ssl', 'enable', 'optional'):
if kw not in kwargs.keys():
kwargs[kw] = None
svr.set_dict(kwargs)
svr.rename(server)
else:
old_server = None
config.ConfigServer(server, kwargs)
config.save_config()
downloader.update_server(old_server, server)
if root:
raise dcRaiser(root, kwargs)
def handle_server_test(kwargs, root):
result, msg = test_nntp_server_dict(kwargs)
return msg
def handle_server_api(output, kwargs):
""" Special handler for API-call 'set_config'
"""
name = kwargs.get('keyword')
if not name:
name = kwargs.get('name')
if not name:
host = kwargs.get('host')
port = kwargs.get('port', '119')
if host:
name = '%s:%s' % (host, port)
else:
return False
server = config.get_config('servers', name)
if server:
server.set_dict(kwargs)
old_name = name
else:
config.ConfigServer(name, kwargs)
old_name = None
downloader.update_server(old_name, name)
#------------------------------------------------------------------------------
class ConfigRss:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['script_list'] = ListScripts(default=True)
pick_script = conf['script_list'] != []
conf['cat_list'] = ListCats(default=True)
pick_cat = conf['cat_list'] != []
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]['filtercount'] = len(filters)
rss[feed]['pick_cat'] = pick_cat
rss[feed]['pick_script'] = pick_script
conf['rss'] = rss
# Find a unique new Feed name
unum = 1
while 'Feed'+str(unum) in feeds:
unum += 1
conf['feed'] = 'Feed' + 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 upd_rss_feed(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
if kwargs.get('enable') is not None:
del kwargs['enable']
try:
cfg = config.get_rss()[kwargs.get('feed')]
except KeyError:
cfg = None
if cfg and Strip(kwargs.get('uri')):
cfg.set_dict(kwargs)
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def toggle_rss_feed(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
try:
cfg = config.get_rss()[kwargs.get('feed')]
except KeyError:
cfg = None
if cfg:
cfg.enable.set(not cfg.enable())
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def add_rss_feed(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
feed= Strip(kwargs.get('feed'))
uri = Strip(kwargs.get('uri'))
try:
cfg = config.get_rss()[feed]
except KeyError:
cfg = None
if (not cfg) and 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()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def upd_rss_filter(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
try:
cfg = config.get_rss()[kwargs.get('feed')]
except KeyError:
raise dcRaiser(self.__root, kwargs)
pp = kwargs.get('pp')
if IsNone(pp): pp = ''
script = ConvertSpecials(kwargs.get('script'))
cat = ConvertSpecials(kwargs.get('cat'))
cfg.filters.update(int(kwargs.get('index',0)), (cat, pp, script, kwargs.get('filter_type'), kwargs.get('filter_text')))
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def pos_rss_filter(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
feed = kwargs.get('feed')
current = kwargs.get('current', 0)
new = kwargs.get('new', 0)
try:
cfg = config.get_rss()[feed]
except KeyError:
raise dcRaiser(self.__root, kwargs)
if current != new:
cfg.filters.move(int(current), int(new))
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def del_rss_feed(self, *args, **kwargs):
msg = check_session(kwargs)
if msg: return msg
kwargs['section'] = 'rss'
kwargs['keyword'] = kwargs.get('feed')
del_from_section(kwargs)
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def del_rss_filter(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
try:
cfg = config.get_rss()[kwargs.get('feed')]
except KeyError:
raise dcRaiser(self.__root, kwargs)
cfg.filters.delete(int(kwargs.get('index', 0)))
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def download_rss_feed(self, *args, **kwargs):
msg = check_session(kwargs)
if msg: return msg
if 'feed' in kwargs:
feed = kwargs['feed']
msg = sabnzbd.rss.run_feed(feed, download=True, force=True)
if msg:
return badParameterResponse(msg)
else:
return ShowRssLog(feed, False)
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def test_rss_feed(self, *args, **kwargs):
msg = check_session(kwargs)
if msg: return msg
if 'feed' in kwargs:
feed = kwargs['feed']
msg = sabnzbd.rss.run_feed(feed, download=False, ignoreFirst=True)
if msg:
return badParameterResponse(msg)
else:
return ShowRssLog(feed, True)
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def rss_download(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
feed = kwargs.get('feed')
id = kwargs.get('id')
cat = kwargs.get('cat')
pp = kwargs.get('pp')
script = kwargs.get('script')
priority = kwargs.get('priority', NORMAL_PRIORITY)
nzbname = kwargs.get('nzbname')
if id and id.isdigit():
sabnzbd.add_msgid(id, pp, script, cat, priority, nzbname)
elif id:
sabnzbd.add_url(id, pp, script, cat, priority, nzbname)
# Need to pass the title instead
sabnzbd.rss.flag_downloaded(feed, id)
raise dcRaiser(self.__root, kwargs)
#------------------------------------------------------------------------------
_SCHED_ACTIONS = ('resume', 'pause', 'pause_all', 'shutdown', 'restart', 'speedlimit', 'pause_post', 'resume_post')
class ConfigScheduling:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
actions = []
actions.extend(_SCHED_ACTIONS)
days = get_days()
conf['schedlines'] = []
snum = 1
conf['taskinfo'] = []
for ev in scheduler.sort_schedules(forward=True):
line = ev[3]
conf['schedlines'].append(line)
try:
m, h, day, action = line.split(' ', 3)
except:
continue
action = action.strip()
if action in actions:
action = T("sch-" + action)
else:
try:
act, server = action.split()
except ValueError:
act = ''
if act in ('enable_server', 'disable_server'):
action = T("sch-" + act) + ' ' + server
item = (snum, h, '%02d' % int(m), days[day], action)
conf['taskinfo'].append(item)
snum += 1
actions_lng = {}
for action in actions:
actions_lng[action] = T("sch-" + action)
for server in config.get_servers():
actions.append(server)
actions_lng[server] = server
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
minute = kwargs.get('minute')
hour = kwargs.get('hour')
dayofweek = kwargs.get('dayofweek')
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 dayofweek and action:
if (action == 'speedlimit') and arguments.isdigit():
pass
elif action in _SCHED_ACTIONS:
arguments = ''
elif action.find(':') > 0:
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, dayofweek, 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 ConfigNewzbin:
def __init__(self, web_dir, root, prim):
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
self.__bookmarks = []
@cherrypy.expose
def index(self, **kwargs):
if cfg.configlock():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['username_newzbin'] = cfg.newzbin_username()
conf['password_newzbin'] = cfg.newzbin_password.get_stars()
conf['newzbin_bookmarks'] = int(cfg.newzbin_bookmarks())
conf['newzbin_unbookmark'] = int(cfg.newzbin_unbookmark())
conf['bookmark_rate'] = cfg.bookmark_rate()
conf['bookmarks_list'] = self.__bookmarks
conf['matrix_username'] = cfg.matrix_username()
conf['matrix_apikey'] = cfg.matrix_apikey()
template = Template(file=os.path.join(self.__web_dir, 'config_newzbin.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
return template.respond()
@cherrypy.expose
def saveNewzbin(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
cfg.newzbin_username.set(kwargs.get('username_newzbin'))
cfg.newzbin_password.set(kwargs.get('password_newzbin'))
cfg.newzbin_bookmarks.set(kwargs.get('newzbin_bookmarks'))
cfg.newzbin_unbookmark.set(kwargs.get('newzbin_unbookmark'))
cfg.bookmark_rate.set(kwargs.get('bookmark_rate'))
cfg.matrix_username.set(kwargs.get('matrix_username'))
cfg.matrix_apikey.set(kwargs.get('matrix_apikey'))
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def saveMatrix(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
cfg.matrix_username.set(kwargs.get('matrix_username'))
cfg.matrix_apikey.set(kwargs.get('matrix_apikey'))
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def getBookmarks(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
Bookmarks.do.run()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def showBookmarks(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
self.__bookmarks = Bookmarks.do.bookmarksList()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def hideBookmarks(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
self.__bookmarks = []
raise dcRaiser(self.__root, kwargs)
#------------------------------------------------------------------------------
class ConfigCats:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
if cfg.newzbin_username() and cfg.newzbin_password():
conf['newzbinDetails'] = True
conf['script_list'] = ListScripts(default=True)
categories = config.get_categories()
conf['have_cats'] = categories != {}
conf['defdir'] = cfg.complete_dir.get_path()
empty = { 'name':'', 'pp':'-1', 'script':'', 'dir':'', 'newzbin':'', 'priority':DEFAULT_PRIORITY }
slotinfo = []
slotinfo.append(empty)
for cat in sorted(categories):
slot = categories[cat].get_dict()
slot['name'] = cat
slotinfo.append(slot)
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
newname = kwargs.get('newname', '').strip()
name = kwargs.get('name')
if newname:
if name:
config.delete('categories', name)
name = newname.lower()
config.ConfigCat(name, kwargs)
config.save_config()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def init_newzbin(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
config.define_categories(force=True)
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:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['complete_dir'] = cfg.complete_dir.get_path()
for kw in SORT_LIST:
conf[kw] = config.get_config('misc', kw)()
conf['cat_list'] = ListCats(True)
#tvSortList = []
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)
#------------------------------------------------------------------------------
class ConnectionInfo:
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):
header, pnfo_list, bytespersec = build_header(self.__prim)
header['logfile'] = sabnzbd.LOGFILE
header['weblogfile'] = sabnzbd.WEBLOGFILE
header['loglevel'] = str(cfg.log_level())
header['lastmail'] = self.__lastmail
header['servers'] = []
for server in downloader.servers()[:]:
busy = []
connected = 0
for nw in server.idle_threads[:]:
if nw.connected:
connected += 1
for nw in server.busy_threads[:]:
article = nw.article
art_name = ""
nzf_name = ""
nzo_name = ""
if article:
nzf = article.nzf
nzo = nzf.nzo
art_name = xml_name(article.article)
#filename field is not always present
try:
nzf_name = xml_name(nzf.get_filename())
except: #attribute error
nzf_name = xml_name(nzf.get_subject())
nzo_name = xml_name(nzo.get_dirname())
busy.append((nw.thrdnum, art_name, nzf_name, nzo_name))
if nw.connected:
connected += 1
if server.warning and not (connected or server.errormsg):
connected = unicoder(server.warning)
if server.request and not server.info:
connected = T('server-resolving')
busy.sort()
header['servers'].append((server.host, server.port, connected, busy, server.ssl,
server.active, server.errormsg, server.fillserver, server.optional))
wlist = []
for w in sabnzbd.GUIHANDLER.content():
w = w.replace('WARNING', Ta('warning')).replace('ERROR', Ta('error'))
wlist.append(xml_name(w))
header['warnings'] = wlist
template = Template(file=os.path.join(self.__web_dir, 'connection_info.tmpl'),
filter=FILTER, searchList=[header], compilerSettings=DIRECTIVES)
return template.respond()
@cherrypy.expose
def disconnect(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
downloader.disconnect()
raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def testmail(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
logging.info("Sending testmail")
pack = {}
pack['download'] = ['action 1', 'action 2']
pack['unpack'] = ['action 1', 'action 2']
self.__lastmail= emailer.endjob('Test Job', 123, 'unknown', True,
os.path.normpath(os.path.join(cfg.complete_dir.get_path(), '/unknown/Test Job')),
str(123*MEBI), pack, 'my_script', 'Line 1\nLine 2\nLine 3\n', 0)
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
return cherrypy.lib.static.serve_file(sabnzbd.LOGFILE, "application/x-download", "attachment")
@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.unblock(kwargs.get('server'))
# Short sleep so that UI shows new server status
time.sleep(1.0)
raise dcRaiser(self.__root, kwargs)
def Protected():
return badParameterResponse("Configuration is locked")
def badParameterResponse(msg):
"""Return a html page with error message and a 'back' button
"""
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('badParm'), msg, T('button-back'))
def ShowFile(name, path):
"""Return a html page listing a file and a 'back' button
"""
try:
f = open(path, "r")
msg = TRANS(f.read())
f.close()
except:
msg = "FILE NOT FOUND\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><br/><br/>
</body>
</html>
''' % (name, T('button-back'), name, escape(msg))
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><br/><br/>
</body>
</html>
''' % (xml_name(name), T('button-back'), xml_name(name), escape(unicoder(msg)))
def ShowOK(url):
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>
<br/><br/>
%s
<br/><br/>
</body>
</html>
''' % (escape(url), T('button-back'), T('msg-reAdded@1') % escape(url))
def _make_link(qfeed, job):
# Return downlink for a job
name = urllib.quote_plus(job['url'])
title = job['title'].encode('latin-1')
nzbname = '&nzbname=%s' % urllib.quote(sanitize_foldername(title))
if job['cat']:
cat = '&cat=' + escape(job['cat'])
else:
cat = ''
if job['pp'] is None:
pp = ''
else:
pp = '&pp=' + escape(str(job['pp']))
if job['script']:
script = '&script=' + escape(job['script'])
else:
script = ''
if job['prio']:
prio = '&priority=' + str(job['prio'])
star = '&nbsp;*' * int(job['status'].endswith('*'))
title = xml_name(job['title'])
if job['url'].isdigit():
title = '<a href="https://www.newzbin.com/browse/post/%s/" target="_blank">%s</a>' % (job['url'], title)
return '<a href="rss_download?session=%s&feed=%s&id=%s%s%s%s%s%s">%s</a>&nbsp;&nbsp;&nbsp;%s%s<br/>' % \
(cfg.api_key() ,qfeed, name, cat, pp, script, prio, nzbname, T('link-download'), title, star)
def ShowRssLog(feed, all):
"""Return a html page listing an RSS log and a 'back' button
"""
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))
qfeed = escape(feed.replace('/','%2F').replace('?', '%3F'))
doneStr = []
for x in names:
job = jobs[x]
if job['status'][0] == 'D':
doneStr.append('%s<br/>' % xml_name(job['title']))
goodStr = []
for x in names:
job = jobs[x]
if job['status'][0] == 'G':
goodStr.append(_make_link(qfeed, job))
badStr = []
for x in names:
job = jobs[x]
if job['status'][0] == 'B':
badStr.append(_make_link(qfeed, job))
if all:
return '''
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html>
<head>
<title>%s</title>
</head>
<body>
<form>
<input type="submit" onclick="this.form.action='.'; this.form.submit(); return false;" value="%s"/>
</form>
<h3>%s</h3>
%s<br/><br/>
<b>%s</b><br/>
%s
<br/>
<b>%s</b><br/>
%s
<br/>
<b>%s</b><br/>
%s
<br/>
</body>
</html>
''' % (escape(feed), T('button-back'), escape(feed), T('explain-rssStar'), T('rss-matched'), \
''.join(goodStr), T('rss-notMatched'), ''.join(badStr), T('rss-done'), ''.join(doneStr))
else:
return '''
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html>
<head>
<title>%s</title>
</head>
<body>
<form>
<input type="submit" onclick="this.form.action='.'; this.form.submit(); return false;" value="%s"/>
</form>
<h3>%s</h3>
<b>%s</b><br/>
%s
<br/>
</body>
</html>
''' % (escape(feed), T('button-back'), escape(feed), T('rss-downloaded'), ''.join(doneStr))
def build_header(prim):
try:
uptime = calc_age(sabnzbd.START)
except:
uptime = "-"
if prim:
color = sabnzbd.WEB_COLOR
else:
color = sabnzbd.WEB_COLOR2
if color:
color = color + '.css'
else:
color = ''
header = { 'T': T, 'Tspec': Tspec, 'version':sabnzbd.__version__, 'paused':downloader.paused(),
'pause_int': scheduler.pause_int(), 'paused_all': sabnzbd.PAUSED_ALL,
'uptime':uptime, 'color_scheme':color }
speed_limit = downloader.get_limit()
if speed_limit <= 0:
speed_limit = ''
header['helpuri'] = 'http://wiki.sabnzbd.org/'
header['diskspace1'] = "%.2f" % diskfree(cfg.download_dir.get_path())
header['diskspace2'] = "%.2f" % diskfree(cfg.complete_dir.get_path())
header['diskspacetotal1'] = "%.2f" % disktotal(cfg.download_dir.get_path())
header['diskspacetotal2'] = "%.2f" % disktotal(cfg.complete_dir.get_path())
header['loadavg'] = loadavg()
header['speedlimit'] = "%s" % speed_limit
header['restart_req'] = sabnzbd.RESTART_REQ
header['have_warnings'] = str(sabnzbd.GUIHANDLER.count())
header['last_warning'] = sabnzbd.GUIHANDLER.last()
header['active_lang'] = cfg.language()
if prim:
header['webdir'] = sabnzbd.WEB_DIR
else:
header['webdir'] = sabnzbd.WEB_DIR2
header['finishaction'] = sabnzbd.QUEUECOMPLETE
header['nt'] = sabnzbd.WIN32
header['darwin'] = sabnzbd.DARWIN
header['power_options'] = sabnzbd.WIN32 or sabnzbd.DARWIN or sabnzbd.LINUX_POWER
header['session'] = cfg.api_key()
bytespersec = BPSMeter.do.get_bps()
qnfo = nzbqueue.queue_info()
bytesleft = qnfo[QNFO_BYTES_LEFT_FIELD]
bytes = qnfo[QNFO_BYTES_FIELD]
header['kbpersec'] = "%.2f" % (bytespersec / KIBI)
header['speed'] = to_units(bytespersec, spaces=1)
header['mbleft'] = "%.2f" % (bytesleft / MEBI)
header['mb'] = "%.2f" % (bytes / MEBI)
header['sizeleft'] = format_bytes(bytesleft)
header['size'] = format_bytes(bytes)
status = ''
if downloader.paused():
status = 'Paused'
elif bytespersec > 0:
status = 'Downloading'
else:
status = 'Idle'
header['status'] = "%s" % status
anfo = ArticleCache.do.cache_info()
header['cache_art'] = str(anfo[ANFO_ARTICLE_SUM_FIELD])
header['cache_size'] = format_bytes(anfo[ANFO_CACHE_SIZE_FIELD])
header['cache_max'] = str(anfo[ANFO_CACHE_LIMIT_FIELD])
header['nzb_quota'] = ''
if sabnzbd.NEW_VERSION:
header['new_release'], header['new_rel_url'] = sabnzbd.NEW_VERSION.split(';')
else:
header['new_release'] = ''
header['new_rel_url'] = ''
header['timeleft'] = calc_timeleft(bytesleft, bytespersec)
try:
datestart = datetime.datetime.now() + datetime.timedelta(seconds=bytesleft / bytespersec)
#new eta format: 16:00 Fri 07 Feb
header['eta'] = '%s' % datestart.strftime('%H:%M %a %d %b')
except:
datestart = datetime.datetime.now()
header['eta'] = T('unknown')
return (header, qnfo[QNFO_PNFO_LIST_FIELD], bytespersec)
def calc_timeleft(bytesleft, bps):
"""
Calculate the time left in the format HH:MM:SS
"""
try:
totalseconds = int(bytesleft / bps)
minutes, seconds = divmod(totalseconds, 60)
hours, minutes = divmod(minutes, 60)
if minutes <10:
minutes = '0%s' % minutes
if seconds <10:
seconds = '0%s' % seconds
return '%s:%s:%s' % (hours, minutes, seconds)
except:
return '0:00:00'
def calc_age(date):
"""
Calculate the age difference between now and date.
Value is returned as either days, hours, or minutes.
"""
try:
now = datetime.datetime.now()
#age = str(now - date).split(".")[0] #old calc_age
#time difference
dage = now-date
seconds = dage.seconds
#only one value should be returned
#if it is less than 1 day then it returns in hours, unless it is less than one hour where it returns in minutes
if dage.days:
age = '%sd' % (dage.days)
elif seconds/3600:
age = '%sh' % (seconds/3600)
else:
age = '%sm' % (seconds/60)
except:
age = "-"
return age
#------------------------------------------------------------------------------
LIST_EMAIL = (
'email_endjob', 'email_full',
'email_server', 'email_to', 'email_from',
'email_account', 'email_pwd', 'email_dir', 'email_rss'
)
class ConfigEmail:
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():
return Protected()
conf, pnfo_list, bytespersec = build_header(self.__prim)
conf['my_home'] = sabnzbd.DIR_HOME
conf['my_lcldata'] = sabnzbd.DIR_LCLDATA
for kw in LIST_EMAIL:
if kw == 'email_pwd':
conf[kw] = config.get_config('misc', kw).get_stars()
else:
conf[kw] = config.get_config('misc', kw)()
template = Template(file=os.path.join(self.__web_dir, 'config_email.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
return template.respond()
@cherrypy.expose
def saveEmail(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
for kw in LIST_EMAIL:
msg = config.get_config('misc', kw).set(kwargs.get(kw))
if msg:
return badParameterResponse(T('error-badValue@2') % (kw, msg))
config.save_config()
raise dcRaiser(self.__root, kwargs)
def std_time(when):
# Fri, 16 Nov 2007 16:42:01 GMT +0100
item = time.strftime('%a, %d %b %Y %H:%M:%S', time.localtime(when))
item += " GMT %+05d" % (-time.timezone/36)
return item
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://sourceforge.net/projects/sabnzbdplus/"
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['report']:
item.link = "https://www.newzbin.com/browse/post/%s/" % history['report']
elif history['url_info']:
item.link = history['url_info']
else:
item.link = url
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 format_bytes(bytes):
b = to_units(bytes)
if b == '':
return b
else:
return b + 'B'
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://sourceforge.net/projects/sabnzbdplus/"
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()
def qstatus_data():
"""Build up the queue status as a nested object and output as a JSON object
"""
qnfo = nzbqueue.queue_info()
pnfo_list = qnfo[QNFO_PNFO_LIST_FIELD]
jobs = []
bytesleftprogess = 0
bpsnow = BPSMeter.do.get_bps()
for pnfo in pnfo_list:
filename = pnfo[PNFO_FILENAME_FIELD]
msgid = pnfo[PNFO_MSGID_FIELD]
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD] / MEBI
bytesleftprogess += pnfo[PNFO_BYTES_LEFT_FIELD]
bytes = pnfo[PNFO_BYTES_FIELD] / MEBI
nzo_id = pnfo[PNFO_NZO_ID_FIELD]
jobs.append( { "id" : nzo_id,
"mb":bytes,
"mbleft":bytesleft,
"filename":unicoder(filename),
"msgid":msgid,
"timeleft":calc_timeleft(bytesleftprogess, bpsnow) } )
state = "IDLE"
if downloader.paused():
state = "PAUSED"
elif qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI > 0:
state = "DOWNLOADING"
status = {
"state" : state,
"paused" : downloader.paused(),
"pause_int" : scheduler.pause_int(),
"kbpersec" : BPSMeter.do.get_bps() / KIBI,
"speed" : to_units(BPSMeter.do.get_bps()),
"mbleft" : qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI,
"mb" : qnfo[QNFO_BYTES_FIELD] / MEBI,
"noofslots" : len(pnfo_list),
"have_warnings" : str(sabnzbd.GUIHANDLER.count()),
"diskspace1" : diskfree(cfg.download_dir.get_path()),
"diskspace2" : diskfree(cfg.complete_dir.get_path()),
"timeleft" : calc_timeleft(qnfo[QNFO_BYTES_LEFT_FIELD], bpsnow),
"loadavg" : loadavg(),
"jobs" : jobs
}
return status
def build_file_list(id):
qnfo = nzbqueue.queue_info()
pnfo_list = qnfo[QNFO_PNFO_LIST_FIELD]
jobs = []
for pnfo in pnfo_list:
nzo_id = pnfo[PNFO_NZO_ID_FIELD]
if nzo_id == id:
finished_files = pnfo[PNFO_FINISHED_FILES_FIELD]
active_files = pnfo[PNFO_ACTIVE_FILES_FIELD]
queued_files = pnfo[PNFO_QUEUED_FILES_FIELD]
n = 0
for tup in finished_files:
bytes_left, bytes, fn, date = tup
fn = xml_name(fn)
age = calc_age(date)
line = {'filename':fn,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'bytes':"%.2f" % bytes,
'age':age, 'id':str(n), 'status':'finished'}
jobs.append(line)
n += 1
for tup in active_files:
bytes_left, bytes, fn, date, nzf_id = tup
fn = xml_name(fn)
age = calc_age(date)
line = {'filename':fn,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'bytes':"%.2f" % bytes,
'nzf_id':nzf_id,
'age':age, 'id':str(n), 'status':'active'}
jobs.append(line)
n += 1
for tup in queued_files:
_set, bytes_left, bytes, fn, date = tup
fn = xml_name(fn)
_set = xml_name(_set)
age = calc_age(date)
line = {'filename':fn, 'set':_set,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'bytes':"%.2f" % bytes,
'age':age, 'id':str(n), 'status':'queued'}
jobs.append(line)
n += 1
return jobs
def get_history_size():
history_db = cherrypy.thread_data.history_db
bytes, month, week = history_db.get_history_size()
return (format_bytes(bytes), format_bytes(month), format_bytes(week))
def build_history(loaded=False, start=None, limit=None, verbose=False, verbose_list=None, search=None):
if not verbose_list:
verbose_list = []
try:
limit = int(limit)
except:
limit = 0
try:
start = int(start)
except:
start = 0
def matches_search(text, search_text):
# Replace * with .* and ' ' with .
search_text = search_text.strip().replace('*','.*').replace(' ','.*') + '.*?'
try:
re_search = re.compile(search_text, re.I)
except:
logging.error(Ta('error-regex@1'), search_text)
return False
return re_search.search(text)
# Grab any items that are active or queued in postproc
queue = PostProcessor.do.get_queue()
# Filter out any items that don't match the search
if search:
queue = [nzo for nzo in queue if matches_search(nzo.get_original_dirname(), search)]
# Multi-page support for postproc items
if start > len(queue):
# On a page where we shouldn't show postproc items
queue = []
else:
try:
if start:
if limit:
queue = queue[start:start+limit]
else:
queue = queue[start:]
except:
pass
# Remove the amount of postproc items from the db request for history items
limit -= len(queue)
# Aquire the db instance
history_db = cherrypy.thread_data.history_db
# Fetch history items
items, fetched_items, total_items = history_db.fetch_history(start,limit,search)
# Fetch which items should show details from the cookie
k = []
if verbose:
details_show_all = True
else:
details_show_all = False
cookie = cherrypy.request.cookie
if cookie.has_key('history_verbosity'):
k = cookie['history_verbosity'].value
c_path = cookie['history_verbosity']['path']
c_age = cookie['history_verbosity']['max-age']
c_version = cookie['history_verbosity']['version']
if k == 'all':
details_show_all = True
k = k.split(',')
k.extend(verbose_list)
# Reverse the queue to add items to the top (faster than insert)
items.reverse()
# Add the postproc items to the top of the history
items = get_active_history(queue, items)
# Unreverse the queue
items.reverse()
for item in items:
if details_show_all:
item['show_details'] = 'True'
else:
if item['nzo_id'] in k:
item['show_details'] = 'True'
else:
item['show_details'] = ''
if item['bytes']:
item['size'] = format_bytes(item['bytes'])
else:
item['size'] = ''
if not item.has_key('loaded'):
item['loaded'] = False
return (items, fetched_items, total_items)
def json_list(section, lst):
"""Output a simple list as a JSON object
"""
i = 0
d = []
for item in lst:
c = {}
c['id'] = '%s' % i
c['name'] = item
i += 1
d.append(c)
return { section : d }
class xml_factory:
"""
Recursive xml string maker. Feed it a mixed tuple/dict/item object and will output into an xml string
Current limitations:
In Two tiered lists hardcoded name of "item": <cat_list><item> </item></cat_list>
In Three tiered lists hardcoded name of "slot": <tier1><slot><tier2> </tier2></slot></tier1>
"""
def __init__(self):
self.__text = ''
def _tuple(self, keyw, lst):
text = []
for item in lst:
text.append(self.run(keyw, item))
return ''.join(text)
def _dict(self, keyw, lst):
text = []
for key in lst.keys():
text.append(self.run(key, lst[key]))
if keyw:
return '<%s>%s</%s>\n' % (keyw, ''.join(text), keyw)
else:
return ''
def _list(self, keyw, lst):
text = []
for cat in lst:
if isinstance(cat, dict):
text.append(self._dict(plural_to_single(keyw, 'slot'), cat))
elif isinstance(cat, list):
text.append(self._list(plural_to_single(keyw, 'list'), cat))
elif isinstance(cat, tuple):
text.append(self._tuple(plural_to_single(keyw, 'tuple'), cat))
else:
if not isinstance(cat, basestring):
cat = str(cat)
name = plural_to_single(keyw, 'item')
text.append('<%s>%s</%s>\n' % (name, xml_name(cat, encoding='utf-8'), name))
if keyw:
return '<%s>%s</%s>\n' % (keyw, ''.join(text), keyw)
else:
return ''
def run(self, keyw, lst):
if isinstance(lst, dict):
text = self._dict(keyw, lst)
elif isinstance(lst, list):
text = self._list(keyw, lst)
elif isinstance(lst, tuple):
text = self._tuple(keyw, lst)
elif keyw:
text = '<%s>%s</%s>\n' % (keyw, xml_name(lst, encoding='utf-8'), keyw)
else:
text = ''
return text
def build_queue(web_dir=None, root=None, verbose=False, prim=True, verboseList=None,
dictionary=None, history=False, start=None, limit=None, dummy2=None, output=None):
if output:
converter = unicoder
else:
converter = xml_name
if not verboseList:
verboseList = []
if dictionary:
dictn = dictionary
else:
dictn = []
#build up header full of basic information
info, pnfo_list, bytespersec = build_header(prim)
info['isverbose'] = verbose
cookie = cherrypy.request.cookie
if cookie.has_key('queue_details'):
info['queue_details'] = str(IntConv(cookie['queue_details'].value))
else:
info['queue_details'] = '0'
if cfg.newzbin_username() and cfg.newzbin_password():
info['newzbinDetails'] = True
if cfg.refresh_rate() > 0:
info['refresh_rate'] = str(cfg.refresh_rate())
else:
info['refresh_rate'] = ''
datestart = datetime.datetime.now()
info['script_list'] = ListScripts()
info['cat_list'] = ListCats()
n = 0
found_active = False
running_bytes = 0
slotinfo = []
nzo_ids = []
limit = IntConv(limit)
start = IntConv(start)
if history:
#Collect nzo's from the history that are downloaded but not finished (repairing, extracting)
slotinfo = format_history_for_queue()
#if the specified start value is greater than the amount of history items, do no include the history (used for paging the queue)
if len(slotinfo) < start:
slotinfo = []
else:
slotinfo = []
info['noofslots'] = len(pnfo_list) + len(slotinfo)
info['start'] = start
info['limit'] = limit
info['finish'] = info['start'] + info['limit']
if info['finish'] > info['noofslots']:
info['finish'] = info['noofslots']
for pnfo in pnfo_list:
repair = pnfo[PNFO_REPAIR_FIELD]
unpack = pnfo[PNFO_UNPACK_FIELD]
delete = pnfo[PNFO_DELETE_FIELD]
script = pnfo[PNFO_SCRIPT_FIELD]
nzo_id = pnfo[PNFO_NZO_ID_FIELD]
cat = pnfo[PNFO_EXTRA_FIELD1]
if not cat:
cat = 'None'
filename = pnfo[PNFO_FILENAME_FIELD]
msgid = pnfo[PNFO_MSGID_FIELD]
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD]
bytes = pnfo[PNFO_BYTES_FIELD]
average_date = pnfo[PNFO_AVG_DATE_FIELD]
status = pnfo[PNFO_STATUS_FIELD]
priority = pnfo[PNFO_PRIORITY_FIELD]
mbleft = (bytesleft / MEBI)
mb = (bytes / MEBI)
if verbose or verboseList:
finished_files = pnfo[PNFO_FINISHED_FILES_FIELD]
active_files = pnfo[PNFO_ACTIVE_FILES_FIELD]
queued_files = pnfo[PNFO_QUEUED_FILES_FIELD]
nzo_ids.append(nzo_id)
slot = {'index':n, 'nzo_id':str(nzo_id)}
unpackopts = sabnzbd.opts_to_pp(repair, unpack, delete)
slot['unpackopts'] = str(unpackopts)
if script:
slot['script'] = script
else:
slot['script'] = 'None'
slot['msgid'] = msgid
slot['filename'] = converter(filename)
slot['cat'] = cat
slot['mbleft'] = "%.2f" % mbleft
slot['mb'] = "%.2f" % mb
slot['size'] = format_bytes(bytes)
slot['sizeleft'] = format_bytes(bytesleft)
if not downloader.paused() and status != 'Paused' and status != 'Fetching' and not found_active:
slot['status'] = "Downloading"
found_active = True
else:
slot['status'] = "%s" % (status)
if priority == TOP_PRIORITY:
slot['priority'] = 'Force'
elif priority == HIGH_PRIORITY:
slot['priority'] = 'High'
elif priority == LOW_PRIORITY:
slot['priority'] = 'Low'
else:
slot['priority'] = 'Normal'
if mb == mbleft:
slot['percentage'] = "0"
else:
slot['percentage'] = "%s" % (int(((mb-mbleft) / mb) * 100))
if status == 'Paused':
slot['timeleft'] = '0:00:00'
slot['eta'] = 'unknown'
else:
running_bytes += bytesleft
slot['timeleft'] = calc_timeleft(running_bytes, bytespersec)
try:
datestart = datestart + datetime.timedelta(seconds=bytesleft / bytespersec)
#new eta format: 16:00 Fri 07 Feb
slot['eta'] = '%s' % datestart.strftime('%H:%M %a %d %b')
except:
datestart = datetime.datetime.now()
slot['eta'] = 'unknown'
slot['avg_age'] = calc_age(average_date)
slot['verbosity'] = ""
if web_dir:
finished = []
active = []
queued = []
if verbose or nzo_id in verboseList:#this will list files in the xml output, wanted yes/no?
slot['verbosity'] = "True"
for tup in finished_files:
bytes_left, bytes, fn, date = tup
fn = converter(fn)
age = calc_age(date)
line = {'filename':fn,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'size': format_bytes(bytes),
'sizeleft': format_bytes(bytes_left),
'age':age}
finished.append(line)
for tup in active_files:
bytes_left, bytes, fn, date, nzf_id = tup
fn = converter(fn)
age = calc_age(date)
line = {'filename':fn,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'size': format_bytes(bytes),
'sizeleft': format_bytes(bytes_left),
'nzf_id':nzf_id,
'age':age}
active.append(line)
for tup in queued_files:
_set, bytes_left, bytes, fn, date = tup
fn = converter(fn)
_set = converter(_set)
age = calc_age(date)
line = {'filename':fn, 'set':_set,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'size': format_bytes(bytes),
'sizeleft': format_bytes(bytes_left),
'age':age}
queued.append(line)
slot['finished'] = finished
slot['active'] = active
slot['queued'] = queued
if (start <= n and n < start + limit) or not limit:
slotinfo.append(slot)
n += 1
if slotinfo:
info['slots'] = slotinfo
else:
info['slots'] = ''
verboseList = []
#Paging of the queue using limit and/or start values
if limit > 0:
try:
if start > 0:
if start > len(pnfo_list):
pnfo_list = []
else:
end = start+limit
if start+limit > len(pnfo_list):
end = len(pnfo_list)
pnfo_list = pnfo_list[start:end]
else:
if not limit > len(pnfo_list):
pnfo_list = pnfo_list[:limit]
except:
pass
return info, pnfo_list, bytespersec, verboseList, dictn
def rss_qstatus():
""" Return a RSS feed with the queue status
"""
qnfo = nzbqueue.queue_info()
pnfo_list = qnfo[QNFO_PNFO_LIST_FIELD]
rss = RSS()
rss.channel.title = "SABnzbd Queue"
rss.channel.description = "Overview of current downloads"
rss.channel.link = "http://%s:%s/sabnzbd/queue" % ( \
cfg.cherryhost(), cfg.cherryport() )
rss.channel.language = "en"
item = Item()
item.title = 'Total ETA: %s - Queued: %.2f MB - Speed: %.2f kB/s' % \
(
calc_timeleft(qnfo[QNFO_BYTES_LEFT_FIELD], BPSMeter.do.get_bps()),
qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI,
BPSMeter.do.get_bps() / KIBI
)
rss.addItem(item)
sum_bytesleft = 0
for pnfo in pnfo_list:
filename = pnfo[PNFO_FILENAME_FIELD]
msgid = pnfo[PNFO_MSGID_FIELD]
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD] / MEBI
bytes = pnfo[PNFO_BYTES_FIELD] / MEBI
mbleft = (bytesleft / MEBI)
mb = (bytes / MEBI)
if mb == mbleft:
percentage = "0%"
else:
percentage = "%s%%" % (int(((mb-mbleft) / mb) * 100))
filename = xml_name(filename)
name = u'%s (%s)' % (filename, percentage)
item = Item()
item.title = name
if msgid:
item.link = "https://newzbin.com/browse/post/%s/" % msgid
else:
item.link = "http://%s:%s/sabnzbd/history" % ( \
cfg.cherryhost(), cfg.cherryport() )
statusLine = []
statusLine.append('<tr>')
#Total MB/MB left
statusLine.append('<dt>Remain/Total: %.2f/%.2f MB</dt>' % (bytesleft, bytes))
#ETA
sum_bytesleft += pnfo[PNFO_BYTES_LEFT_FIELD]
statusLine.append("<dt>ETA: %s </dt>" % calc_timeleft(sum_bytesleft, BPSMeter.do.get_bps()))
statusLine.append("<dt>Age: %s</dt>" % calc_age(pnfo[PNFO_AVG_DATE_FIELD]))
statusLine.append("</tr>")
item.description = ''.join(statusLine)
rss.addItem(item)
rss.channel.lastBuildDate = std_time(time.time())
rss.channel.pubDate = rss.channel.lastBuildDate
rss.channel.ttl = "1"
return rss.write()
def format_history_for_queue():
''' Retrieves the information on currently active history items, and formats them for displaying in the queue '''
slotinfo = []
history_items = get_active_history()
for item in history_items:
slot = {'nzo_id':item['nzo_id'],
'msgid':item['report'], 'filename':xml_name(item['name']), 'loaded':True,
'stages':item['stage_log'], 'status':item['status'], 'bytes':item['bytes'],
'size':item['size']}
slotinfo.append(slot)
return slotinfo
def get_active_history(queue=None, items=None):
# Get the currently in progress and active history queue.
if items is None:
items = []
if queue is None:
queue = PostProcessor.do.get_queue()
for nzo in queue:
t = build_history_info(nzo)
item = {}
item['completed'], item['name'], item['nzb_name'], item['category'], item['pp'], item['script'], item['report'], \
item['url'], item['status'], item['nzo_id'], item['storage'], item['path'], item['script_log'], \
item['script_line'], item['download_time'], item['postproc_time'], item['stage_log'], \
item['downloaded'], item['completeness'], item['fail_message'], item['url_info'], item['bytes'] = t
item['action_line'] = nzo.get_action_line()
item = unpack_history_info(item)
item['loaded'] = True
if item['bytes']:
item['size'] = format_bytes(item['bytes'])
else:
item['size'] = ''
# Queue display needs Unicode instead of UTF-8
for kw in item:
if isinstance(item[kw], str):
item[kw] = item[kw].decode('utf-8')
items.append(item)
return items
def options_list(output):
return report(output, keyword='options', data=
{
'yenc' : sabnzbd.decoder.HAVE_YENC,
'par2' : sabnzbd.newsunpack.PAR2_COMMAND,
'par2c' : sabnzbd.newsunpack.PAR2C_COMMAND,
'rar' : sabnzbd.newsunpack.RAR_COMMAND,
'zip' : sabnzbd.newsunpack.ZIP_COMMAND,
'nice' : sabnzbd.newsunpack.NICE_COMMAND,
'ionice' : sabnzbd.newsunpack.IONICE_COMMAND,
'ssl' : sabnzbd.newswrapper.HAVE_SSL
})