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.
926 lines
28 KiB
926 lines
28 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.config - Configuration Support
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
import threading
|
|
import sabnzbd.misc
|
|
import sabnzbd.constants as constants
|
|
from sabnzbd.utils import listquote
|
|
from sabnzbd.utils import configobj
|
|
from sabnzbd.decorators import synchronized
|
|
from sabnzbd.lang import Ta
|
|
|
|
CONFIG_LOCK = threading.Lock()
|
|
SAVE_CONFIG_LOCK = threading.Lock()
|
|
|
|
|
|
__CONFIG_VERSION = '18' # Minumum INI file version required
|
|
|
|
CFG = {} # Holds INI structure
|
|
# uring re-write this variable is global allow
|
|
# direct access to INI structure
|
|
|
|
database = {} # Holds the option dictionary
|
|
|
|
modified = False # Signals a change in option dictionary
|
|
# Should be reset after saving to settings file
|
|
|
|
|
|
class Option:
|
|
""" Basic option class, basic fields """
|
|
def __init__(self, section, keyword, default_val=None, add=True):
|
|
""" Basic option
|
|
section : single section or comma-separated list of sections
|
|
a list will be a hierarchy: "foo, bar" --> [foo][[bar]]
|
|
keyword : keyword in the (last) section
|
|
default_val : value returned when no value has been set
|
|
callback : procedure to call when value is succesfully changed
|
|
"""
|
|
self.__sections = section.split(',')
|
|
self.__keyword = keyword
|
|
self.__default_val = default_val
|
|
self.__value = None
|
|
self.__callback = None
|
|
|
|
# Add myself to the config dictionary
|
|
if add:
|
|
global database
|
|
anchor = database
|
|
for section in self.__sections:
|
|
if section not in anchor:
|
|
anchor[section] = {}
|
|
anchor = anchor[section]
|
|
anchor[keyword] = self
|
|
|
|
def __call__(self):
|
|
""" get() replacement """
|
|
return self.get()
|
|
|
|
def get(self):
|
|
""" Retrieve value field """
|
|
if self.__value != None:
|
|
return self.__value
|
|
else:
|
|
return self.__default_val
|
|
|
|
def get_string(self):
|
|
return str(self.get())
|
|
|
|
def get_dict(self, safe=False):
|
|
""" Return value a dictionary """
|
|
return { self.__keyword : self.get() }
|
|
|
|
def set_dict(self, dict):
|
|
""" Set value based on dictionary """
|
|
try:
|
|
return self.set(dict['value'])
|
|
except KeyError:
|
|
return False
|
|
|
|
def __set(self, value):
|
|
""" Set new value, no validation """
|
|
global modified
|
|
if (value != None):
|
|
if type(value) == type([]) or type(value) == type({}) or value != self.__value:
|
|
self.__value = value
|
|
modified = True
|
|
if self.__callback:
|
|
self.__callback()
|
|
return None
|
|
|
|
def set(self, value):
|
|
return self.__set(value)
|
|
|
|
def callback(self, callback):
|
|
""" Set callback function """
|
|
self.__callback = callback
|
|
|
|
def ident(self):
|
|
""" Return section-list and keyword """
|
|
return self.__sections, self.__keyword
|
|
|
|
|
|
|
|
class OptionNumber(Option):
|
|
""" Numeric option class, int/float is determined from default value """
|
|
def __init__(self, section, keyword, default_val=0, minval=None, maxval=None, validation=None, add=True):
|
|
Option.__init__(self, section, keyword, default_val, add=add)
|
|
self.__minval = minval
|
|
self.__maxval = maxval
|
|
self.__validation = validation
|
|
self.__int = type(default_val) == type(0)
|
|
|
|
def set(self, value):
|
|
""" set new value, limited by range """
|
|
if value != None:
|
|
try:
|
|
if self.__int:
|
|
value = int(value)
|
|
else:
|
|
value = float(value)
|
|
except ValueError:
|
|
value = 0
|
|
if self.__validation:
|
|
error, val = self.__validation(value)
|
|
self._Option__set(val)
|
|
else:
|
|
if (self.__maxval != None) and value > self.__maxval:
|
|
value = self.__maxval
|
|
elif (self.__minval != None) and value < self.__minval:
|
|
value = self.__minval
|
|
self._Option__set(value)
|
|
return None
|
|
|
|
|
|
class OptionBool(Option):
|
|
""" Boolean option class """
|
|
def __init__(self, section, keyword, default_val=False, add=True):
|
|
Option.__init__(self, section, keyword, int(default_val), add=add)
|
|
|
|
def set(self, value):
|
|
if value is None:
|
|
value = 0
|
|
try:
|
|
self._Option__set(int(value))
|
|
except ValueError:
|
|
self._Option__set(0)
|
|
return None
|
|
|
|
|
|
class OptionDir(Option):
|
|
""" Directory option class """
|
|
def __init__(self, section, keyword, default_val='', apply_umask=False, create=True, validation=None, add=True):
|
|
self.__validation = validation
|
|
self.__root = '' # Base directory for relative paths
|
|
self.__apply_umask = apply_umask
|
|
self.__create = create
|
|
Option.__init__(self, section, keyword, default_val, add=add)
|
|
|
|
def get_path(self):
|
|
""" Return full absolute path """
|
|
value = self.get()
|
|
path = ''
|
|
if value:
|
|
path = sabnzbd.misc.real_path(self.__root, value)
|
|
if self.__create and not os.path.exists(path):
|
|
res, path = sabnzbd.misc.create_real_path(self.ident()[1], self.__root, value, self.__apply_umask)
|
|
return path
|
|
|
|
def set_root(self, root):
|
|
""" Set new root, is assumed to be valid """
|
|
self.__root = root
|
|
|
|
def set(self, value):
|
|
""" Set new dir value, validate and create if needed
|
|
Return None when directory is accepted
|
|
Return error-string when not accepted, value will not be changed
|
|
"""
|
|
error = None
|
|
if value != None and value != self.get():
|
|
value = value.strip()
|
|
if self.__validation:
|
|
error, value = self.__validation(self.__root, value, self._Option__default_val)
|
|
if not error:
|
|
if value and self.__create:
|
|
res, path = sabnzbd.misc.create_real_path(self.ident()[1], self.__root, value, self.__apply_umask)
|
|
if not res:
|
|
error = "Cannot create %s folder %s" % (self.ident()[1], path)
|
|
if not error:
|
|
self._Option__set(value)
|
|
return error
|
|
|
|
class OptionList(Option):
|
|
""" List option class """
|
|
def __init__(self, section, keyword, default_val=None, validation=None, add=True):
|
|
self.__validation = validation
|
|
if default_val is None:
|
|
default_val = []
|
|
Option.__init__(self, section, keyword, default_val, add=add)
|
|
|
|
def set(self, value):
|
|
""" Set the list given a comma-separated string or a list"""
|
|
error = None
|
|
if value is not None:
|
|
if not isinstance(value, list):
|
|
value = listquote.simplelist(value)
|
|
if self.__validation:
|
|
error, value = self.__validation(value)
|
|
if not error:
|
|
self._Option__set(value)
|
|
return error
|
|
|
|
def get_string(self):
|
|
""" Return the list as a comma-separated string """
|
|
lst = self.get()
|
|
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
|
|
|
|
|
|
class OptionStr(Option):
|
|
""" String class """
|
|
def __init__(self, section, keyword, default_val='', validation=None, add=True, strip=True):
|
|
Option.__init__(self, section, keyword, default_val, add=add)
|
|
self.__validation = validation
|
|
self.__strip = strip
|
|
|
|
def get_float(self):
|
|
""" Return value converted to a float, allowing KMGT notation """
|
|
return sabnzbd.misc.from_units(self.get())
|
|
|
|
def get_int(self):
|
|
""" Return value converted to an int, allowing KMGT notation """
|
|
return int(self.get_float())
|
|
|
|
def set(self, value):
|
|
""" Set stripped value """
|
|
error = None
|
|
if type(value) == type('') and self.__strip:
|
|
value = value.strip()
|
|
if self.__validation:
|
|
error, val = self.__validation(value)
|
|
self._Option__set(val)
|
|
else:
|
|
self._Option__set(value)
|
|
return error
|
|
|
|
|
|
class OptionPassword(Option):
|
|
""" Password class """
|
|
def __init__(self, section, keyword, default_val='', add=True):
|
|
Option.__init__(self, section, keyword, default_val, add=add)
|
|
self.get_string = self.get_stars
|
|
|
|
def get(self):
|
|
""" Return decoded password """
|
|
value = self._Option__value
|
|
if value is None:
|
|
return self._Option__default_val
|
|
else:
|
|
return decode_password(value, self.ident())
|
|
|
|
def get_stars(self):
|
|
""" Return decoded password as asterisk string """
|
|
return '*' * len(decode_password(self.get(), self.ident()))
|
|
|
|
def get_dict(self, safe=False):
|
|
""" Return value a dictionary """
|
|
if safe:
|
|
return { self._Option__keyword : self.get_stars() }
|
|
else:
|
|
return { self._Option__keyword : self.get() }
|
|
|
|
def set(self, pw):
|
|
""" Set password, encode it """
|
|
if (pw != None and pw == '') or (pw and pw.strip('*')):
|
|
self._Option__set(encode_password(pw))
|
|
return None
|
|
|
|
|
|
@synchronized(CONFIG_LOCK)
|
|
def add_to_database(section, keyword, object):
|
|
global database
|
|
if section not in database:
|
|
database[section] = {}
|
|
database[section][keyword] = object
|
|
|
|
|
|
@synchronized(CONFIG_LOCK)
|
|
def delete_from_database(section, keyword):
|
|
global database, CFG, modified
|
|
del database[section][keyword]
|
|
try:
|
|
del CFG[section][keyword]
|
|
except KeyError:
|
|
pass
|
|
modified = True
|
|
|
|
|
|
class ConfigServer:
|
|
""" Class defining a single server """
|
|
def __init__(self, name, values):
|
|
|
|
self.__name = name
|
|
name = 'servers,' + self.__name
|
|
|
|
self.host = OptionStr(name, 'host', '', add=False)
|
|
self.port = OptionNumber(name, 'port', 119, 0, 2**16-1, add=False)
|
|
self.timeout = OptionNumber(name, 'timeout', 120, 30, 240, add=False)
|
|
self.username = OptionStr(name, 'username', '', add=False)
|
|
self.password = OptionPassword(name, 'password', '', add=False)
|
|
self.connections = OptionNumber(name, 'connections', 1, 0, 100, add=False)
|
|
self.fillserver = OptionBool(name, 'fillserver', False, add=False)
|
|
self.ssl = OptionBool(name, 'ssl', False, add=False)
|
|
self.enable = OptionBool(name, 'enable', True, add=False)
|
|
self.optional = OptionBool(name, 'optional', False, add=False)
|
|
|
|
self.set_dict(values)
|
|
add_to_database('servers', self.__name, self)
|
|
|
|
def set_dict(self, values):
|
|
""" Set one or more fields, passed as dictionary """
|
|
for kw in ('host', 'port', 'timeout', 'username', 'password', 'connections',
|
|
'fillserver', 'ssl', 'enable', 'optional'):
|
|
try:
|
|
value = values[kw]
|
|
except KeyError:
|
|
continue
|
|
exec 'self.%s.set(value)' % kw
|
|
return True
|
|
|
|
def get_dict(self, safe=False):
|
|
""" Return a dictionary with all attributes """
|
|
dict = {}
|
|
dict['name'] = self.__name
|
|
dict['host'] = self.host()
|
|
dict['port'] = self.port()
|
|
dict['timeout'] = self.timeout()
|
|
dict['username'] = self.username()
|
|
if safe:
|
|
dict['password'] = self.password.get_stars()
|
|
else:
|
|
dict['password'] = self.password()
|
|
dict['connections'] = self.connections()
|
|
dict['fillserver'] = self.fillserver()
|
|
dict['ssl'] = self.ssl()
|
|
dict['enable'] = self.enable()
|
|
dict['optional'] = self.optional()
|
|
return dict
|
|
|
|
def delete(self):
|
|
""" Remove from database """
|
|
delete_from_database('servers', self.__name)
|
|
|
|
def rename(self, name):
|
|
""" Give server new identity """
|
|
delete_from_database('servers', self.__name)
|
|
self.__name = name
|
|
add_to_database('servers', self.__name, self)
|
|
|
|
def ident(self):
|
|
return 'servers', self.__name
|
|
|
|
|
|
class ConfigCat:
|
|
""" Class defining a single category """
|
|
def __init__(self, name, values):
|
|
self.__name = name
|
|
name = 'categories,' + name
|
|
|
|
self.pp = OptionStr(name, 'pp', '', add=False)
|
|
self.script = OptionStr(name, 'script', 'Default', add=False)
|
|
self.dir = OptionDir(name, 'dir', add=False, create=False)
|
|
self.newzbin = OptionList(name, 'newzbin', add=False)
|
|
self.priority = OptionNumber(name, 'priority', constants.DEFAULT_PRIORITY, add=False)
|
|
|
|
self.set_dict(values)
|
|
add_to_database('categories', self.__name, self)
|
|
|
|
def set_dict(self, values):
|
|
""" Set one or more fields, passed as dictionary """
|
|
for kw in ('pp', 'script', 'dir', 'newzbin', 'priority'):
|
|
try:
|
|
value = values[kw]
|
|
except KeyError:
|
|
continue
|
|
exec 'self.%s.set(value)' % kw
|
|
return True
|
|
|
|
def get_dict(self, safe=False):
|
|
""" Return a dictionary with all attributes """
|
|
dict = {}
|
|
dict['name'] = self.__name
|
|
dict['pp'] = self.pp()
|
|
dict['script'] = self.script()
|
|
dict['dir'] = self.dir()
|
|
dict['newzbin'] = self.newzbin.get_string()
|
|
dict['priority'] = self.priority()
|
|
return dict
|
|
|
|
def delete(self):
|
|
""" Remove from database """
|
|
delete_from_database('categories', self.__name)
|
|
|
|
|
|
class OptionFilters(Option):
|
|
""" Filter list class """
|
|
def __init__(self, section, keyword, add=True):
|
|
Option.__init__(self, section, keyword, add=add)
|
|
self.set([])
|
|
|
|
def move(self, current, new):
|
|
""" Move filter from position 'current' to 'new' """
|
|
lst = self.get()
|
|
try:
|
|
item = lst.pop(current)
|
|
lst.insert(new, item)
|
|
except IndexError:
|
|
return
|
|
self.set(lst)
|
|
|
|
def update(self, pos, value):
|
|
""" Update filter 'pos' definition, value is a list
|
|
Append if 'pos' outside list
|
|
"""
|
|
lst = self.get()
|
|
try:
|
|
lst[pos] = value
|
|
except IndexError:
|
|
lst.append(value)
|
|
self.set(lst)
|
|
|
|
def delete(self, pos):
|
|
""" Remove filter 'pos' """
|
|
lst = self.get()
|
|
try:
|
|
lst.pop(pos)
|
|
except IndexError:
|
|
return
|
|
self.set(lst)
|
|
|
|
def get_dict(self, safe=False):
|
|
""" Return filter list as a dictionary with keys 'filter[0-9]+' """
|
|
dict = {}
|
|
n = 0
|
|
for filter in self.get():
|
|
dict['filter'+str(n)] = filter
|
|
n = n + 1
|
|
return dict
|
|
|
|
def set_dict(self, values):
|
|
""" Create filter list from dictionary with keys 'filter[0-9]+' """
|
|
filters = []
|
|
for n in xrange(len(values)):
|
|
kw = 'filter%d' % n
|
|
val = values.get(kw)
|
|
if val is not None:
|
|
val = values[kw]
|
|
if type(val) == type([]):
|
|
filters.append(val)
|
|
else:
|
|
filters.append(listquote.simplelist(val))
|
|
if filters:
|
|
self.set(filters)
|
|
return True
|
|
|
|
class ConfigRSS:
|
|
""" Class defining a single Feed definition """
|
|
def __init__(self, name, values):
|
|
self.__name = name
|
|
name = 'rss,' + name
|
|
|
|
self.uri = OptionStr(name, 'uri', add=False)
|
|
self.cat = OptionStr(name, 'cat', add=False)
|
|
self.pp = OptionStr(name, 'pp', '', add=False)
|
|
self.script = OptionStr(name, 'script', add=False)
|
|
self.enable = OptionBool(name, 'enable', add=False)
|
|
self.priority = OptionNumber(name, 'priority', constants.DEFAULT_PRIORITY, constants.DEFAULT_PRIORITY, 2, add=False)
|
|
self.filters = OptionFilters(name, 'filters', add=False)
|
|
self.filters.set([['', '', '', 'A', '*']])
|
|
|
|
self.set_dict(values)
|
|
add_to_database('rss', self.__name, self)
|
|
|
|
def set_dict(self, values):
|
|
""" Set one or more fields, passed as dictionary """
|
|
for kw in ('uri', 'cat', 'pp', 'script', 'priority', 'enable'):
|
|
try:
|
|
value = values[kw]
|
|
except KeyError:
|
|
continue
|
|
exec 'self.%s.set(value)' % kw
|
|
|
|
self.filters.set_dict(values)
|
|
return True
|
|
|
|
def get_dict(self, safe=False):
|
|
""" Return a dictionary with all attributes """
|
|
dict = {}
|
|
dict['name'] = self.__name
|
|
dict['uri'] = self.uri()
|
|
dict['cat'] = self.cat()
|
|
dict['pp'] = self.pp()
|
|
dict['script'] = self.script()
|
|
dict['enable'] = self.enable()
|
|
dict['priority'] = self.priority()
|
|
filters = self.filters.get_dict()
|
|
for kw in filters:
|
|
dict[kw] = filters[kw]
|
|
return dict
|
|
|
|
def delete(self):
|
|
""" Remove from database """
|
|
delete_from_database('rss', self.__name)
|
|
|
|
def ident(self):
|
|
return 'rss', self.__name
|
|
|
|
|
|
|
|
def get_dconfig(section, keyword, nested=False):
|
|
""" Return a config values dictonary,
|
|
Single item or slices based on 'section', 'keyword'
|
|
"""
|
|
data = {}
|
|
if not section:
|
|
for section in database.keys():
|
|
res, conf = get_dconfig(section, None, True)
|
|
data.update(conf)
|
|
|
|
elif not keyword:
|
|
try:
|
|
sect = database[section]
|
|
except KeyError:
|
|
return False, {}
|
|
if section in ('servers', 'categories', 'rss'):
|
|
data[section] = []
|
|
for keyword in sect.keys():
|
|
res, conf = get_dconfig(section, keyword, True)
|
|
data[section].append(conf)
|
|
else:
|
|
data[section] = {}
|
|
for keyword in sect.keys():
|
|
res, conf = get_dconfig(section, keyword, True)
|
|
data[section].update(conf)
|
|
|
|
else:
|
|
try:
|
|
item = database[section][keyword]
|
|
except KeyError:
|
|
return False, {}
|
|
data = item.get_dict(safe=True)
|
|
if not nested:
|
|
if section in ('servers', 'categories', 'rss'):
|
|
data = {section : [ data ]}
|
|
else:
|
|
data = {section : data}
|
|
|
|
return True, data
|
|
|
|
|
|
def get_config(section, keyword):
|
|
""" Return a config object, based on 'section', 'keyword'
|
|
"""
|
|
try:
|
|
return database[section][keyword]
|
|
except KeyError:
|
|
logging.info('Missing configuration item %s,%s', section, keyword)
|
|
return None
|
|
|
|
|
|
def set_config(kwargs):
|
|
""" Set a config item, using values in dictionary
|
|
"""
|
|
try:
|
|
item = database[kwargs.get('section')][kwargs.get('keyword')]
|
|
except KeyError:
|
|
return False
|
|
item.set_dict(kwargs)
|
|
return True
|
|
|
|
|
|
def delete(section, keyword):
|
|
""" Delete specific config item
|
|
"""
|
|
try:
|
|
database[section][keyword].delete()
|
|
except KeyError:
|
|
return
|
|
|
|
|
|
################################################################################
|
|
#
|
|
# INI file support
|
|
#
|
|
# This does input and output of configuration to an INI file.
|
|
# It translates this data structure to the config database.
|
|
|
|
@synchronized(SAVE_CONFIG_LOCK)
|
|
def read_config(path):
|
|
""" Read the complete INI file and check its version number
|
|
if OK, pass values to config-database
|
|
"""
|
|
global CFG, database, modified
|
|
|
|
if not os.path.exists(path):
|
|
# No file found, create default INI file
|
|
try:
|
|
fp = open(path, "w")
|
|
fp.write("__version__=%s\n[misc]\n[logging]\n" % __CONFIG_VERSION)
|
|
fp.close()
|
|
except IOError:
|
|
return False, 'Cannot create INI file %s' % path
|
|
|
|
try:
|
|
CFG = configobj.ConfigObj(path)
|
|
try:
|
|
if int(CFG['__version__']) > int(__CONFIG_VERSION):
|
|
return False, "Incorrect version number %s in %s" %(CFG['__version__'], path)
|
|
except KeyError:
|
|
CFG['__version__'] = __CONFIG_VERSION
|
|
except ValueError:
|
|
CFG['__version__'] = __CONFIG_VERSION
|
|
except configobj.ConfigObjError, strerror:
|
|
return False, '"%s" is not a valid configuration file<br>Error message: %s' % (path, strerror)
|
|
|
|
if 'misc' in CFG:
|
|
compatibility_fix(CFG['misc'])
|
|
|
|
# Use CFG data to set values for all static options
|
|
for section in database:
|
|
if section not in ('servers', 'categories', 'rss'):
|
|
for option in database[section]:
|
|
sec, kw = database[section][option].ident()
|
|
sec = sec[-1]
|
|
try:
|
|
database[section][option].set(CFG[sec][kw])
|
|
except KeyError:
|
|
pass
|
|
|
|
define_categories()
|
|
define_rss()
|
|
define_servers()
|
|
|
|
modified = False
|
|
return True, ""
|
|
|
|
|
|
|
|
@synchronized(SAVE_CONFIG_LOCK)
|
|
def save_config(force=False):
|
|
""" Update Setup file with current option values """
|
|
global CFG, database, modified
|
|
|
|
if not (modified or force):
|
|
return True
|
|
|
|
for section in database:
|
|
if section in ('servers', 'categories', 'rss'):
|
|
try:
|
|
CFG[section]
|
|
except:
|
|
CFG[section] = {}
|
|
for subsec in database[section]:
|
|
subsec_mod = subsec.replace('[', '{').replace(']','}')
|
|
try:
|
|
CFG[section][subsec_mod]
|
|
except:
|
|
CFG[section][subsec_mod] = {}
|
|
items = database[section][subsec].get_dict()
|
|
CFG[section][subsec_mod] = items
|
|
else:
|
|
for option in database[section]:
|
|
sec, kw = database[section][option].ident()
|
|
sec = sec[-1]
|
|
try:
|
|
CFG[sec]
|
|
except:
|
|
CFG[sec] = {}
|
|
value = database[section][option]()
|
|
if type(value) == type(True):
|
|
CFG[sec][kw] = str(int(value))
|
|
elif type(value) == type(0):
|
|
CFG[sec][kw] = str(value)
|
|
else:
|
|
CFG[sec][kw] = value
|
|
|
|
try:
|
|
CFG.write()
|
|
f = open(CFG.filename)
|
|
x = f.read()
|
|
f.close()
|
|
f = open(CFG.filename, "w")
|
|
f.write(x)
|
|
f.flush()
|
|
f.close()
|
|
modified = False
|
|
return True
|
|
except IOError:
|
|
return False
|
|
|
|
|
|
|
|
def define_servers():
|
|
""" Define servers listed in the Setup file
|
|
return a list of ConfigServer instances
|
|
"""
|
|
global CFG
|
|
try:
|
|
for server in CFG['servers']:
|
|
svr = CFG['servers'][server]
|
|
ConfigServer(server.replace('{', '[').replace('}', ']'), svr)
|
|
except KeyError:
|
|
pass
|
|
|
|
def get_servers():
|
|
global database
|
|
try:
|
|
return database['servers']
|
|
except:
|
|
return {}
|
|
|
|
|
|
def define_categories(force=False):
|
|
""" Define categories listed in the Setup file
|
|
return a list of ConfigCat instances
|
|
"""
|
|
global CFG, categories
|
|
cats = ['Unknown', 'Anime', 'Apps', 'Books', 'Consoles', 'Emulation', 'Games',
|
|
'Misc', 'Movies', 'Music', 'PDA', 'Resources', 'TV']
|
|
|
|
try:
|
|
for cat in CFG['categories']:
|
|
ConfigCat(cat, CFG['categories'][cat])
|
|
except KeyError:
|
|
force = True
|
|
|
|
if force:
|
|
for cat in cats:
|
|
val = { 'newzbin' : cat, 'dir' : cat }
|
|
ConfigCat(cat.lower(), val)
|
|
|
|
def get_categories():
|
|
global database
|
|
try:
|
|
return database['categories']
|
|
except:
|
|
return {}
|
|
|
|
def define_rss():
|
|
""" Define rss-ffeds listed in the Setup file
|
|
return a list of ConfigRSS instances
|
|
"""
|
|
global CFG
|
|
try:
|
|
for r in CFG['rss']:
|
|
ConfigRSS(r, CFG['rss'][r])
|
|
except KeyError:
|
|
pass
|
|
|
|
def get_rss():
|
|
global database
|
|
try:
|
|
return database['rss']
|
|
except:
|
|
return {}
|
|
|
|
def get_filename():
|
|
global CFG
|
|
return CFG.filename
|
|
|
|
|
|
################################################################################
|
|
#
|
|
# Default Validation handlers
|
|
#
|
|
__PW_PREFIX = '!!!encoded!!!'
|
|
|
|
#------------------------------------------------------------------------------
|
|
def encode_password(pw):
|
|
""" Encode password in hexadecimal if needed """
|
|
enc = False
|
|
if pw:
|
|
encPW = __PW_PREFIX
|
|
for c in pw:
|
|
cnum = ord(c)
|
|
if c == '#' or cnum<33 or cnum>126:
|
|
enc = True
|
|
encPW += '%2x' % cnum
|
|
if enc:
|
|
return encPW
|
|
return pw
|
|
|
|
|
|
def decode_password(pw, name):
|
|
""" Decode hexadecimal encoded password
|
|
but only decode when prefixed
|
|
"""
|
|
decPW = ''
|
|
if pw and pw.startswith(__PW_PREFIX):
|
|
for n in range(len(__PW_PREFIX), len(pw), 2):
|
|
try:
|
|
ch = chr( int(pw[n] + pw[n+1], 16) )
|
|
except:
|
|
logging.error(Ta('error-encPw@1'), name)
|
|
return ''
|
|
decPW += ch
|
|
return decPW
|
|
else:
|
|
return pw
|
|
|
|
|
|
def no_nonsense(value):
|
|
""" Strip and Filter out None and 'None' from strings """
|
|
value = str(value).strip()
|
|
if value.lower() == 'none':
|
|
value = ''
|
|
return None, value
|
|
|
|
|
|
def validate_octal(value):
|
|
""" Check if string is valid octal number """
|
|
if not value:
|
|
return None, value
|
|
try:
|
|
int(value, 8)
|
|
return None, value
|
|
except:
|
|
return Ta('error-notOctal@1') % value, None
|
|
|
|
|
|
def validate_no_unc(root, value, default):
|
|
""" Check if path isn't a UNC path """
|
|
# Only need to check the 'value' part
|
|
if value and not value.startswith(r'\\'):
|
|
return validate_notempty(root, value, default)
|
|
else:
|
|
return Ta('error-noUNC@1') % value, None
|
|
|
|
|
|
def validate_safedir(root, value, default):
|
|
""" Allow only when queues are empty and no UNC """
|
|
if sabnzbd.empty_queues():
|
|
return validate_no_unc(root, value, default)
|
|
else:
|
|
return Ta('error-QnotEmpty'), None
|
|
|
|
|
|
def validate_dir_exists(root, value, default):
|
|
""" Check if directory exists """
|
|
p = sabnzbd.misc.real_path(root, value)
|
|
if os.path.exists(p):
|
|
return None, value
|
|
else:
|
|
return Ta('error-noFolder@1') % p, None
|
|
|
|
|
|
def validate_notempty(root, value, default):
|
|
""" If value is empty, return default """
|
|
if value:
|
|
return None, value
|
|
else:
|
|
return None, default
|
|
|
|
|
|
def create_api_key():
|
|
import time
|
|
try:
|
|
from hashlib import md5
|
|
except ImportError:
|
|
from md5 import md5
|
|
import random
|
|
# Create some values to seed md5
|
|
t = str(time.time())
|
|
r = str(random.random())
|
|
# Create the md5 instance and give it the current time
|
|
m = md5(t)
|
|
# Update the md5 instance with the random variable
|
|
m.update(r)
|
|
|
|
# Return a hex digest of the md5, eg 49f68a5c8493ec2c0bf489821c21fc3b
|
|
return m.hexdigest()
|
|
|
|
|
|
#------------------------------------------------------------------------------
|
|
_FIXES = \
|
|
(
|
|
('bandwith_limit', 'bandwidth_limit'),
|
|
('enable_par_multicore', 'par2_multicore')
|
|
)
|
|
|
|
def compatibility_fix(cf):
|
|
# Convert obsolete entries
|
|
for item in _FIXES:
|
|
old, new = item
|
|
try:
|
|
cf[new]
|
|
except KeyError:
|
|
try:
|
|
cf[new] = cf[old]
|
|
del cf[old]
|
|
except KeyError:
|
|
pass
|
|
|