def _getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile="<buffered info>"): """ Extract the name and module of a plugin from the content of the info file that describes it and which is stored in infoFileObject. .. note:: Prefer using ``_gatherCorePluginInfo`` instead, whenever possible... .. warning:: ``infoFileObject`` must be a file-like object: either an opened file for instance or a string buffer wrapped in a StringIO instance as another example. .. note:: ``candidate_infofile`` must be provided whenever possible to get better error messages. Return a 3-uple with the name of the plugin, its module and the config_parser used to gather the core data *in a tuple*, if the required info could be localised, else return ``(None,None,None)``. .. note:: This is supposed to be used internally by subclasses and decorators. """ # parse the information buffer to get info about the plugin config_parser = ConfigParser.SafeConfigParser() try: config_parser.readfp(infoFileObject) except Exception,e: log.warning("Could not parse the plugin file '%s' (exception raised was '%s')" % (candidate_infofile,e)) return (None, None, None)
def install(self, directory, plugin_info_filename): """ Giving the plugin's info file (e.g. ``myplugin.yapsy-plugin``), and the directory where it is located, get all the files that define the plugin and copy them into the correct directory. Return ``True`` if the installation is a success, ``False`` if it is a failure. """ # start collecting essential info about the new plugin plugin_info, config_parser = self._gatherCorePluginInfo(directory, plugin_info_filename) # now determine the path of the file to execute, # depending on wether the path indicated is a # directory or a file if not (os.path.exists(plugin_info.path) or os.path.exists(plugin_info.path+".py") ): log.warning("Could not find the plugin's implementation for %s." % plugin_info.name) return False if os.path.isdir(plugin_info.path): try: shutil.copytree(plugin_info.path, os.path.join(self.install_dir,os.path.basename(plugin_info.path))) shutil.copy(os.path.join(directory, plugin_info_filename), self.install_dir) except: log.error("Could not install plugin: %s." % plugin_info.name) return False else: return True elif os.path.isfile(plugin_info.path+".py"): try: shutil.copy(plugin_info.path+".py", self.install_dir) shutil.copy(os.path.join(directory, plugin_info_filename), self.install_dir) except: log.error("Could not install plugin: %s." % plugin_info.name) return False else: return True else: return False
def installFromZIP(self, plugin_ZIP_filename): """ Giving the plugin's zip file (e.g. ``myplugin.zip``), check that their is a valid info file in it and correct all the plugin files into the correct directory. .. warning:: Only available for python 2.6 and later. Return ``True`` if the installation is a success, ``False`` if it is a failure. """ if sys.version_info < (2, 6): raise NotImplementedError("Installing fom a ZIP file is only supported for Python2.6 and later.") if not os.path.isfile(plugin_ZIP_filename): log.warning("Could not find the plugin's zip file at '%s'." % plugin_ZIP_filename) return False try: candidateZipFile = zipfile.ZipFile(plugin_ZIP_filename) first_bad_file = candidateZipFile.testzip() if first_bad_file: raise Exception("Corrupted ZIP with first bad file '%s'" % first_bad_file) except Exception,e: log.warning("Invalid zip file '%s' (error: %s)." % (plugin_ZIP_filename,e)) return False
def locatePlugins(self): """ Walk through the plugins' places and look for plugins. Return the candidates and number of plugins found. """ # print "%s.locatePlugins" % self.__class__ _candidates = [] _discovered = {} for directory in map(os.path.abspath, self.plugins_places): # first of all, is it a directory :) if not os.path.isdir(directory): log.debug("%s skips %s (not a directory)" % (self.__class__.__name__, directory)) continue if self.recursive: debug_txt_mode = "recursively" walk_iter = os.walk(directory) else: debug_txt_mode = "non-recursively" walk_iter = [(directory,[],os.listdir(directory))] # iteratively walks through the directory log.debug("%s walks (%s) into directory: %s" % (self.__class__.__name__, debug_txt_mode, directory)) for item in walk_iter: dirpath = item[0] for filename in item[2]: # print "testing candidate file %s" % filename for analyzer in self._analyzers: # print "... with analyzer %s" % analyzer.name # eliminate the obvious non plugin files if not analyzer.isValidPlugin(filename): log.debug("%s is not a valid plugin for strategy %s" % (filename, analyzer.name)) continue candidate_infofile = os.path.join(dirpath, filename) if candidate_infofile in _discovered: log.debug("%s (with strategy %s) rejected because already discovered" % (candidate_infofile, analyzer.name)) continue log.debug("%s found a candidate:\n %s" % (self.__class__.__name__, candidate_infofile)) # print candidate_infofile plugin_info = self._getInfoForPluginFromAnalyzer(analyzer, dirpath, filename) if plugin_info is None: log.warning("Plugin candidate '%s' rejected by strategy '%s'" % (candidate_infofile, analyzer.name)) break # we consider this was the good strategy to use for: it failed -> not a plugin -> don't try another strategy # now determine the path of the file to execute, # depending on wether the path indicated is a # directory or a file # print plugin_info.path # Remember all the files belonging to a discovered # plugin, so that strategies (if several in use) won't # collide if os.path.isdir(plugin_info.path): candidate_filepath = os.path.join(plugin_info.path, "__init__") # it is a package, adds all the files concerned for _file in os.listdir(plugin_info.path): if _file.endswith(".py"): self._discovered_plugins[os.path.join(plugin_info.path, _file)] = candidate_filepath _discovered[os.path.join(plugin_info.path, _file)] = candidate_filepath elif (plugin_info.path.endswith(".py") and os.path.isfile(plugin_info.path)) or os.path.isfile(plugin_info.path+".py"): candidate_filepath = plugin_info.path if candidate_filepath.endswith(".py"): candidate_filepath = candidate_filepath[:-3] # it is a file, adds it self._discovered_plugins[".".join((plugin_info.path, "py"))] = candidate_filepath _discovered[".".join((plugin_info.path, "py"))] = candidate_filepath else: log.error("Plugin candidate rejected: cannot find the file or directory module for '%s'" % (candidate_infofile)) break # print candidate_filepath _candidates.append((candidate_infofile, candidate_filepath, plugin_info)) # finally the candidate_infofile must not be discovered again _discovered[candidate_infofile] = candidate_filepath self._discovered_plugins[candidate_infofile] = candidate_filepath # print "%s found by strategy %s" % (candidate_filepath, analyzer.name) return _candidates, len(_candidates)
def installFromZIP(self, plugin_ZIP_filename): """ Giving the plugin's zip file (e.g. ``myplugin.zip``), check that their is a valid info file in it and correct all the plugin files into the correct directory. .. warning:: Only available for python 2.6 and later. Return ``True`` if the installation is a success, ``False`` if it is a failure. """ if not os.path.isfile(plugin_ZIP_filename): log.warning("Could not find the plugin's zip file at '%s'." % plugin_ZIP_filename) return False try: candidateZipFile = zipfile.ZipFile(plugin_ZIP_filename) first_bad_file = candidateZipFile.testzip() if first_bad_file: raise Exception("Corrupted ZIP with first bad file '%s'" % first_bad_file) except Exception as e: log.warning("Invalid zip file '%s' (error: %s)." % (plugin_ZIP_filename,e)) return False zipContent = candidateZipFile.namelist() log.info("Investigating the content of a zip file containing: '%s'" % zipContent) log.info("Sanity checks on zip's contained files (looking for hazardous path symbols).") # check absence of root path and ".." shortcut that would # send the file oustide the desired directory for containedFileName in zipContent: # WARNING: the sanity checks below are certainly not # exhaustive (maybe we could do something a bit smarter by # using os.path.expanduser, os.path.expandvars and # os.path.normpath) if containedFileName.startswith("/"): log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with '/'" % containedFileName) return False if containedFileName.startswith(r"\\") or containedFileName.startswith("//"): log.warning(r"Unsecure zip file, rejected because one of its file paths ('%s') starts with '\\'" % containedFileName) return False if os.path.splitdrive(containedFileName)[0]: log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with a drive letter" % containedFileName) return False if os.path.isabs(containedFileName): log.warning("Unsecure zip file, rejected because one of its file paths ('%s') is absolute" % containedFileName) return False pathComponent = os.path.split(containedFileName) if ".." in pathComponent: log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '..'" % containedFileName) return False if "~" in pathComponent: log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '~'" % containedFileName) return False infoFileCandidates = [filename for filename in zipContent if os.path.dirname(filename)==""] if not infoFileCandidates: log.warning("Zip file structure seems wrong in '%s', no info file found." % plugin_ZIP_filename) return False isValid = False log.info("Looking for the zipped plugin's info file among '%s'" % infoFileCandidates) for infoFileName in infoFileCandidates: infoFile = candidateZipFile.read(infoFileName) log.info("Assuming the zipped plugin info file to be '%s'" % infoFileName) pluginName,moduleName,_ = self._getPluginNameAndModuleFromStream(StringIO(str(infoFile,encoding="utf-8"))) if moduleName is None: continue log.info("Checking existence of the expected module '%s' in the zip file" % moduleName) if moduleName in zipContent or os.path.join(moduleName,"__init__.py") in zipContent: isValid = True break if not isValid: log.warning("Zip file structure seems wrong in '%s', " "could not match info file with the implementation of plugin '%s'." % (plugin_ZIP_filename,pluginName)) return False else: try: candidateZipFile.extractall(self.install_dir) return True except Exception as e: log.error("Could not install plugin '%s' from zip file '%s' (exception: '%s')." % (pluginName,plugin_ZIP_filename,e)) return False
def loadPlugins(self, callback=None): """ Load the candidate plugins that have been identified through a previous call to locatePlugins. For each plugin candidate look for its category, load it and store it in the appropriate slot of the ``category_mapping``. If a callback function is specified, call it before every load attempt. The ``plugin_info`` instance is passed as an argument to the callback. Return a list of processed ``plugin_info`` instances. For any plugins which were not loaded, the ``error`` attribute will be populated with a (type, value, traceback) tuple, from sys.exc_info(). """ # print "%s.loadPlugins" % self.__class__ if not hasattr(self, '_candidates'): raise ValueError("locatePlugins must be called before loadPlugins") processed_plugins = [] for candidate_infofile, candidate_filepath, plugin_info in self._candidates: # if a callback exists, call it before attempting to load # the plugin so that a message can be displayed to the # user if callback is not None: callback(plugin_info) # now execute the file and get its content into a # specific dictionnary candidate_globals = {"__file__":candidate_filepath+".py"} if "__init__" in os.path.basename(candidate_filepath): sys.path.append(plugin_info.path) try: candidateMainFile = open(candidate_filepath+".py","r") exec(candidateMainFile,candidate_globals) processed_plugins.append(plugin_info) except Exception: log.error("Unable to execute the code in plugin: %s" % candidate_filepath, exc_info=True) if "__init__" in os.path.basename(candidate_filepath): sys.path.remove(plugin_info.path) plugin_info.error = sys.exc_info() processed_plugins.append(plugin_info) continue if "__init__" in os.path.basename(candidate_filepath): sys.path.remove(plugin_info.path) # now try to find and initialise the first subclass of the correct plugin interface for element in candidate_globals.itervalues(): current_category = None for category_name in self.categories_interfaces: try: is_correct_subclass = issubclass(element, self.categories_interfaces[category_name]) except: continue if is_correct_subclass: if element is not self.categories_interfaces[category_name]: current_category = category_name break if current_category is not None: if not (candidate_infofile in self._category_file_mapping[current_category]): # we found a new plugin: initialise it and search for the next one plugin_info.plugin_object = element() plugin_info.category = current_category self.category_mapping[current_category].append(plugin_info) self._category_file_mapping[current_category].append(candidate_infofile) current_category = None break if not (plugin_info.plugin_object or plugin_info.error): log.warning('No plubin object found for "%s": it may not do anything! Check that the plugin ' + 'extends IPlugin, and if there is an __init__ file that it imports ' + 'the class which extends IPlugin.', plugin_info.name) # Remove candidates list since we don't need them any more and # don't need to take up the space delattr(self, '_candidates') return processed_plugins
def locatePlugins(self): """ Walk through the plugins' places and look for plugins. Return the candidates and number of plugins found. """ # print "%s.locatePlugins" % self.__class__ _candidates = [] _discovered = {} for directory in map(os.path.abspath, self.plugins_places): # first of all, is it a directory :) if not os.path.isdir(directory): log.debug("%s skips %s (not a directory)" % (self.__class__.__name__, directory)) continue if self.recursive: debug_txt_mode = "recursively" walk_iter = os.walk(directory) else: debug_txt_mode = "non-recursively" walk_iter = [(directory, [], os.listdir(directory))] # iteratively walks through the directory log.debug("%s walks (%s) into directory: %s" % (self.__class__.__name__, debug_txt_mode, directory)) for item in walk_iter: dirpath = item[0] for filename in item[2]: # print "testing candidate file %s" % filename for analyzer in self._analyzers: # print "... with analyzer %s" % analyzer.name # eliminate the obvious non plugin files if not analyzer.isValidPlugin(filename): log.debug( "%s is not a valid plugin for strategy %s" % (filename, analyzer.name)) continue candidate_infofile = os.path.join(dirpath, filename) if candidate_infofile in _discovered: log.debug( "%s (with strategy %s) rejected because already discovered" % (candidate_infofile, analyzer.name)) continue log.debug( "%s found a candidate:\n %s" % (self.__class__.__name__, candidate_infofile)) # print candidate_infofile plugin_info = self._getInfoForPluginFromAnalyzer( analyzer, dirpath, filename) if plugin_info is None: log.warning( "Plugin candidate '%s' rejected by strategy '%s'" % (candidate_infofile, analyzer.name)) break # we consider this was the good strategy to use for: it failed -> not a plugin -> don't try another strategy # now determine the path of the file to execute, # depending on wether the path indicated is a # directory or a file # print plugin_info.path # Remember all the files belonging to a discovered # plugin, so that strategies (if several in use) won't # collide if os.path.isdir(plugin_info.path): candidate_filepath = os.path.join( plugin_info.path, "__init__") # it is a package, adds all the files concerned for _file in os.listdir(plugin_info.path): if _file.endswith(".py"): self._discovered_plugins[os.path.join( plugin_info.path, _file)] = candidate_filepath _discovered[os.path.join( plugin_info.path, _file)] = candidate_filepath elif (plugin_info.path.endswith(".py") and os.path.isfile(plugin_info.path) ) or os.path.isfile(plugin_info.path + ".py"): candidate_filepath = plugin_info.path if candidate_filepath.endswith(".py"): candidate_filepath = candidate_filepath[:-3] # it is a file, adds it self._discovered_plugins[".".join( (plugin_info.path, "py"))] = candidate_filepath _discovered[".".join( (plugin_info.path, "py"))] = candidate_filepath else: log.error( "Plugin candidate rejected: cannot find the file or directory module for '%s'" % (candidate_infofile)) break # print candidate_filepath _candidates.append((candidate_infofile, candidate_filepath, plugin_info)) # finally the candidate_infofile must not be discovered again _discovered[candidate_infofile] = candidate_filepath self._discovered_plugins[ candidate_infofile] = candidate_filepath # print "%s found by strategy %s" % (candidate_filepath, analyzer.name) return _candidates, len(_candidates)