def check_list_needs(self, pkg): """ Check the needs from list against pkg @param pkg the plugin to check @return [] if everything is OK or a list of not used needs (name, op, version) """ # Clone the list and drops out pkg lst = self.pkg_lst[:] lst.remove(pkg) conflicts_lst = [] for provide in pkg.provides: p_name, p_op, p_ver = Version.extract_version(provide) for n_pkg in lst: for need in n_pkg.needs: n_name, n_op, n_ver = Version.extract_version(need) if n_name != p_name: continue # It's our need and we have a dep! :D if n_op(p_ver, n_ver) is True: conflicts_lst.append((n_pkg, n_op, n_ver)) return conflicts_lst
def load_plugin(self, pkg, force=False): """ Load a plugin @param pkg the plugin to load @param force if True no checks for deps will performed @return None or raise a PluginException """ if pkg in self.pkg_lst: raise PluginException(pkg, _("Plugin already loaded.")) if not force: # 1) Check for conflicts ret = self.check_conflicts(pkg) if ret: reasons = [] for phase, (p_name, p_op, p_ver), (c_name, c_op, c_ver) in ret: reasons.append( \ "-%d- Plugin '%s' provides %s which conflicts with " \ "%s conflict entry for plugin '%s'" % \ ( \ phase, pkg, \ Version.stringify_version(p_name, p_op, p_ver), \ Version.stringify_version(p_name, c_op, c_ver), \ c_name \ ) \ ) raise PluginException(pkg, "\n".join(reasons)) # 2) Check for needs ret = self.check_needs(pkg) if ret: reasons = [] for (n_name, n_op, n_ver) in ret: reasons.append( \ "-3- Plugin '%s' needs %s, which actually is not " \ "provided by any plugin." % \ ( \ pkg, \ Version.stringify_version(n_name, n_op, n_ver) \ ) \ ) raise PluginException(pkg, "\n".join(reasons)) self.__load_hook(pkg) # Export to cache self.add_plugin_to_cache(pkg) self.dump()
def unload_plugin(self, pkg, force=False): """ Unload a plugin @param pkg the plugin to unload @param force if False check the depends @return None or raise a PluginException """ if pkg not in self.pkg_lst: raise Exception("Plugin not loaded.") if not force: # No conflict check here. # 1) We should check need's list against pkg ret = self.check_list_needs(pkg) if ret: reasons = [] for i in ret: reasons.append( "Plugin %s requires %s" % \ (i[0], Version.stringify_version(pkg, i[1], i[2])) ) raise PluginException(pkg, "\n".join(reasons)) self.__unload_hook(pkg) self.remove_plugin_from_cache(pkg) self.dump()
def add_plugin_to_cache(self, pkg): """ Exports the needs/provides/conflicts to the global dicts """ for attr in ('conflicts', 'provides', 'needs'): for dep in getattr(pkg, attr, []): try: name, op, ver = Version.extract_version(dep) d = getattr(self, "who_%s" % attr) d[name] = (op, ver, pkg) except Exception, err: log.warning(err) log.warning("Ignoring %s entry" % dep)
def check_conflicts(self, pkg): """ Check the conflicts between pkg and global list of provides or loaded plugins. The return list contains entry like: (phase-number:int, (p_name:str, p_op:Operator, p_ver:Version) -> Provider (c_name:str, c_op:Operator, c_ver:Version) -> Conflicter ) @param pkg the plugin to check @return [] if everything is OK or a list of plugins conflicting with pkg """ lst = [] # # 1) We should check the cache conflicts agains pkg for provide in pkg.provides: try: name, op, ver = Version.extract_version(provide) if not name in self.who_conflicts: continue for c_op, c_ver, c_pkg in self.who_conflicts[name]: # We could have # c_op, c_ver >=, 2.0.0 # ver = 3.0.0 # We could have a None if a conflict entry didn't provide # any version or operator if c_op.op_str == '.' or c_op(ver, c_ver) is True: lst.append( (1, (name, op, ver), # Provider (c_pkg, c_op, c_ver) # Conflicter ) ) except Exception, err: log.error(err) log.error("1) Ignoring conflict entry ...") continue
def check_needs(self, pkg): """ Check the needs between pkg and global list of provides. The return list contains entry like: (name:str, op:Operator, ver:Version) -> need not found @param pkg the plugin to check @return [] if everything is OK or a list of not resolved needs """ # We have passed the conflict stage # so check for needs lst = [] for need in pkg.needs: try: found = False name, op, ver = Version.extract_version(need) if not name in self.who_provides: lst.append((name, op, ver)) continue for n_op, n_ver, n_pkg in self.who_provides[name]: # for example we could have # name, op, ver = dummy, >=, 2.0.0 # n_ver = 2.0.3 # TODO: check me if op(n_ver, ver) is True or op(n_ver, ver) is None: found = True break # If we are here not applicable deps were found so add if not found: lst.append((name, op, ver)) except Exception, err: log.error(err) log.error("Ignoring need entry ...") continue
def get_need(self, reader, needstr, classname=None, need_module=False): """ Usefull function to return an instance of needstr:classname @param reader a PluginReader object or None """ lst = [] if reader: # We create a list of needs for the same package for need in reader.needs: name, op, ver = Version.extract_version(need) if name == needstr: lst.append((op, ver, name)) from umit.pm.gui.plugins.engine import PluginEngine ret = PluginEngine().tree.get_provide(needstr, lst, need_module) log.debug(">>> Core.get_need() -> %s (module: %s)" % (ret, need_module)) if not ret: return None if not need_module: if not classname: log.debug(">>> Core.get_need(): No classname specified. " \ "Returning first instance") return ret[0] for instance in ret: if instance.__class__.__name__ == classname: return instance else: # We need a module if len(ret) > 1: log.debug(">>> Core.get_need(): Returning the first module") return ret[0] return None
def remove_plugin_from_cache(self, pkg): for attr in ('conflicts', 'provides', 'needs'): for dep in getattr(pkg, attr, []): try: name, op, ver = Version.extract_version(dep) d = getattr(self, "who_%s" % attr) # It's more probably that the entry is in the last for i in xrange(len(d[name]) - 1, -1, -1): _op, _ver, _pkg = d[name][i] if pkg == _pkg and \ ver == _ver and \ op == _op: del d[name][i] # Remove unused keys. if not d[name]: del d[name] except Exception, err: log.warning(err) log.warning("Ignoring %s entry" % dep)
def __update_rich_list(self): """ This is the timeout callback called to update the gui in the find phase """ working = False for upd_obj in self.p_window.update_eng.list: upd_obj.lock.acquire() try: # Check if we are working if upd_obj.status == LATEST_GETTING: working = True # Update the row row = upd_obj.object row.message = upd_obj.label finally: upd_obj.lock.release() # No locking from here we have finished if not working: # Mark as finished self.p_window.update_eng.stop() self.find_updates_btn.set_sensitive(True) lst = filter( \ lambda x: (x.status == LATEST_GETTED) and (x) or (None),\ self.p_window.update_eng.list \ ) elst = filter( \ lambda x: (x.status == LATEST_ERROR) and (x) or (None),\ self.p_window.update_eng.list \ ) if not lst and not elst: self.p_window.toolbar.unset_status() self.p_window.animated_bar.label = \ _('<b>No updates found</b>') self.p_window.animated_bar.start_animation(True) self.richlist.clear() self.populate() self.menu_enabled = True else: # Now prepare the download page if not elst: self.p_window.toolbar.show_message( \ _("<b>Updates found: %d</b>") % len(lst), \ stock=gtk.STOCK_APPLY \ ) else: self.p_window.toolbar.show_message( \ _('<b>Updates found: %d with %d errors</b>') \ % (len(lst), len(elst)), stock=gtk.STOCK_APPLY \ ) for obj in self.p_window.update_eng.list: row = obj.object active = (obj.status == LATEST_GETTED) if active: obj.last_update_idx = 0 row.show_include = True log.debug("Connecting 'query-tooltip' for %s" % obj) # The tooltip is showed and hidden continuously row.versions_button.props.has_tooltip = True row.versions_button.connect( 'query-tooltip', self.__query_tooltip_versions_button, obj) row.versions_model.clear() row.versions_model.append( [gtk.STOCK_CANCEL, _("Skip")]) for update in obj.updates: cur_v = Version(row.reader.version) new_v = Version(update.version) if new_v > cur_v: row.versions_model.append([ gtk.STOCK_GO_UP, _("Update to %s") % \ update.version ]) elif new_v == cur_v: row.versions_model.append([ gtk.STOCK_REFRESH, _("Reinstall %s") % \ update.version ]) else: row.versions_model.append([ gtk.STOCK_GO_DOWN, _("Downgrade to %s") % \ update.version ]) row.versions_button.set_active(0) row.message = obj.label else: row.saturate = True if lst: self.install_updates_btn.show() self.find_updates_btn.hide() self.skip_install_btn.show() return working
class PluginsTree(object): """ Manages and tracks the loads/unloads of plugins objects """ def __init__(self): """ Create a new PluginsTree @param parent a PluginEngine instance """ # A dict to trace plugin instances self.instances = {} self.modules = {} self.who_conflicts, \ self.who_provides, \ self.who_needs = DepDict(), DepDict(), DepDict() self.pkg_lst = list() def dump(self): log.info(">>> dump(): conflicts/provides/needs: %d / %d / %d" % \ ( len(self.who_conflicts), len(self.who_provides), len(self.who_needs) ) ) def check_conflicts(self, pkg): """ Check the conflicts between pkg and global list of provides or loaded plugins. The return list contains entry like: (phase-number:int, (p_name:str, p_op:Operator, p_ver:Version) -> Provider (c_name:str, c_op:Operator, c_ver:Version) -> Conflicter ) @param pkg the plugin to check @return [] if everything is OK or a list of plugins conflicting with pkg """ lst = [] # # 1) We should check the cache conflicts agains pkg for provide in pkg.provides: try: name, op, ver = Version.extract_version(provide) if not name in self.who_conflicts: continue for c_op, c_ver, c_pkg in self.who_conflicts[name]: # We could have # c_op, c_ver >=, 2.0.0 # ver = 3.0.0 # We could have a None if a conflict entry didn't provide # any version or operator if c_op.op_str == '.' or c_op(ver, c_ver) is True: lst.append( (1, (name, op, ver), # Provider (c_pkg, c_op, c_ver) # Conflicter ) ) except Exception, err: log.error(err) log.error("1) Ignoring conflict entry ...") continue # # 2) We should check the package conflicts against cache provides for conflict in pkg.conflicts: try: name, op, ver = Version.extract_version(conflict) if not name in self.who_provides: continue # Only '=' operator presents: # providers['woot'] = [('=', '2.0', pkg1), ('=', '2.1', pkg2)] for p_op, p_ver, p_pkg in self.who_provides[name]: # for example we could have # name, op, ver = dummy, >=, 2.0.0 # p_ver = 2.0.3 # strict checking for avoid false results # if None was returned if op(p_ver, ver) is True: # So we have a conflict. Just add to the list lst.append( (2, (p_pkg, p_op, p_ver), # Provider (name, op, ver) # Conflicter ) ) except Exception, err: log.error(err) log.error("2) Ignoring conflict entry ...") continue
def __process_manifest(self, file, data, exc, obj): """ Callback to parse latest.xml file containing meta information about the avaiable update. """ if isinstance(exc, ErrorNetException): obj.lock.acquire() try: if obj.last_update_idx + 1 < len(obj.object.reader.update): obj.last_update_idx += 1 obj.status = LATEST_GETTING obj.label = _('Cycling to next update url. Waiting...') self.update_lst.append(obj) elif isinstance(exc, ErrorNetException): obj.status = LATEST_ERROR obj.label = _( 'Cannot find newer version (%s)') % exc.reason self.__process_next() return finally: obj.lock.release() elif isinstance(exc, StopNetException): try: obj.parse_latest_file() except Exception, exc: if obj.last_update_idx + 1 < len(obj.object.reader.update): obj.last_update_idx += 1 obj.status = LATEST_GETTING obj.label = _('Cycling to next update url. Waiting...') self.update_lst.append(obj) else: obj.status = LATEST_ERROR obj.label = _('Cannot find newer version (%s)') % str(exc) self.__process_next() return version = None type = -1 # -1 no action / 0 update / 1 downgrade # If we have only 1 update.. if len(obj.updates) == 1: version = obj.updates[0].version new_v = Version(version) cur_v = Version(obj.object.reader.version) if new_v > cur_v: type = 0 elif cur_v < new_v: type = 1 obj.lock.acquire() try: if obj.updates: # We check if the path is the plugins in config_dir if os.path.dirname(obj.object.reader.get_path()) != PM_PLUGINS_DIR and \ os.access(PM_PLUGINS_DIR, os.O_RDWR): obj.status = LATEST_ERROR if type == -1: obj.label = _( 'Various versions available but require manual intervention.' ) elif type == 0: obj.label = _( 'Version %s available but need manual update.' ) % version elif type == 1: obj.label = _( 'Version %s available but need manual downgrade.' ) % version else: obj.status = LATEST_GETTED if type == -1: obj.label = _('Various versions available') else: obj.label = _('Version %s available.') % version else: obj.status = LATEST_ERROR if type < 0: obj.label = _('Unable to parse latest.xml') else: obj.label = _('No applicable updates found') self.__process_next() finally: obj.lock.release()