~mvo/update-manager/pae-kernel-transtion

« back to all changes in this revision

Viewing changes to Janitor/computerjanitor/plugin.py

start of the u-m <-> computerjanitor merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# plugin.py - plugin base class for computerjanitor
 
2
# Copyright (C) 2008  Canonical, Ltd.
 
3
#
 
4
# This program is free software: you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation, version 3 of the License.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 
 
16
 
 
17
import imp
 
18
import inspect
 
19
import logging
 
20
import os
 
21
 
 
22
import computerjanitor
 
23
_ = computerjanitor.setup_gettext()
 
24
 
 
25
 
 
26
class Plugin(object):
 
27
 
 
28
    """Base class for plugins.
 
29
    
 
30
    These plugins only do one thing: identify cruft. See the 'get_cruft'
 
31
    method for details.
 
32
    
 
33
    """
 
34
 
 
35
    def get_condition(self):
 
36
        if hasattr(self, "_condition"):
 
37
            return self._condition
 
38
        else:
 
39
            return None
 
40
            
 
41
    def set_condition(self, condition):
 
42
        self._condition = condition
 
43
 
 
44
    condition = property(get_condition, set_condition)
 
45
    
 
46
    def set_application(self, app):
 
47
        """Set the Application instance this plugin belongs to.
 
48
        
 
49
        This is used by the plugin manager when creating the plugin
 
50
        instance. In a perfect world, this would be done via the
 
51
        __init__ method, but since I took a wrong left turn, I ended
 
52
        up in an imperfect world, and therefore giving the Application
 
53
        instance to __init__ would mandate that all sub-classes would
 
54
        have to deal with that explicitly. That is a lot of unnecessary
 
55
        make-work, which we should avoid. Therefore, we do it via this
 
56
        method.
 
57
        
 
58
        The class may access the Application instance via the
 
59
        'app' attribute.
 
60
        
 
61
        """
 
62
        
 
63
        self.app = app
 
64
 
 
65
    def do_cleanup_cruft(self):
 
66
        """Find cruft and clean it up
 
67
 
 
68
        This is a helper method
 
69
        """
 
70
        for cruft in self.get_cruft():
 
71
            cruft.cleanup()
 
72
        self.post_cleanup()
 
73
 
 
74
    def get_cruft(self):
 
75
        """Find some cruft in the system.
 
76
        
 
77
        This method MUST return an iterator (see 'yield' statement).
 
78
        This interface design allows cruft to be collected piecemeal,
 
79
        which makes it easier to show progress in the user interface.
 
80
        
 
81
        The base class default implementation of this raises an
 
82
        exception. Subclasses MUST override this method.
 
83
 
 
84
        """
 
85
 
 
86
        raise computerjanitor.UnimplementedMethod(self.get_cruft)
 
87
 
 
88
    def post_cleanup(self):
 
89
        """Does plugin wide cleanup after the individual cleanup
 
90
           was performed.
 
91
           
 
92
           This is useful for stuff that needs to be proccessed
 
93
           in batches (e.g. for performance reasons) like package
 
94
           removal.
 
95
        """
 
96
        pass
 
97
 
 
98
 
 
99
class PluginManager(object):
 
100
 
 
101
    """Class to find and load plugins.
 
102
    
 
103
    Plugins are stored in files named '*_plugin.py' in the list of
 
104
    directories given to the initializer.
 
105
    
 
106
    """
 
107
    
 
108
    def __init__(self, app, plugin_dirs):
 
109
        self._app = app
 
110
        self._plugin_dirs = plugin_dirs
 
111
        self._plugins = None
 
112
 
 
113
    def get_plugin_files(self):
 
114
        """Return all filenames in which plugins may be stored."""
 
115
        
 
116
        names = []
 
117
        
 
118
        for dirname in self._plugin_dirs:
 
119
            basenames = [x for x in os.listdir(dirname) 
 
120
                            if x.endswith("_plugin.py")]
 
121
            logging.debug(_("Plugin modules in %s: %s") % 
 
122
                            (dirname, " ".join(basenames)))
 
123
            names += [os.path.join(dirname, x) for x in basenames]
 
124
        
 
125
        return names
 
126
 
 
127
    def _find_plugins(self, module):
 
128
        """Find and instantiate all plugins in a module."""
 
129
        plugins = []
 
130
        for dummy, member in inspect.getmembers(module):
 
131
            if inspect.isclass(member) and issubclass(member, Plugin):
 
132
                plugins.append(member)
 
133
        logging.debug(_("Plugins in %s: %s") %
 
134
                      (module, " ".join(str(x) for x in plugins)))
 
135
        return [plugin() for plugin in plugins]
 
136
 
 
137
    def _load_module(self, filename):
 
138
        """Load a module from a filename."""
 
139
        logging.debug(_("Loading module %s") % filename)
 
140
        module_name, dummy = os.path.splitext(os.path.basename(filename))
 
141
        f = file(filename, "r")
 
142
        module = imp.load_module(module_name, f, filename,
 
143
                                 (".py", "r", imp.PY_SOURCE))
 
144
        f.close()
 
145
        return module
 
146
 
 
147
    def get_plugins(self, condition=None, callback=None):
 
148
        """Return all plugins that have been found.
 
149
        
 
150
        If callback is specified, it is called after each plugin has
 
151
        been found, with the following arguments: filename, index of
 
152
        filename in list of files to be examined (starting with 0), and
 
153
        total number of files to be examined. The purpose of this is to
 
154
        allow the callback to inform the user in case things take a long
 
155
        time.
 
156
        
 
157
        """
 
158
 
 
159
        if self._plugins is None:
 
160
            self._plugins = []
 
161
            filenames = self.get_plugin_files()
 
162
            for i in range(len(filenames)):
 
163
                if callback:
 
164
                    callback(filenames[i], i, len(filenames))
 
165
                module = self._load_module(filenames[i])
 
166
                for plugin in self._find_plugins(module):
 
167
                    plugin.set_application(self._app)
 
168
                    self._plugins.append(plugin)
 
169
        
 
170
        return [p for p in self._plugins 
 
171
                if p.condition == condition or condition == "*"]