Reverse index database and searching support.
authorDanek Duvall <danek.duvall@sun.com>
Thu, 19 Jul 2007 14:30:15 -0700
changeset 60 6bd5cd83cfb1
parent 59 eef94b0c0694
child 61 cc32381ed52e
Reverse index database and searching support.
src/client.py
src/modules/actions/driver.py
src/modules/actions/file.py
src/modules/actions/generic.py
src/modules/client/image.py
src/modules/client/imageplan.py
src/modules/client/pkgplan.py
--- a/src/client.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/client.py	Thu Jul 19 14:30:15 2007 -0700
@@ -77,6 +77,7 @@
         pkg uninstall pkg_fmri
         pkg freeze [--version version_spec] [--release] [--branch] pkg_fmri
         pkg unfreeze pkg_fmri
+        pkg search token
 
         pkg image [--full|--partial|--user] dir
         pkg image [-FPU] dir
@@ -202,6 +203,27 @@
 
         return
 
+def search(config, image, args):
+        """Search through the reverse index databases for the given token."""
+
+        idxdir = os.path.join(image.imgdir, "index")
+
+        # Avoid enumerating any particular index directory, since some index
+        # databases may contain hundreds of thousands of keys.  Any given key in
+        # an index, however, hopefully won't point to more than a few hundred
+        # packages.
+        results = [
+            (dir, link)
+            for dir in os.listdir(idxdir)
+            if os.path.isdir(os.path.join(idxdir, dir, args[0]))
+            for link in os.listdir(os.path.join(idxdir, dir, args[0]))
+        ]
+
+        for idx, link in results:
+                print idx, fmri.PkgFmri(urllib.unquote(link), None)
+
+        return
+
 def create_image(config, args):
         """Create an image of the requested kind, at the given path."""
 
@@ -270,6 +292,8 @@
                 freeze(pcfg, icfg, pargs)
         elif subcommand == "unfreeze":
                 unfreeze(pcfg, icfg, pargs)
+        elif subcommand == "search":
+                search(pcfg, icfg, pargs)
         else:
                 print "pkg: unknown subcommand '%s'" % subcommand
                 usage()
--- a/src/modules/actions/driver.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/modules/actions/driver.py	Thu Jul 19 14:30:15 2007 -0700
@@ -104,3 +104,9 @@
         def update_install(self, image):
                 # XXX This needs to run update_drv or something.
                 pass
+
+        def generate_indices(self):
+                return {
+                    "driver_name": self.attrs["name"],
+                    "driver_aliases": self.attrs["alias"]
+                }
--- a/src/modules/actions/file.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/modules/actions/file.py	Thu Jul 19 14:30:15 2007 -0700
@@ -84,3 +84,9 @@
         def postinstall(self):
                 """Client-side method that performs post-install actions."""
                 pass
+
+        def generate_indices(self):
+                return {
+                    "content": self.hash,
+                    "basename": os.path.basename(self.attrs["path"])
+                }
--- a/src/modules/actions/generic.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/modules/actions/generic.py	Thu Jul 19 14:30:15 2007 -0700
@@ -195,6 +195,37 @@
                 else:
                         return cmp(id(self), id(other))
 
+        def generate_indices(self):
+                """Generate for the reverse index database data for this action.
+
+                See pkg.client.pkgplan.make_indices for more information about
+                the reverse index database.
+                
+                This method returns a dictionary mapping attribute names to
+                their values.  This is not simply the action attribute
+                dictionary, 'attrs', as not necessarily all of these attributes
+                are interesting to look up, and there may be others which are
+                derived from the canonical attributes (like the path's basename).
+                """
+
+                indices = {}
+
+                # XXX What about derived indices -- those which aren't one of
+                # the attributes, such as basename?  Just push computing them
+                # into the subclasses?  Or is this simple enough that we have no
+                # need for a generic.generate_indices() that does anything
+                # interesting?
+                if hasattr(self, "reverse_indices"):
+                        indices.update(
+                            (idx, self.attrs[idx])
+                            for idx in self.reverse_indices
+                        )
+
+                if hasattr(self, "hash"):
+                        indices["content"] = self.hash
+
+                return indices
+
         def preinstall(self, image):
                 """Client-side method that performs pre-install actions."""
                 pass
--- a/src/modules/client/image.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/modules/client/image.py	Thu Jul 19 14:30:15 2007 -0700
@@ -74,6 +74,9 @@
           $IROOT/pkg
                Directory containing manifests and states of installed packages.
 
+          $IROOT/index
+               Directory containing reverse-index databases.
+
         XXX Root path probably can't be absolute, so that we can combine or
         reuse Image contents.
 
@@ -130,6 +133,8 @@
                         os.makedirs(self.imgdir + "/file")
                 if not os.path.isdir(self.imgdir + "/pkg"):
                         os.makedirs(self.imgdir + "/pkg")
+                if not os.path.isdir(self.imgdir + "/index"):
+                        os.makedirs(self.imgdir + "/index")
 
         def set_attrs(self, type, root):
                 self.type = type
--- a/src/modules/client/imageplan.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/modules/client/imageplan.py	Thu Jul 19 14:30:15 2007 -0700
@@ -211,5 +211,8 @@
                 for p in self.pkg_plans:
                         p.postexecute()
 
+                for p in self.pkg_plans:
+                        p.make_indices()
+
                 self.state = EXECUTED_OK
 
--- a/src/modules/client/pkgplan.py	Tue Jul 17 12:50:06 2007 -0700
+++ b/src/modules/client/pkgplan.py	Thu Jul 19 14:30:15 2007 -0700
@@ -23,6 +23,7 @@
 # Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 
+import errno
 import os
 import re
 import urllib
@@ -130,3 +131,50 @@
                     self.destination_fmri.get_dir_path()), "w")
 
                 return
+
+        def make_indices(self):
+                """Create the reverse index databases for a particular package.
+                
+                These are the databases mapping packaging object attribute
+                values back to their corresponding packages, allowing the
+                packaging system to look up a package based on, say, the
+                basename of a file that was installed.
+
+                XXX Need a method to remove what we put down here.
+                """
+
+                target = os.path.join("..", "..", "..", "pkg",
+                    self.destination_fmri.get_dir_path())
+
+                gen = (
+                    (k, v)
+                    for action in self.actions
+                    for k, v in action.generate_indices().iteritems()
+                )
+
+                for idx, val in gen:
+                        idxdir = os.path.join(self.image.imgdir, "index", idx)
+
+                        try:
+                                os.makedirs(idxdir)
+                        except OSError, e:
+                                if e.errno != errno.EEXIST:
+                                        raise
+
+                        if not isinstance(val, list):
+                                val = [ val ]
+
+                        for v in val:
+                                dir = os.path.join(idxdir, v)
+
+                                try:
+                                        os.makedirs(dir)
+                                except OSError, e:
+                                        if e.errno != errno.EEXIST:
+                                                raise
+
+                                link = os.path.join(dir,
+                                    self.destination_fmri.get_url_path())
+
+                                if not os.path.lexists(link):
+                                        os.symlink(target, link)