summaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
Diffstat (limited to 'module')
-rw-r--r--module/Api.py135
-rw-r--r--module/ConfigParser.py396
-rw-r--r--module/HookManager.py21
-rw-r--r--module/Utils.py5
-rw-r--r--module/config/ConfigParser.py251
-rw-r--r--module/config/__init__.py1
-rw-r--r--module/config/converter.py26
-rw-r--r--module/config/default.conf65
-rw-r--r--module/config/default.py114
-rw-r--r--module/config/gui_default.xml13
-rw-r--r--module/database/DatabaseBackend.py40
-rw-r--r--module/lib/namedtuple.py114
-rw-r--r--module/lib/new_collections.py375
-rw-r--r--module/plugins/Base.py4
-rw-r--r--module/plugins/Hook.py2
-rw-r--r--module/plugins/Plugin.py2
-rw-r--r--module/plugins/PluginManager.py41
-rw-r--r--module/plugins/hooks/UpdateManager.py2
-rw-r--r--module/remote/socketbackend/ttypes.py22
-rw-r--r--module/remote/thriftbackend/pyload.thrift39
-rwxr-xr-xmodule/remote/thriftbackend/thriftgen/pyload/Pyload-remote23
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/Pyload.py152
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/ttypes.py140
-rw-r--r--module/web/json_app.py29
-rw-r--r--module/web/media/js/settings.coffee4
-rw-r--r--module/web/media/js/settings.js2
-rw-r--r--module/web/pyload_app.py11
-rw-r--r--module/web/templates/default/settings_item.html14
28 files changed, 1134 insertions, 909 deletions
diff --git a/module/Api.py b/module/Api.py
index f0bf5e264..c787819e2 100644
--- a/module/Api.py
+++ b/module/Api.py
@@ -27,11 +27,13 @@ from utils import freeSpace, compare_time
from common.packagetools import parseNames
from network.RequestFactory import getURL
from remote import activated
+from config.converter import to_string
if activated:
try:
from remote.thriftbackend.thriftgen.pyload.ttypes import *
from remote.thriftbackend.thriftgen.pyload.Pyload import Iface
+
BaseObject = TBase
except ImportError:
print "Thrift not imported"
@@ -49,7 +51,7 @@ def permission(bits):
def __new__(cls, func, *args, **kwargs):
permMap[func.__name__] = bits
return func
-
+
return _Dec
@@ -67,10 +69,12 @@ class PERMS:
ACCOUNTS = 256 # can access accounts
LOGS = 512 # can see server logs
+
class ROLE:
ADMIN = 0 #admin has all permissions implicit
USER = 1
+
def has_permission(userperms, perms):
# bytewise or perms before if needed
return perms == (userperms & perms)
@@ -97,65 +101,33 @@ class Api(Iface):
def _convertPyFile(self, p):
f = FileData(p["id"], p["url"], p["name"], p["plugin"], p["size"],
- p["format_size"], p["status"], p["statusmsg"],
- p["package"], p["error"], p["order"])
+ p["format_size"], p["status"], p["statusmsg"],
+ p["package"], p["error"], p["order"])
return f
- def _convertConfigFormat(self, c):
- sections = {}
- for sectionName, sub in c.iteritems():
- section = ConfigSection(sectionName, sub["desc"])
- items = []
- for key, data in sub.iteritems():
- if key in ("desc", "outline"):
- continue
- item = ConfigItem()
- item.name = key
- item.description = data["desc"]
- item.value = str(data["value"]) if not isinstance(data["value"], basestring) else data["value"]
- item.type = data["type"]
- items.append(item)
- section.items = items
- sections[sectionName] = section
- if "outline" in sub:
- section.outline = sub["outline"]
- return sections
-
@permission(PERMS.SETTINGS)
- def getConfigValue(self, category, option, section="core"):
+ def getConfigValue(self, section, option):
"""Retrieve config value.
- :param category: name of category, or plugin
+ :param section: name of category, or plugin
:param option: config option
- :param section: 'plugin' or 'core'
:return: config value as string
"""
- if section == "core":
- value = self.core.config[category][option]
- else:
- value = self.core.config.getPlugin(category, option)
-
- return str(value) if not isinstance(value, basestring) else value
+ value = self.core.config.get(section, option)
+ return to_string(value)
@permission(PERMS.SETTINGS)
- def setConfigValue(self, category, option, value, section="core"):
+ def setConfigValue(self, section, option, value):
"""Set new config value.
- :param category:
+ :param section:
:param option:
:param value: new config value
- :param section: 'plugin' or 'core
"""
- self.core.hookManager.dispatchEvent("configChanged", category, option, value, section)
+ if option in ("limit_speed", "max_speed"): #not so nice to update the limit
+ self.core.requestFactory.updateBucket()
- if section == "core":
- self.core.config[category][option] = value
-
- if option in ("limit_speed", "max_speed"): #not so nice to update the limit
- self.core.requestFactory.updateBucket()
-
- elif section == "plugin":
- self.core.config.setPlugin(category, option, value)
+ self.core.config.set(section, option, value)
@permission(PERMS.SETTINGS)
def getConfig(self):
@@ -163,14 +135,11 @@ class Api(Iface):
:return: list of `ConfigSection`
"""
- return self._convertConfigFormat(self.core.config.config)
+ return [ConfigSection(section, data.name, data.description, data.long_desc, [
+ ConfigItem(option, d.name, d.description, d.type, d.default, self.core.config.get(section, option)) for
+ option, d in data.config.iteritems()]) for
+ section, data in self.core.config.getBaseSectionns()]
- def getConfigDict(self):
- """Retrieves complete config in dict format, not for RPC.
-
- :return: dict
- """
- return self.core.config.config
@permission(PERMS.SETTINGS)
def getPluginConfig(self):
@@ -178,15 +147,23 @@ class Api(Iface):
:return: list of `ConfigSection`
"""
- return self._convertConfigFormat(self.core.config.plugin)
+ return [ConfigSection(section, data.name, data.description, data.long_desc) for
+ section, data in self.core.config.getPluginSections()]
- def getPluginConfigDict(self):
- """Plugin config as dict, not for RPC.
+ def configureSection(self, section):
+ data = self.core.config.config[section]
+ sec = ConfigSection(section, data.name, data.description, data.long_desc)
+ sec.items = [ConfigItem(option, d.name, d.description, d.type, d.default, self.core.config.get(section, option))
+ for
+ option, d in data.config.iteritems()]
- :return: dict
- """
- return self.core.config.plugin
+ #TODO: config handler
+
+ return sec
+ def getConfigPointer(self):
+ """Config instance, not for RPC"""
+ return self.core.config
@permission(PERMS.STATUS)
def pauseServer(self):
@@ -223,9 +200,9 @@ class Api(Iface):
:return: `ServerStatus`
"""
serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()),
- self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
- not self.core.threadManager.pause and self.isTimeDownload(),
- self.core.config['reconnect']['activated'] and self.isTimeReconnect())
+ self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
+ not self.core.threadManager.pause and self.isTimeDownload(),
+ self.core.config['reconnect']['activated'] and self.isTimeReconnect())
for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]:
serverStatus.speed += pyfile.getSpeed() #bytes/s
@@ -471,8 +448,8 @@ class Api(Iface):
raise PackageDoesNotExists(pid)
pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- links=[self._convertPyFile(x) for x in data["links"].itervalues()])
+ data["queue"], data["order"],
+ links=[self._convertPyFile(x) for x in data["links"].itervalues()])
return pdata
@@ -484,13 +461,13 @@ class Api(Iface):
:return: `PackageData` with .fid attribute
"""
data = self.core.files.getPackageData(int(pid))
-
+
if not data:
raise PackageDoesNotExists(pid)
pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- fids=[int(x) for x in data["links"]])
+ data["queue"], data["order"],
+ fids=[int(x) for x in data["links"]])
return pdata
@@ -538,9 +515,9 @@ class Api(Iface):
:return: list of `PackageInfo`
"""
return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- pack["linkstotal"])
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ pack["linkstotal"])
for pack in self.core.files.getInfoData(Destination.Queue).itervalues()]
@permission(PERMS.LIST)
@@ -551,9 +528,9 @@ class Api(Iface):
:return: list of `PackageData`
"""
return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()]
@permission(PERMS.LIST)
@@ -563,9 +540,9 @@ class Api(Iface):
:return: list of `PackageInfo`
"""
return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- pack["linkstotal"])
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ pack["linkstotal"])
for pack in self.core.files.getInfoData(Destination.Collector).itervalues()]
@permission(PERMS.LIST)
@@ -575,9 +552,9 @@ class Api(Iface):
:return: list of `PackageInfo`
"""
return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()]
@@ -876,7 +853,7 @@ class Api(Iface):
accounts = []
for group in accs.values():
accounts.extend([AccountInfo(acc["validuntil"], acc["login"], acc["options"], acc["valid"],
- acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"])
+ acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"])
for acc in group])
return accounts
@@ -946,7 +923,7 @@ class Api(Iface):
@permission(PERMS.ALL)
def getUserData(self, username, password):
"""similar to `checkAuth` but returns UserData thrift type """
- user = self.checkAuth(username, password)
+ user = self.checkAuth(username, password)
if user:
return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"])
else:
diff --git a/module/ConfigParser.py b/module/ConfigParser.py
deleted file mode 100644
index 85c58a0a3..000000000
--- a/module/ConfigParser.py
+++ /dev/null
@@ -1,396 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-from time import sleep
-from os.path import exists, join
-from shutil import copy
-
-from traceback import print_exc
-from utils import chmod
-
-# ignore these plugin configs, mainly because plugins were wiped out
-IGNORE = (
- "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('hooks', 'UnRar'),
- 'EasyShareCom', 'FlyshareCz'
- )
-
-CONF_VERSION = 1
-
-from namedtuple import namedtuple
-
-ConfigTuple = namedtuple("ConfigTuple", "TODO") #TODO
-
-class ConfigParser:
- """
- holds and manage the configuration
-
- current dict layout:
-
- {
-
- section : {
- option : {
- value:
- type:
- desc:
- }
- desc:
-
- }
-
-
- """
-
-
- def __init__(self):
- """Constructor"""
- self.config = {} # the config values
- self.plugin = {} # the config for plugins
- self.oldRemoteData = {}
-
- self.pluginCB = None # callback when plugin config value is changed
-
- self.checkVersion()
-
- self.readConfig()
-
- self.deleteOldPlugins()
-
-
- def checkVersion(self, n=0):
- """determines if config need to be copied"""
- try:
- if not exists("pyload.conf"):
- copy(join(pypath, "module", "config", "default.conf"), "pyload.conf")
-
- if not exists("plugin.conf"):
- f = open("plugin.conf", "wb")
- f.write("version: " + str(CONF_VERSION))
- f.close()
-
- f = open("pyload.conf", "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":") + 1:].strip()
-
- if not v or int(v) < CONF_VERSION:
- copy(join(pypath, "module", "config", "default.conf"), "pyload.conf")
- print "Old version of config was replaced"
-
- f = open("plugin.conf", "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":") + 1:].strip()
-
- if not v or int(v) < CONF_VERSION:
- f = open("plugin.conf", "wb")
- f.write("version: " + str(CONF_VERSION))
- f.close()
- print "Old version of plugin-config replaced"
- except:
- if n < 3:
- sleep(0.3)
- self.checkVersion(n + 1)
- else:
- raise
-
- def readConfig(self):
- """reads the config file"""
-
- self.config = self.parseConfig(join(pypath, "module", "config", "default.conf"))
- self.plugin = self.parseConfig("plugin.conf")
-
- try:
- homeconf = self.parseConfig("pyload.conf")
- if "username" in homeconf["remote"]:
- if "password" in homeconf["remote"]:
- self.oldRemoteData = {"username": homeconf["remote"]["username"]["value"],
- "password": homeconf["remote"]["username"]["value"]}
- del homeconf["remote"]["password"]
- del homeconf["remote"]["username"]
- self.updateValues(homeconf, self.config)
-
- except Exception, e:
- print "Config Warning"
- print_exc()
-
-
- def parseConfig(self, config):
- """parses a given configfile"""
-
- f = open(config)
-
- config = f.read()
-
- config = config.splitlines()[1:]
-
- conf = {}
-
- section, option, value, typ, desc = "", "", "", "", ""
-
- listmode = False
-
- for line in config:
- comment = line.rfind("#")
- if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace():
- line = line.rpartition("#") # removes comments
- if line[1]:
- line = line[0]
- else:
- line = line[2]
-
- line = line.strip()
-
- try:
- if line == "":
- continue
- elif line.endswith(":"):
- section, none, desc = line[:-1].partition('-')
- section = section.strip()
- desc = desc.replace('"', "").strip()
- conf[section] = {"desc": desc}
- else:
- if listmode:
- if line.endswith("]"):
- listmode = False
- line = line.replace("]", "")
-
- value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
-
- if not listmode:
- conf[section][option] = {"desc": desc,
- "type": typ,
- "value": value}
-
-
- else:
- content, none, value = line.partition("=")
-
- content, none, desc = content.partition(":")
-
- desc = desc.replace('"', "").strip()
-
- typ, none, option = content.strip().rpartition(" ")
-
- value = value.strip()
-
- if value.startswith("["):
- if value.endswith("]"):
- listmode = False
- value = value[:-1]
- else:
- listmode = True
-
- value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
- else:
- value = self.cast(typ, value)
-
- if not listmode:
- conf[section][option] = {"desc": desc,
- "type": typ,
- "value": value}
-
- except Exception, e:
- print "Config Warning"
- print_exc()
-
- f.close()
- return conf
-
-
- def updateValues(self, config, dest):
- """sets the config values from a parsed config file to values in destination"""
-
- for section in config.iterkeys():
- if section in dest:
- for option in config[section].iterkeys():
- if option in ("desc", "outline"): continue
-
- if option in dest[section]:
- dest[section][option]["value"] = config[section][option]["value"]
-
- #else:
- # dest[section][option] = config[section][option]
-
-
- #else:
- # dest[section] = config[section]
-
- def saveConfig(self, config, filename):
- """saves config to filename"""
- with open(filename, "wb") as f:
- chmod(filename, 0600)
- f.write("version: %i \n" % CONF_VERSION)
- for section in config.iterkeys():
- f.write('\n%s - "%s":\n' % (section, config[section]["desc"]))
-
- for option, data in config[section].iteritems():
- if option in ("desc", "outline"): continue
-
- if isinstance(data["value"], list):
- value = "[ \n"
- for x in data["value"]:
- value += "\t\t" + str(x) + ",\n"
- value += "\t\t]\n"
- else:
- if type(data["value"]) in (str, unicode):
- value = data["value"] + "\n"
- else:
- value = str(data["value"]) + "\n"
- try:
- f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value))
- except UnicodeEncodeError:
- f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value.encode("utf8")))
-
- def cast(self, typ, value):
- """cast value to given format"""
- if type(value) not in (str, unicode):
- return value
-
- elif typ == "int":
- return int(value)
- elif typ == "bool":
- return True if value.lower() in ("1", "true", "on", "an", "yes") else False
- elif typ == "time":
- if not value: value = "0:00"
- if not ":" in value: value += ":00"
- return value
- elif typ in ("str", "file", "folder"):
- try:
- return value.encode("utf8")
- except:
- return value
- else:
- return value
-
-
- def save(self):
- """saves the configs to disk"""
-
- self.saveConfig(self.config, "pyload.conf")
- self.saveConfig(self.plugin, "plugin.conf")
-
-
- def __getitem__(self, section):
- """provides dictonary like access: c['section']['option']"""
- return Section(self, section)
-
-
- def get(self, section, option):
- """get value"""
- val = self.config[section][option]["value"]
- try:
- if type(val) in (str, unicode):
- return val.decode("utf8")
- else:
- return val
- except:
- return val
-
- def set(self, section, option, value):
- """set value"""
-
- value = self.cast(self.config[section][option]["type"], value)
-
- self.config[section][option]["value"] = value
- self.save()
-
- def getPlugin(self, plugin, option):
- """gets a value for a plugin"""
- val = self.plugin[plugin][option]["value"]
- try:
- if type(val) in (str, unicode):
- return val.decode("utf8")
- else:
- return val
- except:
- return val
-
- def setPlugin(self, plugin, option, value):
- """sets a value for a plugin"""
-
- value = self.cast(self.plugin[plugin][option]["type"], value)
-
- if self.pluginCB: self.pluginCB(plugin, option, value)
-
- self.plugin[plugin][option]["value"] = value
- self.save()
-
- def getMetaData(self, section, option):
- """ get all config data for an option """
- return self.config[section][option]
-
- def addPluginConfig(self, name, config, outline=""):
- """adds config options with tuples (name, type, desc, default)"""
- if name not in self.plugin:
- conf = {"desc": name,
- "outline": outline}
- self.plugin[name] = conf
- else:
- conf = self.plugin[name]
- conf["outline"] = outline
-
- for item in config:
- if item[0] in conf:
- conf[item[0]]["type"] = item[1]
- conf[item[0]]["desc"] = item[2]
- else:
- conf[item[0]] = {
- "desc": item[2],
- "type": item[1],
- "value": self.cast(item[1], item[3])
- }
-
- values = [x[0] for x in config] + ["desc", "outline"]
- #delete old values
- for item in conf.keys():
- if item not in values:
- del conf[item]
-
- def deleteConfig(self, name):
- """Removes a plugin config"""
- if name in self.plugin:
- del self.plugin[name]
-
-
- def deleteOldPlugins(self):
- """ remove old plugins from config """
-
- for name in IGNORE:
- if name in self.plugin:
- del self.plugin[name]
-
-
-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)
-
-
-if __name__ == "__main__":
- pypath = ""
-
- from time import time
-
- a = time()
-
- c = ConfigParser()
-
- b = time()
-
- print "sec", b - a
-
- print c.config
-
- c.saveConfig(c.config, "user.conf")
diff --git a/module/HookManager.py b/module/HookManager.py
index 8bfcadca6..e32508c48 100644
--- a/module/HookManager.py
+++ b/module/HookManager.py
@@ -50,8 +50,7 @@ class HookManager:
allDownloadsProcessed Every link was handled, pyload would idle afterwards.
allDownloadsFinished Every download in queue is finished.
unrarFinished folder, fname An Unrar job finished
- configChanged The config was changed via the api.
- pluginConfigChanged The plugin config changed, due to api or internal process.
+ configChanged sec,opt,value The config was changed.
===================== ============== ==================================
| Notes:
@@ -74,14 +73,15 @@ class HookManager:
self.events = {} # contains events
- #registering callback for config event
- self.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring)
-
- self.addEvent("pluginConfigChanged", self.manageHooks)
-
self.lock = RLock()
self.createIndex()
+ #registering callback for config event
+ self.config.changeCB = MethodType(self.dispatchEvent, "configChanged", basestring)
+
+ # manage hooks an config change
+ self.addEvent("configChanged", self.manageHooks)
+
def try_catch(func):
def new(*args):
try:
@@ -123,7 +123,7 @@ class HookManager:
try:
#hookClass = getattr(plugin, plugin.__name__)
- if self.core.config.getPlugin(pluginname, "activated"):
+ if self.core.config.get(pluginname, "activated"):
pluginClass = self.core.pluginManager.loadClass("hooks", pluginname)
if not pluginClass: continue
@@ -147,6 +147,11 @@ class HookManager:
self.plugins = plugins
def manageHooks(self, plugin, name, value):
+
+ # check if section was a plugin
+ if plugin not in self.core.pluginManager.getPlugins("hooks"):
+ return
+
if name == "activated" and value:
self.activateHook(plugin)
elif name == "activated" and not value:
diff --git a/module/Utils.py b/module/Utils.py
index 9ad7c2737..b26a9960c 100644
--- a/module/Utils.py
+++ b/module/Utils.py
@@ -21,7 +21,10 @@ def chmod(*args):
def decode(string):
""" decode string with utf if possible """
try:
- return string.decode("utf8", "replace")
+ if type(string) == str:
+ return string.decode("utf8", "replace")
+ else:
+ return string
except:
return string
diff --git a/module/config/ConfigParser.py b/module/config/ConfigParser.py
new file mode 100644
index 000000000..01f4268cb
--- /dev/null
+++ b/module/config/ConfigParser.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+from time import sleep
+from os.path import exists
+from gettext import gettext
+
+from module.utils import chmod, decode
+
+CONF_VERSION = 2
+
+from converter import from_string
+from new_collections import namedtuple, OrderedDict
+from default import make_config
+
+SectionTuple = namedtuple("SectionTuple", "name description long_desc config")
+ConfigData = namedtuple("ConfigData", "name type description default")
+
+class ConfigParser:
+ """
+ Holds and manage the configuration + meta data.
+ Actually only the values are read from disk, all meta data have to be provided first via addConfigSection.
+ """
+
+ CONFIG = "pyload.conf"
+ PLUGIN = "plugin.conf"
+
+ def __init__(self):
+ """Constructor"""
+
+ # core config sections from pyload
+ self.baseSections = []
+
+ # Meta data information
+ self.config = OrderedDict()
+ # The actual config values
+ self.values = {}
+
+ self.changeCB = None # callback when config value was changed
+
+ self.checkVersion()
+
+ self.loadDefault()
+ self.parseValues(self.CONFIG)
+
+ def loadDefault(self):
+ make_config(self)
+
+ def checkVersion(self):
+ """Determines if config need to be deleted"""
+ e = None
+ # workaround conflict, with GUI (which also access the config) so try read in 3 times
+ for i in range(0, 3):
+ try:
+ for conf in (self.CONFIG, self.PLUGIN):
+ if exists(conf):
+ f = open(conf, "rb")
+ v = f.readline()
+ f.close()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ f = open(conf, "wb")
+ f.write("version: " + str(CONF_VERSION))
+ f.close()
+ print "Old version of %s deleted" % conf
+
+ except Exception, ex:
+ e = ex
+ sleep(0.3)
+ if e: raise e
+
+
+ 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 "Unrecognzied 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)
+ else:
+ print "Unrecognized option", section, name
+
+
+ def save(self):
+ """saves config to filename"""
+
+ # seperate pyload and plugin conf
+ configs = []
+ for c in (self.CONFIG, self.PLUGIN):
+ f = open(c, "wb")
+ configs.append(f)
+ chmod(c)
+ f.write("version: %i\n\n" % CONF_VERSION)
+
+
+ # write on 2 files
+ for section, data in self.config.iteritems():
+ f = configs[0] if section in self.baseSections else configs[1]
+
+ 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() for f in configs]
+
+ def __getitem__(self, section):
+ """provides dictonary like access: c['section']['option']"""
+ return Section(self, section)
+
+ def get(self, section, option):
+ """get value"""
+ if option in self.values[section]:
+ return self.values[section][option]
+ else:
+ return self.config[section].config[option].default
+
+ def set(self, section, option, value):
+ """set value"""
+
+ data = self.config[section].config[option]
+ value = from_string(value, data.type)
+
+ # only save when different to defaul values
+ if value != data.default or (option in self.values[section] and value != self.values[section][option]):
+ self.values[section][option] = value
+ if self.changeCB: self.changeCB(section, option, value)
+ self.save()
+
+ def getPlugin(self, *args):
+ """gets a value for a plugin"""
+ ret = self.get(*args)
+ print "Deprecated method getPlugin%s -> %s" % (str(args), ret)
+ return ret
+
+ def setPlugin(self, *args):
+ """sets a value for a plugin"""
+ print "Deprecated method setPlugin%s" % str(args)
+ self.set(*args)
+
+ def getMetaData(self, section, option):
+ """ get all config data for an option """
+ return self.config[section].config[option]
+
+ def getBaseSections(self):
+ for section, data in self.config.iteritems():
+ if section in self.baseSections:
+ yield section, data
+ return
+
+ def getPluginSections(self):
+ for section, data in self.config.iteritems():
+ if section not in self.baseSections:
+ yield section, data
+ return
+
+ def addConfigSection(self, section, name, desc, long_desc, config, base=False):
+ """Adds a section to the config. `config` is a list of config tuples as used in plugin api definied as:
+ Either (name, type, verbose_name, default_value) or
+ (name, type, verbose_name, short_description, default_value)
+ The ordner of the config elements are preserved with OrdererDict
+ """
+ d = OrderedDict()
+
+ for entry in config:
+ if len(entry) == 5:
+ conf_name, type, conf_desc, conf_verbose, default = entry
+ else: # config options without tooltip / description
+ conf_name, type, conf_desc, default = entry
+ conf_verbose = ""
+
+ d[conf_name] = ConfigData(gettext(conf_desc), type, gettext(conf_verbose), from_string(default, type))
+
+ if base:
+ self.baseSections.append(section)
+ else:
+ if section in self.config:
+ print "Section already exists", section
+ return
+
+ data = SectionTuple(gettext(name), gettext(desc), gettext(long_desc), d)
+ self.config[section] = data
+
+ if section not in self.values:
+ self.values[section] = {}
+
+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)
+
+
+if __name__ == "__main__":
+ pypath = ""
+
+ from time import time
+
+ a = time()
+
+ c = ConfigParser()
+
+ b = time()
+
+ print "sec", b - a
+
+ print c.config
+
+ c.saveConfig(c.config, "user.conf")
diff --git a/module/config/__init__.py b/module/config/__init__.py
new file mode 100644
index 000000000..4b31e848b
--- /dev/null
+++ b/module/config/__init__.py
@@ -0,0 +1 @@
+__author__ = 'christian'
diff --git a/module/config/converter.py b/module/config/converter.py
new file mode 100644
index 000000000..f3b4dc327
--- /dev/null
+++ b/module/config/converter.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from module.utils import decode
+
+def to_string(value):
+ return str(value) if not isinstance(value, basestring) else value
+
+# cast value to given type, unicode for strings
+def from_string(value, typ=None):
+
+ # value is no string
+ if not isinstance(value, basestring):
+ return value
+
+ value = decode(value)
+
+ if typ == "int":
+ return int(value)
+ elif typ == "bool":
+ return True if value.lower() in ("1", "true", "on", "an", "yes") else False
+ elif typ == "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/module/config/default.conf b/module/config/default.conf
deleted file mode 100644
index 335ca10fe..000000000
--- a/module/config/default.conf
+++ /dev/null
@@ -1,65 +0,0 @@
-version: 1
-
-remote - "Remote":
- int port : "Port" = 7227
- ip listenaddr : "Adress" = 0.0.0.0
- bool nolocalauth : "No authentication on local connections" = True
- bool activated : "Activated" = True
-ssl - "SSL":
- bool activated : "Activated"= False
- file cert : "SSL Certificate" = ssl.crt
- file key : "SSL Key" = ssl.key
-webinterface - "Webinterface":
- bool activated : "Activated" = True
- builtin;threaded;fastcgi;lightweight server : "Server" = builtin
- bool https : "Use HTTPS" = False
- ip host : "IP" = 0.0.0.0
- int port : "Port" = 8001
- str template : "Template" = default
- str prefix: "Path Prefix" =
-log - "Log":
- bool file_log : "File Log" = True
- folder log_folder : "Folder" = Logs
- int log_count : "Count" = 5
- int log_size : "Size in kb" = 100
- bool log_rotate : "Log Rotate" = True
-general - "General":
- en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR language : "Language" = en
- folder download_folder : "Download Folder" = Downloads
- bool debug_mode : "Debug Mode" = False
- bool checksum : "Use Checksum" = False
- int min_free_space : "Min Free Space (MB)" = 200
- bool folder_per_package : "Create folder for each package" = True
- int renice : "CPU Priority" = 0
-download - "Download":
- int chunks : "Max connections for one download" = 3
- int max_downloads : "Max Parallel Downloads" = 3
- int max_speed : "Max Download Speed in kb/s" = -1
- bool limit_speed : "Limit Download Speed" = False
- str interface : "Download interface to bind (ip or Name)" = None
- bool ipv6 : "Allow IPv6" = False
- bool skip_existing : "Skip already existing files" = False
-permission - "Permissions":
- bool change_user : "Change user of running process" = False
- str user : "Username" = user
- str folder : "Folder Permission mode" = 0755
- bool change_file : "Change file mode of downloads" = False
- str file : "Filemode for Downloads" = 0644
- bool change_group : "Change group of running process" = False
- str group : "Groupname" = users
- bool change_dl : "Change Group and User of Downloads" = False
-reconnect - "Reconnect":
- bool activated : "Use Reconnect" = False
- str method : "Method" = None
- time startTime : "Start" = 0:00
- time endTime : "End" = 0:00
-downloadTime - "Download Time":
- time start : "Start" = 0:00
- time end : "End" = 0:00
-proxy - "Proxy":
- str address : "Address" = "localhost"
- int port : "Port" = 7070
- http;socks4;socks5 type : "Protocol" = http
- str username : "Username" = None
- password password : "Password" = None
- bool proxy : "Use Proxy" = False
diff --git a/module/config/default.py b/module/config/default.py
new file mode 100644
index 000000000..1dbb58eca
--- /dev/null
+++ b/module/config/default.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+"""
+Configuration layout for default base config
+"""
+
+#TODO: write tooltips and descriptions
+
+def make_config(config):
+ # Check if gettext is installed
+ _ = lambda x: x
+
+ config.addConfigSection("remote", _("Remote"), _("Description"), _("Long description"),
+ [
+ ("nolocalauth", "bool", _("No authentication on local connections"), _("Tooltip"), True),
+ ("activated", "bool", _("Activated"), _("Tooltip"), True),
+ ("port", "int", _("Port"), _("Tooltip"), 7227),
+ ("listenaddr", "ip", _("Adress"), _("Tooltip"), "0.0.0.0"),
+ ],
+ True)
+
+ config.addConfigSection("log", _("Log"), _("Description"), _("Long description"),
+ [
+ ("log_size", "int", _("Size in kb"), _("Tooltip"), 100),
+ ("log_folder", "folder", _("Folder"), _("Tooltip"), "Logs"),
+ ("file_log", "bool", _("File Log"), _("Tooltip"), True),
+ ("log_count", "int", _("Count"), _("Tooltip"), 5),
+ ("log_rotate", "bool", _("Log Rotate"), _("Tooltip"), True),
+ ],
+ True)
+
+ config.addConfigSection("permission", _("Permissions"), _("Description"), _("Long description"),
+ [
+ ("group", "str", _("Groupname"), _("Tooltip"), "users"),
+ ("change_dl", "bool", _("Change Group and User of Downloads"), _("Tooltip"), False),
+ ("change_file", "bool", _("Change file mode of downloads"), _("Tooltip"), False),
+ ("user", "str", _("Username"), _("Tooltip"), "user"),
+ ("file", "str", _("Filemode for Downloads"), _("Tooltip"), "0644"),
+ ("change_group", "bool", _("Change group of running process"), _("Tooltip"), False),
+ ("folder", "str", _("Folder Permission mode"), _("Tooltip"), "0755"),
+ ("change_user", "bool", _("Change user of running process"), _("Tooltip"), False),
+ ],
+ True)
+
+ config.addConfigSection("general", _("General"), _("Description"), _("Long description"),
+ [
+ ("language", "en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR", _("Language"), _("Tooltip"), "en"),
+ ("download_folder", "folder", _("Download Folder"), _("Tooltip"), "Downloads"),
+ ("checksum", "bool", _("Use Checksum"), _("Tooltip"), False),
+ ("folder_per_package", "bool", _("Create folder for each package"), _("Tooltip"), True),
+ ("debug_mode", "bool", _("Debug Mode"), _("Tooltip"), False),
+ ("min_free_space", "int", _("Min Free Space (MB)"), _("Tooltip"), 200),
+ ("renice", "int", _("CPU Priority"), _("Tooltip"), 0),
+ ],
+ True)
+
+ config.addConfigSection("ssl", _("SSL"), _("Description"), _("Long description"),
+ [
+ ("cert", "file", _("SSL Certificate"), _("Tooltip"), "ssl.crt"),
+ ("activated", "bool", _("Activated"), _("Tooltip"), False),
+ ("key", "file", _("SSL Key"), _("Tooltip"), "ssl.key"),
+ ],
+ True)
+
+ config.addConfigSection("webinterface", _("Webinterface"), _("Description"), _("Long description"),
+ [
+ ("template", "str", _("Template"), _("Tooltip"), "default"),
+ ("activated", "bool", _("Activated"), _("Tooltip"), True),
+ ("prefix", "str", _("Path Prefix"), _("Tooltip"), ""),
+ ("server", "builtin;threaded;fastcgi;lightweight", _("Server"), _("Tooltip"), "builtin"),
+ ("host", "ip", _("IP"), _("Tooltip"), "0.0.0.0"),
+ ("https", "bool", _("Use HTTPS"), _("Tooltip"), False),
+ ("port", "int", _("Port"), _("Tooltip"), 8001),
+ ],
+ True)
+
+ config.addConfigSection("proxy", _("Proxy"), _("Description"), _("Long description"),
+ [
+ ("username", "str", _("Username"), _("Tooltip"), ""),
+ ("proxy", "bool", _("Use Proxy"), _("Tooltip"), False),
+ ("address", "str", _("Address"), _("Tooltip"), "localhost"),
+ ("password", "password", _("Password"), _("Tooltip"), ""),
+ ("type", "http;socks4;socks5", _("Protocol"), _("Tooltip"), "http"),
+ ("port", "int", _("Port"), _("Tooltip"), 7070),
+ ],
+ True)
+
+ config.addConfigSection("reconnect", _("Reconnect"), _("Description"), _("Long description"),
+ [
+ ("endTime", "time", _("End"), _("Tooltip"), "0:00"),
+ ("activated", "bool", _("Use Reconnect"), _("Tooltip"), False),
+ ("method", "str", _("Method"), _("Tooltip"), "./reconnect.sh"),
+ ("startTime", "time", _("Start"), _("Tooltip"), "0:00"),
+ ],
+ True)
+
+ config.addConfigSection("download", _("Download"), _("Description"), _("Long description"),
+ [
+ ("max_downloads", "int", _("Max Parallel Downloads"), _("Tooltip"), 3),
+ ("limit_speed", "bool", _("Limit Download Speed"), _("Tooltip"), False),
+ ("interface", "str", _("Download interface to bind (ip or Name)"), _("Tooltip"), ""),
+ ("skip_existing", "bool", _("Skip already existing files"), _("Tooltip"), False),
+ ("max_speed", "int", _("Max Download Speed in kb/s"), _("Tooltip"), -1),
+ ("ipv6", "bool", _("Allow IPv6"), _("Tooltip"), False),
+ ("chunks", "int", _("Max connections for one download"), _("Tooltip"), 3),
+ ],
+ True)
+
+ config.addConfigSection("downloadTime", _("Download Time"), _("Description"), _("Long description"),
+ [
+ ("start", "time", _("Start"), _("Tooltip"), "0:00"),
+ ("end", "time", _("End"), _("Tooltip"), "0:00"),
+ ],
+ True)
diff --git a/module/config/gui_default.xml b/module/config/gui_default.xml
deleted file mode 100644
index 1faed776f..000000000
--- a/module/config/gui_default.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" ?>
-<root>
- <connections>
- <connection default="True" type="local" id="33965310e19b4a869112c43b39a16440">
- <name>Local</name>
- </connection>
- </connections>
- <mainWindow>
- <state></state>
- <geometry></geometry>
- </mainWindow>
- <language>en</language>
-</root>
diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py
index 9530390c3..db8b1aa3c 100644
--- a/module/database/DatabaseBackend.py
+++ b/module/database/DatabaseBackend.py
@@ -138,7 +138,6 @@ class DatabaseBackend(Thread):
self._convertDB(convert)
self._createTables()
- self._migrateUser()
self.conn.commit()
@@ -191,22 +190,10 @@ class DatabaseBackend(Thread):
print "Filedatabase could NOT be converted."
#--convert scripts start
-
- def _convertV2(self):
- self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
- try:
- self.manager.core.log.info(_("Database was converted from v2 to v3."))
- except:
- print "Database was converted from v2 to v3."
- self._convertV3()
-
- def _convertV3(self):
- self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
- try:
- self.manager.core.log.info(_("Database was converted from v3 to v4."))
- except:
- print "Database was converted from v3 to v4."
-
+
+ def _convertV4(self):
+ pass
+
#--convert scripts end
def _createTables(self):
@@ -246,25 +233,6 @@ class DatabaseBackend(Thread):
self.c.execute('VACUUM')
- def _migrateUser(self):
- if exists("pyload.db"):
- try:
- self.core.log.info(_("Converting old Django DB"))
- except:
- print "Converting old Django DB"
- conn = sqlite3.connect('pyload.db')
- c = conn.cursor()
- c.execute("SELECT username, password, email from auth_user WHERE is_superuser")
- users = []
- for r in c:
- pw = r[1].split("$")
- users.append((r[0], pw[1] + pw[2], r[2]))
- c.close()
- conn.close()
-
- self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users)
- move("pyload.db", "pyload.old.db")
-
def createCursor(self):
return self.conn.cursor()
diff --git a/module/lib/namedtuple.py b/module/lib/namedtuple.py
deleted file mode 100644
index 6ff07e839..000000000
--- a/module/lib/namedtuple.py
+++ /dev/null
@@ -1,114 +0,0 @@
-## {{{ http://code.activestate.com/recipes/500261/ (r15)
-from operator import itemgetter as _itemgetter
-from keyword import iskeyword as _iskeyword
-import sys as _sys
-
-def namedtuple(typename, field_names, verbose=False, rename=False):
- """Returns a new subclass of tuple with named fields.
-
- >>> Point = namedtuple('Point', 'x y')
- >>> Point.__doc__ # docstring for the new class
- 'Point(x, y)'
- >>> p = Point(11, y=22) # instantiate with positional args or keywords
- >>> p[0] + p[1] # indexable like a plain tuple
- 33
- >>> x, y = p # unpack like a regular tuple
- >>> x, y
- (11, 22)
- >>> p.x + p.y # fields also accessable by name
- 33
- >>> d = p._asdict() # convert to a dictionary
- >>> d['x']
- 11
- >>> Point(**d) # convert from a dictionary
- Point(x=11, y=22)
- >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
- Point(x=100, y=22)
-
- """
-
- # Parse and validate the field names. Validation serves two purposes,
- # generating informative error messages and preventing template injection attacks.
- if isinstance(field_names, basestring):
- field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
- field_names = tuple(map(str, field_names))
- if rename:
- names = list(field_names)
- seen = set()
- for i, name in enumerate(names):
- if (not min(c.isalnum() or c=='_' for c in name) or _iskeyword(name)
- or not name or name[0].isdigit() or name.startswith('_')
- or name in seen):
- names[i] = '_%d' % i
- seen.add(name)
- field_names = tuple(names)
- for name in (typename,) + field_names:
- if not min(c.isalnum() or c=='_' for c in name):
- raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
- if _iskeyword(name):
- raise ValueError('Type names and field names cannot be a keyword: %r' % name)
- if name[0].isdigit():
- raise ValueError('Type names and field names cannot start with a number: %r' % name)
- seen_names = set()
- for name in field_names:
- if name.startswith('_') and not rename:
- raise ValueError('Field names cannot start with an underscore: %r' % name)
- if name in seen_names:
- raise ValueError('Encountered duplicate field name: %r' % name)
- seen_names.add(name)
-
- # Create and fill-in the class template
- numfields = len(field_names)
- argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
- reprtxt = ', '.join('%s=%%r' % name for name in field_names)
- template = '''class %(typename)s(tuple):
- '%(typename)s(%(argtxt)s)' \n
- __slots__ = () \n
- _fields = %(field_names)r \n
- def __new__(_cls, %(argtxt)s):
- return _tuple.__new__(_cls, (%(argtxt)s)) \n
- @classmethod
- def _make(cls, iterable, new=tuple.__new__, len=len):
- 'Make a new %(typename)s object from a sequence or iterable'
- result = new(cls, iterable)
- if len(result) != %(numfields)d:
- raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
- return result \n
- def __repr__(self):
- return '%(typename)s(%(reprtxt)s)' %% self \n
- def _asdict(self):
- 'Return a new dict which maps field names to their values'
- return dict(zip(self._fields, self)) \n
- def _replace(_self, **kwds):
- 'Return a new %(typename)s object replacing specified fields with new values'
- result = _self._make(map(kwds.pop, %(field_names)r, _self))
- if kwds:
- raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
- return result \n
- def __getnewargs__(self):
- return tuple(self) \n\n''' % locals()
- for i, name in enumerate(field_names):
- template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
- if verbose:
- print template
-
- # Execute the template string in a temporary namespace
- namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
- _property=property, _tuple=tuple)
- try:
- exec template in namespace
- except SyntaxError, e:
- raise SyntaxError(e.message + ':\n' + template)
- result = namespace[typename]
-
- # For pickling to work, the __module__ variable needs to be set to the frame
- # where the named tuple is created. Bypass this step in enviroments where
- # sys._getframe is not defined (Jython for example) or sys._getframe is not
- # defined for arguments greater than 0 (IronPython).
- try:
- result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
- except (AttributeError, ValueError):
- pass
-
- return result
-## end of http://code.activestate.com/recipes/500261/ }}}
diff --git a/module/lib/new_collections.py b/module/lib/new_collections.py
new file mode 100644
index 000000000..12d05b4b9
--- /dev/null
+++ b/module/lib/new_collections.py
@@ -0,0 +1,375 @@
+## {{{ http://code.activestate.com/recipes/576693/ (r9)
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+
+try:
+ from thread import get_ident as _get_ident
+except ImportError:
+ from dummy_thread import get_ident as _get_ident
+
+try:
+ from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+ pass
+
+
+class OrderedDict(dict):
+ 'Dictionary that remembers insertion order'
+ # An inherited dict maps keys to values.
+ # The inherited dict provides __getitem__, __len__, __contains__, and get.
+ # The remaining methods are order-aware.
+ # Big-O running times for all methods are the same as for regular dictionaries.
+
+ # The internal self.__map dictionary maps keys to links in a doubly linked list.
+ # The circular doubly linked list starts and ends with a sentinel element.
+ # The sentinel element never gets deleted (this simplifies the algorithm).
+ # Each link is stored as a list of length three: [PREV, NEXT, KEY].
+
+ def __init__(self, *args, **kwds):
+ '''Initialize an ordered dictionary. Signature is the same as for
+ regular dictionaries, but keyword arguments are not recommended
+ because their insertion order is arbitrary.
+
+ '''
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ try:
+ self.__root
+ except AttributeError:
+ self.__root = root = [] # sentinel node
+ root[:] = [root, root, None]
+ self.__map = {}
+ self.__update(*args, **kwds)
+
+ def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+ 'od.__setitem__(i, y) <==> od[i]=y'
+ # Setting a new item creates a new link which goes at the end of the linked
+ # list, and the inherited dictionary is updated with the new key/value pair.
+ if key not in self:
+ root = self.__root
+ last = root[0]
+ last[1] = root[0] = self.__map[key] = [last, root, key]
+ dict_setitem(self, key, value)
+
+ def __delitem__(self, key, dict_delitem=dict.__delitem__):
+ 'od.__delitem__(y) <==> del od[y]'
+ # Deleting an existing item uses self.__map to find the link which is
+ # then removed by updating the links in the predecessor and successor nodes.
+ dict_delitem(self, key)
+ link_prev, link_next, key = self.__map.pop(key)
+ link_prev[1] = link_next
+ link_next[0] = link_prev
+
+ def __iter__(self):
+ 'od.__iter__() <==> iter(od)'
+ root = self.__root
+ curr = root[1]
+ while curr is not root:
+ yield curr[2]
+ curr = curr[1]
+
+ def __reversed__(self):
+ 'od.__reversed__() <==> reversed(od)'
+ root = self.__root
+ curr = root[0]
+ while curr is not root:
+ yield curr[2]
+ curr = curr[0]
+
+ def clear(self):
+ 'od.clear() -> None. Remove all items from od.'
+ try:
+ for node in self.__map.itervalues():
+ del node[:]
+ root = self.__root
+ root[:] = [root, root, None]
+ self.__map.clear()
+ except AttributeError:
+ pass
+ dict.clear(self)
+
+ def popitem(self, last=True):
+ '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+ Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+ '''
+ if not self:
+ raise KeyError('dictionary is empty')
+ root = self.__root
+ if last:
+ link = root[0]
+ link_prev = link[0]
+ link_prev[1] = root
+ root[0] = link_prev
+ else:
+ link = root[1]
+ link_next = link[1]
+ root[1] = link_next
+ link_next[0] = root
+ key = link[2]
+ del self.__map[key]
+ value = dict.pop(self, key)
+ return key, value
+
+ # -- the following methods do not depend on the internal structure --
+
+ def keys(self):
+ 'od.keys() -> list of keys in od'
+ return list(self)
+
+ def values(self):
+ 'od.values() -> list of values in od'
+ return [self[key] for key in self]
+
+ def items(self):
+ 'od.items() -> list of (key, value) pairs in od'
+ return [(key, self[key]) for key in self]
+
+ def iterkeys(self):
+ 'od.iterkeys() -> an iterator over the keys in od'
+ return iter(self)
+
+ def itervalues(self):
+ 'od.itervalues -> an iterator over the values in od'
+ for k in self:
+ yield self[k]
+
+ def iteritems(self):
+ 'od.iteritems -> an iterator over the (key, value) items in od'
+ for k in self:
+ yield (k, self[k])
+
+ def update(*args, **kwds):
+ '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
+
+ If E is a dict instance, does: for k in E: od[k] = E[k]
+ If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
+ Or if E is an iterable of items, does: for k, v in E: od[k] = v
+ In either case, this is followed by: for k, v in F.items(): od[k] = v
+
+ '''
+ if len(args) > 2:
+ raise TypeError('update() takes at most 2 positional '
+ 'arguments (%d given)' % (len(args),))
+ elif not args:
+ raise TypeError('update() takes at least 1 argument (0 given)')
+ self = args[0]
+ # Make progressively weaker assumptions about "other"
+ other = ()
+ if len(args) == 2:
+ other = args[1]
+ if isinstance(other, dict):
+ for key in other:
+ self[key] = other[key]
+ elif hasattr(other, 'keys'):
+ for key in other.keys():
+ self[key] = other[key]
+ else:
+ for key, value in other:
+ self[key] = value
+ for key, value in kwds.items():
+ self[key] = value
+
+ __update = update # let subclasses override update without breaking __init__
+
+ __marker = object()
+
+ def pop(self, key, default=__marker):
+ '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+ If key is not found, d is returned if given, otherwise KeyError is raised.
+
+ '''
+ if key in self:
+ result = self[key]
+ del self[key]
+ return result
+ if default is self.__marker:
+ raise KeyError(key)
+ return default
+
+ def setdefault(self, key, default=None):
+ 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+ if key in self:
+ return self[key]
+ self[key] = default
+ return default
+
+ def __repr__(self, _repr_running={}):
+ 'od.__repr__() <==> repr(od)'
+ call_key = id(self), _get_ident()
+ if call_key in _repr_running:
+ return '...'
+ _repr_running[call_key] = 1
+ try:
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, self.items())
+ finally:
+ del _repr_running[call_key]
+
+ def __reduce__(self):
+ 'Return state information for pickling'
+ items = [[k, self[k]] for k in self]
+ inst_dict = vars(self).copy()
+ for k in vars(OrderedDict()):
+ inst_dict.pop(k, None)
+ if inst_dict:
+ return (self.__class__, (items,), inst_dict)
+ return self.__class__, (items,)
+
+ def copy(self):
+ 'od.copy() -> a shallow copy of od'
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+ and values equal to v (which defaults to None).
+
+ '''
+ d = cls()
+ for key in iterable:
+ d[key] = value
+ return d
+
+ def __eq__(self, other):
+ '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
+ while comparison to a regular mapping is order-insensitive.
+
+ '''
+ if isinstance(other, OrderedDict):
+ return len(self)==len(other) and self.items() == other.items()
+ return dict.__eq__(self, other)
+
+ def __ne__(self, other):
+ return not self == other
+
+ # -- the following methods are only used in Python 2.7 --
+
+ def viewkeys(self):
+ "od.viewkeys() -> a set-like object providing a view on od's keys"
+ return KeysView(self)
+
+ def viewvalues(self):
+ "od.viewvalues() -> an object providing a view on od's values"
+ return ValuesView(self)
+
+ def viewitems(self):
+ "od.viewitems() -> a set-like object providing a view on od's items"
+ return ItemsView(self)
+## end of http://code.activestate.com/recipes/576693/ }}}
+
+## {{{ http://code.activestate.com/recipes/500261/ (r15)
+from operator import itemgetter as _itemgetter
+from keyword import iskeyword as _iskeyword
+import sys as _sys
+
+def namedtuple(typename, field_names, verbose=False, rename=False):
+ """Returns a new subclass of tuple with named fields.
+
+ >>> Point = namedtuple('Point', 'x y')
+ >>> Point.__doc__ # docstring for the new class
+ 'Point(x, y)'
+ >>> p = Point(11, y=22) # instantiate with positional args or keywords
+ >>> p[0] + p[1] # indexable like a plain tuple
+ 33
+ >>> x, y = p # unpack like a regular tuple
+ >>> x, y
+ (11, 22)
+ >>> p.x + p.y # fields also accessable by name
+ 33
+ >>> d = p._asdict() # convert to a dictionary
+ >>> d['x']
+ 11
+ >>> Point(**d) # convert from a dictionary
+ Point(x=11, y=22)
+ >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
+ Point(x=100, y=22)
+
+ """
+
+ # Parse and validate the field names. Validation serves two purposes,
+ # generating informative error messages and preventing template injection attacks.
+ if isinstance(field_names, basestring):
+ field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
+ field_names = tuple(map(str, field_names))
+ if rename:
+ names = list(field_names)
+ seen = set()
+ for i, name in enumerate(names):
+ if (not min(c.isalnum() or c=='_' for c in name) or _iskeyword(name)
+ or not name or name[0].isdigit() or name.startswith('_')
+ or name in seen):
+ names[i] = '_%d' % i
+ seen.add(name)
+ field_names = tuple(names)
+ for name in (typename,) + field_names:
+ if not min(c.isalnum() or c=='_' for c in name):
+ raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
+ if _iskeyword(name):
+ raise ValueError('Type names and field names cannot be a keyword: %r' % name)
+ if name[0].isdigit():
+ raise ValueError('Type names and field names cannot start with a number: %r' % name)
+ seen_names = set()
+ for name in field_names:
+ if name.startswith('_') and not rename:
+ raise ValueError('Field names cannot start with an underscore: %r' % name)
+ if name in seen_names:
+ raise ValueError('Encountered duplicate field name: %r' % name)
+ seen_names.add(name)
+
+ # Create and fill-in the class template
+ numfields = len(field_names)
+ argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
+ reprtxt = ', '.join('%s=%%r' % name for name in field_names)
+ template = '''class %(typename)s(tuple):
+ '%(typename)s(%(argtxt)s)' \n
+ __slots__ = () \n
+ _fields = %(field_names)r \n
+ def __new__(_cls, %(argtxt)s):
+ return _tuple.__new__(_cls, (%(argtxt)s)) \n
+ @classmethod
+ def _make(cls, iterable, new=tuple.__new__, len=len):
+ 'Make a new %(typename)s object from a sequence or iterable'
+ result = new(cls, iterable)
+ if len(result) != %(numfields)d:
+ raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
+ return result \n
+ def __repr__(self):
+ return '%(typename)s(%(reprtxt)s)' %% self \n
+ def _asdict(self):
+ 'Return a new dict which maps field names to their values'
+ return dict(zip(self._fields, self)) \n
+ def _replace(_self, **kwds):
+ 'Return a new %(typename)s object replacing specified fields with new values'
+ result = _self._make(map(kwds.pop, %(field_names)r, _self))
+ if kwds:
+ raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
+ return result \n
+ def __getnewargs__(self):
+ return tuple(self) \n\n''' % locals()
+ for i, name in enumerate(field_names):
+ template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
+ if verbose:
+ print template
+
+ # Execute the template string in a temporary namespace
+ namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
+ _property=property, _tuple=tuple)
+ try:
+ exec template in namespace
+ except SyntaxError, e:
+ raise SyntaxError(e.message + ':\n' + template)
+ result = namespace[typename]
+
+ # For pickling to work, the __module__ variable needs to be set to the frame
+ # where the named tuple is created. Bypass this step in enviroments where
+ # sys._getframe is not defined (Jython for example) or sys._getframe is not
+ # defined for arguments greater than 0 (IronPython).
+ try:
+ result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ pass
+
+ return result
+## end of http://code.activestate.com/recipes/500261/ }}}
diff --git a/module/plugins/Base.py b/module/plugins/Base.py
index b166ebae8..5a78fddc3 100644
--- a/module/plugins/Base.py
+++ b/module/plugins/Base.py
@@ -75,7 +75,7 @@ class Base(object):
def setConf(self, option, value):
""" see `setConfig` """
- self.core.config.setPlugin(self.__name__, option, value)
+ self.core.config.set(self.__name__, option, value)
def setConfig(self, option, value):
""" Set config value for current plugin
@@ -88,7 +88,7 @@ class Base(object):
def getConf(self, option):
""" see `getConfig` """
- return self.core.config.getPlugin(self.__name__, option)
+ return self.core.config.get(self.__name__, option)
def getConfig(self, option):
""" Returns config value for current plugin
diff --git a/module/plugins/Hook.py b/module/plugins/Hook.py
index 5efd08bae..860dc76bb 100644
--- a/module/plugins/Hook.py
+++ b/module/plugins/Hook.py
@@ -119,7 +119,7 @@ class Hook(Base):
def isActivated(self):
""" checks if hook is activated"""
- return self.config.getPlugin(self.__name__, "activated")
+ return self.config.get(self.__name__, "activated")
#event methods - overwrite these if needed
diff --git a/module/plugins/Plugin.py b/module/plugins/Plugin.py
index b3c22f983..327e717a0 100644
--- a/module/plugins/Plugin.py
+++ b/module/plugins/Plugin.py
@@ -258,7 +258,7 @@ class Plugin(Base):
temp_file.write(img)
temp_file.close()
- has_plugin = self.__name__ in self.core.pluginManager.captchaPlugins
+ has_plugin = self.__name__ in self.core.pluginManager.getPlugins("captcha")
if self.core.captcha:
Ocr = self.core.pluginManager.loadClass("captcha", self.__name__)
diff --git a/module/plugins/PluginManager.py b/module/plugins/PluginManager.py
index b1a82bcb6..9845590cf 100644
--- a/module/plugins/PluginManager.py
+++ b/module/plugins/PluginManager.py
@@ -27,12 +27,17 @@ from time import time
from traceback import print_exc
from module.lib.SafeEval import const_eval as literal_eval
-from module.ConfigParser import IGNORE
from module.plugins.Base import Base
-from namedtuple import namedtuple
+from new_collections import namedtuple
-PluginTuple = namedtuple("PluginTuple", "version re desc long_desc deps user path")
+# ignore these plugin configs, mainly because plugins were wiped out
+IGNORE = (
+ "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('hooks', 'UnRar'),
+ 'EasyShareCom', 'FlyshareCz'
+ )
+
+PluginTuple = namedtuple("PluginTuple", "version re deps user path")
class PluginManager:
ROOT = "module.plugins."
@@ -57,6 +62,9 @@ class PluginManager:
self.history = [] # match history to speedup parsing (type, name)
self.createIndex()
+
+ self.core.config.parseValues(self.core.config.PLUGIN)
+
#register for import hook
sys.meta_path.append(self)
@@ -168,19 +176,23 @@ class PluginManager:
plugin_re = None
else: plugin_re = None
+ deps = attrs.get("dependencies", None)
# create plugin tuple
- plugin = PluginTuple(version, plugin_re, None, None, None, bool(home), filename)
+ plugin = PluginTuple(version, plugin_re, deps, bool(home), filename)
# internals have no config
if folder == "internal":
- self.core.config.deleteConfig(name)
return plugin
+ if folder == "hooks" and "config" not in attrs:
+ attrs["config"] = (["activated", "bool", "Activated", False],)
+
if "config" in attrs and attrs["config"]:
config = attrs["config"]
- desc = attrs["description"] if "description" in attrs else ""
+ desc = attrs.get("description", "")
+ long_desc = attrs.get("long_description", "")
if type(config[0]) == tuple:
config = [list(x) for x in config]
@@ -193,21 +205,12 @@ class PluginManager:
if item[0] == "activated": append = False
# activated flag missing
- if append: config.append(["activated", "bool", "Activated", False])
-
- try:
- self.core.config.addPluginConfig(name, config, desc)
- except:
- self.log.error("Invalid config in %s: %s" % (name, config))
-
- elif folder == "hooks": #force config creation
- desc = attrs["description"] if "description" in attrs else ""
- config = (["activated", "bool", "Activated", False],)
+ if append: config.insert(0, ("activated", "bool", "Activated", False))
try:
- self.core.config.addPluginConfig(name, config, desc)
+ self.core.config.addConfigSection(name, name, desc, long_desc, config)
except:
- self.log.error("Invalid config in %s: %s" % (name, config))
+ self.logDebug(folder, name, "Invalid config %s" % config)
return plugin
@@ -294,7 +297,7 @@ class PluginManager:
# convert path to python recognizable import
path = basename(plugins[name].path).replace(".pyc", "").replace(".py", "")
module = __import__(self.ROOT + "%s.%s" % (type, path), globals(), locals(), path)
- self.modules[(type, name)] = module # cache import, maybne unneeded
+ self.modules[(type, name)] = module # cache import, maybe unneeded
return module
except Exception, e:
self.log.error(_("Error importing %(name)s: %(msg)s") % {"name": name, "msg": str(e)})
diff --git a/module/plugins/hooks/UpdateManager.py b/module/plugins/hooks/UpdateManager.py
index 8cbc56c42..4324a96ba 100644
--- a/module/plugins/hooks/UpdateManager.py
+++ b/module/plugins/hooks/UpdateManager.py
@@ -24,7 +24,7 @@ from os import stat
from os.path import join, exists
from time import time
-from module.ConfigParser import IGNORE
+from module.plugins.PluginManager import IGNORE
from module.network.RequestFactory import getURL
from module.plugins.Hook import threaded, Expose, Hook
diff --git a/module/remote/socketbackend/ttypes.py b/module/remote/socketbackend/ttypes.py
index f8ea121fa..ff5ae2467 100644
--- a/module/remote/socketbackend/ttypes.py
+++ b/module/remote/socketbackend/ttypes.py
@@ -71,22 +71,26 @@ class CaptchaTask(BaseObject):
self.resultType = resultType
class ConfigItem(BaseObject):
- __slots__ = ['name', 'description', 'value', 'type']
+ __slots__ = ['name', 'long_name', 'description', 'type', 'default_value', 'value']
- def __init__(self, name=None, description=None, value=None, type=None):
+ def __init__(self, name=None, long_name=None, description=None, type=None, default_value=None, value=None):
self.name = name
+ self.long_name = long_name
self.description = description
- self.value = value
self.type = type
+ self.default_value = default_value
+ self.value = value
class ConfigSection(BaseObject):
- __slots__ = ['name', 'description', 'items', 'outline']
+ __slots__ = ['name', 'long_name', 'description', 'long_description', 'items', 'handler']
- def __init__(self, name=None, description=None, items=None, outline=None):
+ def __init__(self, name=None, long_name=None, description=None, long_description=None, items=None, handler=None):
self.name = name
+ self.long_name = long_name
self.description = description
+ self.long_description = long_description
self.items = items
- self.outline = outline
+ self.handler = handler
class DownloadInfo(BaseObject):
__slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin']
@@ -252,6 +256,8 @@ class Iface:
pass
def checkURLs(self, urls):
pass
+ def configureSection(self, section):
+ pass
def deleteFiles(self, fids):
pass
def deleteFinished(self):
@@ -282,7 +288,7 @@ class Iface:
pass
def getConfig(self):
pass
- def getConfigValue(self, category, option, section):
+ def getConfigValue(self, section, option):
pass
def getEvents(self, uuid):
pass
@@ -356,7 +362,7 @@ class Iface:
pass
def setCaptchaResult(self, tid, result):
pass
- def setConfigValue(self, category, option, value, section):
+ def setConfigValue(self, section, option, value):
pass
def setPackageData(self, pid, data):
pass
diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift
index 1542e651a..ace24e709 100644
--- a/module/remote/thriftbackend/pyload.thrift
+++ b/module/remote/thriftbackend/pyload.thrift
@@ -93,20 +93,6 @@ struct ServerStatus {
7: bool reconnect
}
-struct ConfigItem {
- 1: string name,
- 2: string description,
- 3: string value,
- 4: string type,
-}
-
-struct ConfigSection {
- 1: string name,
- 2: string description,
- 3: list<ConfigItem> items,
- 4: optional string outline
-}
-
struct FileData {
1: FileID fid,
2: string url,
@@ -149,6 +135,24 @@ struct InteractionTask {
9: string plugin,
}
+struct ConfigItem {
+ 1: string name,
+ 2: string long_name,
+ 3: string description,
+ 4: string type,
+ 5: string default_value,
+ 6: string value,
+}
+
+struct ConfigSection {
+ 1: string name,
+ 2: string long_name,
+ 3: string description,
+ 4: string long_description,
+ 5: optional list<ConfigItem> items,
+ 6: optional map<string, InteractionTask> handler,
+}
+
struct CaptchaTask {
1: i16 tid,
2: binary data,
@@ -225,10 +229,11 @@ exception ServiceException{
service Pyload {
//config
- string getConfigValue(1: string category, 2: string option, 3: string section),
- void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section),
+ string getConfigValue(1: string section, 2: string option),
+ void setConfigValue(1: string section, 2: string option, 3: string value),
map<string, ConfigSection> getConfig(),
- map<string, ConfigSection> getPluginConfig(),
+ map<PluginName, ConfigSection> getPluginConfig(),
+ ConfigSection configureSection(1: string section),
// server status
void pauseServer(),
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
index bfaf5b078..f8bcc2863 100755
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
+++ b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
@@ -23,10 +23,11 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]'
print ''
print 'Functions:'
- print ' string getConfigValue(string category, string option, string section)'
- print ' void setConfigValue(string category, string option, string value, string section)'
+ print ' string getConfigValue(string section, string option)'
+ print ' void setConfigValue(string section, string option, string value)'
print ' getConfig()'
print ' getPluginConfig()'
+ print ' ConfigSection configureSection(string section)'
print ' void pauseServer()'
print ' void unpauseServer()'
print ' bool togglePause()'
@@ -145,16 +146,16 @@ client = Pyload.Client(protocol)
transport.open()
if cmd == 'getConfigValue':
- if len(args) != 3:
- print 'getConfigValue requires 3 args'
+ if len(args) != 2:
+ print 'getConfigValue requires 2 args'
sys.exit(1)
- pp.pprint(client.getConfigValue(args[0],args[1],args[2],))
+ pp.pprint(client.getConfigValue(args[0],args[1],))
elif cmd == 'setConfigValue':
- if len(args) != 4:
- print 'setConfigValue requires 4 args'
+ if len(args) != 3:
+ print 'setConfigValue requires 3 args'
sys.exit(1)
- pp.pprint(client.setConfigValue(args[0],args[1],args[2],args[3],))
+ pp.pprint(client.setConfigValue(args[0],args[1],args[2],))
elif cmd == 'getConfig':
if len(args) != 0:
@@ -168,6 +169,12 @@ elif cmd == 'getPluginConfig':
sys.exit(1)
pp.pprint(client.getPluginConfig())
+elif cmd == 'configureSection':
+ if len(args) != 1:
+ print 'configureSection requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.configureSection(args[0],))
+
elif cmd == 'pauseServer':
if len(args) != 0:
print 'pauseServer requires 0 args'
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
index 78a42f16a..1e2f78b66 100644
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
+++ b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
@@ -13,22 +13,20 @@ from thrift.protocol.TBase import TBase, TExceptionBase
class Iface(object):
- def getConfigValue(self, category, option, section):
+ def getConfigValue(self, section, option):
"""
Parameters:
- - category
- - option
- section
+ - option
"""
pass
- def setConfigValue(self, category, option, value, section):
+ def setConfigValue(self, section, option, value):
"""
Parameters:
- - category
+ - section
- option
- value
- - section
"""
pass
@@ -38,6 +36,13 @@ class Iface(object):
def getPluginConfig(self, ):
pass
+ def configureSection(self, section):
+ """
+ Parameters:
+ - section
+ """
+ pass
+
def pauseServer(self, ):
pass
@@ -434,22 +439,20 @@ class Client(Iface):
self._oprot = oprot
self._seqid = 0
- def getConfigValue(self, category, option, section):
+ def getConfigValue(self, section, option):
"""
Parameters:
- - category
- - option
- section
+ - option
"""
- self.send_getConfigValue(category, option, section)
+ self.send_getConfigValue(section, option)
return self.recv_getConfigValue()
- def send_getConfigValue(self, category, option, section):
+ def send_getConfigValue(self, section, option):
self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid)
args = getConfigValue_args()
- args.category = category
- args.option = option
args.section = section
+ args.option = option
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
@@ -468,24 +471,22 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result");
- def setConfigValue(self, category, option, value, section):
+ def setConfigValue(self, section, option, value):
"""
Parameters:
- - category
+ - section
- option
- value
- - section
"""
- self.send_setConfigValue(category, option, value, section)
+ self.send_setConfigValue(section, option, value)
self.recv_setConfigValue()
- def send_setConfigValue(self, category, option, value, section):
+ def send_setConfigValue(self, section, option, value):
self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid)
args = setConfigValue_args()
- args.category = category
+ args.section = section
args.option = option
args.value = value
- args.section = section
args.write(self._oprot)
self._oprot.writeMessageEnd()
self._oprot.trans.flush()
@@ -552,6 +553,36 @@ class Client(Iface):
return result.success
raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result");
+ def configureSection(self, section):
+ """
+ Parameters:
+ - section
+ """
+ self.send_configureSection(section)
+ return self.recv_configureSection()
+
+ def send_configureSection(self, section):
+ self._oprot.writeMessageBegin('configureSection', TMessageType.CALL, self._seqid)
+ args = configureSection_args()
+ args.section = section
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_configureSection(self, ):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = configureSection_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "configureSection failed: unknown result");
+
def pauseServer(self, ):
self.send_pauseServer()
self.recv_pauseServer()
@@ -2427,6 +2458,7 @@ class Processor(Iface, TProcessor):
self._processMap["setConfigValue"] = Processor.process_setConfigValue
self._processMap["getConfig"] = Processor.process_getConfig
self._processMap["getPluginConfig"] = Processor.process_getPluginConfig
+ self._processMap["configureSection"] = Processor.process_configureSection
self._processMap["pauseServer"] = Processor.process_pauseServer
self._processMap["unpauseServer"] = Processor.process_unpauseServer
self._processMap["togglePause"] = Processor.process_togglePause
@@ -2514,7 +2546,7 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = getConfigValue_result()
- result.success = self._handler.getConfigValue(args.category, args.option, args.section)
+ result.success = self._handler.getConfigValue(args.section, args.option)
oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
@@ -2525,7 +2557,7 @@ class Processor(Iface, TProcessor):
args.read(iprot)
iprot.readMessageEnd()
result = setConfigValue_result()
- self._handler.setConfigValue(args.category, args.option, args.value, args.section)
+ self._handler.setConfigValue(args.section, args.option, args.value)
oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
@@ -2553,6 +2585,17 @@ class Processor(Iface, TProcessor):
oprot.writeMessageEnd()
oprot.trans.flush()
+ def process_configureSection(self, seqid, iprot, oprot):
+ args = configureSection_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = configureSection_result()
+ result.success = self._handler.configureSection(args.section)
+ oprot.writeMessageBegin("configureSection", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
def process_pauseServer(self, seqid, iprot, oprot):
args = pauseServer_args()
args.read(iprot)
@@ -3302,28 +3345,24 @@ class Processor(Iface, TProcessor):
class getConfigValue_args(TBase):
"""
Attributes:
- - category
- - option
- section
+ - option
"""
__slots__ = [
- 'category',
- 'option',
'section',
+ 'option',
]
thrift_spec = (
None, # 0
- (1, TType.STRING, 'category', None, None, ), # 1
+ (1, TType.STRING, 'section', None, None, ), # 1
(2, TType.STRING, 'option', None, None, ), # 2
- (3, TType.STRING, 'section', None, None, ), # 3
)
- def __init__(self, category=None, option=None, section=None,):
- self.category = category
- self.option = option
+ def __init__(self, section=None, option=None,):
self.section = section
+ self.option = option
class getConfigValue_result(TBase):
@@ -3347,32 +3386,28 @@ class getConfigValue_result(TBase):
class setConfigValue_args(TBase):
"""
Attributes:
- - category
+ - section
- option
- value
- - section
"""
__slots__ = [
- 'category',
+ 'section',
'option',
'value',
- 'section',
]
thrift_spec = (
None, # 0
- (1, TType.STRING, 'category', None, None, ), # 1
+ (1, TType.STRING, 'section', None, None, ), # 1
(2, TType.STRING, 'option', None, None, ), # 2
(3, TType.STRING, 'value', None, None, ), # 3
- (4, TType.STRING, 'section', None, None, ), # 4
)
- def __init__(self, category=None, option=None, value=None, section=None,):
- self.category = category
+ def __init__(self, section=None, option=None, value=None,):
+ self.section = section
self.option = option
self.value = value
- self.section = section
class setConfigValue_result(TBase):
@@ -3438,6 +3473,43 @@ class getPluginConfig_result(TBase):
self.success = success
+class configureSection_args(TBase):
+ """
+ Attributes:
+ - section
+ """
+
+ __slots__ = [
+ 'section',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'section', None, None, ), # 1
+ )
+
+ def __init__(self, section=None,):
+ self.section = section
+
+
+class configureSection_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (ConfigSection, ConfigSection.thrift_spec), None, ), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
class pauseServer_args(TBase):
__slots__ = [
diff --git a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
index 1299b515d..3ccca992b 100644
--- a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
+++ b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
@@ -270,68 +270,6 @@ class ServerStatus(TBase):
self.reconnect = reconnect
-class ConfigItem(TBase):
- """
- Attributes:
- - name
- - description
- - value
- - type
- """
-
- __slots__ = [
- 'name',
- 'description',
- 'value',
- 'type',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'description', None, None, ), # 2
- (3, TType.STRING, 'value', None, None, ), # 3
- (4, TType.STRING, 'type', None, None, ), # 4
- )
-
- def __init__(self, name=None, description=None, value=None, type=None,):
- self.name = name
- self.description = description
- self.value = value
- self.type = type
-
-
-class ConfigSection(TBase):
- """
- Attributes:
- - name
- - description
- - items
- - outline
- """
-
- __slots__ = [
- 'name',
- 'description',
- 'items',
- 'outline',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'description', None, None, ), # 2
- (3, TType.LIST, 'items', (TType.STRUCT,(ConfigItem, ConfigItem.thrift_spec)), None, ), # 3
- (4, TType.STRING, 'outline', None, None, ), # 4
- )
-
- def __init__(self, name=None, description=None, items=None, outline=None,):
- self.name = name
- self.description = description
- self.items = items
- self.outline = outline
-
-
class FileData(TBase):
"""
Attributes:
@@ -509,6 +447,84 @@ class InteractionTask(TBase):
self.plugin = plugin
+class ConfigItem(TBase):
+ """
+ Attributes:
+ - name
+ - long_name
+ - description
+ - type
+ - default_value
+ - value
+ """
+
+ __slots__ = [
+ 'name',
+ 'long_name',
+ 'description',
+ 'type',
+ 'default_value',
+ 'value',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.STRING, 'long_name', None, None, ), # 2
+ (3, TType.STRING, 'description', None, None, ), # 3
+ (4, TType.STRING, 'type', None, None, ), # 4
+ (5, TType.STRING, 'default_value', None, None, ), # 5
+ (6, TType.STRING, 'value', None, None, ), # 6
+ )
+
+ def __init__(self, name=None, long_name=None, description=None, type=None, default_value=None, value=None,):
+ self.name = name
+ self.long_name = long_name
+ self.description = description
+ self.type = type
+ self.default_value = default_value
+ self.value = value
+
+
+class ConfigSection(TBase):
+ """
+ Attributes:
+ - name
+ - long_name
+ - description
+ - long_description
+ - items
+ - handler
+ """
+
+ __slots__ = [
+ 'name',
+ 'long_name',
+ 'description',
+ 'long_description',
+ 'items',
+ 'handler',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None, ), # 1
+ (2, TType.STRING, 'long_name', None, None, ), # 2
+ (3, TType.STRING, 'description', None, None, ), # 3
+ (4, TType.STRING, 'long_description', None, None, ), # 4
+ (5, TType.LIST, 'items', (TType.STRUCT,(ConfigItem, ConfigItem.thrift_spec)), None, ), # 5
+ (6, TType.MAP, 'handler', (TType.STRING,None,TType.STRUCT,(InteractionTask, InteractionTask.thrift_spec)), None, ), # 6
+ )
+
+ def __init__(self, name=None, long_name=None, description=None, long_description=None, items=None, handler=None,):
+ self.name = name
+ self.long_name = long_name
+ self.description = description
+ self.long_description = long_description
+ self.items = items
+ self.handler = handler
+
+
class CaptchaTask(TBase):
"""
Attributes:
diff --git a/module/web/json_app.py b/module/web/json_app.py
index f3626405c..196c9e36d 100644
--- a/module/web/json_app.py
+++ b/module/web/json_app.py
@@ -232,38 +232,23 @@ def set_captcha():
return {'captcha': False}
-@route("/json/load_config/:category/:section")
+@route("/json/load_config/:section")
@login_required("SETTINGS")
-def load_config(category, section):
- conf = None
- if category == "general":
- conf = PYLOAD.getConfigDict()
- elif category == "plugin":
- conf = PYLOAD.getPluginConfigDict()
+def load_config(section):
+ data = PYLOAD.configureSection(section)
+ return render_to_response("settings_item.html", {"section": data})
- for key, option in conf[section].iteritems():
- if key in ("desc","outline"): continue
- if ";" in option["type"]:
- option["list"] = option["type"].split(";")
-
- option["value"] = decode(option["value"])
-
- return render_to_response("settings_item.html", {"skey": section, "section": conf[section]})
-
-
-@route("/json/save_config/:category", method="POST")
+@route("/json/save_config", method="POST")
@login_required("SETTINGS")
-def save_config(category):
+def save_config():
for key, value in request.POST.iteritems():
try:
section, option = key.split("|")
except:
continue
- if category == "general": category = "core"
-
- PYLOAD.setConfigValue(section, option, decode(value), category)
+ PYLOAD.setConfigValue(section, option, decode(value))
@route("/json/add_account", method="POST")
diff --git a/module/web/media/js/settings.coffee b/module/web/media/js/settings.coffee
index 9205233e3..04d352dae 100644
--- a/module/web/media/js/settings.coffee
+++ b/module/web/media/js/settings.coffee
@@ -51,7 +51,7 @@ class SettingsUI
new Request({
"method" : "get"
- "url" : "/json/load_config/#{category}/#{section}"
+ "url" : "/json/load_config/#{section}"
"onSuccess": (data) =>
target.set "html", data
target.reveal()
@@ -65,7 +65,7 @@ class SettingsUI
form.set "send", {
"method": "post"
- "url": "/json/save_config/#{category}"
+ "url": "/json/save_config"
"onSuccess" : ->
root.notify.alert '{{ _("Settings saved.")}}', {
'className': 'success'
diff --git a/module/web/media/js/settings.js b/module/web/media/js/settings.js
index 9191fac72..3604c38b0 100644
--- a/module/web/media/js/settings.js
+++ b/module/web/media/js/settings.js
@@ -1,3 +1,3 @@
{% autoescape true %}
-var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config",onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
{% endautoescape %} \ No newline at end of file
diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py
index df4a4b3d4..5e6d18584 100644
--- a/module/web/pyload_app.py
+++ b/module/web/pyload_app.py
@@ -241,17 +241,16 @@ def get_download(path):
@route("/settings")
@login_required('SETTINGS')
def config():
- conf = PYLOAD.getConfig()
- plugin = PYLOAD.getPluginConfig()
+ conf = PYLOAD.getConfigPointer()
conf_menu = []
plugin_menu = []
- for entry in sorted(conf.keys()):
- conf_menu.append((entry, conf[entry].description))
+ for section, data in conf.getBaseSections():
+ conf_menu.append((section, data.name))
- for entry in sorted(plugin.keys()):
- plugin_menu.append((entry, plugin[entry].description))
+ for section, data in conf.getPluginSections():
+ plugin_menu.append((section, data.name))
accs = PYLOAD.getAccounts(False)
diff --git a/module/web/templates/default/settings_item.html b/module/web/templates/default/settings_item.html
index 813383343..b81ba1b95 100644
--- a/module/web/templates/default/settings_item.html
+++ b/module/web/templates/default/settings_item.html
@@ -1,12 +1,13 @@
<table class="settable">
{% if section.outline %}
- <tr><th colspan="2">{{ section.outline }}</th></tr>
+ <tr><th colspan="2">{{ section.description }}</th></tr>
{% endif %}
- {% for okey, option in section.iteritems() %}
- {% if okey not in ("desc","outline") %}
+ {% for option in section.items %}
+ {% set okey = option.name %}
+ {% set skey = section.name %}
<tr>
- <td><label for="{{skey}}|{{okey}}"
- style="color:#424242;">{{ option.desc }}:</label></td>
+ <td><label for="{{section.name}}|{{option.name}}"
+ style="color:#424242;">{{ option.long_name }}:</label></td>
<td>
{% if option.type == "bool" %}
<select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
@@ -17,7 +18,7 @@
</select>
{% elif ";" in option.type %}
<select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
- {% for entry in option.list %}
+ {% for entry in option.type.split(";") %}
<option {% if option.value == entry %}
selected="selected" {% endif %}>{{ entry }}</option>
{% endfor %}
@@ -43,6 +44,5 @@
{% endif %}
</td>
</tr>
- {% endif %}
{% endfor %}
</table> \ No newline at end of file