diff options
Diffstat (limited to 'module')
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 |