~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/plugin_loader.py

  • Committer: Bazaar Package Importer
  • Author(s): Rolf Leggewie
  • Date: 2008-07-26 13:29:41 UTC
  • Revision ID: james.westby@ubuntu.com-20080726132941-6ldd73qmacrzz0bn
Tags: upstream-0.14.0
ImportĀ upstreamĀ versionĀ 0.14.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
PRE = 0
 
2
POST = 1
 
3
import gglobals, os.path, glob, sys
 
4
import gobject
 
5
import plugin
 
6
from defaults.defaults import loc
 
7
 
 
8
try:
 
9
    current_path = os.path.split(os.path.join(os.getcwd(),__file__))[0]
 
10
except:
 
11
    current_path = ''
 
12
 
 
13
# This module provides a base class for loading plugins. Everything
 
14
# that is plug-in-able in Gourmet should subclass the plugin loader.
 
15
 
 
16
# Everything that is a plugin needs to provide a python module with a
 
17
# plugins attribute containing the plugin classes that make up the
 
18
# plugin. In addition, we need a .gourmet-plugin configuration file
 
19
# pointing to the module (with the module parameter) and giving the
 
20
# name and comment for the plugin.
 
21
 
 
22
class MasterLoader:
 
23
 
 
24
    # Singleton design pattern lifted from:
 
25
    # http://www.python.org/workshops/1997-10/proceedings/savikko.html
 
26
    # to get an instance, use the convenience function
 
27
    # get_master_loader()
 
28
    __single = None
 
29
    default_active_plugin_sets = [
 
30
        'unit_converter',
 
31
        'duplicate_finder',
 
32
        'key_editor',
 
33
        ]
 
34
    active_plugin_filename = os.path.join(gglobals.gourmetdir,'active_plugins')
 
35
 
 
36
    def __init__ (self):
 
37
        if MasterLoader.__single:
 
38
            raise MasterLoader.__single
 
39
        MasterLoader.__single = self
 
40
        curfile = gglobals.__file__ # we count on plugins/ being in the same dir as gglobals  
 
41
        self.plugin_directories = [os.path.join(gglobals.gourmetdir,'plugins/'), # user plug-ins
 
42
                                   os.path.join(current_path,'plugins'), # pre-installed plugins
 
43
                                   os.path.join(current_path,'plugins','import_export'), # pre-installed exporter plugins
 
44
                                   os.path.join(gglobals.datad,'plugins/'), # system-wide plug-ins (?)
 
45
                                   ]
 
46
        self.pluggables_by_class = {}
 
47
        self.load_plugin_directories()
 
48
        self.load_active_plugins()
 
49
 
 
50
    def load_plugin_directories (self):
 
51
        """Look through plugin directories for plugins.
 
52
        """
 
53
        self.available_plugin_sets = {}
 
54
        for d in self.plugin_directories:
 
55
            plugins = glob.glob('%s/*.gourmet-plugin'%d)
 
56
            print 'found plugins:',plugins,'in',d
 
57
            for ppath in plugins:
 
58
                plugin_set = PluginSet(ppath)
 
59
                if self.available_plugin_sets.has_key(plugin_set.module):
 
60
                    print 'Ignoring duplicate plugin ',plugin_set.module,'found in ',ppath
 
61
                else:
 
62
                    self.available_plugin_sets[plugin_set.module] = plugin_set
 
63
 
 
64
    def load_active_plugins (self):
 
65
        """Activate plugins that have been activated on startup
 
66
        """
 
67
        if os.path.exists(self.active_plugin_filename):
 
68
            infi = file(self.active_plugin_filename,'r')
 
69
            self.active_plugin_sets = [l.strip() for l in infi.readlines()]
 
70
            print 'active_plugin_sets = ',self.active_plugin_sets
 
71
        else:
 
72
            self.active_plugin_sets = self.default_active_plugin_sets[:]
 
73
        self.active_plugins = []
 
74
        self.instantiated_plugins = {}
 
75
        for p in self.active_plugin_sets:
 
76
            if self.available_plugin_sets.has_key(p):
 
77
                self.active_plugins.extend(self.available_plugin_sets[p].plugins)
 
78
            else:
 
79
                print 'Plugin ',p,'not found'
 
80
 
 
81
    def save_active_plugins (self):
 
82
        # If we have not changed from the defaults and no
 
83
        # configuration file exists, don't bother saving one.
 
84
        if ((self.active_plugin_sets != self.default_active_plugin_sets)
 
85
            or
 
86
            os.path.exists(self.active_plugin_filename)):
 
87
            print 'Saving active plugins to',self.active_plugin_filename
 
88
            ofi = file(self.active_plugin_filename,'w')
 
89
            for plugin_set in self.active_plugin_sets:
 
90
                print 'Save module...',plugin_set
 
91
                ofi.write(plugin_set+'\n')
 
92
            print 'Done saving.'
 
93
            ofi.close()
 
94
        elif self.active_plugin_sets == self.default_active_plugin_sets:
 
95
            print 'No change to plugins, nothing to save.'
 
96
 
 
97
    def activate_plugin_set (self, plugin_set):
 
98
        """Activate a set of plugins.
 
99
        """
 
100
        if not plugin_set in self.active_plugin_sets:
 
101
            print 'ADD ',plugin_set,'TO ACTIVE_PLUGIN_SETS'            
 
102
            self.active_plugin_sets.append(plugin_set.module)
 
103
        self.active_plugins.extend(plugin_set.plugins)
 
104
        for plugin in plugin_set.plugins:
 
105
            for klass in self.pluggables_by_class:
 
106
                if issubclass(plugin,klass):
 
107
                    for pluggable in self.pluggables_by_class[klass]:
 
108
                        pluggable.plugin_plugin(self.get_instantiated_plugin(plugin))
 
109
 
 
110
    def deactivate_plugin_set (self, plugin_set):
 
111
        if plugin_set in self.active_plugin_sets:
 
112
            print 'REMOVE ',plugin_set,'FROM ACTIVE_PLUGIN_SETS'
 
113
            self.active_plugin_sets.remove(plugin_set)
 
114
        for plugin in plugin_set.plugins:
 
115
            if self.instantiated_plugins.has_key(plugin):
 
116
                self.instantiated_plugins[plugin].remove()
 
117
            self.active_plugins.remove(plugin)
 
118
 
 
119
    def get_instantiated_plugin (self, plugin):
 
120
        if self.instantiated_plugins.has_key(plugin):
 
121
            return self.instantiated_plugins[plugin]
 
122
        else:
 
123
            self.instantiated_plugins[plugin] = plugin()
 
124
            return self.instantiated_plugins[plugin]
 
125
            
 
126
    def register_pluggable (self, pluggable, klass):
 
127
        if not self.pluggables_by_class.has_key(klass):
 
128
            self.pluggables_by_class[klass] = []
 
129
        self.pluggables_by_class[klass].append(pluggable)
 
130
        for plugin in self.active_plugins:
 
131
            print 'checking active plugin',plugin
 
132
            if issubclass(plugin,klass):
 
133
                print 'plugin ',plugin
 
134
                try:
 
135
                    plugin_instance = self.get_instantiated_plugin(plugin)
 
136
                except:
 
137
                    print 'Failed to instantiate plugin'
 
138
                    import traceback; traceback.print_exc()
 
139
                else:
 
140
                    pluggable.plugin_plugin(plugin_instance)
 
141
 
 
142
    def unregister_pluggable (self, pluggable, klass):
 
143
        self.pluggables_by_class[klass].remove(pluggable)
 
144
 
 
145
def get_master_loader ():
 
146
    # Singleton design pattern lifted from:
 
147
    # http://www.python.org/workshops/1997-10/proceedings/savikko.html    
 
148
    try:
 
149
        return MasterLoader()
 
150
    except MasterLoader, ml:
 
151
        return ml                
 
152
 
 
153
class PluginSet:
 
154
    """A lazy-loading set of plugins.
 
155
 
 
156
    This class encapsulates what to the end-user is a plugin.
 
157
 
 
158
    From our perspective, plugins can really be a bundle of plugins --
 
159
    for example, your plugin might require a DatabasePlugin, a
 
160
    RecCardDisplayPlugin and a MainPlugin to function.
 
161
    """
 
162
 
 
163
    _loaded = False
 
164
    
 
165
    def __init__ (self, plugin_info_path):
 
166
        f = file(plugin_info_path,'r')
 
167
        self.load_plugin_file_data(f)
 
168
        f.close()
 
169
        self.curdir, plugin_info_file = os.path.split(plugin_info_path)
 
170
        self.module = self.props['Module']
 
171
 
 
172
    def get_module (self):
 
173
        if self._loaded:
 
174
            return self._loaded
 
175
        else:
 
176
            if not self.curdir in sys.path:
 
177
                sys.path.append(self.curdir)
 
178
            try:
 
179
                self._loaded = __import__(self.module)
 
180
            except ImportError:
 
181
                print 'PATH:',sys.path
 
182
                raise
 
183
            return self._loaded
 
184
 
 
185
    def __getattr__ (self, attr):
 
186
        if attr == 'plugins': return self.get_plugins()
 
187
        elif self.props.has_key(attr): return self.props[attr]
 
188
        elif self.props.has_key(attr.capitalize()): return self.props[attr.capitalize()]
 
189
        else: raise AttributeError
 
190
 
 
191
    def get_plugins (self):
 
192
        return self.get_module().plugins
 
193
 
 
194
    def load_plugin_file_data (self,plugin_info_file):        
 
195
        # This should really use GKeyFile but there are no python
 
196
        # bindings that I can find atm. One possibility would be to
 
197
        # use this:
 
198
        # http://svn.async.com.br/cgi-bin/viewvc.cgi/kiwi/trunk/kiwi/desktopparser.py?revision=7336&view=markup
 
199
        self.props = dict([(k,None) for k in ['Name','Comment','Authors','Version','API_Version','Website','Copyright']])
 
200
 
 
201
        for line in plugin_info_file.readlines():
 
202
            if line=='[Gourmet Plugin]\n': pass
 
203
            elif line.find('=')>0:
 
204
                key,val = line.split('=')
 
205
                key = key.strip(); val = val.strip()
 
206
                key = key.strip('_')
 
207
                if '[' in key:
 
208
                    key,locale = key.strip(']').split('[')
 
209
                    if locale==loc:
 
210
                        self.props[key] = val
 
211
                    elif locale[:2]==loc[:2]:
 
212
                        self.props[key] = val
 
213
                else:
 
214
                    self.props[key]=val
 
215
            else:
 
216
                print 'Ignoring line',line
 
217
 
 
218
class Pluggable:
 
219
    """A plugin-able class."""
 
220
    
 
221
    def __init__ (self, plugin_klasses):
 
222
        """plugin_klasses is the list class of which each of our
 
223
        plugins should be a sub-class.
 
224
 
 
225
        A pluggable can take multiple types of sub-classes if it
 
226
        likes.
 
227
        """
 
228
        print 'Pluggabe.__init__([',plugin_klasses,'])'
 
229
        self.pre_hooks = {} # stores hooks to be run before methods by
 
230
                            # method name
 
231
        self.post_hooks = {} # stores hooks to be run after methods by
 
232
                             # method name
 
233
        self.loader = get_master_loader()
 
234
        self.klasses = plugin_klasses
 
235
        self.plugins = []
 
236
        for klass in self.klasses:
 
237
            print 'register self ',self,'as pluggable for ',klass
 
238
            self.loader.register_pluggable(self,klass)
 
239
 
 
240
    def plugin_plugin (self, plugin_instance):
 
241
        try:
 
242
            print 'plugging in ',plugin_instance
 
243
            self.plugins.append(plugin_instance)
 
244
            print 'activate!'
 
245
            plugin_instance.activate(self)
 
246
        except:
 
247
            print 'PLUGIN FAILED TO LOAD'
 
248
            import traceback; traceback.print_exc()
 
249
 
 
250
    def destroy (self):
 
251
        self.loader.unregister_pluggable(self,self.klass)
 
252
        for pi in self.plugins:
 
253
            print 'deactivate plugin',pi
 
254
            pi.deactivate(self)
 
255
 
 
256
    def run_pre_hook (self, fname, *args, **kwargs):
 
257
        print 'Looking for pre hooks for',fname
 
258
        for hook in self.pre_hooks.get(fname,[]):
 
259
            print 'run hook',hook
 
260
            hook(self,*args,**kwargs)
 
261
 
 
262
    def run_post_hook (self, fname, retval, *args, **kwargs):
 
263
        print 'Looking for post hooks for',fname        
 
264
        for hook in self.post_hooks.get(fname,[]):
 
265
            print 'run hook',hook
 
266
            hook(retval,self,*args,**kwargs)
 
267
 
 
268
    def add_hook (self, type, name, hook):
 
269
        if type==PRE: hookdic = self.pre_hooks
 
270
        else: hookdic = self.post_hooks
 
271
        if not hookdic.has_key(name):
 
272
            hookdic[name] = []
 
273
        hookdic[name].append(hook)
 
274
 
 
275
    def remove_hook (self, type, name, hook):
 
276
        if type==PRE: hookdic = self.pre_hooks
 
277
        else: hookdic = self.post_hooks
 
278
        del hookdic[name]
 
279
 
 
280
def pluggable_method (f):
 
281
    def _ (self, *args, **kwargs):
 
282
        '''Run hooks around method'''
 
283
        self.run_pre_hook(f.__name__,*args,**kwargs)
 
284
        retval = f(self,*args,**kwargs)
 
285
        self.run_post_hook(f.__name__,retval,*args,**kwargs)
 
286
        return retval
 
287
    return _
 
288
 
 
289
if __name__ == '__main__':
 
290
    class TestPlugin (plugin.Plugin):
 
291
        def activate ():
 
292
            print 'Activate!'
 
293
        def deactivate ():
 
294
            print 'Deactivate!'
 
295
    
 
296
    class UniversalPluggable (Pluggable):
 
297
        def __init__ (self):
 
298
            Pluggable.__init__(self,[plugin.Plugin])
 
299
 
 
300
    up = UniversalPluggable()
 
301
    #up.loader.activate_plugin(
 
302
    print up.plugins