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