Back to index

obnam  1.1
hooks.py
Go to the documentation of this file.
00001 # Copyright (C) 2009  Lars Wirzenius
00002 #
00003 # This program is free software: you can redistribute it and/or modify
00004 # it under the terms of the GNU General Public License as published by
00005 # the Free Software Foundation, either version 3 of the License, or
00006 # (at your option) any later version.
00007 #
00008 # This program is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 # GNU General Public License for more details.
00012 #
00013 # You should have received a copy of the GNU General Public License
00014 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
00015 
00016 
00017 '''Hooks with callbacks.
00018 
00019 In order to de-couple parts of the application, especially when plugins
00020 are used, hooks can be used. A hook is a location in the application
00021 code where plugins may want to do something. Each hook has a name and
00022 a list of callbacks. The application defines the name and the location
00023 where the hook will be invoked, and the plugins (or other parts of the
00024 application) will register callbacks.
00025 
00026 '''
00027 
00028 
00029 import logging
00030 import tracing
00031 
00032 import obnamlib
00033 
00034 
00035 class Hook(object):
00036 
00037     '''A hook.'''
00038 
00039     EARLY_PRIORITY = 250
00040     DEFAULT_PRIORITY = 500
00041     LATE_PRIORITY = 750
00042 
00043     def __init__(self):
00044         self.callbacks = []
00045         self.priorities = {}
00046         
00047     def add_callback(self, callback, priority=DEFAULT_PRIORITY):
00048         '''Add a callback to this hook.
00049         
00050         Return an identifier that can be used to remove this callback.
00051 
00052         '''
00053 
00054         if callback not in self.callbacks:
00055             self.priorities[callback] = priority
00056             self.callbacks.append(callback)
00057             self.callbacks.sort(lambda x,y: cmp(self.priorities[x], 
00058                                                 self.priorities[y]))
00059             
00060         return callback
00061         
00062     def call_callbacks(self, *args, **kwargs):
00063         '''Call all callbacks with the given arguments.'''
00064         for callback in self.callbacks:
00065             callback(*args, **kwargs)
00066         
00067     def remove_callback(self, callback_id):
00068         '''Remove a specific callback.'''
00069         if callback_id in self.callbacks:
00070             self.callbacks.remove(callback_id)
00071             del self.priorities[callback_id]
00072 
00073 
00074 class MissingFilterError(obnamlib.Error):
00075 
00076     '''Missing tag encountered reading filtered data.'''
00077 
00078     def __init__(self, tagname):
00079         self.tagname = tagname
00080         logging.warning("Missing tag: " + repr(tagname))
00081         obnamlib.Error.__init__(self, "Unknown filter tag encountered: %s" %
00082                                 repr(tagname))
00083 
00084 
00085 class FilterHook(Hook):
00086 
00087     '''A hook which filters data through callbacks.
00088     
00089     Every hook of this type accepts a piece of data as its first argument
00090     Each callback gets the return value of the previous one as its
00091     argument. The caller gets the value of the final callback.
00092     
00093     Other arguments (with or without keywords) are passed as-is to
00094     each callback.
00095     
00096     '''
00097     
00098     def __init__(self):
00099         Hook.__init__(self)
00100         self.bytag = {}
00101 
00102     def add_callback(self, callback, priority=Hook.DEFAULT_PRIORITY):
00103         assert(hasattr(callback, "tag"))
00104         assert(hasattr(callback, "filter_read"))
00105         assert(hasattr(callback, "filter_write"))
00106         self.bytag[callback.tag] = callback
00107         return Hook.add_callback(self, callback, priority)
00108 
00109     def remove_callback(self, callback_id):
00110         Hook.remove_callback(self, callback_id)
00111         del self.bytag[callback_id.tag]
00112 
00113     def call_callbacks(self, data, *args, **kwargs):
00114         raise NotImplementedError()
00115         
00116     def run_filter_read(self, data, *args, **kwargs):
00117         tag, content = data.split("\0", 1)
00118         while tag != "":
00119             if tag not in self.bytag:
00120                 raise MissingFilterError(tag)
00121             data = self.bytag[tag].filter_read(content, *args, **kwargs)
00122             tag, content = data.split("\0", 1)
00123         return content
00124 
00125     def run_filter_write(self, data, *args, **kwargs):
00126         tracing.trace('called')
00127         data = "\0" + data
00128         for filt in self.callbacks:
00129             tracing.trace('calling %s' % filt)
00130             new_data = filt.filter_write(data, *args, **kwargs)
00131             assert new_data is not None, \
00132                    filt.tag + ": Returned None from filter_write()"
00133             if data != new_data:
00134                 tracing.trace('filt.tag=%s' % filt.tag)
00135                 data = filt.tag + "\0" + new_data
00136         tracing.trace('done')
00137         return data
00138 
00139 
00140 class HookManager(object):
00141 
00142     '''Manage the set of hooks the application defines.'''
00143     
00144     def __init__(self):
00145         self.hooks = {}
00146         self.filters = {}
00147         
00148     def new(self, name):
00149         '''Create a new hook.
00150         
00151         If a hook with that name already exists, nothing happens.
00152         
00153         '''
00154 
00155         if name not in self.hooks:
00156             self.hooks[name] = Hook()
00157 
00158     def new_filter(self, name):
00159         '''Create a new filter hook.'''
00160         if name not in self.filters:
00161             self.filters[name] = FilterHook()
00162 
00163     def add_callback(self, name, callback, priority=Hook.DEFAULT_PRIORITY):
00164         '''Add a callback to a named hook.'''
00165         if name in self.hooks:
00166             return self.hooks[name].add_callback(callback, priority)
00167         else:
00168             return self.filters[name].add_callback(callback, priority)
00169         
00170     def remove_callback(self, name, callback_id):
00171         '''Remove a specific callback from a named hook.'''
00172         if name in self.hooks:
00173             self.hooks[name].remove_callback(callback_id)
00174         else:
00175             self.filters[name].remove_callback(callback_id)
00176         
00177     def call(self, name, *args, **kwargs):
00178         '''Call callbacks for a named hook, using given arguments.'''
00179         self.hooks[name].call_callbacks(*args, **kwargs)
00180 
00181     def filter_read(self, name, *args, **kwargs):
00182         '''Run reader filter for named filter, using given arguments.'''
00183         return self.filters[name].run_filter_read(*args, **kwargs)
00184 
00185     def filter_write(self, name, *args, **kwargs):
00186         '''Run writer filter for named filter, using given arguments.'''
00187         return self.filters[name].run_filter_write(*args, **kwargs)