Make it possible to use PluginLoader without store
[cascardo/ipsilon.git] / ipsilon / util / plugin.py
1 # Copyright (C) 2013 Ipsilon project Contributors, for license see COPYING
2
3 import os
4 import imp
5 import cherrypy
6 import inspect
7 import logging
8 from ipsilon.util.data import AdminStore, Store
9 from ipsilon.util.log import Log
10
11
12 class Plugins(object):
13
14     def __init__(self):
15         self._providers_tree = None
16
17     def _load_class(self, tree, class_type, file_name, *pargs):
18         cherrypy.log.error('Check module %s for class %s' % (file_name,
19                            class_type), severity=logging.DEBUG)
20         name, ext = os.path.splitext(os.path.split(file_name)[-1])
21         try:
22             if ext.lower() == '.py':
23                 mod = imp.load_source(name, file_name)
24             # elif ext.lower() == '.pyc':
25             #    mod = imp.load_compiled(name, file_name)
26             else:
27                 return
28         except Exception, e:  # pylint: disable=broad-except
29             cherrypy.log.error('Failed to load "%s" module: [%s]' % (name, e),
30                                severity=logging.ERROR)
31             return
32
33         if hasattr(mod, class_type):
34             instance = getattr(mod, class_type)(*pargs)
35             public_name = getattr(instance, 'name', name)
36             tree[public_name] = instance
37             cherrypy.log.error('Added module %s as %s' % (name, public_name),
38                                severity=logging.DEBUG)
39
40     def _load_classes(self, tree, path, class_type, *pargs):
41         files = None
42         try:
43             files = os.listdir(path)
44         except Exception, e:  # pylint: disable=broad-except
45             cherrypy.log.error('No modules in %s: [%s]' % (path, e),
46                                severity=logging.ERROR)
47             return
48
49         for name in files:
50             filename = os.path.join(path, name)
51             self._load_class(tree, class_type, filename, *pargs)
52
53     def get_plugins(self, path, class_type, *pargs):
54         plugins = dict()
55         self._load_classes(plugins, path, class_type, *pargs)
56         return plugins
57
58
59 class PluginLoader(Log):
60
61     def __init__(self, baseobj, facility, plugin_type, uses_store=True):
62         self._pathname, _ = os.path.split(inspect.getfile(baseobj))
63         self.facility = facility
64         self._plugin_type = plugin_type
65         self.available = dict()
66         self.enabled = list()
67         self.__data = False
68         self.uses_store = uses_store
69
70     # Defer initialization or instantiating the store will fail at load
71     # time when used with Installer plugins as the cherrypy config context
72     # is created after all Installer plugins are loaded.
73     @property
74     def _data(self):
75         if not self.uses_store:
76             raise Exception('Tried to get plugin data while ' +
77                             'uses_store=False (%s)' % self.facility)
78         if not self.__data:
79             self.__data = AdminStore()
80         return self.__data
81
82     @property
83     def is_readonly(self):
84         return self._data.is_readonly
85
86     def get_plugins(self):
87         p = Plugins()
88         return p.get_plugins(self._pathname, self._plugin_type, self)
89
90     def refresh_enabled(self):
91         config = self._data.load_options(self.facility, name='global')
92         self.enabled = []
93         if config:
94             if 'enabled' in config:
95                 self.enabled = config['enabled'].split(',')
96
97     def get_plugin_data(self):
98         self.available = self.get_plugins()
99         if self.uses_store:
100             self.refresh_enabled()
101
102     def save_enabled(self, enabled):
103         if enabled:
104             self._data.save_options(self.facility, 'global',
105                                     {'enabled': ','.join(enabled)})
106         else:
107             self._data.delete_options(self.facility, 'global',
108                                       {'enabled': '*'})
109         self.debug('Plugin enabled state saved: %s' % enabled)
110         self.refresh_enabled()
111
112
113 class PluginInstaller(PluginLoader):
114     def __init__(self, baseobj, facility):
115         super(PluginInstaller, self).__init__(baseobj, facility, 'Installer')
116
117
118 class PluginObject(Log):
119
120     def __init__(self, plugins):
121         self.name = None
122         self._config = None
123         self._data = AdminStore()
124         self._plugins = plugins
125         self.is_enabled = False
126
127     @property
128     def is_readonly(self):
129         return self._data.is_readonly
130
131     def on_enable(self):
132         return
133
134     def on_disable(self):
135         return
136
137     def save_enabled_state(self):
138         enabled = []
139         self._plugins.refresh_enabled()
140         enabled.extend(self._plugins.enabled)
141         if self.is_enabled:
142             if self.name not in enabled:
143                 enabled.append(self.name)
144         else:
145             if self.name in enabled:
146                 enabled.remove(self.name)
147         self._plugins.save_enabled(enabled)
148
149     def enable(self):
150         if self.is_enabled:
151             return
152
153         self.refresh_plugin_config()
154         is_upgrade = Store._is_upgrade  # pylint: disable=protected-access
155         try:
156             Store._is_upgrade = True  # pylint: disable=protected-access
157             self.on_enable()
158             self._data.create_plugin_data_table(self.name)
159             for store in self.used_datastores():
160                 store.upgrade_database()
161         finally:
162             Store._is_upgrade = is_upgrade  # pylint: disable=protected-access
163         self.is_enabled = True
164         self.debug('Plugin enabled: %s' % self.name)
165
166     def disable(self):
167         if not self.is_enabled:
168             return
169
170         self.on_disable()
171
172         self.is_enabled = False
173         self.debug('Plugin disabled: %s' % self.name)
174
175     def used_datastores(self):
176         return []
177
178     def import_config(self, config):
179         self._config = config
180
181     def export_config(self):
182         return self._config
183
184     def get_plugin_config(self):
185         return self._data.load_options(self._plugins.facility, self.name)
186
187     def refresh_plugin_config(self):
188         config = self.get_plugin_config()
189         if config:
190             try:
191                 self.import_config(config)
192             except Exception, e:  # pylint: disable=broad-except
193                 self.error('Failed to refresh config for %s (%s)' %
194                            (self.name, e))
195
196     def save_plugin_config(self, config=None):
197         if config is None:
198             config = self.export_config()
199
200         self._data.save_options(self._plugins.facility, self.name, config)
201
202     def get_data(self, idval=None, name=None, value=None):
203         return self._data.get_data(self.name, idval=idval, name=name,
204                                    value=value)
205
206     def save_data(self, data):
207         self._data.save_data(self.name, data)
208
209     def new_datum(self, datum):
210         self._data.new_datum(self.name, datum)
211
212     def del_datum(self, idval):
213         self._data.del_datum(self.name, idval)
214
215     def wipe_config_values(self):
216         self._data.delete_options(self._plugins.facility, self.name, None)
217
218     def wipe_data(self):
219         self._data.wipe_data(self.name)