diff options
Diffstat (limited to 'pyload/config')
-rw-r--r-- | pyload/config/ConfigManager.py | 140 | ||||
-rw-r--r-- | pyload/config/ConfigParser.py | 195 | ||||
-rw-r--r-- | pyload/config/__init__.py | 1 | ||||
-rw-r--r-- | pyload/config/convert.py | 68 | ||||
-rw-r--r-- | pyload/config/default.py | 108 |
5 files changed, 512 insertions, 0 deletions
diff --git a/pyload/config/ConfigManager.py b/pyload/config/ConfigManager.py new file mode 100644 index 000000000..33bd151c3 --- /dev/null +++ b/pyload/config/ConfigManager.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from new_collections import OrderedDict + +from pyload.Api import InvalidConfigSection +from pyload.utils import json + +from ConfigParser import ConfigParser + +from convert import to_input, from_string + +def convertKeyError(func): + """ converts KeyError into InvalidConfigSection """ + + def conv(*args, **kwargs): + try: + return func(*args, **kwargs) + except KeyError: + raise InvalidConfigSection(args[1]) + + return conv + + +class ConfigManager(ConfigParser): + """ Manages the core config and configs for addons and single user. + Has similar interface to ConfigParser. """ + + def __init__(self, core, parser): + # No __init__ call to super class is needed! + + self.core = core + self.db = core.db + # The config parser, holding the core config + self.parser = parser + + # similar to parser, separated meta data and values + self.config = OrderedDict() + + # Value cache for multiple user configs + # Values are populated from db on first access + # Entries are saved as (user, section) keys + self.values = {} + # TODO: similar to a cache, could be deleted periodically + + def save(self): + self.parser.save() + + @convertKeyError + def get(self, section, option, user=None): + """get config value, core config only available for admins. + if user is not valid default value will be returned""" + + # Core config loaded from parser, when no user is given or he is admin + if section in self.parser and user is None: + return self.parser.get(section, option) + else: + # We need the id and not the instance + # Will be None for admin user and so the same as internal access + try: + # Check if this config exists + # Configs without meta data can not be loaded! + data = self.config[section].config[option] + return self.loadValues(user, section)[option] + except KeyError: + pass # Returns default value later + + return self.config[section].config[option].input.default_value + + def loadValues(self, user, section): + if (user, section) not in self.values: + conf = self.db.loadConfig(section, user) + try: + self.values[user, section] = json.loads(conf) if conf else {} + except ValueError: # Something did go wrong when parsing + self.values[user, section] = {} + self.core.print_exc() + + return self.values[user, section] + + @convertKeyError + def set(self, section, option, value, sync=True, user=None): + """ set config value """ + + changed = False + if section in self.parser and user is None: + changed = self.parser.set(section, option, value, sync) + else: + data = self.config[section].config[option] + value = from_string(value, data.input.type) + old_value = self.get(section, option) + + # Values will always be saved to db, sync is ignored + if value != old_value: + changed = True + self.values[user, section][option] = value + if sync: self.saveValues(user, section) + + if changed: self.core.evm.dispatchEvent("config:changed", section, option, value) + return changed + + def saveValues(self, user, section): + if section in self.parser and user is None: + self.save() + elif (user, section) in self.values: + self.db.saveConfig(section, json.dumps(self.values[user, section]), user) + + def delete(self, section, user=None): + """ Deletes values saved in db and cached values for given user, NOT meta data + Does not trigger an error when nothing was deleted. """ + if (user, section) in self.values: + del self.values[user, section] + + self.db.deleteConfig(section, user) + self.core.evm.dispatchEvent("config:deleted", section, user) + + def iterCoreSections(self): + return self.parser.iterSections() + + def iterSections(self, user=None): + """ Yields: section, metadata, values """ + values = self.db.loadConfigsForUser(user) + + # Every section needs to be json decoded + for section, data in values.items(): + try: + values[section] = json.loads(data) if data else {} + except ValueError: + values[section] = {} + self.core.print_exc() + + for name, config in self.config.iteritems(): + yield name, config, values[name] if name in values else {} + + def getSection(self, section, user=None): + if section in self.parser and user is None: + return self.parser.getSection(section) + + values = self.loadValues(user, section) + return self.config.get(section), values diff --git a/pyload/config/ConfigParser.py b/pyload/config/ConfigParser.py new file mode 100644 index 000000000..0f96fd8b9 --- /dev/null +++ b/pyload/config/ConfigParser.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement +from os.path import exists +from gettext import gettext +from new_collections import namedtuple, OrderedDict + +from pyload.Api import Input, InputType +from pyload.utils.fs import chmod + +from default import make_config +from convert import to_configdata, from_string + +CONF_VERSION = 2 +SectionTuple = namedtuple("SectionTuple", "label description explanation config") + + +class ConfigParser: + """ + Holds and manages the configuration + meta data for config read from file. + """ + + CONFIG = "pyload.conf" + + def __init__(self, config=None): + + if config: self.CONFIG = config + + # Meta data information + self.config = OrderedDict() + # The actual config values + self.values = {} + + self.checkVersion() + + self.loadDefault() + self.parseValues(self.CONFIG) + + def loadDefault(self): + make_config(self) + + def checkVersion(self): + """Determines if config needs to be deleted""" + if exists(self.CONFIG): + f = open(self.CONFIG, "rb") + v = f.readline() + f.close() + v = v[v.find(":") + 1:].strip() + + if not v or int(v) < CONF_VERSION: + f = open(self.CONFIG, "wb") + f.write("version: " + str(CONF_VERSION)) + f.close() + print "Old version of %s deleted" % self.CONFIG + else: + f = open(self.CONFIG, "wb") + f.write("version:" + str(CONF_VERSION)) + f.close() + + def parseValues(self, filename): + """read config values from file""" + f = open(filename, "rb") + config = f.readlines()[1:] + + # save the current section + section = "" + + for line in config: + line = line.strip() + + # comment line, different variants + if not line or line.startswith("#") or line.startswith("//") or line.startswith(";"): continue + + if line.startswith("["): + section = line.replace("[", "").replace("]", "") + + if section not in self.config: + print "Unrecognized section", section + section = "" + + else: + name, non, value = line.rpartition("=") + name = name.strip() + value = value.strip() + + if not section: + print "Value without section", name + continue + + if name in self.config[section].config: + self.set(section, name, value, sync=False) + else: + print "Unrecognized option", section, name + + + def save(self): + """saves config to filename""" + + configs = [] + f = open(self.CONFIG, "wb") + configs.append(f) + chmod(self.CONFIG, 0600) + f.write("version: %i\n\n" % CONF_VERSION) + + for section, data in self.config.iteritems(): + f.write("[%s]\n" % section) + + for option, data in data.config.iteritems(): + value = self.get(section, option) + if type(value) == unicode: + value = value.encode("utf8") + else: + value = str(value) + + f.write('%s = %s\n' % (option, value)) + + f.write("\n") + + f.close() + + def __getitem__(self, section): + """provides dictionary like access: c['section']['option']""" + return Section(self, section) + + def __contains__(self, section): + """ checks if parser contains section """ + return section in self.config + + def get(self, section, option): + """get value or default""" + try: + return self.values[section][option] + except KeyError: + return self.config[section].config[option].input.default_value + + def set(self, section, option, value, sync=True): + """set value""" + + data = self.config[section].config[option] + value = from_string(value, data.input.type) + old_value = self.get(section, option) + + # only save when different values + if value != old_value: + if section not in self.values: self.values[section] = {} + self.values[section][option] = value + if sync: + self.save() + return True + + return False + + def getMetaData(self, section, option): + """ get all config data for an option """ + return self.config[section].config[option] + + def iterSections(self): + """ Yields section, config info, values, for all sections """ + + for name, config in self.config.iteritems(): + yield name, config, self.values[name] if name in self.values else {} + + def getSection(self, section): + """ Retrieves single config as tuple (section, values) """ + return self.config[section], self.values[section] if section in self.values else {} + + def addConfigSection(self, section, label, desc, expl, config): + """Adds a section to the config. `config` is a list of config tuple as used in plugin api defined as: + The order of the config elements is preserved with OrderedDict + """ + d = OrderedDict() + + for entry in config: + name, data = to_configdata(entry) + d[name] = data + + data = SectionTuple(gettext(label), gettext(desc), gettext(expl), d) + self.config[section] = data + + +class Section: + """provides dictionary like access for configparser""" + + def __init__(self, parser, section): + """Constructor""" + self.parser = parser + self.section = section + + def __getitem__(self, item): + """getitem""" + return self.parser.get(self.section, item) + + def __setitem__(self, item, value): + """setitem""" + self.parser.set(self.section, item, value) diff --git a/pyload/config/__init__.py b/pyload/config/__init__.py new file mode 100644 index 000000000..4b31e848b --- /dev/null +++ b/pyload/config/__init__.py @@ -0,0 +1 @@ +__author__ = 'christian' diff --git a/pyload/config/convert.py b/pyload/config/convert.py new file mode 100644 index 000000000..59f814020 --- /dev/null +++ b/pyload/config/convert.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +from gettext import gettext + +from new_collections import namedtuple + +from pyload.Api import Input, InputType +from pyload.utils import decode, to_bool + +ConfigData = namedtuple("ConfigData", "label description input") + +# Maps old config formats to new values +input_dict = { + "int": InputType.Int, + "bool": InputType.Bool, + "time": InputType.Time, + "file": InputType.File, + "list": InputType.List, + "folder": InputType.Folder +} + + +def to_input(typ): + """ Converts old config format to input type""" + return input_dict.get(typ, InputType.Text) + + +def to_configdata(entry): + if len(entry) != 4: + raise ValueError("Config entry must be of length 4") + + # Values can have different roles depending on the two config formats + conf_name, type_label, label_desc, default_input = entry + + # name, label, desc, input + if isinstance(default_input, Input): + _input = default_input + conf_label = type_label + conf_desc = label_desc + # name, type, label, default + else: + _input = Input(to_input(type_label)) + _input.default_value = from_string(default_input, _input.type) + conf_label = label_desc + conf_desc = "" + + return conf_name, ConfigData(gettext(conf_label), gettext(conf_desc), _input) + + +def from_string(value, typ=None): + """ cast value to given type, unicode for strings """ + + # value is no string + if not isinstance(value, basestring): + return value + + value = decode(value) + + if typ == InputType.Int: + return int(value) + elif typ == InputType.Bool: + return to_bool(value) + elif typ == InputType.Time: + if not value: value = "0:00" + if not ":" in value: value += ":00" + return value + else: + return value
\ No newline at end of file diff --git a/pyload/config/default.py b/pyload/config/default.py new file mode 100644 index 000000000..8e2dcae74 --- /dev/null +++ b/pyload/config/default.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +""" +Configuration layout for default base config +""" + +#TODO: write tooltips and descriptions +#TODO: use apis config related classes + +def make_config(config): + # Check if gettext is installed + _ = lambda x: x + + config.addConfigSection("remote", _("Remote"), _("Description"), _("Long description"), + [ + ("activated", "bool", _("Activated"), True), + ("port", "int", _("Port"), 7227), + ("listenaddr", "ip", _("Address"), "0.0.0.0"), + ]) + + config.addConfigSection("log", _("Log"), _("Description"), _("Long description"), + [ + ("log_size", "int", _("Size in kb"), 100), + ("log_folder", "folder", _("Folder"), "Logs"), + ("file_log", "bool", _("File Log"), True), + ("log_count", "int", _("Count"), 5), + ("log_rotate", "bool", _("Log Rotate"), True), + ("console_color", "No;Light;Full", _("Colorize Console"), "Light"), + ]) + + config.addConfigSection("permission", _("Permissions"), _("Description"), _("Long description"), + [ + ("group", "str", _("Groupname"), "users"), + ("change_dl", "bool", _("Change Group and User of Downloads"), False), + ("change_file", "bool", _("Change file mode of downloads"), False), + ("user", "str", _("Username"), "user"), + ("file", "str", _("Filemode for Downloads"), "0644"), + ("change_group", "bool", _("Change group of running process"), False), + ("folder", "str", _("Folder Permission mode"), "0755"), + ("change_user", "bool", _("Change user of running process"), False), + ]) + + config.addConfigSection("general", _("General"), _("Description"), _("Long description"), + [ + ("language", "en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR", _("Language"), "en"), + ("download_folder", "folder", _("Download Folder"), "Downloads"), + ("checksum", "bool", _("Use Checksum"), False), + ("folder_per_package", "bool", _("Create folder for each package"), True), + ("debug_mode", "bool", _("Debug Mode"), False), + ("min_free_space", "int", _("Min Free Space (MB)"), 200), + ("renice", "int", _("CPU Priority"), 0), + ]) + + config.addConfigSection("ssl", _("SSL"), _("Description"), _("Long description"), + [ + ("cert", "file", _("SSL Certificate"), "ssl.crt"), + ("activated", "bool", _("Activated"), False), + ("key", "file", _("SSL Key"), "ssl.key"), + ]) + + config.addConfigSection("webinterface", _("Webinterface"), _("Description"), _("Long description"), + [ + ("template", "str", _("Template"), "default"), + ("activated", "bool", _("Activated"), True), + ("prefix", "str", _("Path Prefix"), ""), + ("server", "auto;threaded;fallback;fastcgi", _("Server"), "auto"), + ("force_server", "str", _("Favor specific server"), ""), + ("host", "ip", _("IP"), "0.0.0.0"), + ("https", "bool", _("Use HTTPS"), False), + ("port", "int", _("Port"), 8001), + ("develop", "bool", _("Development mode"), False), + ]) + + config.addConfigSection("proxy", _("Proxy"), _("Description"), _("Long description"), + [ + ("username", "str", _("Username"), ""), + ("proxy", "bool", _("Use Proxy"), False), + ("address", "str", _("Address"), "localhost"), + ("password", "password", _("Password"), ""), + ("type", "http;socks4;socks5", _("Protocol"), "http"), + ("port", "int", _("Port"), 7070), + ]) + + config.addConfigSection("reconnect", _("Reconnect"), _("Description"), _("Long description"), + [ + ("endTime", "time", _("End"), "0:00"), + ("activated", "bool", _("Use Reconnect"), False), + ("method", "str", _("Method"), "./reconnect.sh"), + ("startTime", "time", _("Start"), "0:00"), + ]) + + config.addConfigSection("download", _("Download"), _("Description"), _("Long description"), + [ + ("max_downloads", "int", _("Max Parallel Downloads"), 3), + ("limit_speed", "bool", _("Limit Download Speed"), False), + ("interface", "str", _("Download interface to bind (ip or Name)"), ""), + ("skip_existing", "bool", _("Skip already existing files"), False), + ("max_speed", "int", _("Max Download Speed in kb/s"), -1), + ("ipv6", "bool", _("Allow IPv6"), False), + ("chunks", "int", _("Max connections for one download"), 3), + ("restart_failed", "bool", _("Restart failed downloads on startup"), False), + ]) + + config.addConfigSection("downloadTime", _("Download Time"), _("Description"), _("Long description"), + [ + ("start", "time", _("Start"), "0:00"), + ("end", "time", _("End"), "0:00"), + ]) |