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.
 
 
 
 
 

2397 lines
84 KiB

#!/usr/bin/python -OO
# Copyright 2005 Gregor Kaufmann <tdian@users.sourceforge.net>
# 2007 The ShyPike <shypike@users.sourceforge.net>
#
# 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
"""
__NAME__ = "interface"
import os
import datetime
import time
import cherrypy
import logging
import re
import glob
from xml.sax.saxutils import escape
from sabnzbd.utils.rsslib import RSS, Item, Namespace
from sabnzbd.utils.json import JsonWriter
import sabnzbd
from cherrypy.filters.gzipfilter import GzipFilter
from sabnzbd.utils.multiauth.filter import MultiAuthFilter
from sabnzbd.utils.multiauth.auth import ProtectedClass, SecureResource
from sabnzbd.utils.multiauth.providers import DictAuthProvider
from sabnzbd.utils import listquote
from sabnzbd.utils.configobj import ConfigObj
from Cheetah.Template import Template
from sabnzbd.email import email_send
from sabnzbd.misc import real_path, create_real_path, save_configfile, \
to_units, from_units, SameFile, encode_for_xml, \
decodePassword, encodePassword
from sabnzbd.nzbstuff import SplitFileName
from sabnzbd.newswrapper import GetServerParms
from sabnzbd.newzbin import InitCats, IsNewzbin
from sabnzbd.constants import *
#------------------------------------------------------------------------------
PROVIDER = DictAuthProvider({})
USERNAME = None
PASSWORD = None
#------------------------------------------------------------------------------
try:
os.statvfs
import statvfs
# posix diskfree
def diskfree(_dir):
try:
s = os.statvfs(_dir)
return (s[statvfs.F_BAVAIL] * s[statvfs.F_FRSIZE]) / GIGI
except OSError:
return 0.0
def disktotal(_dir):
try:
s = os.statvfs(_dir)
return (s[statvfs.F_BLOCKS] * s[statvfs.F_FRSIZE]) / GIGI
except OSError:
return 0.0
except AttributeError:
try:
import win32api
except ImportError:
pass
# windows diskfree
def diskfree(_dir):
try:
secp, byteper, freecl, noclu = win32api.GetDiskFreeSpace(_dir)
return (secp * byteper * freecl) / GIGI
except:
return 0.0
def disktotal(_dir):
try:
secp, byteper, freecl, noclu = win32api.GetDiskFreeSpace(_dir)
return (secp * byteper * noclu) / GIGI
except:
return 0.0
def CheckFreeSpace():
if sabnzbd.DOWNLOAD_FREE > 0 and not sabnzbd.paused():
if diskfree(sabnzbd.DOWNLOAD_DIR) < float(sabnzbd.DOWNLOAD_FREE) / GIGI:
logging.info('Too little diskspace forcing PAUSE')
# Pause downloader, but don't save, since the disk is almost full!
sabnzbd.pause_downloader(save=False)
if sabnzbd.EMAIL_FULL:
email_send("SABnzbd has halted", "SABnzbd has halted because diskspace is below the minimum.\n\nSABnzbd")
def check_timeout(timeout):
""" Check sensible ranges for server timeout """
if timeout.isdigit():
if int(timeout) < MIN_TIMEOUT:
timeout = MIN_TIMEOUT
elif int(timeout) > MAX_TIMEOUT:
timeout = MAX_TIMEOUT
else:
timeout = DEF_TIMEOUT
return timeout
def check_server(host, port):
""" Check if server address resolves properly """
if host.lower() == 'localhost' and sabnzbd.AMBI_LOCALHOST:
return badParameterResponse('Warning: LOCALHOST is ambiguous, use numerical IP-address.')
if GetServerParms(host, port):
return ""
else:
return badParameterResponse('Server address "%s:%s" is not valid.' % (host, port))
def ListScripts():
""" Return a list of script names """
lst = []
dd = sabnzbd.SCRIPT_DIR
if dd and os.access(dd, os.R_OK):
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():
""" Return list of categories """
lst = ['None']
for cat in sabnzbd.CFG['categories']:
lst.append(cat)
if len(lst) < 2:
return []
return lst
def Raiser(root, dummy):
if dummy:
root += '?dummy=' + dummy
return cherrypy.HTTPRedirect(root)
def IntConv(value):
"""Safe conversion to int"""
try:
value = int(value)
except:
value = 0
return value
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
txt = ''
r = len(lst)
for n in xrange(r):
txt += lst[n]
if n < r-1: txt += ', '
return txt
def String2List(txt):
""" Return comma-separated string as a list """
#------------------------------------------------------------------------------
class DummyFilter(MultiAuthFilter):
def beforeMain(self):
pass
def beforeFinalize(self):
if isinstance(cherrypy.response.body, SecureResource):
rsrc = cherrypy.response.body
cherrypy.response.body = rsrc.callable(rsrc.instance,
*rsrc.callable_args,
**rsrc.callable_kwargs)
#------------------------------------------------------------------------------
class LoginPage:
def __init__(self, web_dir, root, web_dir2=None, root2=None):
self._cpFilterList = [GzipFilter()]
if USERNAME and PASSWORD:
PROVIDER.add(USERNAME, PASSWORD, ['admins'])
self._cpFilterList.append(MultiAuthFilter('/unauthorized', PROVIDER))
else:
self._cpFilterList.append(DummyFilter('', PROVIDER))
self.sabnzbd = MainPage(web_dir, root, prim=True)
if web_dir2:
self.sabnzbd.m = MainPage(web_dir2, root2, prim=False)
else:
self.sabnzbd.m = NoPage()
@cherrypy.expose
def index(self, dummy = None):
return ""
@cherrypy.expose
def unauthorized(self):
return "<h1>You are not authorized to view this resource</h1>"
#------------------------------------------------------------------------------
class NoPage(ProtectedClass):
def __init__(self):
pass
@cherrypy.expose
def index(self, dummy = None):
return badParameterResponse('Error: No secondary interface defined.')
#------------------------------------------------------------------------------
class MainPage(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
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)
@cherrypy.expose
def index(self, dummy = None):
info, pnfo_list, bytespersec = build_header(self.__prim)
if sabnzbd.USERNAME_NEWZBIN and sabnzbd.PASSWORD_NEWZBIN:
info['newzbinDetails'] = True
info['script_list'] = ListScripts()
info['script'] = sabnzbd.DIRSCAN_SCRIPT
info['cat'] = 'None'
info['cat_list'] = ListCats()
info['warning'] = ""
if not sabnzbd.CFG['servers']:
info['warning'] = "No Usenet server defined, please check Config-->Servers<br/>"
if not sabnzbd.newsunpack.PAR2_COMMAND:
info['warning'] += "No PAR2 program found, repairs not possible<br/>"
template = Template(file=os.path.join(self.__web_dir, 'main.tmpl'),
searchList=[info],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def addID(self, id = None, pp=None, script=None, cat=None, redirect = None):
if pp and pp=="-1": pp = None
if id: id = id.strip()
if id and (id.isdigit() or len(id)==5):
sabnzbd.add_msgid(id, pp, script, cat)
elif id:
sabnzbd.add_url(id, pp, script, cat)
if not redirect:
redirect = self.__root
raise cherrypy.HTTPRedirect(redirect)
@cherrypy.expose
def addURL(self, url = None, pp=None, script=None, cat=None, redirect = None):
if pp and pp=="-1": pp = None
if url: 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 not redirect:
redirect = self.__root
raise cherrypy.HTTPRedirect(redirect)
@cherrypy.expose
def addFile(self, nzbfile, pp=None, script=None, cat=None, dummy = None):
if pp and pp=="-1": pp = None
if nzbfile.filename and nzbfile.value:
sabnzbd.add_nzbfile(nzbfile, pp, script, cat)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def shutdown(self):
yield "Initiating shutdown..."
sabnzbd.halt()
cherrypy.server.stop()
yield "<br>SABnzbd-%s shutdown finished" % sabnzbd.__version__
raise KeyboardInterrupt()
@cherrypy.expose
def pause(self, dummy = None):
sabnzbd.pause_downloader()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def resume(self, dummy = None):
sabnzbd.resume_downloader()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def debug(self):
return '''cache_limit: %s<br>
cache_size: %s<br>
downloaded_items: %s<br>
nzo_list: %s<br>
article_list: %s<br>
nzo_table: %s<br>
nzf_table: %s<br>
article_table: %s<br>
try_list: %s''' % sabnzbd.debug()
@cherrypy.expose
def rss(self, mode='history'):
if mode == 'history':
return rss_history()
elif mode == 'warnings':
return rss_warnings()
@cherrypy.expose
def api(self, mode='', name=None, pp=None, script=None, cat=None,
output='plain', value = None, dummy = None):
"""Handler for API over http
"""
if mode == 'qstatus':
if output == 'json':
return json_qstatus()
elif output == 'xml':
return xml_qstatus()
else:
return 'not implemented\n'
elif mode == 'addfile':
if name.filename and name.value:
sabnzbd.add_nzbfile(name, pp, script, cat)
return 'ok\n'
else:
return 'error\n'
elif mode == 'addurl':
if name:
sabnzbd.add_url(name, pp, script, cat)
return 'ok\n'
else:
return 'error\n'
elif mode == 'addid':
if name and (name.isdigit() or len(name)==5):
sabnzbd.add_msgid(name, pp, script)
return 'ok\n'
else:
return 'error\n'
elif mode == 'pause':
sabnzbd.pause_downloader()
return 'ok\n'
elif mode == 'resume':
sabnzbd.resume_downloader()
return 'ok\n'
elif mode == 'shutdown':
sabnzbd.halt()
cherrypy.server.stop()
raise KeyboardInterrupt()
elif mode == 'warnings':
if output == 'json':
return json_list("warnings", sabnzbd.GUIHANDLER.content())
elif output == 'xml':
return xml_list("warnings", "warning", sabnzbd.GUIHANDLER.content())
else:
return 'not implemented\n'
elif mode == 'queue':
if name == 'change_complete_action': # http://localhost:8080/sabnzbd/api?mode=queue&name=change_complete_action&value=hibernate_pc
if value:
sabnzbd.change_queue_complete_action(value)
return 'ok\n'
else:
return 'error: Please submit a value\n'
elif name == 'purge':
sabnzbd.remove_all_nzo()
return 'ok\n'
else:
return 'error: Please submit a value\n'
elif mode == 'config':
if name == 'speedlimit': # http://localhost:8080/sabnzbd/api?mode=config&name=speedlimit&value=400
if value.isdigit():
sabnzbd.CFG['misc']['bandwith_limit'] = value
sabnzbd.BANDWITH_LIMIT = value
save_configfile(sabnzbd.CFG)
return 'ok\n'
else:
return 'error: Please submit a value\n'
else:
return 'not implemented\n'
elif mode == 'get_cats':
if output == 'json':
return json_list("categories", ListCats())
elif output == 'xml':
return xml_list("categories", "category", ListCats())
else:
return 'not implemented\n'
elif mode == 'get_scripts':
if output == 'json':
return json_list("scripts", ListScripts())
elif output == 'xml':
return xml_list("scripts", "script", ListScripts())
else:
return 'not implemented\n'
else:
return 'not implemented\n'
@cherrypy.expose
def scriptlog(self, name=None, dummy=None):
""" Duplicate of scriptlog of History, needed for some skins """
if name:
path = os.path.dirname(sabnzbd.LOGFILE)
return ShowFile(name, os.path.join(path, name))
else:
raise Raiser(self.__root, dummy)
#------------------------------------------------------------------------------
class NzoPage(ProtectedClass):
def __init__(self, web_dir, root, nzo_id, prim):
self.roles = ['admins']
self.__nzo_id = nzo_id
self.__root = '%s%s/' % (root, nzo_id)
self.__web_dir = web_dir
self.__verbose = False
self.__prim = prim
self.__cached_selection = {} #None
@cherrypy.expose
def index(self, dummy = None):
info, pnfo_list, bytespersec = build_header(self.__prim)
this_pnfo = None
for pnfo in pnfo_list:
if pnfo[PNFO_NZO_ID_FIELD] == self.__nzo_id:
this_pnfo = pnfo
break
if this_pnfo:
info['nzo_id'] = self.__nzo_id
info['filename'] = pnfo[PNFO_FILENAME_FIELD]
active = []
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':str(fn),
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'nzf_id':nzf_id,
'age':calc_age(date),
'checked':checked}
active.append(line)
info['active_files'] = active
template = Template(file=os.path.join(self.__web_dir, 'nzo.tmpl'),
searchList=[info],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
else:
return "ERROR: %s deleted" % self.__nzo_id
@cherrypy.expose
def bulk_operation(self, *args, **kwargs):
self.__cached_selection = kwargs
if kwargs['action_key'] == 'Delete':
for key in kwargs:
if kwargs[key] == 'on':
sabnzbd.remove_nzf(self.__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':
sabnzbd.move_top_bulk(self.__nzo_id, nzf_ids)
elif kwargs['action_key'] == 'Up':
sabnzbd.move_up_bulk(self.__nzo_id, nzf_ids)
elif kwargs['action_key'] == 'Down':
sabnzbd.move_down_bulk(self.__nzo_id, nzf_ids)
elif kwargs['action_key'] == 'Bottom':
sabnzbd.move_bottom_bulk(self.__nzo_id, nzf_ids)
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def tog_verbose(self, dummy = None):
self.__verbose = not self.__verbose
raise Raiser(self.__root, dummy)
#------------------------------------------------------------------------------
class QueuePage(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__verbose = False
self.__verboseList = []
self.__prim = prim
self.__nzo_pages = []
@cherrypy.expose
def index(self, dummy = None):
info, pnfo_list, bytespersec = build_header(self.__prim)
info['isverbose'] = self.__verbose
if sabnzbd.USERNAME_NEWZBIN and sabnzbd.PASSWORD_NEWZBIN:
info['newzbinDetails'] = True
if int(sabnzbd.CFG['misc']['refresh_rate']) > 0:
info['refresh_rate'] = sabnzbd.CFG['misc']['refresh_rate']
else:
info['refresh_rate'] = ''
info['noofslots'] = len(pnfo_list)
datestart = datetime.datetime.now()
info['script_list'] = ListScripts()
info['cat_list'] = ListCats()
n = 0
running_bytes = 0
slotinfo = []
nzo_ids = []
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]
filename = pnfo[PNFO_FILENAME_FIELD]
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD]
bytes = pnfo[PNFO_BYTES_FIELD]
average_date = pnfo[PNFO_AVG_DATE_FIELD]
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)
if nzo_id not in self.__dict__:
self.__dict__[nzo_id] = NzoPage(self.__web_dir, self.__root, nzo_id, self.__prim)
self.__nzo_pages.append(nzo_id)
slot = {'index':n, 'nzo_id':str(nzo_id)}
n += 1
unpackopts = sabnzbd.opts_to_pp(repair, unpack, delete)
slot['unpackopts'] = str(unpackopts)
slot['script'] = str(script)
fn, slot['msgid'] = SplitFileName(filename)
slot['filename'] = escape(fn)
slot['cat'] = str(cat)
slot['mbleft'] = "%.2f" % (bytesleft / MEBI)
slot['mb'] = "%.2f" % (bytes / MEBI)
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)
finished = []
active = []
queued = []
if self.__verbose or nzo_id in self.__verboseList:
date_combined = 0
num_dates = 0
for tup in finished_files:
bytes_left, bytes, fn, date = tup
if isinstance(fn, unicode):
fn = escape(fn.encode('utf-8'))
age = calc_age(date)
line = {'filename':str(fn),
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'age':age}
finished.append(line)
for tup in active_files:
bytes_left, bytes, fn, date, nzf_id = tup
if isinstance(fn, unicode):
fn = escape(fn.encode('utf-8'))
age = calc_age(date)
line = {'filename':str(fn),
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'nzf_id':nzf_id,
'age':age}
active.append(line)
for tup in queued_files:
_set, bytes_left, bytes, fn, date = tup
if isinstance(fn, unicode):
fn = escape(fn.encode('utf-8'))
age = calc_age(date)
line = {'filename':str(fn), 'set':_set,
'mbleft':"%.2f" % (bytes_left / MEBI),
'mb':"%.2f" % (bytes / MEBI),
'age':age}
queued.append(line)
slot['finished'] = finished
slot['active'] = active
slot['queued'] = queued
slotinfo.append(slot)
if slotinfo:
info['slotinfo'] = slotinfo
else:
self.__verboseList = []
for nzo_id in self.__nzo_pages[:]:
if nzo_id not in nzo_ids:
self.__nzo_pages.remove(nzo_id)
self.__dict__.pop(nzo_id)
template = Template(file=os.path.join(self.__web_dir, 'queue.tmpl'),
searchList=[info],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def delete(self, uid = None, dummy = None):
if uid:
sabnzbd.remove_nzo(uid, False)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def purge(self, dummy = None):
sabnzbd.remove_all_nzo()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def removeNzf(self, nzo_id = None, nzf_id = None, dummy = None):
if nzo_id and nzf_id:
sabnzbd.remove_nzf(nzo_id, nzf_id)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def tog_verbose(self, dummy = None):
self.__verbose = not self.__verbose
raise Raiser(self.__root, dummy)
@cherrypy.expose
def tog_uid_verbose(self, uid, dummy = None):
if self.__verboseList.count(uid):
self.__verboseList.remove(uid)
else:
self.__verboseList.append(uid)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def change_queue_complete_action(self, action = None, dummy = None):
"""
Action or script to be performed once the queue has been completed
Scripts are prefixed with 'script_'
"""
sabnzbd.change_queue_complete_action(action)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def switch(self, uid1 = None, uid2 = None, dummy = None):
if uid1 and uid2:
sabnzbd.switch(uid1, uid2)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def change_opts(self, nzo_id = None, pp = None, dummy = None):
if nzo_id and pp and pp.isdigit():
sabnzbd.change_opts(nzo_id, int(pp))
raise Raiser(self.__root, dummy)
@cherrypy.expose
def change_script(self, nzo_id = None, script = None, dummy = None):
if nzo_id and script:
if script == 'None':
script = None
sabnzbd.change_script(nzo_id, script)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def change_cat(self, nzo_id = None, cat = None, dummy = None):
if nzo_id and cat:
if cat == 'None':
cat = None
sabnzbd.change_cat(nzo_id, cat)
try:
script = sabnzbd.CFG['categories'][cat]['script']
except:
script = sabnzbd.DIRSCAN_SCRIPT
try:
pp = int(sabnzbd.CFG['categories'][cat]['pp'])
except:
pp = sabnzbd.DIRSCAN_PP
sabnzbd.change_script(nzo_id, script)
sabnzbd.change_opts(nzo_id, pp)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def shutdown(self):
yield "Initiating shutdown..."
sabnzbd.halt()
cherrypy.server.stop()
yield "<br>SABnzbd-%s shutdown finished" % sabnzbd.__version__
raise KeyboardInterrupt()
@cherrypy.expose
def pause(self, dummy = None):
sabnzbd.pause_downloader()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def resume(self, dummy = None):
sabnzbd.resume_downloader()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def sort_by_avg_age(self, dummy = None):
sabnzbd.sort_by_avg_age()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def sort_by_name(self, dummy = None):
sabnzbd.sort_by_name()
raise Raiser(self.__root, dummy)
class HistoryPage(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__verbose = True
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
history, pnfo_list, bytespersec = build_header(self.__prim)
history['isverbose'] = self.__verbose
if sabnzbd.USERNAME_NEWZBIN and sabnzbd.PASSWORD_NEWZBIN:
history['newzbinDetails'] = True
history_items, total_bytes, bytes_beginning = sabnzbd.history_info()
history['total_bytes'] = "%.2f" % (total_bytes / GIGI)
history['bytes_beginning'] = "%.2f" % (bytes_beginning / GIGI)
items = []
while history_items:
added = max(history_items.keys())
history_item_list = history_items.pop(added)
for history_item in history_item_list:
filename, unpackstrht, loaded, bytes, nzo, status = history_item
name, msgid = SplitFileName(filename)
stages = []
item = {'added':time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(added)),
'nzo':nzo,
'msgid':msgid, 'filename':escape(name), 'loaded':loaded,
'stages':stages, 'status':status}
if self.__verbose:
stage_keys = unpackstrht.keys()
stage_keys.sort()
for stage in stage_keys:
stageLine = {'name':STAGENAMES[stage]}
actions = []
for action in unpackstrht[stage]:
actionLine = {'name':action, 'value':unpackstrht[stage][action]}
actions.append(actionLine)
actions.sort()
actions.reverse()
stageLine['actions'] = actions
stages.append(stageLine)
item['stages'] = stages
items.append(item)
history['lines'] = items
template = Template(file=os.path.join(self.__web_dir, 'history.tmpl'),
searchList=[history],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def purge(self, dummy = None):
sabnzbd.purge_history()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def delete(self, job=None, dummy = None):
if job:
sabnzbd.purge_history(job)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def reset(self, dummy = None):
sabnzbd.reset_byte_counter()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def tog_verbose(self, dummy = None):
self.__verbose = not self.__verbose
raise Raiser(self.__root, dummy)
@cherrypy.expose
def scriptlog(self, name=None, dummy=None):
if name:
path = os.path.dirname(sabnzbd.LOGFILE)
return ShowFile(name, os.path.join(path, name))
else:
raise Raiser(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigPage(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
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)
@cherrypy.expose
def index(self, dummy = None):
config, pnfo_list, bytespersec = build_header(self.__prim)
config['configfn'] = sabnzbd.CFG.filename
template = Template(file=os.path.join(self.__web_dir, 'config.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def restart(self, dummy = None):
sabnzbd.halt()
init_ok = sabnzbd.initialize()
if init_ok:
sabnzbd.start()
raise Raiser(self.__root, dummy)
else:
return "SABnzbd restart failed! See logfile(s)."
#------------------------------------------------------------------------------
class ConfigDirectories(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['download_dir'] = sabnzbd.CFG['misc']['download_dir']
config['download_free'] = sabnzbd.CFG['misc']['download_free'].upper()
config['complete_dir'] = sabnzbd.CFG['misc']['complete_dir']
config['cache_dir'] = sabnzbd.CFG['misc']['cache_dir']
config['log_dir'] = sabnzbd.CFG['misc']['log_dir']
config['nzb_backup_dir'] = sabnzbd.CFG['misc']['nzb_backup_dir']
config['dirscan_dir'] = sabnzbd.CFG['misc']['dirscan_dir']
config['dirscan_speed'] = sabnzbd.CFG['misc']['dirscan_speed']
config['script_dir'] = sabnzbd.CFG['misc']['script_dir']
config['my_home'] = sabnzbd.DIR_HOME
config['my_lcldata'] = sabnzbd.DIR_LCLDATA
config['permissions'] = sabnzbd.UMASK
config['enable_tv_sorting'] = IntConv(sabnzbd.CFG['misc']['enable_tv_sorting'])
config['tv_sort_seasons'] = IntConv(sabnzbd.CFG['misc']['tv_sort_seasons'])
config['tv_sort'] = IntConv(sabnzbd.CFG['misc']['tv_sort'])
tvSortList = []
for tvsort in TVSORTINGLIST:
tvSortList.append(tvsort)
config['tvsort_list'] = tvSortList
template = Template(file=os.path.join(self.__web_dir, 'config_directories.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def saveDirectories(self, download_dir = None, download_free = None, complete_dir = None, log_dir = None,
cache_dir = None, nzb_backup_dir = None, permissions=None,
tv_sort = None, enable_tv_sorting = None, tv_sort_seasons = None,
dirscan_dir = None, dirscan_speed = None, script_dir = None, dummy = None):
if permissions:
try:
int(permissions,8)
except:
return badParameterResponse('Error: use octal notation for permissions')
(dd, path) = create_real_path('download_dir', sabnzbd.DIR_HOME, download_dir)
if not dd:
return badParameterResponse('Error: cannot create download directory "%s".' % path)
(dd, path) = create_real_path('cache_dir', sabnzbd.DIR_LCLDATA, cache_dir)
if not dd:
return badParameterResponse('Error: cannot create cache directory "%s".' % path)
(dd, path) = create_real_path('log_dir', sabnzbd.DIR_LCLDATA, log_dir)
if not dd:
return badParameterResponse('Error: cannot create log directory "%s".' % path)
if dirscan_dir:
(dd, path) = create_real_path('dirscan_dir', sabnzbd.DIR_HOME, dirscan_dir)
if not dd:
return badParameterResponse('Error: cannot create dirscan_dir directory "%s".' % path)
(dd, path) = create_real_path('complete_dir', sabnzbd.DIR_HOME, complete_dir)
if not dd:
return badParameterResponse('Error: cannot create complete_dir directory "%s".' % path)
if nzb_backup_dir:
(dd, path) = create_real_path('nzb_backup_dir', sabnzbd.DIR_LCLDATA, nzb_backup_dir)
if not dd:
return badParameterResponse('Error: cannot create nzb_backup_dir directory %s".' % path)
if script_dir:
(dd, path) = create_real_path('script_dir', sabnzbd.DIR_HOME, script_dir)
if not dd:
return badParameterResponse('Error: cannot create script_dir directory "%s".' % path)
#if SameFile(download_dir, complete_dir):
# return badParameterResponse('Error: DOWNLOAD_DIR and COMPLETE_DIR should not be the same (%s)!' % path)
sabnzbd.CFG['misc']['download_dir'] = download_dir
sabnzbd.CFG['misc']['download_free'] = download_free
sabnzbd.CFG['misc']['cache_dir'] = cache_dir
sabnzbd.CFG['misc']['log_dir'] = log_dir
sabnzbd.CFG['misc']['dirscan_dir'] = dirscan_dir
sabnzbd.CFG['misc']['dirscan_speed'] = dirscan_speed
sabnzbd.CFG['misc']['script_dir'] = script_dir
sabnzbd.CFG['misc']['complete_dir'] = complete_dir
sabnzbd.CFG['misc']['nzb_backup_dir'] = nzb_backup_dir
if permissions: sabnzbd.CFG['misc']['permissions'] = permissions
sabnzbd.CFG['misc']['tv_sort'] = IntConv(tv_sort)
sabnzbd.CFG['misc']['enable_tv_sorting'] = IntConv(enable_tv_sorting)
sabnzbd.CFG['misc']['tv_sort_seasons'] = IntConv(tv_sort_seasons)
return saveAndRestart(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigSwitches(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['enable_unrar'] = IntConv(sabnzbd.CFG['misc']['enable_unrar'])
config['enable_unzip'] = IntConv(sabnzbd.CFG['misc']['enable_unzip'])
config['enable_filejoin'] = IntConv(sabnzbd.CFG['misc']['enable_filejoin'])
config['enable_save'] = IntConv(sabnzbd.CFG['misc']['enable_save'])
config['enable_par_cleanup'] = IntConv(sabnzbd.CFG['misc']['enable_par_cleanup'])
config['send_group'] = IntConv(sabnzbd.CFG['misc']['send_group'])
config['fail_on_crc'] = IntConv(sabnzbd.CFG['misc']['fail_on_crc'])
config['create_group_folders'] = IntConv(sabnzbd.CFG['misc']['create_group_folders'])
config['dirscan_opts'] = IntConv(sabnzbd.CFG['misc']['dirscan_opts'])
config['top_only'] = IntConv(sabnzbd.CFG['misc']['top_only'])
config['auto_sort'] = IntConv(sabnzbd.CFG['misc']['auto_sort'])
config['check_rel'] = IntConv(sabnzbd.CFG['misc']['check_new_rel'])
config['auto_disconnect'] = IntConv(sabnzbd.CFG['misc']['auto_disconnect'])
config['replace_spaces'] = IntConv(sabnzbd.CFG['misc']['replace_spaces'])
config['safe_postproc'] = IntConv(sabnzbd.CFG['misc']['safe_postproc'])
config['auto_browser'] = IntConv(sabnzbd.CFG['misc']['auto_browser'])
config['ignore_samples'] = IntConv(sabnzbd.CFG['misc']['ignore_samples'])
config['pause_on_post_processing'] = IntConv(sabnzbd.CFG['misc']['pause_on_post_processing'])
config['script'] = sabnzbd.CFG['misc']['dirscan_script']
if not config['script']:
config['script'] = 'None'
config['script_list'] = ListScripts()
template = Template(file=os.path.join(self.__web_dir, 'config_switches.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def saveSwitches(self, enable_unrar = None, enable_unzip = None,
enable_filejoin = None, enable_save = None,
send_group = None, fail_on_crc = None, top_only = None,
create_group_folders = None, dirscan_opts = None,
enable_par_cleanup = None, auto_sort = None,
check_rel = None,
auto_disconnect = None,
safe_postproc = None,
replace_spaces = None,
auto_browser = None,
ignore_samples = None,
pause_on_post_processing = None,
script = None,
dummy = None
):
sabnzbd.CFG['misc']['enable_unrar'] = IntConv(enable_unrar)
sabnzbd.CFG['misc']['enable_unzip'] = IntConv(enable_unzip)
sabnzbd.CFG['misc']['enable_filejoin'] = IntConv(enable_filejoin)
sabnzbd.CFG['misc']['enable_save'] = IntConv(enable_save)
sabnzbd.CFG['misc']['send_group'] = IntConv(send_group)
sabnzbd.CFG['misc']['fail_on_crc'] = IntConv(fail_on_crc)
sabnzbd.CFG['misc']['create_group_folders'] = IntConv(create_group_folders)
sabnzbd.CFG['misc']['dirscan_opts'] = IntConv(dirscan_opts)
if script == 'None':
sabnzbd.CFG['misc']['dirscan_script'] = None
else:
sabnzbd.CFG['misc']['dirscan_script'] = script
sabnzbd.CFG['misc']['enable_par_cleanup'] = IntConv(enable_par_cleanup)
sabnzbd.CFG['misc']['top_only'] = IntConv(top_only)
sabnzbd.CFG['misc']['auto_sort'] = IntConv(auto_sort)
sabnzbd.CFG['misc']['check_new_rel'] = IntConv(check_rel)
sabnzbd.CFG['misc']['auto_disconnect'] = IntConv(auto_disconnect)
sabnzbd.CFG['misc']['safe_postproc'] = IntConv(safe_postproc)
sabnzbd.CFG['misc']['replace_spaces'] = IntConv(replace_spaces)
sabnzbd.CFG['misc']['auto_browser'] = IntConv(auto_browser)
sabnzbd.CFG['misc']['ignore_samples'] = IntConv(ignore_samples)
sabnzbd.CFG['misc']['pause_on_post_processing'] = IntConv(pause_on_post_processing)
return saveAndRestart(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigGeneral(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
def ListColors(web_dir):
lst = []
dd = os.path.abspath(web_dir + '/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
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['configfn'] = sabnzbd.CFG.filename
config['host'] = sabnzbd.CFG['misc']['host']
config['port'] = sabnzbd.CFG['misc']['port']
config['username'] = sabnzbd.CFG['misc']['username']
config['password'] = '*' * len(decodePassword(sabnzbd.CFG['misc']['password'], 'web'))
config['bandwith_limit'] = sabnzbd.CFG['misc']['bandwith_limit']
config['refresh_rate'] = sabnzbd.CFG['misc']['refresh_rate']
config['rss_rate'] = sabnzbd.CFG['misc']['rss_rate']
config['cache_limitstr'] = sabnzbd.CFG['misc']['cache_limit'].upper()
wlist = [DEF_STDINTF]
wlist2 = ['None', DEF_STDINTF]
for web in glob.glob(sabnzbd.DIR_INTERFACES + "/*"):
rweb= os.path.basename(web)
if rweb != DEF_STDINTF and rweb != "_svn" and rweb != ".svn" and \
os.access(web + '/' + DEF_MAIN_TMPL, os.R_OK):
wlist.append(rweb)
wlist2.append(rweb)
config['web_list'] = wlist
config['web_list2'] = wlist2
config['web_dir'] = sabnzbd.CFG['misc']['web_dir']
config['web_dir2'] = sabnzbd.CFG['misc']['web_dir2']
if self.__prim:
config['web_colors'] = ListColors(sabnzbd.WEB_DIR)
config['web_color'] = sabnzbd.WEB_COLOR
else:
config['web_colors'] = ListColors(sabnzbd.WEB_DIR2)
config['web_color'] = sabnzbd.WEB_COLOR2
config['cleanup_list'] = List2String(sabnzbd.CFG['misc']['cleanup_list'])
template = Template(file=os.path.join(self.__web_dir, 'config_general.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def saveGeneral(self, host = None, port = None, username = None, password = None, web_dir = None,
web_dir2 = None, web_color = None,
cronlines = None, refresh_rate = None, rss_rate = None,
bandwith_limit = None, cleanup_list = None, cache_limitstr = None, dummy = None):
sabnzbd.CFG['misc']['web_dir'] = web_dir
if web_dir2 == 'None':
sabnzbd.CFG['misc']['web_dir2'] = ''
else:
sabnzbd.CFG['misc']['web_dir2'] = web_dir2
if web_color:
if self.__prim:
sabnzbd.CFG['misc']['web_color'] = web_color
else:
sabnzbd.CFG['misc']['web_color2'] = web_color
sabnzbd.CFG['misc']['host'] = host
sabnzbd.CFG['misc']['port'] = port
sabnzbd.CFG['misc']['username'] = username
if (not password) or (password and password.strip('*')):
sabnzbd.CFG['misc']['password'] = encodePassword(password)
sabnzbd.CFG['misc']['bandwith_limit'] = bandwith_limit
sabnzbd.CFG['misc']['refresh_rate'] = refresh_rate
sabnzbd.CFG['misc']['rss_rate'] = rss_rate
sabnzbd.CFG['misc']['cleanup_list'] = listquote.simplelist(cleanup_list)
sabnzbd.CFG['misc']['cache_limit'] = cache_limitstr
return saveAndRestart(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigServer(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
new = {}
org = sabnzbd.CFG['servers']
for svr in org:
new[svr] = {}
new[svr]['host'] = org[svr]['host']
new[svr]['port'] = org[svr]['port']
new[svr]['username'] = org[svr]['username']
new[svr]['password'] = '*' * len(decodePassword(org[svr]['password'], 'server'))
new[svr]['connections'] = org[svr]['connections']
new[svr]['timeout'] = org[svr]['timeout']
new[svr]['fillserver'] = org[svr]['fillserver']
new[svr]['ssl'] = org[svr]['ssl']
config['servers'] = new
if sabnzbd.newswrapper.HAVE_SSL:
config['have_ssl'] = 1
else:
config['have_ssl'] = 0
template = Template(file=os.path.join(self.__web_dir, 'config_server.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def addServer(self, server = None, host = None, port = None, timeout = None, username = None,
password = None, connections = None, ssl = None, fillserver = None, dummy = None):
timeout = check_timeout(timeout)
if connections == "":
connections = '1'
if port == "":
port = '119'
if not fillserver:
fillserver = 0
if not ssl:
ssl = 0
if host and port and port.isdigit() \
and connections.isdigit() and fillserver and fillserver.isdigit() \
and ssl and ssl.isdigit():
msg = check_server(host, port)
if msg:
return msg
server = "%s:%s" % (host, port)
msg = check_server(host, port)
if msg:
return msg
if server not in sabnzbd.CFG['servers']:
sabnzbd.CFG['servers'][server] = {}
sabnzbd.CFG['servers'][server]['host'] = host
sabnzbd.CFG['servers'][server]['port'] = port
sabnzbd.CFG['servers'][server]['username'] = username
sabnzbd.CFG['servers'][server]['password'] = encodePassword(password)
sabnzbd.CFG['servers'][server]['timeout'] = timeout
sabnzbd.CFG['servers'][server]['connections'] = connections
sabnzbd.CFG['servers'][server]['fillserver'] = fillserver
sabnzbd.CFG['servers'][server]['ssl'] = ssl
return saveAndRestart(self.__root, dummy)
@cherrypy.expose
def saveServer(self, server = None, host = None, port = None, username = None, timeout = None,
password = None, connections = None, fillserver = None, ssl = None, dummy = None):
timeout = check_timeout(timeout)
if connections == "":
connections = '1'
if port == "":
port = '119'
if not ssl:
ssl = 0
if host and port and port.isdigit() \
and connections.isdigit() and fillserver and fillserver.isdigit() \
and ssl and ssl.isdigit():
msg = check_server(host, port)
if msg:
return msg
if password and not password.strip('*'):
password = sabnzbd.CFG['servers'][server]['password']
del sabnzbd.CFG['servers'][server]
# Allow IPV6 numerical addresses, '[]' is not compatible with
# INI file handling, replace by '{}'
ihost = host.replace('[','{')
ihost = ihost.replace(']','}')
server = "%s:%s" % (ihost, port)
sabnzbd.CFG['servers'][server] = {}
sabnzbd.CFG['servers'][server]['host'] = host
sabnzbd.CFG['servers'][server]['port'] = port
sabnzbd.CFG['servers'][server]['username'] = username
sabnzbd.CFG['servers'][server]['password'] = encodePassword(password)
sabnzbd.CFG['servers'][server]['connections'] = connections
sabnzbd.CFG['servers'][server]['timeout'] = timeout
sabnzbd.CFG['servers'][server]['fillserver'] = fillserver
sabnzbd.CFG['servers'][server]['ssl'] = ssl
return saveAndRestart(self.__root, dummy)
@cherrypy.expose
def delServer(self, *args, **kwargs):
if 'server' in kwargs and kwargs['server'] in sabnzbd.CFG['servers']:
del sabnzbd.CFG['servers'][kwargs['server']]
if 'dummy' in kwargs:
return saveAndRestart(self.__root, kwargs['dummy'])
else:
return saveAndRestart(self.__root, '')
#------------------------------------------------------------------------------
def ListFilters(feed):
""" Make a list of all filters of this feed """
n = 0
filters= []
cfg = sabnzbd.CFG['rss'][feed]
while True:
try:
tup = cfg['filter'+str(n)]
filters.append(tup)
n = n + 1
except:
break
return filters
def UnlistFilters(feed, filters):
""" Convert list to filter list for this feed """
cfg = sabnzbd.CFG['rss'][feed]
n = 0
while True:
try:
del cfg['filter'+str(n)]
n = n + 1
except:
break
for n in xrange(len(filters)):
cfg['filter'+str(n)] = filters[n]
class ConfigRss(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['have_feedparser'] = sabnzbd.rss.HAVE_FEEDPARSER
config['script_list'] = ListScripts()
config['script_list'].insert(0, 'Default')
config['cat_list'] = ListCats()
rss = {}
unum = 1
for feed in sabnzbd.CFG['rss']:
rss[feed] = {}
cfg = sabnzbd.CFG['rss'][feed]
rss[feed]['uri'] = cfg['uri']
rss[feed]['cat'] = cfg['cat']
rss[feed]['pp'] = cfg['pp']
rss[feed]['script'] = cfg['script']
rss[feed]['enable'] = IntConv(cfg['enable'])
rss[feed]['pick_cat'] = config['cat_list'] != [] and not IsNewzbin(cfg['uri'])
rss[feed]['pick_script'] = config['script_list'] != []
filters = ListFilters(feed)
rss[feed]['filters'] = filters
rss[feed]['filtercount'] = len(filters)
unum += 1
config['rss'] = rss
config['feed'] = 'Feed' + str(unum)
template = Template(file=os.path.join(self.__web_dir, 'config_rss.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def upd_rss_feed(self, feed=None, uri=None, cat=None, pp=None, script=None, dummy=None):
try:
cfg = sabnzbd.CFG['rss'][feed]
except:
feed = None
if feed and uri:
cfg['uri'] = uri
if IsNone(cat): cat = ''
cfg['cat'] = cat
if IsNone(pp): pp = ''
cfg['pp'] = pp
if script==None or script=='Default': script = ''
cfg['script'] = script
cfg['enable'] = 0
save_configfile(sabnzbd.CFG)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def add_rss_feed(self, feed=None, uri=None, dummy=None):
try:
sabnzbd.CFG['rss'][feed]
except:
sabnzbd.CFG['rss'][feed] = {}
cfg = sabnzbd.CFG['rss'][feed]
cfg['uri'] = uri
cfg['cat'] = ''
cfg['pp'] = ''
cfg['script'] = ''
cfg['enable'] = 0
save_configfile(sabnzbd.CFG)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def upd_rss_filter(self, feed=None, index=None, filter_text=None,
filter_type=None, cat=None, pp=None, script=None, dummy=None):
try:
cfg = sabnzbd.CFG['rss'][feed]
except:
raise Raiser(self.__root, dummy)
if IsNone(cat): cat = ''
if IsNone(pp): pp = ''
if script==None or script=='Default': script = ''
cfg['filter'+str(index)] = (cat, pp, script, filter_type, filter_text)
cfg['enable'] = 0
save_configfile(sabnzbd.CFG)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def pos_rss_filter(self, feed=None, current=None, new=None, dummy=None):
if current != new:
filters = ListFilters(feed)
filter = filters.pop(int(current))
filters.insert(int(new), filter)
UnlistFilters(feed, filters)
sabnzbd.CFG['rss'][feed]['enable'] = 0
save_configfile(sabnzbd.CFG)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def del_rss_feed(self, *args, **kwargs):
if 'feed' in kwargs:
feed = kwargs['feed']
try:
del sabnzbd.CFG['rss'][feed]
sabnzbd.del_rss_feed(feed)
except:
pass
save_configfile(sabnzbd.CFG)
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def del_rss_filter(self, feed=None, index=None, dummy=None):
if feed and index!=None:
filters = ListFilters(feed)
filter = filters.pop(int(index))
UnlistFilters(feed, filters)
sabnzbd.CFG['rss'][feed]['enable'] = 0
save_configfile(sabnzbd.CFG)
raise Raiser(self.__root, dummy)
@cherrypy.expose
def query_rss_feed(self, *args, **kwargs):
if 'feed' in kwargs:
feed = kwargs['feed']
sabnzbd.CFG['rss'][feed]['enable'] = 0
sabnzbd.run_rss_feed(feed)
return ShowRssLog(feed)
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def rematch_rss_feed(self, *args, **kwargs):
if 'feed' in kwargs:
feed = kwargs['feed']
sabnzbd.CFG['rss'][feed]['enable'] = 0
sabnzbd.run_rss_feed(feed, True)
return ShowRssLog(feed)
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def rsslog(self, *args, **kwargs):
if 'feed' in kwargs:
return ShowRssLog(kwargs['feed'])
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def enable_rss_feed(self, *args, **kwargs):
if 'feed' in kwargs:
try:
feed = kwargs['feed']
sabnzbd.CFG['rss'][feed]['enable'] = 1
save_configfile(sabnzbd.CFG)
sabnzbd.run_rss_feed(feed, True)
except:
pass
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def disable_rss_feed(self, *args, **kwargs):
if 'feed' in kwargs:
try:
sabnzbd.CFG['rss'][kwargs['feed']]['enable'] = 0
save_configfile(sabnzbd.CFG)
except:
pass
if 'dummy' in kwargs:
raise Raiser(self.__root, kwargs['dummy'])
else:
raise Raiser(self.__root, '')
@cherrypy.expose
def rss_download(self, feed=None, id=None, cat=None, pp=None, script=None, dummy=None):
if id and id.isdigit():
sabnzbd.add_msgid(id, pp, script, cat)
elif id:
sabnzbd.add_url(id, pp, script, cat)
sabnzbd.rss_flag_downloaded(feed, id)
raise Raiser(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigScheduling(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['schedlines'] = sabnzbd.CFG['misc']['schedlines']
template = Template(file=os.path.join(self.__web_dir, 'config_scheduling.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def addSchedule(self, minute = None, hour = None, dayofweek = None,
action = None, arguments = None, dummy = None):
if minute and hour and dayofweek and action:
sabnzbd.CFG['misc']['schedlines'].append('%s %s %s %s %s' %
(minute, hour, dayofweek, action, arguments))
return saveAndRestart(self.__root, dummy)
@cherrypy.expose
def delSchedule(self, line = None, dummy = None):
if line and line in sabnzbd.CFG['misc']['schedlines']:
sabnzbd.CFG['misc']['schedlines'].remove(line)
return saveAndRestart(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigNewzbin(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
self.__bookmarks = []
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['username_newzbin'] = sabnzbd.CFG['newzbin']['username']
config['password_newzbin'] = '*' * len(decodePassword(sabnzbd.CFG['newzbin']['password'], 'password_newzbin'))
config['create_category_folders'] = IntConv(sabnzbd.CFG['newzbin']['create_category_folders'])
config['newzbin_bookmarks'] = IntConv(sabnzbd.CFG['newzbin']['bookmarks'])
config['newzbin_unbookmark'] = IntConv(sabnzbd.CFG['newzbin']['unbookmark'])
config['bookmark_rate'] = sabnzbd.BOOKMARK_RATE
config['bookmarks_list'] = self.__bookmarks
template = Template(file=os.path.join(self.__web_dir, 'config_newzbin.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def saveNewzbin(self, username_newzbin = None, password_newzbin = None,
create_category_folders = None, newzbin_bookmarks = None,
newzbin_unbookmark = None, bookmark_rate = None, dummy = None):
sabnzbd.CFG['newzbin']['username'] = username_newzbin
if (not password_newzbin) or (password_newzbin and password_newzbin.strip('*')):
sabnzbd.CFG['newzbin']['password'] = encodePassword(password_newzbin)
sabnzbd.CFG['newzbin']['create_category_folders'] = create_category_folders
sabnzbd.CFG['newzbin']['bookmarks'] = newzbin_bookmarks
sabnzbd.CFG['newzbin']['unbookmark'] = newzbin_unbookmark
sabnzbd.CFG['newzbin']['bookmark_rate'] = bookmark_rate
return saveAndRestart(self.__root, dummy)
@cherrypy.expose
def getBookmarks(self, dummy = None):
sabnzbd.getBookmarksNow()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def showBookmarks(self, dummy = None):
self.__bookmarks = sabnzbd.getBookmarksList()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def hideBookmarks(self, dummy = None):
self.__bookmarks = []
raise Raiser(self.__root, dummy)
#------------------------------------------------------------------------------
class ConfigCats(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
if sabnzbd.USERNAME_NEWZBIN and sabnzbd.PASSWORD_NEWZBIN:
config['newzbinDetails'] = True
config['script_list'] = ListScripts()
config['have_cats'] = len(sabnzbd.CFG['categories']) > 0
config['defdir'] = sabnzbd.COMPLETE_DIR
empty = { 'name':'', 'pp':'0', 'script':'', 'dir':'', 'newzbin':'' }
slotinfo = []
slotinfo.append(empty)
for cat in sabnzbd.CFG['categories']:
slot = {}
slot['name'] = cat
try:
slot['pp'] = str(sabnzbd.CFG['categories'][cat]['pp'])
except:
slot['pp'] = '0'
try:
slot['script'] = sabnzbd.CFG['categories'][cat]['script']
except:
slot['script'] = 'None'
try:
slot['dir'] = sabnzbd.CFG['categories'][cat]['dir']
except:
slot['dir'] = ''
#try:
slot['newzbin'] = List2String(sabnzbd.CFG['categories'][cat]['newzbin'])
#except:
# slot['newzbin'] = ''
slotinfo.append(slot)
config['slotinfo'] = slotinfo
template = Template(file=os.path.join(self.__web_dir, 'config_cat.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def delete(self, name = None, dummy = None):
if name:
try:
del sabnzbd.CFG['categories'][name]
except:
pass
return saveAndRestart(self.__root, dummy)
@cherrypy.expose
def save(self, name=None, newname=None, pp=None, script=None, dir=None, newzbin=None, dummy=None):
if newname:
if name:
try:
del sabnzbd.CFG['categories'][name]
except:
pass
name = newname.lower()
sabnzbd.CFG['categories'][name] = {}
if pp and pp.isdigit():
try:
sabnzbd.CFG['categories'][name]['pp'] = str(pp)
except:
pass
if script:
if script.lower() == 'None':
script = ''
try:
sabnzbd.CFG['categories'][name]['script'] = script
except:
pass
if dir:
try:
sabnzbd.CFG['categories'][name]['dir'] = dir
except:
pass
if newzbin:
try:
sabnzbd.CFG['categories'][name]['newzbin'] = listquote.simplelist(newzbin)
except:
pass
return saveAndRestart(self.__root, dummy)
@cherrypy.expose
def init_newzbin(self, dummy = None):
InitCats()
return saveAndRestart(self.__root, dummy)
#------------------------------------------------------------------------------
class ConnectionInfo(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
self.__lastmail = None
@cherrypy.expose
def index(self, dummy = None):
header, pnfo_list, bytespersec = build_header(self.__prim)
header['logfile'] = sabnzbd.LOGFILE
header['weblogfile'] = sabnzbd.WEBLOGFILE
header['loglevel'] = str(sabnzbd.LOGLEVEL)
header['lastmail'] = self.__lastmail
header['servers'] = []
for server in sabnzbd.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 = escape(article.article)
#filename field is not always present
try:
nzf_name = escape(nzf.get_filename())
except: #attribute error
nzf_name = escape(nzf.get_subject())
nzo_name = escape(nzo.get_filename())
busy.append((nw.thrdnum, art_name, nzf_name, nzo_name))
if nw.connected:
connected += 1
busy.sort()
header['servers'].append((server.host, server.port, connected, busy, server.ssl))
wlist = []
for w in sabnzbd.GUIHANDLER.content():
wlist.append(escape(w))
header['warnings'] = wlist
template = Template(file=os.path.join(self.__web_dir, 'connection_info.tmpl'),
searchList=[header],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def disconnect(self, dummy = None):
sabnzbd.disconnect()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def testmail(self, dummy = None):
logging.info("Sending testmail")
self.__lastmail= email_send("SABnzbd testing email connection", "All is OK")
raise Raiser(self.__root, dummy)
@cherrypy.expose
def showlog(self):
try:
sabnzbd.LOGHANDLER.flush()
except:
pass
return cherrypy.lib.cptools.serveFile(sabnzbd.LOGFILE, disposition='attachment')
@cherrypy.expose
def showweb(self):
if sabnzbd.WEBLOGFILE:
return cherrypy.lib.cptools.serveFile(sabnzbd.WEBLOGFILE, disposition='attachment')
else:
return "Web logging is off!"
@cherrypy.expose
def clearwarnings(self, dummy = None):
sabnzbd.GUIHANDLER.clear()
raise Raiser(self.__root, dummy)
@cherrypy.expose
def change_loglevel(self, loglevel=None, dummy = None):
loglevel = IntConv(loglevel)
if loglevel >= 0 and loglevel < 3:
sabnzbd.LOGLEVEL = loglevel
sabnzbd.CFG['logging']['log_level'] = loglevel
save_configfile(sabnzbd.CFG)
raise Raiser(self.__root, dummy)
def saveAndRestart(redirect_root, dummy):
save_configfile(sabnzbd.CFG)
sabnzbd.halt()
init_ok = sabnzbd.initialize()
if init_ok:
sabnzbd.start()
raise Raiser(redirect_root, dummy)
else:
return "SABnzbd restart failed! See logfile(s)."
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 - Error</title>
</head>
<body>
<h3>Incorrect parameter</h3>
%s
<br><br>
<FORM><INPUT TYPE="BUTTON" VALUE="Go Back" ONCLICK="history.go(-1)"></FORM>
</body>
</html>
''' % (sabnzbd.__version__, msg)
def ShowFile(name, path):
"""Return a html page listing a file and a 'back' button
"""
try:
f = open(path, "r")
msg = 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="Go Back" ONCLICK="history.go(-1)"></FORM>
<h3>%s</h3>
<code><pre>
%s
</pre></code><br/><br/>
</body>
</html>
''' % (name, name, escape(msg))
def ShowRssLog(feed):
"""Return a html page listing an RSS log and a 'back' button
"""
jobs = sabnzbd.show_rss_result(feed)
qfeed = escape(feed.replace('/','%2F').replace('?', '%3F'))
doneStr = ""
for x in jobs:
job = jobs[x]
if job[0] == 'D':
doneStr += '%s<br/>' % encode_for_xml(escape(job[1]))
goodStr = ""
for x in jobs:
job = jobs[x]
if job[0] == 'G':
goodStr += '%s<br/>' % encode_for_xml(escape(job[1]))
badStr = ""
for x in jobs:
job = jobs[x]
if job[0] == 'B':
name = escape(job[2]).replace('/','%2F').replace('?', '%3F')
if job[3]:
cat = '&cat=' + escape(job[3])
else:
cat = ''
if job[4]:
pp = '&pp=' + escape(job[4])
else:
pp = ''
if job[5]:
script = '&script=' + escape(job[5])
else:
script = ''
badStr += '<a href="rss_download?feed=%s&id=%s%s%s">Download</a>&nbsp;&nbsp;&nbsp;%s<br/>' % \
(qfeed, name, cat, pp, encode_for_xml(escape(job[1])))
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="Back"/>
</form>
<h3>%s</h3>
<b>Matched</b><br/>
%s
<br/>
<b>Not matched</b><br/>
%s
<br/>
<b>Downloaded</b><br/>
%s
<br/>
</body>
</html>
''' % (escape(feed), escape(feed), goodStr, badStr, 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 = { 'version':sabnzbd.__version__, 'paused':sabnzbd.paused(),
'uptime':uptime, 'color_scheme':color }
header['helpuri'] = 'http://sabnzbd.wikidot.com'
header['diskspace1'] = "%.2f" % diskfree(sabnzbd.DOWNLOAD_DIR)
header['diskspace2'] = "%.2f" % diskfree(sabnzbd.COMPLETE_DIR)
header['diskspacetotal1'] = "%.2f" % disktotal(sabnzbd.DOWNLOAD_DIR)
header['diskspacetotal2'] = "%.2f" % disktotal(sabnzbd.COMPLETE_DIR)
header['finishaction'] = sabnzbd.QUEUECOMPLETE
header['nt'] = os.name == 'nt'
if prim:
header['web_name'] = os.path.basename(sabnzbd.CFG['misc']['web_dir'])
else:
header['web_name'] = os.path.basename(sabnzbd.CFG['misc']['web_dir2'])
bytespersec = sabnzbd.bps()
qnfo = sabnzbd.queue_info()
bytesleft = qnfo[QNFO_BYTES_LEFT_FIELD]
bytes = qnfo[QNFO_BYTES_FIELD]
header['kbpersec'] = "%.2f" % (bytespersec / KIBI)
header['mbleft'] = "%.2f" % (bytesleft / MEBI)
header['mb'] = "%.2f" % (bytes / MEBI)
anfo = sabnzbd.cache_info()
header['cache_art'] = str(anfo[ANFO_ARTICLE_SUM_FIELD])
header['cache_size'] = str(anfo[ANFO_CACHE_SIZE_FIELD])
header['cache_limit'] = 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'] = '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
#------------------------------------------------------------------------------
class ConfigEmail(ProtectedClass):
def __init__(self, web_dir, root, prim):
self.roles = ['admins']
self.__root = root
self.__web_dir = web_dir
self.__prim = prim
@cherrypy.expose
def index(self, dummy = None):
if sabnzbd.CONFIGLOCK:
return Protected()
config, pnfo_list, bytespersec = build_header(self.__prim)
config['email_server'] = sabnzbd.CFG['misc']['email_server']
config['email_to'] = sabnzbd.CFG['misc']['email_to']
config['email_from'] = sabnzbd.CFG['misc']['email_from']
config['email_account'] = sabnzbd.CFG['misc']['email_account']
config['email_pwd'] = '*' * len(decodePassword(sabnzbd.CFG['misc']['email_pwd'], 'email'))
config['email_endjob'] = IntConv(sabnzbd.CFG['misc']['email_endjob'])
config['email_full'] = IntConv(sabnzbd.CFG['misc']['email_full'])
template = Template(file=os.path.join(self.__web_dir, 'config_email.tmpl'),
searchList=[config],
compilerSettings={'directiveStartToken': '<!--#',
'directiveEndToken': '#-->'})
return template.respond()
@cherrypy.expose
def saveEmail(self, email_server = None, email_to = None, email_from = None,
email_account = None, email_pwd = None,
email_endjob = None, email_full = None, dummy = None):
VAL = re.compile('[^@ ]+@[^.@ ]+\.[^.@ ]')
if VAL.match(email_to):
sabnzbd.CFG['misc']['email_to'] = email_to
else:
return badParameterResponse('Invalid email address "%s"' % email_to)
if VAL.match(email_from):
sabnzbd.CFG['misc']['email_from'] = email_from
else:
return badParameterResponse('Invalid email address "%s"' % email_from)
sabnzbd.CFG['misc']['email_server'] = email_server
sabnzbd.CFG['misc']['email_account'] = email_account
if (not email_pwd) or (email_pwd and email_pwd.strip('*')):
sabnzbd.CFG['misc']['email_pwd'] = encodePassword(email_pwd)
sabnzbd.CFG['misc']['email_endjob'] = email_endjob
sabnzbd.CFG['misc']['email_full'] = email_full
return saveAndRestart(self.__root, dummy)
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():
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"
if sabnzbd.USERNAME_NEWZBIN and sabnzbd.PASSWORD_NEWZBIN:
newzbin = True
history_items, total_bytes, bytes_beginning = sabnzbd.history_info()
youngest = None
while history_items:
added = max(history_items.keys())
history_item_list = history_items.pop(added)
for history_item in history_item_list:
item = Item()
filename, unpackstrht, loaded, bytes, nzo, status = history_item
if added > youngest:
youngest = added
item.pubDate = std_time(added)
item.title, msgid = SplitFileName(filename)
if (msgid):
item.link = "https://v3.newzbin.com/browse/post/%s/" % msgid
else:
item.link = "http://%s:%s/sabnzbd/history" % ( \
sabnzbd.CFG['misc']['host'], sabnzbd.CFG['misc']['port'] )
if loaded:
stageLine = "Post-processing active.<br>"
else:
stageLine = ""
stageLine += "[%s] Finished at %s and downloaded %sB" % ( \
status,
time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(added)), \
to_units(bytes) )
stage_keys = unpackstrht.keys()
stage_keys.sort()
for stage in stage_keys:
stageLine += "<tr><dt>Stage %s</dt>" % STAGENAMES[stage]
actions = []
for action in unpackstrht[stage]:
actionLine = "<dd>%s %s</dd>" % (action, unpackstrht[stage][action])
actions.append(actionLine)
actions.sort()
actions.reverse()
for act in actions:
stageLine += act
stageLine += "</tr>"
item.description = stageLine
rss.addItem(item)
rss.channel.lastBuildDate = std_time(youngest)
rss.channel.pubDate = std_time(time.time())
return rss.write()
def rss_warnings():
""" Return an RSS feed with last warnings/errors
"""
rss = RSS()
rss.channel.title = "SABnzbd Warnings"
rss.channel.description = "Overview of warnings/errors"
rss.channel.link = "http://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 json_qstatus():
"""Build up the queue status as a nested object and output as a JSON object
"""
qnfo = sabnzbd.queue_info()
pnfo_list = qnfo[QNFO_PNFO_LIST_FIELD]
jobs = []
for pnfo in pnfo_list:
filename, msgid = SplitFileName(pnfo[PNFO_FILENAME_FIELD])
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD] / MEBI
bytes = pnfo[PNFO_BYTES_FIELD] / MEBI
nzo_id = pnfo[PNFO_NZO_ID_FIELD]
jobs.append( { "id" : nzo_id, "mb":bytes, "mbleft":bytesleft, "filename":filename, "msgid":msgid } )
status = {
"paused" : sabnzbd.paused(),
"kbpersec" : sabnzbd.bps() / KIBI,
"mbleft" : qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI,
"mb" : qnfo[QNFO_BYTES_FIELD] / MEBI,
"noofslots" : len(pnfo_list),
"diskspace1" : diskfree(sabnzbd.DOWNLOAD_DIR),
"diskspace2" : diskfree(sabnzbd.COMPLETE_DIR),
"timeleft" : calc_timeleft(qnfo[QNFO_BYTES_LEFT_FIELD], sabnzbd.bps()),
"jobs" : jobs
}
status_str= JsonWriter().write(status)
cherrypy.response.headers['Content-Type'] = "application/json"
cherrypy.response.headers['Pragma'] = 'no-cache'
return status_str
def xml_qstatus():
"""Build up the queue status as a nested object and output as a XML string
"""
qnfo = sabnzbd.queue_info()
pnfo_list = qnfo[QNFO_PNFO_LIST_FIELD]
jobs = []
for pnfo in pnfo_list:
filename, msgid = SplitFileName(pnfo[PNFO_FILENAME_FIELD])
bytesleft = pnfo[PNFO_BYTES_LEFT_FIELD] / MEBI
bytes = pnfo[PNFO_BYTES_FIELD] / MEBI
name = encode_for_xml(escape(filename), 'UTF-8')
nzo_id = pnfo[PNFO_NZO_ID_FIELD]
jobs.append( { "id" : nzo_id, "mb":bytes, "mbleft":bytesleft, "filename":name, "msgid":msgid } )
status = {
"paused" : sabnzbd.paused(),
"kbpersec" : sabnzbd.bps() / KIBI,
"mbleft" : qnfo[QNFO_BYTES_LEFT_FIELD] / MEBI,
"mb" : qnfo[QNFO_BYTES_FIELD] / MEBI,
"noofslots" : len(pnfo_list),
"diskspace1" : diskfree(sabnzbd.DOWNLOAD_DIR),
"diskspace2" : diskfree(sabnzbd.COMPLETE_DIR),
"timeleft" : calc_timeleft(qnfo[QNFO_BYTES_LEFT_FIELD], sabnzbd.bps()),
"jobs" : jobs
}
status_str= '<?xml version="1.0" encoding="UTF-8" ?> \n\
<queue> \n\
<paused>%(paused)s</paused> \n\
<kbpersec>%(kbpersec)s</kbpersec> \n\
<mbleft>%(mbleft)s</mbleft> \n\
<mb>%(mb)s</mb> \n\
<noofslots>%(noofslots)s</noofslots> \n\
<diskspace1>%(diskspace1)s</diskspace1> \n\
<diskspace2>%(diskspace2)s</diskspace2> \n\
<timeleft>%(timeleft)s</timeleft> \n' % status
status_str += '<jobs>\n'
for job in jobs:
status_str += '<job> \n\
<id>%(id)s</id> \n\
<msgid>%(msgid)s</msgid> \n\
<filename>%(filename)s</filename> \n\
<mbleft>%(mbleft)s</mbleft> \n\
<mb>%(mb)s</mb> \n\
</job>\n' % job
status_str += '</jobs>\n'
status_str += '</queue>'
cherrypy.response.headers['Content-Type'] = "text/xml"
cherrypy.response.headers['Pragma'] = 'no-cache'
return status_str
def json_list(section, lst):
"""Output a simple list as a JSON object
"""
obj = { section : lst }
text = JsonWriter().write(obj)
cherrypy.response.headers['Content-Type'] = "application/json"
cherrypy.response.headers['Pragma'] = 'no-cache'
return text
def xml_list(section, keyw, lst):
"""Output a simple list as an XML object
"""
text= '<?xml version="1.0" encoding="UTF-8" ?> \n<%s>\n' % section
for cat in lst:
text += '<%s>%s</%s>\n' % (keyw, escape(cat), keyw)
text += '</%s>' % section
cherrypy.response.headers['Content-Type'] = "text/xml"
cherrypy.response.headers['Pragma'] = 'no-cache'
return text