2015-07-12 20:36:46 +02:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
2016-08-10 12:43:35 +02:00
|
|
|
from collections import defaultdict
|
2015-07-12 20:36:46 +02:00
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
from Debug import Debug
|
|
|
|
from Config import config
|
|
|
|
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
class PluginManager:
|
|
|
|
def __init__(self):
|
|
|
|
self.log = logging.getLogger("PluginManager")
|
|
|
|
self.plugin_path = "plugins" # Plugin directory
|
2016-08-10 12:43:35 +02:00
|
|
|
self.plugins = defaultdict(list) # Registered plugins (key: class name, value: list of plugins for class)
|
|
|
|
self.pluggable = {}
|
2015-07-12 20:36:46 +02:00
|
|
|
self.plugin_names = [] # Loaded plugin names
|
|
|
|
|
|
|
|
sys.path.append(self.plugin_path)
|
|
|
|
|
|
|
|
if config.debug: # Auto reload Plugins on file change
|
|
|
|
from Debug import DebugReloader
|
|
|
|
DebugReloader(self.reloadPlugins)
|
|
|
|
|
|
|
|
# -- Load / Unload --
|
|
|
|
|
|
|
|
# Load all plugin
|
|
|
|
def loadPlugins(self):
|
|
|
|
for dir_name in os.listdir(self.plugin_path):
|
|
|
|
dir_path = os.path.join(self.plugin_path, dir_name)
|
|
|
|
if dir_name.startswith("disabled"):
|
|
|
|
continue # Dont load if disabled
|
|
|
|
if not os.path.isdir(dir_path):
|
|
|
|
continue # Dont load if not dir
|
|
|
|
if dir_name.startswith("Debug") and not config.debug:
|
|
|
|
continue # Only load in debug mode if module name starts with Debug
|
|
|
|
self.log.debug("Loading plugin: %s" % dir_name)
|
|
|
|
try:
|
|
|
|
__import__(dir_name)
|
|
|
|
except Exception, err:
|
|
|
|
self.log.error("Plugin %s load error: %s" % (dir_name, Debug.formatException(err)))
|
|
|
|
if dir_name not in self.plugin_names:
|
|
|
|
self.plugin_names.append(dir_name)
|
|
|
|
|
|
|
|
# Reload all plugins
|
|
|
|
def reloadPlugins(self):
|
2016-08-10 12:43:35 +02:00
|
|
|
self.plugins_before = self.plugins
|
|
|
|
self.plugins = defaultdict(list) # Reset registered plugins
|
2015-07-12 20:36:46 +02:00
|
|
|
for module_name, module in sys.modules.items():
|
|
|
|
if module and "__file__" in dir(module) and self.plugin_path in module.__file__: # Module file within plugin_path
|
2016-08-10 12:43:35 +02:00
|
|
|
if "allow_reload" in dir(module) and not module.allow_reload: # Reload disabled
|
|
|
|
# Re-add non-reloadable plugins
|
|
|
|
for class_name, classes in self.plugins_before.iteritems():
|
|
|
|
for c in classes:
|
|
|
|
if c.__module__ != module.__name__:
|
|
|
|
continue
|
|
|
|
self.plugins[class_name].append(c)
|
|
|
|
else:
|
2015-07-12 20:36:46 +02:00
|
|
|
try:
|
|
|
|
reload(module)
|
|
|
|
except Exception, err:
|
|
|
|
self.log.error("Plugin %s reload error: %s" % (module_name, Debug.formatException(err)))
|
|
|
|
|
|
|
|
self.loadPlugins() # Load new plugins
|
|
|
|
|
2016-08-10 12:43:35 +02:00
|
|
|
# Change current classes in memory
|
|
|
|
import gc
|
|
|
|
patched = {}
|
|
|
|
for class_name, classes in self.plugins.iteritems():
|
|
|
|
classes = classes[:] # Copy the current plugins
|
|
|
|
classes.reverse()
|
|
|
|
base_class = self.pluggable[class_name] # Original class
|
|
|
|
classes.append(base_class) # Add the class itself to end of inherience line
|
|
|
|
plugined_class = type(class_name, tuple(classes), dict()) # Create the plugined class
|
|
|
|
for obj in gc.get_objects():
|
|
|
|
if type(obj).__name__ == class_name:
|
|
|
|
obj.__class__ = plugined_class
|
|
|
|
patched[class_name] = patched.get(class_name, 0) + 1
|
|
|
|
self.log.debug("Patched objects: %s" % patched)
|
|
|
|
|
|
|
|
# Change classes in modules
|
|
|
|
patched = {}
|
|
|
|
for class_name, classes in self.plugins.iteritems():
|
|
|
|
for module_name, module in sys.modules.iteritems():
|
|
|
|
if class_name in dir(module):
|
|
|
|
if "__class__" not in dir(getattr(module, class_name)): # Not a class
|
|
|
|
continue
|
|
|
|
base_class = self.pluggable[class_name]
|
|
|
|
classes = self.plugins[class_name][:]
|
|
|
|
classes.reverse()
|
|
|
|
classes.append(base_class)
|
|
|
|
plugined_class = type(class_name, tuple(classes), dict())
|
|
|
|
setattr(module, class_name, plugined_class)
|
|
|
|
patched[class_name] = patched.get(class_name, 0) + 1
|
|
|
|
|
|
|
|
self.log.debug("Patched modules: %s" % patched)
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
plugin_manager = PluginManager() # Singletone
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
|
|
|
# -- Decorators --
|
|
|
|
|
|
|
|
# Accept plugin to class decorator
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
def acceptPlugins(base_class):
|
2015-07-12 20:36:46 +02:00
|
|
|
class_name = base_class.__name__
|
2016-08-10 12:43:35 +02:00
|
|
|
plugin_manager.pluggable[class_name] = base_class
|
2015-07-12 20:36:46 +02:00
|
|
|
if class_name in plugin_manager.plugins: # Has plugins
|
|
|
|
classes = plugin_manager.plugins[class_name][:] # Copy the current plugins
|
|
|
|
classes.reverse()
|
|
|
|
classes.append(base_class) # Add the class itself to end of inherience line
|
|
|
|
plugined_class = type(class_name, tuple(classes), dict()) # Create the plugined class
|
|
|
|
plugin_manager.log.debug("New class accepts plugins: %s (Loaded plugins: %s)" % (class_name, classes))
|
|
|
|
else: # No plugins just use the original
|
|
|
|
plugined_class = base_class
|
|
|
|
return plugined_class
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
# Register plugin to class name decorator
|
|
|
|
def registerTo(class_name):
|
2015-07-12 20:36:46 +02:00
|
|
|
plugin_manager.log.debug("New plugin registered to: %s" % class_name)
|
|
|
|
if class_name not in plugin_manager.plugins:
|
|
|
|
plugin_manager.plugins[class_name] = []
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
def classDecorator(self):
|
|
|
|
plugin_manager.plugins[class_name].append(self)
|
|
|
|
return self
|
|
|
|
return classDecorator
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
# - Example usage -
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2015-07-12 20:36:46 +02:00
|
|
|
@registerTo("Request")
|
|
|
|
class RequestPlugin(object):
|
|
|
|
|
|
|
|
def actionMainPage(self, path):
|
|
|
|
return "Hello MainPage!"
|
|
|
|
|
|
|
|
@acceptPlugins
|
|
|
|
class Request(object):
|
|
|
|
|
|
|
|
def route(self, path):
|
|
|
|
func = getattr(self, "action" + path, None)
|
|
|
|
if func:
|
|
|
|
return func(path)
|
|
|
|
else:
|
|
|
|
return "Can't route to", path
|
|
|
|
|
|
|
|
print Request().route("MainPage")
|