Return proper errors if config is read-only
[cascardo/ipsilon.git] / ipsilon / util / plugin.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2013  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import os
21 import imp
22 import cherrypy
23 import inspect
24 from ipsilon.util.config import Config
25 from ipsilon.util.data import AdminStore
26 from ipsilon.util.log import Log
27
28
29 class Plugins(object):
30
31     def __init__(self):
32         self._providers_tree = None
33
34     def _load_class(self, tree, class_type, file_name, *pargs):
35         cherrypy.log.error('Check module %s for class %s' % (file_name,
36                                                              class_type))
37         name, ext = os.path.splitext(os.path.split(file_name)[-1])
38         try:
39             if ext.lower() == '.py':
40                 mod = imp.load_source(name, file_name)
41             # elif ext.lower() == '.pyc':
42             #    mod = imp.load_compiled(name, file_name)
43             else:
44                 return
45         except Exception, e:  # pylint: disable=broad-except
46             cherrypy.log.error('Failed to load "%s" module: [%s]' % (name, e))
47             return
48
49         if hasattr(mod, class_type):
50             instance = getattr(mod, class_type)(*pargs)
51             public_name = getattr(instance, 'name', name)
52             tree[public_name] = instance
53             cherrypy.log.error('Added module %s as %s' % (name, public_name))
54
55     def _load_classes(self, tree, path, class_type, *pargs):
56         files = None
57         try:
58             files = os.listdir(path)
59         except Exception, e:  # pylint: disable=broad-except
60             cherrypy.log.error('No modules in %s: [%s]' % (path, e))
61             return
62
63         for name in files:
64             filename = os.path.join(path, name)
65             self._load_class(tree, class_type, filename, *pargs)
66
67     def get_plugins(self, path, class_type, *pargs):
68         plugins = dict()
69         self._load_classes(plugins, path, class_type, *pargs)
70         return plugins
71
72
73 class PluginLoader(Log):
74
75     def __init__(self, baseobj, facility, plugin_type):
76         self._pathname, _ = os.path.split(inspect.getfile(baseobj))
77         self.facility = facility
78         self._plugin_type = plugin_type
79         self.available = dict()
80         self.enabled = list()
81         self.__data = None
82
83     # Defer initialization or instantiating the store will fail at load
84     # time when used with Installer plugins as the cherrypy config context
85     # is created after all Installer plugins are loaded.
86     @property
87     def _data(self):
88         if not self.__data:
89             self.__data = AdminStore()
90         return self.__data
91
92     @property
93     def is_readonly(self):
94         return self._data.is_readonly
95
96     def get_plugins(self):
97         p = Plugins()
98         return p.get_plugins(self._pathname, self._plugin_type, self)
99
100     def refresh_enabled(self):
101         config = self._data.load_options(self.facility, name='global')
102         self.enabled = []
103         if config:
104             if 'enabled' in config:
105                 self.enabled = config['enabled'].split(',')
106
107     def get_plugin_data(self):
108         self.available = self.get_plugins()
109         self.refresh_enabled()
110
111     def save_enabled(self, enabled):
112         if enabled:
113             self._data.save_options(self.facility, 'global',
114                                     {'enabled': ','.join(enabled)})
115         else:
116             self._data.delete_options(self.facility, 'global',
117                                       {'enabled': '*'})
118         self.debug('Plugin enabled state saved: %s' % enabled)
119         self.refresh_enabled()
120
121
122 class PluginInstaller(PluginLoader):
123     def __init__(self, baseobj, facility):
124         super(PluginInstaller, self).__init__(baseobj, facility, 'Installer')
125
126
127 class PluginObject(Log):
128
129     def __init__(self, plugins):
130         self.name = None
131         self._config = None
132         self._data = AdminStore()
133         self._plugins = plugins
134         self.is_enabled = False
135
136     @property
137     def is_readonly(self):
138         return self._data.is_readonly
139
140     def on_enable(self):
141         return
142
143     def on_disable(self):
144         return
145
146     def save_enabled_state(self):
147         enabled = []
148         self._plugins.refresh_enabled()
149         enabled.extend(self._plugins.enabled)
150         if self.is_enabled:
151             if self.name not in enabled:
152                 enabled.append(self.name)
153         else:
154             if self.name in enabled:
155                 enabled.remove(self.name)
156         self._plugins.save_enabled(enabled)
157
158     def enable(self):
159         if self.is_enabled:
160             return
161
162         self.refresh_plugin_config()
163         self.on_enable()
164         self.is_enabled = True
165         self.debug('Plugin enabled: %s' % self.name)
166
167     def disable(self):
168         if not self.is_enabled:
169             return
170
171         self.on_disable()
172
173         self.is_enabled = False
174         self.debug('Plugin disabled: %s' % self.name)
175
176     def import_config(self, config):
177         self._config = config
178
179     def export_config(self):
180         return self._config
181
182     def get_plugin_config(self):
183         return self._data.load_options(self._plugins.facility, self.name)
184
185     def refresh_plugin_config(self):
186         config = self.get_plugin_config()
187         if config:
188             self.import_config(config)
189
190     def save_plugin_config(self, config=None):
191         if config is None:
192             config = self.export_config()
193
194         self._data.save_options(self._plugins.facility, self.name, config)
195
196     def get_data(self, idval=None, name=None, value=None):
197         return self._data.get_data(self.name, idval=idval, name=name,
198                                    value=value)
199
200     def save_data(self, data):
201         self._data.save_data(self.name, data)
202
203     def new_datum(self, datum):
204         self._data.new_datum(self.name, datum)
205
206     def del_datum(self, idval):
207         self._data.del_datum(self.name, idval)
208
209     def wipe_config_values(self):
210         self._data.delete_options(self._plugins.facility, self.name, None)
211
212     def wipe_data(self):
213         self._data.wipe_data(self.name)
214
215
216 class PluginConfig(Log):
217
218     def __init__(self):
219         self._config = None
220
221     def new_config(self, name, *config_args):
222         self._config = Config(name, *config_args)
223
224     def get_config_obj(self):
225         if self._config is None:
226             raise AttributeError('Config not initialized')
227         return self._config
228
229     def import_config(self, config):
230         if not self._config:
231             raise AttributeError('Config not initialized, cannot import')
232
233         for key, value in config.iteritems():
234             if key in self._config:
235                 self._config[key].import_value(str(value))
236
237     def export_config(self):
238         config = dict()
239         for name, option in self._config.iteritems():
240             config[name] = option.export_value()
241         return config
242
243     def get_config_value(self, name):
244         if not self._config:
245             raise AttributeError('Config not initialized')
246         return self._config[name].get_value()
247
248     def set_config_value(self, name, value):
249         if not self._config:
250             raise AttributeError('Config not initialized')
251         return self._config[name].set_value(value)