fixed - --uninstalled-only does not really exclude installed resolvables
authorJan Kupec <jkupec@suse.cz>
Fri, 27 Oct 2006 11:59:31 +0000 (11:59 +0000)
committerJan Kupec <jkupec@suse.cz>
Fri, 27 Oct 2006 11:59:31 +0000 (11:59 +0000)
fixed - search for all resolvables displays installed packages twice
fixed - source (catalog) information missing for installed packages
added rug's v status

src/zypper-search.cc
src/zypper-search.h
src/zypper.cc

index b14a6aa..5a3796f 100644 (file)
@@ -4,13 +4,16 @@
 #include "zmart-sources.h"
 #include "zmart-misc.h"
 
-using namespace zypp;
+#include <zypp/base/Algorithm.h>
+
 using namespace std;
 using namespace boost;
+using namespace zypp;
+using namespace zypp::functor;
+using namespace zypp::resfilter;
 
 // TODO get rid of these globals
 extern RuntimeData gData;
-extern Settings gSettings;
 
 void ZyppSearchOptions::resolveConflicts() {
   if (matchExact()) {
@@ -23,52 +26,48 @@ void ZyppSearchOptions::resolveConflicts() {
   // ??? should we notify user about conflict resolutions?
 }
 
-ZyppSearch::ZyppSearch (const ZyppSearchOptions & options, const vector<string> & qstrings) :
-    _options(options), _qstrings(qstrings) {
-  init();
-}
-
-// TODO clean this up
-bool ZyppSearch::init () const {
-  cond_init_system_sources();
-  cond_init_target();
-  
-  // load additional sources
-  for ( std::list<Url>::const_iterator it = gSettings.additional_sources.begin();
-      it != gSettings.additional_sources.end(); ++it ) {
-    include_source_by_url( *it );
-  }
-  
-  // TODO no sources warning
-  if ( gData.sources.empty() ) {
-    cerr << "Warning! No sources. Operating only over the installed resolvables."
-      " You will not be able to install stuff" << endl;
-  }
-
-  if (_options.installedFilter() != ZyppSearchOptions::UNINSTALLED_ONLY) {
-    cerr_v << "loading target" << endl;
-    load_target();
-  }
-
-  if (_options.installedFilter() != ZyppSearchOptions::INSTALLED_ONLY) {
-    cerr_v << "loading sources" << endl;
-    load_sources();
+/**
+ * Initializes installation sources, creates search regex, caches installed
+ * packages from RPM database, and populates ResPool with items from
+ * installation sources.
+ */ 
+ZyppSearch::ZyppSearch (
+    ZYpp::Ptr & zypp,
+    const ZyppSearchOptions & options,
+    const vector<string> qstrings
+    ) :
+    _zypp(zypp), _options(options), _qstrings(qstrings) {
+
+  cond_init_target();         // calls ZYpp::initializeTarget("/");
+  cond_init_system_sources(); // calls manager->restore("/");
+
+  // no sources warning
+  if (gData.sources.empty()) {
+    cerr << "No sources. Zypper currently searches within installation"
+        "sources only." << endl;
+    exit(2); // TODO #define zypper error codes?
   }
 
-  return true;
+  setupRegexp();
+  cacheInstalled();
+  load_sources(); // populates ResPool with resolvables from inst. sources
 }
 
-void ZyppSearch::doSearch(const boost::function<void(const PoolItem &)> & f) {
-  ResPool pool = getZYpp()->pool();
+/**
+ * Invokes zypp::invokeOnEach() on a subset of pool items restricted by
+ * some search criteria (--type,--match-exact).
+ */
+template <class _Filter, class _Function>
+int ZyppSearch::invokeOnEachSearched(_Filter filter_r, _Function fnc_r) {
+  ResPool pool = _zypp->pool();
 
   // search for specific resolvable type only
   if (_options.kind() != Resolvable::Kind()) {
-    cerr_vv << "Search by type" << endl;
-    setupRegexp();
-    for (ResPool::byKind_iterator it = pool.byKindBegin(_options.kind());
-        it != pool.byKindEnd(_options.kind()); ++it) {
-      if (match(*it)) f(*it);
-    }
+    cerr_vv << "invokeOnEachSearched(): search by type" << endl;
+
+    return invokeOnEach(
+        pool.byKindBegin(_options.kind()), pool.byKindEnd(_options.kind()),
+        filter_r, fnc_r);
   }
   // search for exact package using byName_iterator
   // usable only if there is only one query string and if this string
@@ -76,30 +75,78 @@ void ZyppSearch::doSearch(const boost::function<void(const PoolItem &)> & f) {
   else if (_options.matchExact() && _qstrings.size() == 1 &&
       _qstrings[0].find('*') == string::npos &&
       _qstrings[0].find('?') == string::npos) {
-    cerr_vv << "Exact name match" << endl;
-    for (ResPool::byName_iterator it = pool.byNameBegin(_qstrings[0]);
-        it != pool.byNameEnd(_qstrings[0]); ++it) {
-      f(*it); //table << createRow(*it);
-    }
+    cerr_vv << "invokeOnEachSearched(): exact name match" << endl;
+
+    return invokeOnEach(
+        pool.byNameBegin(_qstrings[0]), pool.byNameEnd(_qstrings[0]),
+        filter_r, fnc_r);
   }
+
   // search among all resolvables
   else {
-    cerr_vv << "Search among all resolvables" << endl;
-    setupRegexp();
-    for (ResPool::const_iterator it = pool.begin(); it != pool.end(); ++it) {
-      if (match(*it)) f(*it); //table << createRow(*it);
-    }
+    cerr_vv << "invokeOnEachSearched(): search among all resolvables" << endl;
+
+    return invokeOnEach(pool.begin(), pool.end(), filter_r, fnc_r);
   }
 }
 
+/**
+ * Cache installed packages matching given search criteria into a hash_map.
+ * Assumption made: names of currently installed resolvables + kind
+ * (+version???) are unique.
+ */
+void ZyppSearch::cacheInstalled() {
+  // don't include kind string in hash map key if search is to be restricted
+  // to particular kind (to improve performance a little bit)
+  if (_options.kind() != Resolvable::Kind())
+    _icache.setIncludeKindInKey(false);
+
+  cout_v << "Pre-caching installed resolvables matching given search criteria... " << endl;
+
+  ResStore tgt_resolvables(_zypp->target()->resolvables());
+
+  _zypp->addResolvables(tgt_resolvables, true /*installed*/);
+
+  invokeOnEachSearched(Match(_reg,_options.searchDescriptions()),
+    functorRef<bool,const zypp::PoolItem &>(_icache));
+
+  _zypp->removeResolvables(tgt_resolvables);
+
+  cout_v << _icache.size() << " out of (" <<  tgt_resolvables.size() << ")"  
+    "cached." << endl;
+}
+
+/**
+ * Invokes functor f on each pool item matching search criteria. 
+ */
+void ZyppSearch::doSearch(const boost::function<bool(const PoolItem &)> & f) {
+  boost::function<bool (const PoolItem &)> filter;
+
+  switch (_options.installedFilter()) {
+    case ZyppSearchOptions::INSTALLED_ONLY:
+      filter = chain(ByInstalledCache(_icache),Match(_reg,_options.searchDescriptions()));
+      break;
+    case ZyppSearchOptions::UNINSTALLED_ONLY:
+      filter = chain(not_c(ByInstalledCache(_icache)),Match(_reg,_options.searchDescriptions()));
+      break;
+    case ZyppSearchOptions::ALL:
+    default:
+      filter = Match(_reg,_options.searchDescriptions());
+  }
+
+  invokeOnEachSearched(filter, f);
+}
+
 //! macro for word boundary tags for regexes
 #define WB (_options.matchWords() ? string("\\b") : string())
 
+// TODO make a regex builder.
 /**
- * Creates a regex for searching in resolvable names.
- * 
+ * Creates a regex for searching in resolvable names and descriptions.
+ *
  * The regex is created according to given search strings and search options.
  * 
+ * <pre>
  * Examples:
  *   - no search string: .*
  *   - one search string: .*searchstring.*
@@ -111,6 +158,7 @@ void ZyppSearch::doSearch(const boost::function<void(const PoolItem &)> & f) {
  *     --match-any:
  *       .*(searchstring1|searchstring2).*
  *       with --match-words: .*\b(searchstring1|searchstring2)\b.*
+ * </pre>
  */
 void ZyppSearch::setupRegexp() {
   string regstr;
@@ -149,7 +197,7 @@ void ZyppSearch::setupRegexp() {
     }
   }
 
-  cerr_vv << "using regex: " << regstr << endl;
+  cout_vv << "using regex: " << regstr << endl;
 
   // regex flags
   unsigned int flags = boost::regex::normal;
@@ -191,23 +239,6 @@ string ZyppSearch::wildcards2regex(const string & str) const {
   return regexed;
 }
 
-/**
- * Decides whether the pool_item (resolvable) matches the search criteria
- * encoded in regular expression.
- */
-bool ZyppSearch::match(const PoolItem & pool_item) {
-  return
-    // match resolvable name
-    regex_match(pool_item.resolvable()->name(), _reg)
-      ||
-    // if required, match also summary and description of the resolvable
-    (_options.searchDescriptions() ?
-      regex_match(pool_item.resolvable()->summary(), _reg) ||
-        regex_match(pool_item.resolvable()->description(), _reg)
-          :
-      false);
-}
-
 // Local Variables:
 // c-basic-offset: 2
 // End:
index 2420891..92aae8f 100644 (file)
 #define ZYPPERSEARCH_H_
 
 #include <string>
+#include <ext/hash_map>
 #include <boost/regex.hpp>
 #include <boost/function.hpp>
 #include <zypp/ZYpp.h>
 
+#include "zypper-tabulator.h"
+
 /**
- * @brief: search options
+ * Represents zypper search options.
  */
 class ZyppSearchOptions {
 public:
@@ -64,23 +67,194 @@ private:
   zypp::Resolvable::Kind _kind;
 };
 
+struct GenericStringHash {
+  size_t operator()(const std::string &str) const {
+    const std::string::size_type size = str.size();
+    size_t h = 0; 
+    for(std::string::size_type i = 0; i < size; i++) {
+      h = 5 * h + str[i];
+    }
+    return h;
+  }
+};
+
+typedef __gnu_cxx::hash_map<std::string, zypp::PoolItem, GenericStringHash> PoolItemHash;
+
+/**
+ * Structure for caching installed PoolItems using a hash map.
+ * Name + edition + (if _incl_kind_in_key) kind is used as a key.
+ * The hash map is to be manipulated through addItem() and getItem() methods. 
+ */
+struct InstalledCache  {
+private:
+  PoolItemHash _items;
+  bool _incl_kind_in_key;
+
+public:
+  InstalledCache(bool incl_kind_in_key = true) :
+    _incl_kind_in_key(incl_kind_in_key)
+    {}
+
+  void setIncludeKindInKey(bool value = true) { _incl_kind_in_key = value; }
+  bool includeKindInKey() { return _incl_kind_in_key; }
+
+  std::string getKey(const zypp::PoolItem & pi) const {
+    return pi.resolvable()->name() + pi.resolvable()->edition().asString() +
+      (_incl_kind_in_key ? pi.resolvable()->kind().asString() : "");
+  }
+
+  void addItem(const zypp::PoolItem & pi) { _items[getKey(pi)] = pi; }
+
+  zypp::PoolItem & getItem(const zypp::PoolItem & pi) {
+    return _items[getKey(pi)];
+  }
+  
+  unsigned int size() {
+    return _items.size();
+  }
+
+  /** defined for use as a functor for filling the hashmap in a for_each */ 
+  bool operator()(const zypp::PoolItem & pi) {
+    addItem(pi);
+  }
+};
+
 /**
- *
+ * TODO
  */
 class ZyppSearch {
+
 public:
-  ZyppSearch (const ZyppSearchOptions & options, const std::vector<std::string> & qstrings = std::vector<std::string>());
-  void doSearch(const boost::function<void(const zypp::PoolItem &)> & f);
+  ZyppSearch (zypp::ZYpp::Ptr & zypp, const ZyppSearchOptions & options,
+      const std::vector<std::string> qstrings = std::vector<std::string>());
+
+  void doSearch(const boost::function<bool(const zypp::PoolItem &)> & f);
+
+  InstalledCache & installedCache() { return _icache; }
+
+  template <class _Filter, class _Function>
+  int invokeOnEachSearched(_Filter filter_r, _Function fnc_r);
 
 private:
+  zypp::ZYpp::Ptr & _zypp;
   const ZyppSearchOptions & _options;
-  const std::vector<std::string> _qstrings;
+  const std::vector<std::string> _qstrings;
   boost::regex _reg;
 
-  bool init() const;
+  InstalledCache _icache;
+
   void setupRegexp();
+  void cacheInstalled();
   std::string wildcards2regex(const std::string & str) const;
-  bool match(const zypp::PoolItem & pool_item);
+};
+
+/**
+ * Filter resolvables by their installed status.
+ * This filter does it by comparing resolvables with matching resolvables
+ * from the InstalledCache. A resolvable is considered to be installed if
+ * its name, kind, edition, and architecture matches the one in installed
+ * cache.
+ * <p> 
+ * Not an effective filter, surely, but can't find another way to do this.
+ */
+struct ByInstalledCache
+{
+  ByInstalledCache(InstalledCache & icache) :
+    _icache(&icache)
+    {}
+
+  bool operator()(const zypp::PoolItem & pool_item) const {
+    zypp::PoolItem inst_item = _icache->getItem(pool_item);
+    if (inst_item) {
+      // yes, this one is installed, indeed
+      if (inst_item.resolvable()->edition() == pool_item.resolvable()->edition() &&
+          inst_item.resolvable()->arch() == pool_item.resolvable()->arch()) {
+        return true;
+      }
+      // nope, this package is not there on the target (in the InstalledCache)
+      else {
+        return false;
+      }
+    }
+
+    return false;
+  }
+
+  InstalledCache * _icache;
+};
+
+/**
+ * Functor for filling search output table in rug style.
+ */
+struct FillTable
+{
+  FillTable(Table & table, InstalledCache & icache) :
+    _table(&table), _icache(&icache) {
+    TableHeader header;
+    header << "S" << "Catalog" << "Bundle" << "Name" << "Version" << "Arch";
+    *_table << header;
+  }
+
+  bool operator()(const zypp::PoolItem & pool_item) const {
+    TableRow row;
+
+    // add status to the result table
+    zypp::PoolItem inst_item = _icache->getItem(pool_item);
+    if (inst_item) {
+      // check whether the pool item is installed...
+      if (inst_item.resolvable()->edition() == pool_item.resolvable()->edition() &&
+          inst_item.resolvable()->arch() == pool_item.resolvable()->arch())
+        row << "i";
+      // ... or there's just another version of it installed
+      else
+        row << "v";
+    }
+    // or it's not installed at all
+    else {
+      row << "";
+    }
+
+    // add other fields to the result table
+    row << pool_item.resolvable()->source().alias()
+        << "" // TODO what about rug's Bundle?
+        << pool_item.resolvable()->name()
+        << pool_item.resolvable()->edition().asString()
+        << pool_item.resolvable()->arch().asString();
+  
+    *_table << row;
+
+    return true;
+  }
+
+  Table * _table;
+
+  InstalledCache * _icache;
+};
+
+/**
+ * Filter functor for Matching PoolItems' names (or also summaries and
+ * descriptions) with a regex created according to search criteria.
+ */
+struct Match {
+  const bool _search_descs;
+  const boost::regex * _regex;
+
+  Match(const boost::regex & regex, bool search_descriptions = false) :
+    _regex(&regex), _search_descs(search_descriptions)
+    {}
+
+  bool operator()(const zypp::PoolItem & pi) const {
+    return
+      // match resolvable name
+      regex_match(pi.resolvable()->name(), *_regex)
+        ||
+      // if required, match also summary and description of the resolvable
+      (_search_descs ?
+        regex_match(pi.resolvable()->summary(), *_regex) ||
+          regex_match(pi.resolvable()->description(), *_regex)
+            :
+        false);
+  }
 };
 
 #endif /*ZYPPERSEARCH_H_*/
index 1c933c7..de3fce5 100644 (file)
@@ -633,10 +633,7 @@ int main(int argc, char **argv)
 
   // --------------------------( search )-------------------------------------
 
-  // FIXME --uninstalled-only does not really exclude installed resolvables
-  // FIXME search for all resolvables displays installed packages twice
-  // FIXME source (catalog) information missing for installed packages
-  // TODO print rug's v status  
+  // TODO -c, --catalog option
 
   if (command == "search" || command == "se") {
     ZyppSearchOptions options;
@@ -671,8 +668,8 @@ int main(int argc, char **argv)
     Table t;
     t.style(Ascii);
 
-    ZyppSearch search(options,arguments);
-    search.doSearch(FillTable(t));
+    ZyppSearch search(God,options,arguments);
+    search.doSearch(FillTable(t, search.installedCache()));
 
     if (t.empty())
       cout << "No packages found." << endl;
@@ -681,7 +678,7 @@ int main(int argc, char **argv)
       else t.sort(3); // sort by name
       cout << t;
     }
-    
+
     return 0;
   }