summaryrefslogtreecommitdiffstats
path: root/pyload/config
diff options
context:
space:
mode:
Diffstat (limited to 'pyload/config')
-rw-r--r--pyload/config/ConfigManager.py140
-rw-r--r--pyload/config/ConfigParser.py195
-rw-r--r--pyload/config/__init__.py1
-rw-r--r--pyload/config/convert.py68
-rw-r--r--pyload/config/default.py108
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"),
+ ])