summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-01-06 15:54:52 +0100
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-01-06 15:54:52 +0100
commit6f8b5347dfa119a3df21f4ca8ba8c2b1537a726a (patch)
tree627c4d99f0aaa4c8022b70b3ebe72f201d924dd6
parentremoved unneeded stuff (diff)
downloadpyload-6f8b5347dfa119a3df21f4ca8ba8c2b1537a726a.tar.xz
first working parts of config api
-rw-r--r--module/Api.py91
-rw-r--r--module/PluginManager.py14
-rw-r--r--module/api/ApiComponent.py7
-rw-r--r--module/api/ConfigApi.py77
-rw-r--r--module/config/ConfigManager.py26
-rw-r--r--module/config/ConfigParser.py5
-rw-r--r--module/database/ConfigDatabase.py10
-rw-r--r--module/remote/json_converter.py4
-rw-r--r--module/remote/pyload.thrift21
-rw-r--r--module/remote/ttypes.py39
-rw-r--r--module/remote/ttypes_debug.py11
-rw-r--r--module/utils/__init__.py4
-rw-r--r--module/web/api_app.py6
-rw-r--r--module/web/static/js/default.js11
-rw-r--r--module/web/static/js/views/settingsView.js25
-rw-r--r--module/web/templates/default/settings.html4
-rw-r--r--tests/manager/test_configManager.py26
-rw-r--r--tests/other/test_configparser.py5
18 files changed, 251 insertions, 135 deletions
diff --git a/module/Api.py b/module/Api.py
index dfc7b608f..80bfd1673 100644
--- a/module/Api.py
+++ b/module/Api.py
@@ -18,12 +18,11 @@
import re
from functools import partial
-from types import MethodType, CodeType
-from dis import opmap
+from types import MethodType
from remote.ttypes import *
-from utils import bits_set, get_index
+from utils import bits_set
# contains function names mapped to their permissions
# unlisted functions are for admins only
@@ -41,88 +40,38 @@ def RequirePerm(bits):
return _Dec
-# we will byte-hacking the method to add user as keyword argument
+# decorator to annotate user methods, these methods must have user=None kwarg.
class UserContext(object):
- """Decorator to mark methods that require a specific user"""
-
def __new__(cls, f, *args, **kwargs):
- fc = f.func_code
-
- try:
- i = get_index(fc.co_names, "user")
- except ValueError: # functions does not uses user, so no need to modify
- return f
-
user_context[f.__name__] = True
- new_names = tuple([x for x in fc.co_names if f != "user"])
- new_varnames = tuple([x for x in fc.co_varnames] + ["user"])
- new_code = fc.co_code
-
- # subtract 1 from higher LOAD_GLOBAL
- for x in range(i + 1, len(fc.co_names)):
- new_code = new_code.replace(chr(opmap['LOAD_GLOBAL']) + chr(x), chr(opmap['LOAD_GLOBAL']) + chr(x - 1))
-
- # load argument instead of global
- new_code = new_code.replace(chr(opmap['LOAD_GLOBAL']) + chr(i), chr(opmap['LOAD_FAST']) + chr(fc.co_argcount))
-
- new_fc = CodeType(fc.co_argcount + 1, fc.co_nlocals + 1, fc.co_stacksize, fc.co_flags, new_code, fc.co_consts,
- new_names, new_varnames, fc.co_filename, fc.co_name, fc.co_firstlineno, fc.co_lnotab, fc.co_freevars,
- fc.co_cellvars)
-
- f.func_code = new_fc
-
- # None as default argument for user
- if f.func_defaults:
- f.func_defaults = tuple([x for x in f.func_defaults] + [None])
- else:
- f.func_defaults = (None,)
-
return f
urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE)
-
stateMap = {
DownloadState.All: frozenset(getattr(DownloadStatus, x) for x in dir(DownloadStatus) if not x.startswith("_")),
- DownloadState.Finished : frozenset((DownloadStatus.Finished, DownloadStatus.Skipped)),
- DownloadState.Unfinished : None, # set below
- DownloadState.Failed : frozenset((DownloadStatus.Failed, DownloadStatus.TempOffline, DownloadStatus.Aborted)),
+ DownloadState.Finished: frozenset((DownloadStatus.Finished, DownloadStatus.Skipped)),
+ DownloadState.Unfinished: None, # set below
+ DownloadState.Failed: frozenset((DownloadStatus.Failed, DownloadStatus.TempOffline, DownloadStatus.Aborted)),
DownloadState.Unmanaged: None, #TODO
}
-stateMap[DownloadState.Unfinished] = frozenset(stateMap[DownloadState.All].difference(stateMap[DownloadState.Finished]))
+stateMap[DownloadState.Unfinished] = frozenset(stateMap[DownloadState.All].difference(stateMap[DownloadState.Finished]))
def state_string(state):
return ",".join(str(x) for x in stateMap[state])
+
def has_permission(userPermission, Permission):
return bits_set(Permission, userPermission)
from datatypes.User import User
-class UserApi(object):
- """ Proxy object for api that provides all methods in user context """
-
- def __init__(self, api, user):
- self.api = api
- self._user = user
-
- def __getattr__(self, item):
- f = self.api.__getattribute__(item)
- if f.func_name in user_context:
- return partial(f, user=self._user)
-
- return f
-
- @property
- def user(self):
- return self._user
-
class Api(Iface):
"""
**pyLoads API**
- This is accessible either internal via core.api, thrift backend or json api.
+ This is accessible either internal via core.api, websocket backend or json api.
see Thrift specification file remote/thriftbackend/pyload.thrift\
for information about data structures and what methods are usable with rpc.
@@ -139,6 +88,10 @@ class Api(Iface):
self.core = core
self.user_apis = {}
+ @property
+ def user(self):
+ return None #TODO return default user?
+
@classmethod
def initComponents(cls):
# Allow extending the api
@@ -164,7 +117,6 @@ class Api(Iface):
return cls.EXTEND
-
def withUserContext(self, uid):
""" Returns a proxy version of the api, to call method in user context
@@ -179,7 +131,7 @@ class Api(Iface):
if not user: #TODO: anonymous user?
return None
- self.user_apis[uid] = UserApi(self, User.fromUserData(self, user))
+ self.user_apis[uid] = UserApi(self.core, User.fromUserData(self, user))
return self.user_apis[uid]
@@ -262,3 +214,18 @@ class Api(Iface):
self.core.db.setPermission(user, permission)
self.core.db.setRole(user, role)
+
+class UserApi(Api):
+ """ Proxy object for api that provides all methods in user context """
+
+ def __init__(self, core, user):
+ # No need to init super class
+ self.core = core
+ self._user = user
+
+ def withUserContext(self, uid):
+ raise Exception("Not allowed")
+
+ @property
+ def user(self):
+ return self._user \ No newline at end of file
diff --git a/module/PluginManager.py b/module/PluginManager.py
index 6f2268388..d74123040 100644
--- a/module/PluginManager.py
+++ b/module/PluginManager.py
@@ -60,10 +60,9 @@ class PluginManager:
self.plugins = {}
self.modules = {} # cached modules
self.history = [] # match history to speedup parsing (type, name)
+ self.user_context = {} # plugins working with user context
self.createIndex()
- # TODO, replacement for this?
- #self.core.config.parseValues(self.core.config.PLUGIN)
#register for import addon
sys.meta_path.append(self)
@@ -213,6 +212,10 @@ class PluginManager:
else: # activated flag missing
config.insert(0, ("activated", "bool", "Activated", False))
+ # Everything that is no addon and user_context=True, is added to dict
+ if folder != "addons" or attrs.get("user_context", False):
+ self.user_context[name] = True
+
try:
self.core.config.addConfigSection(name, name, desc, long_desc, config)
except:
@@ -386,6 +389,13 @@ class PluginManager:
return True
+ def isUserPlugin(self, plugin):
+ """ A plugin suitable for multiple user """
+ return plugin in self.user_context
+
+ def isPluginType(self, plugin, type):
+ return plugin in self.plugins[type]
+
def loadIcons(self):
"""Loads all icons from plugins, plugin type is not in result, because its not important here.
diff --git a/module/api/ApiComponent.py b/module/api/ApiComponent.py
index ba96b3be9..a89e3e0a5 100644
--- a/module/api/ApiComponent.py
+++ b/module/api/ApiComponent.py
@@ -7,11 +7,16 @@ from module.remote.ttypes import Iface
Iface = object
class ApiComponent(Iface):
- def __init__(self, core):
+ __slots__ = []
+
+ def __init__(self, core, user):
# Only for auto completion, this class can not be instantiated
from pyload import Core
+ from module.datatypes.User import User
assert isinstance(core, Core)
assert issubclass(ApiComponent, Iface)
self.core = core
+ assert isinstance(user, User)
+ self.user = user
# No instantiating!
raise Exception() \ No newline at end of file
diff --git a/module/api/ConfigApi.py b/module/api/ConfigApi.py
index f3f2e6950..ffcdbabec 100644
--- a/module/api/ConfigApi.py
+++ b/module/api/ConfigApi.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from module.Api import Api, UserContext, RequirePerm, Permission, ConfigHolder, ConfigItem
+from module.Api import Api, UserContext, RequirePerm, Permission, ConfigHolder, ConfigItem, PluginInfo
from module.utils import to_string
from ApiComponent import ApiComponent
@@ -9,6 +9,7 @@ from ApiComponent import ApiComponent
class ConfigApi(ApiComponent):
""" Everything related to configuration """
+ @UserContext
def getConfigValue(self, section, option):
"""Retrieve config value.
@@ -17,9 +18,10 @@ class ConfigApi(ApiComponent):
:rtype: str
:return: config value as string
"""
- value = self.core.config.get(section, option)
+ value = self.core.config.get(section, option, self.user)
return to_string(value)
+ @UserContext
def setConfigValue(self, section, option, value):
"""Set new config value.
@@ -30,49 +32,69 @@ class ConfigApi(ApiComponent):
if option in ("limit_speed", "max_speed"): #not so nice to update the limit
self.core.requestFactory.updateBucket()
- self.core.config.set(section, option, value)
+ self.core.config.set(section, option, value, self.user)
def getConfig(self):
"""Retrieves complete config of core.
- :rtype: ConfigHolder
- :return: dict with section mapped to config
+ :rtype: dict of section -> ConfigHolder
"""
- # TODO
- return dict([(section, ConfigHolder(section, data.name, data.description, data.long_desc, [
- ConfigItem(option, d.name, d.description, d.type, to_string(d.default),
- to_string(self.core.config.get(section, option))) for
- option, d in data.config.iteritems()])) for
- section, data in self.core.config.getBaseSections()])
+ data = {}
+ for section, config, values in self.core.config.iterCoreSections():
+ holder = ConfigHolder(section, config.name, config.description, config.long_desc)
+ holder.items = [ConfigItem(option, x.name, x.description, x.type, to_string(x.default),
+ to_string(values.get(option, x.default))) for option, x in config.config.iteritems()]
+ data[section] = holder
+ return data
- def getConfigRef(self):
- """Config instance, not for RPC"""
- return self.core.config
+ def getCoreConfig(self):
+ """ Retrieves core config sections
- def getGlobalPlugins(self):
- """All global plugins/addons, only admin can use this
+ :rtype: list of PluginInfo
+ """
+ return [PluginInfo(section, config.name, config.description, False, False)
+ for section, config, values in self.core.config.iterCoreSections()]
- :return: list of `ConfigInfo`
+ @UserContext
+ @RequirePerm(Permission.Plugins)
+ def getPluginConfig(self):
+ """All plugins and addons the current user has configured
+
+ :rtype: list of PluginInfo
"""
- pass
+ # TODO: include addons that are activated by default
+ data = []
+ for name, config, values in self.core.config.iterSections(self.user):
+ if not values: continue
+ item = PluginInfo(name, config.name, config.description,
+ self.core.pluginManager.isPluginType(name, "addons"),
+ self.core.pluginManager.isUserPlugin(name),
+ values.get("activated", False))
+ data.append(item)
+
+ return data
@UserContext
@RequirePerm(Permission.Plugins)
- def getUserPlugins(self):
- """List of plugins every user can configure for himself
+ def getAvailablePlugins(self):
+ """List of all available plugins, that are configurable
- :return: list of `ConfigInfo`
+ :rtype: list of PluginInfo
"""
- pass
+ # TODO: filter user_context / addons when not allowed
+ return [PluginInfo(name, config.name, config.description,
+ self.core.pluginManager.isPluginType(name, "addons"),
+ self.core.pluginManager.isUserPlugin(name))
+ for name, config, values in self.core.config.iterSections(self.user)]
@UserContext
@RequirePerm(Permission.Plugins)
def configurePlugin(self, plugin):
- """Get complete config options for an plugin
+ """Get complete config options for desired section
- :param plugin: Name of the plugin to configure
- :return: :class:`ConfigHolder`
+ :param plugin: Name of plugin or config section
+ :rtype: ConfigHolder
"""
pass
@@ -82,7 +104,7 @@ class ConfigApi(ApiComponent):
def saveConfig(self, config):
"""Used to save a configuration, core config can only be saved by admins
- :param config: :class:`ConfigHolder
+ :param config: :class:`ConfigHolder`
"""
pass
@@ -92,9 +114,8 @@ class ConfigApi(ApiComponent):
"""Deletes modified config
:param plugin: plugin name
- :return:
"""
- pass
+ self.core.config.delete(plugin, self.user)
@RequirePerm(Permission.Plugins)
def setConfigHandler(self, plugin, iid, value):
diff --git a/module/config/ConfigManager.py b/module/config/ConfigManager.py
index 4898b0c66..eb96a49f6 100644
--- a/module/config/ConfigManager.py
+++ b/module/config/ConfigManager.py
@@ -5,7 +5,7 @@ from new_collections import OrderedDict
from module.Api import InvalidConfigSection
from module.common.json_layer import json
-from module.utils import from_string
+from module.utils import from_string, primary_uid
from ConfigParser import ConfigParser
@@ -57,7 +57,7 @@ class ConfigManager(ConfigParser):
else:
# We need the id and not the instance
# Will be None for admin user and so the same as internal access
- user = user.primary if user else None
+ user = primary_uid(user)
try:
# Check if this config exists
# Configs without meta data can not be loaded!
@@ -87,7 +87,7 @@ class ConfigManager(ConfigParser):
changed = self.parser.set(section, option, value, sync)
else:
# associated id
- user = user.primary if user else None
+ user = primary_uid(user)
data = self.config[section].config[option]
value = from_string(value, data.type)
old_value = self.get(section, option)
@@ -113,6 +113,22 @@ class ConfigManager(ConfigParser):
self.db.deleteConfig(section, user)
+ def iterCoreSections(self):
+ return self.parser.iterSections()
- def iterSections(self):
- pass \ No newline at end of file
+ def iterSections(self, user=None):
+ """ Yields: section, metadata, values """
+
+ user = primary_uid(user)
+ values = self.db.loadConfigsForUser(user)
+
+ # Every section needs to be json decoded
+ for section, data in values.items():
+ try:
+ values[section] = json.loads(data) if data else {}
+ except ValueError:
+ values[section] = {}
+ self.core.print_exc()
+
+ for name, config in self.config.iteritems():
+ yield name, config, values[name] if name in values else {}
diff --git a/module/config/ConfigParser.py b/module/config/ConfigParser.py
index 135e84bbc..2f974b75e 100644
--- a/module/config/ConfigParser.py
+++ b/module/config/ConfigParser.py
@@ -163,7 +163,10 @@ class ConfigParser:
return self.config[section].config[option]
def iterSections(self):
- return self.config.iteritems()
+ """ Yields section, config info, values, for all sections """
+
+ for name, config in self.config.iteritems():
+ yield name, config, self.values[name] if name in self.values else {}
def addConfigSection(self, section, name, desc, long_desc, config):
"""Adds a section to the config. `config` is a list of config tuples as used in plugin api defined as:
diff --git a/module/database/ConfigDatabase.py b/module/database/ConfigDatabase.py
index 7dd5909b8..2e9fdd9a0 100644
--- a/module/database/ConfigDatabase.py
+++ b/module/database/ConfigDatabase.py
@@ -42,6 +42,16 @@ class ConfigMethods(DatabaseMethods):
return configs
+ @queue
+ def loadConfigsForUser(self, user=None):
+ if user is None: user = -1
+ self.c.execute('SELECT plugin, config FROM settings WHERE user=?', (user,))
+ configs = {}
+ for r in self.c:
+ configs[r[0]] = r[1]
+
+ return configs
+
@async
def clearAllConfigs(self):
self.c.execute('DELETE FROM settings')
diff --git a/module/remote/json_converter.py b/module/remote/json_converter.py
index ad7645ccc..5b85f30cd 100644
--- a/module/remote/json_converter.py
+++ b/module/remote/json_converter.py
@@ -11,6 +11,9 @@ import ttypes
from ttypes import BaseObject
from ttypes import ExceptionObject
+# compact json separator
+separators = (',', ':')
+
# json encoder that accepts TBase objects
class BaseEncoder(json.JSONEncoder):
@@ -34,6 +37,7 @@ def convert_obj(dct):
def dumps(*args, **kwargs):
kwargs['cls'] = BaseEncoder
+ kwargs['separators'] = separators
return json.dumps(*args, **kwargs)
diff --git a/module/remote/pyload.thrift b/module/remote/pyload.thrift
index 514c3215c..077810ecb 100644
--- a/module/remote/pyload.thrift
+++ b/module/remote/pyload.thrift
@@ -246,11 +246,12 @@ struct ConfigHolder {
}
struct ConfigInfo {
- 1: string name,
+ 1: string name
2: string label,
3: string description,
- 4: bool saved,
- 5: bool activated,
+ 4: bool addon,
+ 5: bool user_context,
+ 6: optional bool activated,
}
struct EventInfo {
@@ -353,10 +354,16 @@ service Pyload {
///////////////////////
map<string, ConfigHolder> getConfig(),
- list<ConfigInfo> getGlobalPlugins(),
- list<ConfigInfo> getUserPlugins(),
+ string getConfigValue(1: string section, 2: string option),
+
+ // two methods with ambigous classification, could be configuration or addon related
+ list<ConfigInfo> getCoreConfig(),
+ list<ConfigInfo> getPluginConfig(),
+ list<ConfigInfo> getAvailablePlugins(),
ConfigHolder configurePlugin(1: PluginName plugin),
+
+ void setConfigValue(1: string section, 2: string option, 3: string value),
void saveConfig(1: ConfigHolder config),
void deleteConfig(1: PluginName plugin),
void setConfigHandler(1: PluginName plugin, 2: InteractionID iid, 3: JSONString value),
@@ -511,8 +518,8 @@ service Pyload {
// Addon Methods
///////////////////////
- map<PluginName, list<AddonInfo>> getAllInfo(),
- list<AddonInfo> getInfoByPlugin(1: PluginName plugin),
+ //map<PluginName, list<AddonInfo>> getAllInfo(),
+ //list<AddonInfo> getInfoByPlugin(1: PluginName plugin),
map<PluginName, list<AddonService>> getAddonHandler(),
bool hasAddonHandler(1: PluginName plugin, 2: string func),
diff --git a/module/remote/ttypes.py b/module/remote/ttypes.py
index 0b9faea98..e8ef26dd2 100644
--- a/module/remote/ttypes.py
+++ b/module/remote/ttypes.py
@@ -138,16 +138,6 @@ class ConfigHolder(BaseObject):
self.info = info
self.handler = handler
-class ConfigInfo(BaseObject):
- __slots__ = ['name', 'label', 'description', 'saved', 'activated']
-
- def __init__(self, name=None, label=None, description=None, saved=None, activated=None):
- self.name = name
- self.label = label
- self.description = description
- self.saved = saved
- self.activated = activated
-
class ConfigItem(BaseObject):
__slots__ = ['name', 'label', 'description', 'type', 'default_value', 'value']
@@ -282,6 +272,17 @@ class PackageStats(BaseObject):
self.sizetotal = sizetotal
self.sizedone = sizedone
+class PluginInfo(BaseObject):
+ __slots__ = ['name', 'label', 'description', 'addon', 'user_context', 'activated']
+
+ def __init__(self, name=None, label=None, description=None, addon=None, user_context=None, activated=None):
+ self.name = name
+ self.label = label
+ self.description = description
+ self.addon = addon
+ self.user_context = user_context
+ self.activated = activated
+
class ProgressInfo(BaseObject):
__slots__ = ['plugin', 'name', 'statusmsg', 'eta', 'done', 'total', 'download']
@@ -413,14 +414,18 @@ class Iface(object):
pass
def getAllFiles(self):
pass
- def getAllInfo(self):
- pass
def getAllUserData(self):
pass
+ def getAvailablePlugins(self):
+ pass
def getCollector(self):
pass
def getConfig(self):
pass
+ def getConfigValue(self, section, option):
+ pass
+ def getCoreConfig(self):
+ pass
def getEvents(self, uuid):
pass
def getFileInfo(self, fid):
@@ -431,10 +436,6 @@ class Iface(object):
pass
def getFilteredFiles(self, state):
pass
- def getGlobalPlugins(self):
- pass
- def getInfoByPlugin(self, plugin):
- pass
def getInteractionTask(self, mode):
pass
def getLog(self, offset):
@@ -445,6 +446,8 @@ class Iface(object):
pass
def getPackageInfo(self, pid):
pass
+ def getPluginConfig(self):
+ pass
def getProgressInfo(self):
pass
def getServerStatus(self):
@@ -453,8 +456,6 @@ class Iface(object):
pass
def getUserData(self):
pass
- def getUserPlugins(self):
- pass
def getWSAddress(self):
pass
def hasAddonHandler(self, plugin, func):
@@ -499,6 +500,8 @@ class Iface(object):
pass
def setConfigHandler(self, plugin, iid, value):
pass
+ def setConfigValue(self, section, option, value):
+ pass
def setInteractionResult(self, iid, result):
pass
def setPackageFolder(self, pid, path):
diff --git a/module/remote/ttypes_debug.py b/module/remote/ttypes_debug.py
index 03d619dd4..17d602f97 100644
--- a/module/remote/ttypes_debug.py
+++ b/module/remote/ttypes_debug.py
@@ -10,7 +10,6 @@ classes = {
'AddonInfo' : [basestring, basestring, basestring],
'AddonService' : [basestring, basestring, (list, basestring), (None, int)],
'ConfigHolder' : [basestring, basestring, basestring, basestring, (list, ConfigItem), (None, (list, AddonInfo)), (None, (list, InteractionTask))],
- 'ConfigInfo' : [basestring, basestring, basestring, bool, bool],
'ConfigItem' : [basestring, basestring, basestring, basestring, (None, basestring), basestring],
'DownloadInfo' : [basestring, basestring, basestring, int, basestring, basestring],
'DownloadProgress' : [int, int, int, int],
@@ -24,6 +23,7 @@ classes = {
'PackageDoesNotExists' : [int],
'PackageInfo' : [int, basestring, basestring, int, int, basestring, basestring, basestring, int, (list, basestring), int, int, PackageStats, (list, int), (list, int)],
'PackageStats' : [int, int, int, int],
+ 'PluginInfo' : [basestring, basestring, basestring, bool, bool, (None, bool)],
'ProgressInfo' : [basestring, basestring, basestring, int, int, int, (None, DownloadProgress)],
'ServerStatus' : [int, int, int, bool, bool, bool],
'ServiceDoesNotExists' : [basestring, basestring],
@@ -64,27 +64,27 @@ methods = {
'getAccounts': (list, AccountInfo),
'getAddonHandler': (dict, basestring, list),
'getAllFiles': TreeCollection,
- 'getAllInfo': (dict, basestring, list),
'getAllUserData': (dict, int, UserData),
+ 'getAvailablePlugins': (list, PluginInfo),
'getCollector': (list, LinkStatus),
'getConfig': (dict, basestring, ConfigHolder),
+ 'getConfigValue': basestring,
+ 'getCoreConfig': (list, PluginInfo),
'getEvents': (list, EventInfo),
'getFileInfo': FileInfo,
'getFileTree': TreeCollection,
'getFilteredFileTree': TreeCollection,
'getFilteredFiles': TreeCollection,
- 'getGlobalPlugins': (list, ConfigInfo),
- 'getInfoByPlugin': (list, AddonInfo),
'getInteractionTask': InteractionTask,
'getLog': (list, basestring),
'getNotifications': (list, InteractionTask),
'getPackageContent': TreeCollection,
'getPackageInfo': PackageInfo,
+ 'getPluginConfig': (list, PluginInfo),
'getProgressInfo': (list, ProgressInfo),
'getServerStatus': ServerStatus,
'getServerVersion': basestring,
'getUserData': UserData,
- 'getUserPlugins': (list, ConfigInfo),
'getWSAddress': basestring,
'hasAddonHandler': bool,
'isInteractionWaiting': bool,
@@ -107,6 +107,7 @@ methods = {
'restartPackage': None,
'saveConfig': None,
'setConfigHandler': None,
+ 'setConfigValue': None,
'setInteractionResult': None,
'setPackageFolder': bool,
'setPassword': bool,
diff --git a/module/utils/__init__.py b/module/utils/__init__.py
index 3adb695ee..4692c59cb 100644
--- a/module/utils/__init__.py
+++ b/module/utils/__init__.py
@@ -221,6 +221,10 @@ def get_index(l, value):
# Matches behavior of list.index
raise ValueError("list.index(x): x not in list")
+def primary_uid(user):
+ """ Gets primary user id for user instances or ints """
+ if type(user) == int: return user
+ return user.primary if user else None
def html_unescape(text):
"""Removes HTML or XML character references and entities from a text string"""
diff --git a/module/web/api_app.py b/module/web/api_app.py
index ff8d0134c..c0a7df528 100644
--- a/module/web/api_app.py
+++ b/module/web/api_app.py
@@ -46,7 +46,7 @@ def call_api(func, args=""):
kwargs[x] = unquote(y)
try:
- return callApi(func, *args, **kwargs)
+ return callApi(api, func, *args, **kwargs)
except ExceptionObject, e:
return HTTPError(400, dumps(e))
except Exception, e:
@@ -55,13 +55,13 @@ def call_api(func, args=""):
# TODO Better error codes on invalid input
-def callApi(func, *args, **kwargs):
+def callApi(api, func, *args, **kwargs):
if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
print "Invalid API call", func
return HTTPError(404, dumps("Not Found"))
# TODO: encoding
- result = getattr(PYLOAD, func)(*[loads(x) for x in args],
+ result = getattr(api, func)(*[loads(x) for x in args],
**dict([(x, loads(y)) for x, y in kwargs.iteritems()]))
# null is invalid json response
diff --git a/module/web/static/js/default.js b/module/web/static/js/default.js
index 4436a85c1..bb9701935 100644
--- a/module/web/static/js/default.js
+++ b/module/web/static/js/default.js
@@ -1,5 +1,5 @@
-define('default', ['jquery', 'app', 'views/headerView', 'views/packageTreeView'],
- function($, App, HeaderView, TreeView) {
+define('default', ['require', 'jquery', 'app', 'views/headerView', 'views/packageTreeView'],
+ function(require, $, App, HeaderView, TreeView) {
App.init = function() {
App.header = new HeaderView();
@@ -13,5 +13,12 @@ define('default', ['jquery', 'app', 'views/headerView', 'views/packageTreeView']
});
};
+ App.initSettingsView = function() {
+ require(['views/settingsView'], function(SettingsView) {
+ App.settingsView = new SettingsView();
+ App.settingsView.render();
+ });
+ };
+
return App;
}); \ No newline at end of file
diff --git a/module/web/static/js/views/settingsView.js b/module/web/static/js/views/settingsView.js
new file mode 100644
index 000000000..f4e118233
--- /dev/null
+++ b/module/web/static/js/views/settingsView.js
@@ -0,0 +1,25 @@
+define(['jquery', 'underscore', 'backbone'],
+ function($, _, Backbone) {
+
+ // Renders a single package item
+ return Backbone.View.extend({
+
+ el: "#content",
+// template: _.compile($("#template-package").html()),
+
+ events: {
+
+ },
+
+ initialize: function() {
+ $.ajax("/api/getCoreConfig");
+ $.ajax("/api/getPluginConfig");
+ $.ajax("/api/getAvailablePlugins");
+ console.log("Settings initialized");
+ },
+
+ render: function() {
+ }
+
+ });
+ }); \ No newline at end of file
diff --git a/module/web/templates/default/settings.html b/module/web/templates/default/settings.html
index 60676187b..591dbc30c 100644
--- a/module/web/templates/default/settings.html
+++ b/module/web/templates/default/settings.html
@@ -8,6 +8,10 @@
<link href="static/css/default/settings.less" rel="stylesheet/less" type="text/css" media="screen"/>
{% endblock %}
+{% block require %}
+ App.initSettingsView();
+{% endblock %}
+
{% block content %}
<div class="container-fluid">
<div class="row-fluid">
diff --git a/tests/manager/test_configManager.py b/tests/manager/test_configManager.py
index 1cfb6a5de..0fed702c1 100644
--- a/tests/manager/test_configManager.py
+++ b/tests/manager/test_configManager.py
@@ -27,6 +27,9 @@ class TestConfigManager(TestCase):
def setUp(self):
self.db.clearAllConfigs()
+ # used by some tests, needs to be deleted
+ self.config.delete("plugin", adminUser)
+
def addConfig(self):
self.config.addConfigSection("plugin", "Name", "desc", "something",
@@ -42,10 +45,12 @@ class TestConfigManager(TestCase):
d = self.db.loadAllConfigs()
assert d[0]["plugin"] == "some value"
+ assert self.db.loadConfigsForUser(0)["plugin"] == "some value"
self.db.deleteConfig("plugin", 0)
assert 0 not in self.db.loadAllConfigs()
+ assert "plugin" not in self.db.loadConfigsForUser(0)
self.db.deleteConfig("plugin")
@@ -86,7 +91,26 @@ class TestConfigManager(TestCase):
def test_sections(self):
- pass
+ self.addConfig()
+
+ i = 0
+ # there should be only one section, with no values
+ for name, config, values in self.config.iterSections(adminUser):
+ assert name == "plugin"
+ assert values == {}
+ i +=1
+ assert i == 1
+
+ assert self.config.set("plugin", "value", True, user=adminUser)
+
+ i = 0
+ # now we assert the correct values
+ for name, config, values in self.config.iterSections(adminUser):
+ assert name == "plugin"
+ assert values == {"value":True}
+ i +=1
+ assert i == 1
+
@raises(InvalidConfigSection)
def test_restricted_access(self):
diff --git a/tests/other/test_configparser.py b/tests/other/test_configparser.py
index 51ffc5a68..09b686738 100644
--- a/tests/other/test_configparser.py
+++ b/tests/other/test_configparser.py
@@ -25,6 +25,11 @@ class TestConfigParser():
assert "general" in self.config
assert "foobaar" not in self.config
+ def test_iter(self):
+ for section, config, values in self.config.iterSections():
+ assert isinstance(section, basestring)
+ assert isinstance(config.config, dict)
+ assert isinstance(values, dict)
@raises(KeyError)
def test_invalid_config(self):