[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] r17414: {updater} Big thandy installation refactoring. Things should be saner (in updater/trunk/lib/thandy: . packagesys)



Author: nickm
Date: 2008-11-30 01:19:04 -0500 (Sun, 30 Nov 2008)
New Revision: 17414

Modified:
   updater/trunk/lib/thandy/ClientCLI.py
   updater/trunk/lib/thandy/__init__.py
   updater/trunk/lib/thandy/checkJson.py
   updater/trunk/lib/thandy/formats.py
   updater/trunk/lib/thandy/packagesys/ExePackages.py
   updater/trunk/lib/thandy/packagesys/PackageDB.py
   updater/trunk/lib/thandy/packagesys/PackageSystem.py
   updater/trunk/lib/thandy/packagesys/RPMPackages.py
   updater/trunk/lib/thandy/packagesys/__init__.py
   updater/trunk/lib/thandy/repository.py
   updater/trunk/lib/thandy/tests.py
Log:
Big thandy installation refactoring.  Things should be saner now: we recognize that checking an item is sometimes orthogonal to installing it; we do not carry around big bits of unimplemented machinery; we actually document what stuff does in thandy-spec.txt; we do more OO in the places that make sense and less in the places that do not; and almost as an afterthought, we support the command installer type internally.  Now all we need is a frontend to make command installers.

Modified: updater/trunk/lib/thandy/ClientCLI.py
===================================================================
--- updater/trunk/lib/thandy/ClientCLI.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/ClientCLI.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -110,10 +110,6 @@
         thandy.socksurls.setSocksProxy("127.0.0.1", socksPort)
 
     repo = thandy.repository.LocalRepository(repoRoot)
-    packagesys = None
-    if use_packagesys:
-        packagesys = thandy.packagesys.PackageSystem.PackageMetasystem.create(repo)
-
     downloader = thandy.download.DownloadManager()
     downloader.start()
 
@@ -125,7 +121,7 @@
         installable = {}
         logging.info("Checking for files to update.")
         files = repo.getFilesToUpdate(trackingBundles=args, hashDict=hashes,
-                                      pkgSystems=packagesys,
+                                      usePackageSystem=use_packagesys,
                                       installableDict=installable)
 
         if forceCheck:
@@ -134,8 +130,12 @@
 
         if installable and not files:
             for p, d in installable.items():
-                for n in d.keys():
-                    logCtrl("CAN_INSTALL", PKG=p, ITEM=n)
+                for n, i in d.items():
+                    if i.canInstall():
+                        logCtrl("CAN_INSTALL", PKG=p, ITEM=n)
+                    else:
+                        logCtrl("NO_INSTALL", PKG=p, ITEM=n)
+                    i.setCacheRoot(repoRoot)
 
             logging.info("Ready to install packages for files: %s",
                            ", ".join(sorted(installable.keys())))
@@ -143,7 +143,9 @@
                 # XXXX handle ordering
                 for p in installable.values():
                     for h in p.values():
-                        h.install()
+                        i = h.getInstaller()
+                        if i != None:
+                            i.install()
             return
 
         elif not files:

Modified: updater/trunk/lib/thandy/__init__.py
===================================================================
--- updater/trunk/lib/thandy/__init__.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/__init__.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -36,3 +36,14 @@
 
 class DownloadError(Exception):
     pass
+
+class CheckNotSupported(Exception):
+    pass
+
+class RemoveNotSupported(Exception):
+    pass
+
+class InstallFailed(Exception):
+    pass
+
+

Modified: updater/trunk/lib/thandy/checkJson.py
===================================================================
--- updater/trunk/lib/thandy/checkJson.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/checkJson.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -137,6 +137,16 @@
 
         raise thandy.FormatException("Object matched no recognized alternative")
 
+class AllOf(Schema):
+    """Matches the intersection of a list of schemas.
+    """
+    def __init__(self, required):
+        self._subschemas = required[:]
+
+    def checkMatche(self, obj):
+        for s in self._subschemas:
+            s.checkMatch(obj)
+
 class ListOf(Schema):
     """
        Matches a homogenous list of some subschema.
@@ -211,16 +221,34 @@
        True
        >>> s.matches([["X"]])
        False
+
+       >>> s = Struct([Str("X"), Int()], [Int()])
+       >>> s.matches([])
+       False
+       >>> s.matches({})
+       False
+       >>> s.matches(["X"])
+       False
+       >>> s.matches(["X", 3])
+       True
+       >>> s.matches(["X", 3, 9])
+       True
+       >>> s.matches(["X", 3, 9, 11])
+       False
+       >>> s.matches(["X", 3, "A"])
+       False
     """
-    def __init__(self, subschemas, allowMore=False, structName="list"):
-        self._subschemas = subschemas[:]
+    def __init__(self, subschemas, optschemas=[], allowMore=False,
+                 structName="list"):
+        self._subschemas = subschemas + optschemas
+        self._min = len(subschemas)
         self._allowMore = allowMore
         self._structName = structName
     def checkMatch(self, obj):
         if not isinstance(obj, (list, tuple)):
             raise thandy.FormatException("Expected %s; got %r"
                                          %(self._structName,obj))
-        elif len(obj) < len(self._subschemas):
+        elif len(obj) < self._min:
             raise thandy.FormatException(
                 "Too few fields in %s"%self._structName)
         elif len(obj) > len(self._subschemas) and not self._allowMore:
@@ -298,6 +326,10 @@
 
 
     def checkMatch(self, obj):
+        if not isinstance(obj, dict):
+            raise thandy.FormatException("Wanted a %s; did not get a dict"%
+                                         self._objname)
+
         for k,schema in self._required:
             try:
                 item = obj[k]
@@ -313,7 +345,64 @@
                     raise thandy.FormatException("%s in %s.%s"
                                                  %(e,self._objname,k))
 
+class TaggedObj(Schema):
+    """
+       Matches an object based on the value of a particular 'tag' field.
+       If tagIsOptional, matches any object when the tag is missing.
+       If ignoreUnrecognized, matches any object when the tag is present
+       but the value is not one we know.
 
+       >>> s = TaggedObj('tp', a=Obj(int1=Int()), b=Obj(s=AnyStr()))
+       >>> s.matches(3)
+       False
+       >>> s.matches([])
+       False
+       >>> s.matches({})
+       False
+       >>> s.matches({'tp' : 'fred'})
+       True
+       >>> s.matches({'tp' : 'a'})
+       False
+       >>> s.matches({'tp' : 'a', 'int1': 3})
+       True
+       >>> s.matches({'tp' : 'a', 'int1': []})
+       False
+       >>> s.matches({'tp' : 'b', 'int1': 3, 's': 'tt'})
+       True
+    """
+    def __init__(self, tagName, tagIsOptional=False, ignoreUnrecognized=True,
+                 **tagvals):
+        self._tagName = tagName
+        self._tagOpt = tagIsOptional
+        self._ignoreOthers = ignoreUnrecognized
+        self._tagvals = tagvals
+
+    def checkMatch(self, obj):
+        try:
+            tag = obj[self._tagName]
+        except KeyError:
+            if self._tagOpt:
+                return
+            else:
+                raise thandy.FormatException("Missing tag %s on object"%
+                                             self._tagName)
+        except TypeError:
+            raise thandy.FormatException("Got a %s, not a tagged object"%
+                                         type(obj))
+        if not isinstance(tag, basestring):
+            raise thandy.FormatException("Expected a string for %s; got a %s"%(
+                    self._tagName, type(tag)))
+        try:
+            subschema = self._tagvals[tag]
+        except KeyError:
+            if self._ignoreOthers:
+                return
+            else:
+                raise thandy.FormatException("Unrecognized value %s for %s"%(
+                        tag, self._tagName))
+
+        subschema.checkMatch(obj)
+
 class Int(Schema):
     """
        Matches an integer.
@@ -359,3 +448,14 @@
     def checkMatch(self, obj):
         if not isinstance(obj, bool):
             raise thandy.FormatException("Got %r instead of a boolean"%obj)
+
+class Func(Schema):
+    def __init__(self, fn, baseSchema=None):
+        self._fn = fn
+        self._base = baseSchema
+    def checkMatch(self, obj):
+        if self._base:
+            self._base.checkMatch(obj)
+        r = self._fn(obj)
+        if r is False:
+            raise thandy.FormatException("%s returned False"%self._fn)

Modified: updater/trunk/lib/thandy/formats.py
===================================================================
--- updater/trunk/lib/thandy/formats.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/formats.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -443,18 +443,6 @@
                     gloss=S.DictOf(S.AnyStr(), S.AnyStr()),
                     longgloss=S.DictOf(S.AnyStr(), S.AnyStr()))))
 
-PACKAGE_SCHEMA = S.Obj(
-            _type=S.Str("Package"),
-            name=S.AnyStr(),
-            location=RELPATH_SCHEMA,
-            version=VERSION_SCHEMA,
-            format=S.Obj(),
-            ts=TIME_SCHEMA,
-            files=S.ListOf(S.Struct([RELPATH_SCHEMA, HASH_SCHEMA],
-                                    allowMore=True)),
-            shortdesc=S.DictOf(S.AnyStr(), S.AnyStr()),
-            longdesc=S.DictOf(S.AnyStr(), S.AnyStr()))
-
 def checkWinRegistryKeyname(keyname):
     """Check keyname for superficial well-formedness as a win32 registry entry
        name."""
@@ -467,6 +455,59 @@
     elif not key or not value:
         raise thandy.FormatException("Bad registry entry.")
 
+REGISTRY_KEY_SCHEMA = S.Func(checkWinRegistryKeyname)
+
+CHECK_ITEM_SCHEMA = S.TaggedObj(
+    tagName='check_type',
+    tagIsOptional=True,
+    registry=S.Obj(registry_ent=S.Struct([REGISTRY_KEY_SCHEMA, S.AnyStr()])),
+    db=S.Obj(item_name=S.AnyStr(),
+             item_version=S.Any() #XXXX wrong!
+             ),
+    rpm=S.Obj(rpm_version=S.AnyStr()))
+
+INSTALL_ITEM_SCHEMA = S.TaggedObj(
+    tagName='install_type',
+    tagIsOptional=True,
+    command=S.Obj(cmd_install=S.ListOf(S.AnyStr()),
+                  cmd_remove=S.Opt(S.ListOf(S.AnyStr()))),
+    rpm=S.Obj())
+
+OBSOLETE_EXE_FORMAT_ITEM_SCHEMA = S.Obj(
+    registry_ent=S.Opt(S.Struct([REGISTRY_KEY_SCHEMA, S.AnyStr()])),
+    exe_args=S.ListOf(S.AnyStr()))
+OBSOLETE_RPM_FORMAT_ITEM_SCHEMA = S.Obj(
+    rpm_version=S.AnyStr())
+
+ITEM_INFO_SCHEMA = S.AllOf([CHECK_ITEM_SCHEMA, INSTALL_ITEM_SCHEMA])
+
+ITEM_SCHEMA = S.Struct([RELPATH_SCHEMA, HASH_SCHEMA], [ITEM_INFO_SCHEMA],
+                       allowMore=True)
+
+def checkPackageFormatConsistency(obj):
+    format = obj.get('format')
+    if format:
+        formatSchema = { 'exe' : OBSOLETE_EXE_FORMAT_ITEM_SCHEMA,
+                         'rpm' : OBSOLETE_RPM_FORMAT_ITEM_SCHEMA }.get(format)
+        if formatSchema:
+            for f in obj['files']:
+                if len(f) >= 3:
+                    formatSchema.checkMatch(f[2])
+
+PACKAGE_SCHEMA = S.Obj(
+            _type=S.Str("Package"),
+            name=S.AnyStr(),
+            location=RELPATH_SCHEMA,
+            version=VERSION_SCHEMA,
+            format=S.Opt(S.AnyStr()),
+            ts=TIME_SCHEMA,
+            files=S.ListOf(S.Struct([RELPATH_SCHEMA, HASH_SCHEMA],
+                                    allowMore=True)),
+            shortdesc=S.DictOf(S.AnyStr(), S.AnyStr()),
+            longdesc=S.DictOf(S.AnyStr(), S.AnyStr()))
+
+PACKAGE_SCHEMA = S.Func(checkPackageFormatConsistency, PACKAGE_SCHEMA)
+
 ALL_ROLES = ('timestamp', 'mirrors', 'bundle', 'package', 'master')
 
 class Key:
@@ -652,6 +693,8 @@
         if not r.get('exe_args'):
             raise thandy.FormatException("missing exe_args value")
         extra['exe_args'] = r['exe_args']
+        extra['install_type'] = 'command'
+        extra['cmd_install'] = [ "${FILE}" ] + r['exe_args']
         if r.get('exe_registry_ent'):
             if len(r['exe_registry_ent']) != 2:
                 raise thandy.FormatException("Bad length on exe_registry_ent")
@@ -660,6 +703,7 @@
             if not isinstance(regval, basestring):
                 raise thandy.FormatException("Bad version on exe_registry_ent")
             extra['registry_ent'] = [ regkey, regval ]
+            extra['check_type'] = 'registry'
 
     PACKAGE_SCHEMA.checkMatch(result)
 
@@ -801,6 +845,7 @@
     KEYLIST_SCHEMA.checkMatch(result)
     return result
 
+#XXXX could use taggedobj.  Defer till this has a unit test.
 SCHEMAS_BY_TYPE = {
     'Keylist' : KEYLIST_SCHEMA,
     'Mirrorlist' : MIRRORLIST_SCHEMA,

Modified: updater/trunk/lib/thandy/packagesys/ExePackages.py
===================================================================
--- updater/trunk/lib/thandy/packagesys/ExePackages.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/packagesys/ExePackages.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -2,84 +2,75 @@
 
 import subprocess
 import logging
+import re
+import os
 
 import thandy.util
-import thandy.packagesys.PackageSystem as ps
-import thandy.packagesys.PackageDB as pdb
+import thandy.packagesys.PackageSystem as PS
+import thandy.packagesys.PackageDB as PDB
 
-class ExePackageSystem(pdb.DBBackedPackageSystem):
-    def __init__(self, repo):
-        pdb.DBBackedPackageSystem.__init__(self)
-        self._repo = repo
+class RegistryChecker(PS.Checker):
+    def __init__(self, key, version):
+        PS.Checker.__init__(self)
+        self._key = key
+        self._version = version
 
-    def getName(self):
-        return "exe"
+    def __repr__(self):
+        return "RegistryChecker(%r, %r)"%(self._key, self._version)
 
-    def packageHandlesFromJSON(self, pkg):
-        if pkg['format'] != 'exe':
-            raise thandy.FormatException()
+    def getInstalledVersions(self):
+        try:
+            return [ thandy.util.getRegistryValue(self._key) ]
+        except thandy.util.NoRegistry:
+            raise thandy.CheckNotSupported("This OS has no registry.")
 
-        handles = []
-        for entry in pkg['files']:
-            if len(entry) < 3:
-                continue
-            rp, h, extra = entry[:3]
-            version = pkg['version']
+    def isInstalled(self):
+        return self._version in self.getInstalledVersions()
 
-            handles.append(
-                ExePackageHandle(self.getDB(),
-                                 pkg['name'],
-                                 version,
-                                 [],  # filelist not implemented in this.
-                                 rp,
-                                 self._repo.getFilename(rp),
-                                 arguments=extra.get('exe_args', []),
-                                 registry_ent=extra.get('registry_ent')))
-        return handles
+class CommandInstaller(PS.Installer):
+    def __init__(self, relPath, installCommand, removeCommand=None):
+        PS.Installer.__init__(self, relPath)
+        self._installCommand = installCommand
+        self._removeCommand = removeCommand
 
-    def canBeAutomatic(self):
-        return True
+    def __repr__(self):
+        parts = [ "CommandInstaller(%r, %r" %(self._relPath,
+                                              self._installCommand) ]
+        if self.removeCommand:
+            parts.append(", %r"%self.removeCommand)
+        parts.append(")")
+        return "".join(parts)
 
-    def canHaveUI(self):
-        return True
+    def install(self):
+        self._runCommand(self._installCommand)
 
-class ExePackageHandle(pdb.DBBackedPackageHandle):
-    def __init__(self, packageDB, name, version, filelist, relpath, filename,
-                 arguments, registry_ent=None):
-        pdb.DBBackedPackageHandle.__init__(self, packageDB, name, version, filelist)
-        self._relPath = relpath
-        self._filename = filename
-        self._arguments = arguments
-        self._registry_ent = registry_ent
+    def remove(self):
+        if self._removeCommand:
+            raise thandy.RemoveNotSupported()
+        self._runCommand(self._removeCommand)
 
-    def getRelativePath(self):
-        return self._relPath
+    def _runCommand(self, command):
+        d = { "FILE": self.getFilename() }
+        def replace(m):
+            return d[m.group(1)]
+        try:
+            c = [ re.sub(r'\$\{([\w_]+)\}', replace, word) for word in command ]
+        except KeyError:
+            raise thandy.InstallFailed("Unrecognized option in command %s"
+                                       %command)
+        logging.info("Installing %s.  Command is %s", self._relPath, c)
 
-    def getInstalledVersion(self, transaction=None):
-        if self._registry_ent != None:
-            try:
-                ver = thandy.util.getRegistryValue(self._registry_ent[0])
-                if ver != None:
-                    return ver
-            except thandy.util.NoRegistry:
-                pass
+        return_code = self._execute(c)
+        if return_code != 0:
+            raise thandy.InstallFailed("Return code %s from calling %s"%
+                                       (return_code, c))
 
-        return pdb.DBBackedPackageHandle.getInstalledVersion(self, transaction)
+    def _execute(self, cmd):
+        try:
+            return subprocess.call(cmd)
+        except OSError, e:
+            logging.warn("Error from trying to call %s: %s", cmd, e)
+            raise thandy.InstallFailed("Could not execute install command %s"
+                                       %cmd)
 
-    def isInstalled(self, transaction=None):
-        if self._registry_ent != None:
-            try:
-                ver = thandy.util.getRegistryValue(self._registry_ent[0])
-                return ver == self._registry_ent[1]
-            except thandy.util.NoRegistry:
-                pass
 
-        return pdb.DBBackedPackageHandle.isInstalled(self, transaction)
-
-
-    def _doInstall(self):
-        commandline = [ self._filename ] + self._arguments
-        logging.info("Installing %s.  Command line: %s", self._filename,
-                     commandline)
-        subprocess.call(commandline)
-

Modified: updater/trunk/lib/thandy/packagesys/PackageDB.py
===================================================================
--- updater/trunk/lib/thandy/packagesys/PackageDB.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/packagesys/PackageDB.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -2,13 +2,18 @@
 
 import atexit
 import shelve
+import logging
 
 import thandy.util
 import thandy.formats
 
-import thandy.packagesys.PackageSystem
+import thandy.packagesys.PackageSystem as PS
 
 class SimplePackageDB:
+    """Trivial wrapper around Python's shelve module to provide storage for
+       installation information for package items that don't automatically
+       record their presence.
+    """
     def __init__(self, filename):
         thandy.util.ensureParentDir(filename)
         self._db = shelve.open(filename, 'c')
@@ -23,71 +28,121 @@
     def setInstallParameters(self, package, params):
         self._db['ip_%s'%str(package)] = params
 
+    def setManifest(self, package, fnameToDigest):
+        self._db['mf_%s'%str(package)] = fnameToDigest
+
     def getCurVersion(self, package):
         v = self._db.get('pv_%s'%str(package))
         if v != None:
             return v[0]
+        else:
+            return None
 
     def getInstallParameters(self, package):
         return self._db.get('pi_%s'%str(package))
 
-class DBBackedPackageSystem(thandy.packagesys.PackageSystem.PackageSystem):
-    def __init__(self):
-        self._packageDB = None
+    def getManifest(self, package):
+        return self._db.get('mf_%'%str(package), {})
 
+    def removeAll(self, package):
+        for template in ["pv_%s", "ip_%s", "mf_%s"]:
+            try:
+                del self._db[template % str(package)]
+            except KeyError:
+                pass
+
+_DB_INSTANCE = None
+
+def getPackageDBInstance():
+    global _DB_INSTANCE
+    if _DB_INSTANCE == None:
+        fname = thandy.util.userFilename("db/packages")
+        logging.info("Opening package database in %s", fname)
+        _DB_INSTANCE = SimplePackageDB(fname)
+    return _DB_INSTANCEx
+
+class _DBMixin:
+    def setDB(self, db):
+        self._db = db
+
     def getDB(self):
-        if self._packageDB is None:
-            fname = thandy.util.userFilename("db/packages")
-            self._packageDB = SimplePackageDB(fname)
-        return self._packageDB
+        if self._db is None:
+            self._db = getPackageDBInstance()
+        return self._db
 
-class DBBackedPackageHandle(thandy.packagesys.PackageSystem.PackageHandle):
-    def __init__(self, packageDB, name, version, filelist):
-        thandy.packagesys.PackageSystem.PackageHandle.__init__(self)
-        self._packageDB = packageDB
+class DBChecker(PS.Checker, _DBMixin):
+    def __init__(self, name, version):
+        PS.Checker.__init__(self)
         self._name = name
         self._version = version
-        self._filelist = filelist
+        self._db = None
 
-        self._metaData = None
+    def __repr__(self):
+        return "DBChecker(%r, %r)"%(self._name, self._version)
 
-    def _getInstallBase(self):
-        raise NotImplemented()
+#    def checkInstall(self):
+#        if not self.isInstalled():
+#            return False
+#        else:
+#            return self._checkManifest()
+#
+#    def _getInstallRoot(self):
+#        return "/"
+#
+#    def _checkManifest(self):
+#        manifest = self.getDB().getManifest(self._name)
+#        root = self._getInstallRoot()
+#        all_ok = True
+#        for fname, digest_want in manifest:
+#            real_fname = os.path.join(self._getInstallRoot(), fname)
+#            logging.info("Checking digest on %s", fname)
+#            try:
+#                digest = thandy.formats.getFileDigest(real_fname):
+#                if digest != digest_want:
+#                    logging.warn("Digest on %s not as expected", real_fname)
+#                    all_ok = False
+#            except OSError:
+#                logging.warn("File %s not found.", real_fname)
+#                all_ok = False
+#        return all_ok
+#
+    def getInstalledVersions(self):
+        return [ self.getDB().getCurVersion(self._name) ]
 
-    def anyVersionInstalled(self, transaction=None):
-        return self.getInstalledVersion(transaction) != None
+    def isInstalled(self):
+        return self._version in self.getInstalledVersions(transaction)
 
-    def getInstalledVersion(self, transaction=None):
-        return self._packageDB.getCurVersion(self._name)
+class DBInstaller(PS.Installer, _DBMixin):
+    def __init__(self, name, version, relPath, installer):
+        PS.Installer.__init__(self, relPath)
+        self._name = name
+        self._version = version
+        self._installer = installer
 
-    def install(self, transaction=None):
-        params = self._doInstall()
-        self._packageDB.setVersion(
-            self._name, self._version, self._filelist)
-        self._packageDB.setInstallParameters(self._name, params)
+    def __repr__(self):
+        return "DBInstaller(%r, %r, %r, %r)"%(self._name,
+                                              self._version,
+                                              self._relPath,
+                                              self._installer)
 
-    def _doInstall(self):
-        raise NotImplemented()
+    def setTransaction(self, transaction):
+        self._installer.setTransaction(transaction)
 
-    def isInstalled(self, transaction=None):
-        return self.getInstalledVersion(transaction) == self._version
+    def setCacheRoot(self, cacheRoot):
+        self._installer.setCacheRoot(cacheRoot)
 
-    def checkInstall(self, transaction=None):
-        base = self._getInstallBase()
+    def install(self):
+        self._installer.install()
 
-        all_ok = True
-        for fn, hash in self._filelist:
-            fn = os.path.join(base, fn)
-            if not os.path.exists(fn):
-                all_ok = False
-            else:
-                try:
-                    d = thandy.formats.getFileDigest(fn)
-                    if d != hash:
-                        all_ok = False
-                except OSError:
-                    all_ok = False
-                    break
+        params, manifest = self._installer.getInstallResult()
+        self.getDB().setCurVersion(self._name, self._version)
+        if params != None:
+            self.getDB().getInstallParameters(self._name, params)
+        if manifest != None:
+            self.getDB().setManifest(self._name, manifest)
 
+    def remove(self):
+        self._installer.remove()
+        self.getDB().removeAll(self._name)
 
-        return all_ok
+

Modified: updater/trunk/lib/thandy/packagesys/PackageSystem.py
===================================================================
--- updater/trunk/lib/thandy/packagesys/PackageSystem.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/packagesys/PackageSystem.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -1,94 +1,166 @@
 # Copyright 2008 The Tor Project, Inc.  See LICENSE for licensing information.
 
-class PackageMetasystem:
-    def __init__(self, repository):
-        self._repostitory = repository
-        self._systems = {}
+import os
 
-    def addPackageSystem(self, system):
-        self._systems[system.getName()] = system
+def getItemsFromPackage(pkg):
+    result = {}
+    format = pkg.get('format')
+    for item in pkg['files']:
+        relPath = item[0]
+        if len(item) >= 3:
+            extra = item[2]
+        else:
+            extra = {}
+        checkFormat = extra.get("check_type")
+        installFormat = extra.get("install_type")
 
-    def getSysForPackage(self, pkg):
-        return self._systems.get(pkg['format'], None)
+        checker = getChecker(checkFormat, relPath, extra, defaultFormat=format,
+                             package=pkg)
+        installer = getInstaller(installFormat, relPath, extra,
+                                 defaultFormat=format, package=pkg)
+        result[relPath] = PackageItem(relPath, checker, installer)
+    return result
 
-    @staticmethod
-    def create(repository):
-        r = PackageMetasystem(repository)
+def getChecker(checkType, relPath, extra, defaultFormat, package):
+    if checkType == None:
+        #DOCDOC obsolete
+        if defaultFormat == 'rpm':
+            import thandy.packagesys.RPMPackages
+            return thandy.packagesys.RPMPackages.RPMChecker(
+                os.path.split(relPath)[1],
+                extra['rpm_version'])
+        elif defaultFormat == 'exe':
+            if extra.has_key('registry_ent'):
+                import thandy.packagesys.ExePackages
+                k,v=extra['registry_ent']
+                return thandy.packagesys.ExePackages.RegistryChecker(k, v)
+            else:
+                import thandy.packagesys.PackageDB
+                return thandy.packagesys.PackageDB.DBChecker(
+                    package['name'], package['version'])
+        else:
+            return None
+    elif checkType == 'rpm':
+        import thandy.packagesys.RPMPackages
+        return thandy.packagesys.RPMPackages.RPMChecker(
+            os.path.split(relPath)[1],
+            extra['rpm_version'])
+    elif checkType == 'db':
+        import thandy.packagesys.PackageDB
+        return thandy.packagesys.PackageDB.DBChecker(
+            extra['item_name'], extra['item_version'])
+    elif checkType == 'registry':
+        import thandy.packagesys.ExePackages
+        k,v=extra['registry_ent']
+        return thandy.packagesys.ExePackages.RegistryChecker(k,v)
+    else:
+        return None
 
-        try:
-            import rpm
-        except ImportError:
-            pass
+def getInstaller(installType, relPath, extra, defaultFormat, package):
+    if installType == None:
+        # XXX obsolete.
+        if defaultFormat == 'rpm':
+            import thandy.packagesys.RPMPackages
+            return thandy.packagesys.RPMPackages.RPMInstaller(
+                relPath, os.path.split(relPath)[1])
+        elif defaultFormat == 'exe':
+            import thandy.packagesys.ExePackages
+            installer = thandy.packagesys.ExePackages.CommandInstaller(
+                relPath, [ "${FILE}" ] + extra.get('exe_args', []))
+            if not extra.has_key('registry_ent'):
+                import thandy.packagesys.PackageDB
+                installer = thandy.packagesys.PackageDB.DBInstaller(
+                    package['name'], package['version'], relPath, installer)
+            return installer
         else:
-            import thandy.packagesys.RPMPackages
-            r.addPackageSystem(thandy.packagesys.RPMPackages.RPMPackageSystem(
-                    repository))
-
+            return None
+    elif installType == 'rpm':
+        import thandy.packagesys.RPMPackages
+        installer = thandy.packagesys.RPMPackages.RPMInstaller(
+            relPath, os.path.split(relPath)[1])
+    elif installType == 'command':
         import thandy.packagesys.ExePackages
-        r.addPackageSystem(thandy.packagesys.ExePackages.ExePackageSystem(
-                repository))
+        installer = thandy.packagesys.ExePackages.CommandInstaller(
+            relPath, extra['cmd_install'], extra.get['cmd_remove'])
+    else:
+        return None
 
-        return r
+    if extra.get('check_type') == 'db':
+        import thandy.packagesys.PackageDB
+        installer = thandy.packagesys.PackageDB.DBInstaller(
+            extra['item_name'], extra['item_version'], installer)
 
-class PackageSystem:
-    def getName(self):
-        raise NotImplemented()
+    return installer
 
-    def packageHandlesFromJSON(self, json):
-        raise NotImplemented()
+class PackageItem:
+    def __init__(self, relativePath, checker, installer):
+        self._relPath = relativePath
+        self._checker = checker
+        self._installer = installer
 
-    def canBeAutomatic(self):
-        return True
+    def setTransaction(self, transaction):
+        if self._cheker is not None:
+            self._checker.setTransaction(transaction)
+        if self._installer is not None:
+            self._installer.setTransaction(transaction)
+    def setCacheRoot(self, cacheRoot):
+        if self._installer is not None:
+            self._installer.setCacheRoot(cacheRoot)
 
-    def canHaveUI(self):
-        return False
+    def canCheck(self):
+        return self._checker != None
+    def canInstall(self):
+        return self._installer != None
+    def getChecker(self):
+        return self._checker
+    def getInstaller(self):
+        return self._installer
 
-    def getTransaction(self):
-        return PackageTransaction()
-
-class PackageTransaction:
+class Checker:
     def __init__(self):
-        self._transactions = []
+        self._transaction = None
 
-    def _start(self):
-        pass
+    def setTransaction(self, transaction):
+        self._transaction = transaction
 
-    def _commit(self):
-        pass
+#    def checkInstall(self):
+#        raise NotImplemented()
 
-    def run(self):
-        self._start()
-        for cb in self._transactions:
-            cb(self)
-        self._commit()
+    def anyVersionInstalled(self):
+        raise len(self.getInstalledVersions()) > 1
 
-    def addInstall(self, packageHandle):
-        self._transactions.append(packageHandle.install)
+    def getInstalledVersions(self):
+        raise NotImplemented()
 
-    def addRemove(self, packageHandle):
-        self._transactions.append(packageHandle.remove)
+    def isInstalled(self):
+        raise NotImplemented()
 
-class PackageHandle:
-    def __init__(self):
-        pass
+class Installer:
+    def __init__(self, relativePath):
+        self._transaction = None
+        self._cacheRoot = None
+        self._relPath = relativePath
 
-    def getRelativePath(self):
-        raise NotImplemented()
+    def setTransaction(self, transaction):
+        self._transaction = transaction
 
-    def isInstalled(self, transaction=None):
-        raise NotImplemented()
+    def setCacheRoot(self, cacheRoot):
+        self._cacheRoot = cacheRoot
 
-    def anyVersionInstalled(self, transaction=None):
-        raise NotImplemented()
+    def getFilename(self):
+        rp = self._relPath
+        if rp.startswith('/'):
+            rp = rp[1:]
+        return os.path.normpath(os.path.join(self._cacheRoot, rp))
 
-    def getInstalledVersion(self, transaction=None):
+    def install(self, relativePath, root):
         raise NotImplemented()
 
-    def install(self, transaction):
+    def remove(self):
         raise NotImplemented()
 
-    def remove(self, transaction):
-        raise NotImplemented()
+    def getInstallResult(self):
+        "DOCDOC params, manifest"
+        return None, None
 
-    def checkInstall(self, transaction=None):
-        raise NotImplemented()
+

Modified: updater/trunk/lib/thandy/packagesys/RPMPackages.py
===================================================================
--- updater/trunk/lib/thandy/packagesys/RPMPackages.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/packagesys/RPMPackages.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -1,48 +1,18 @@
 # Copyright 2008 The Tor Project, Inc.  See LICENSE for licensing information.
 
-import thandy.packagesys.PackageSystem
+import thandy.packagesys.PackageSystem as PS
 
 import os
-import rpm
+try:
+    import rpm
+except ImportError:
+    rpm = None
 import md5
 
 import thandy.formats
 
 __all__ = [ 'RPMPackageSystem' ]
 
-class RPMPackageSystem(thandy.packagesys.PackageSystem.PackageSystem):
-    def __init__(self, repo):
-        self._repo = repo
-
-    def getName(self):
-        return "rpm"
-
-    def packageHandlesFromJSON(self, package):
-        if package['format'] != 'rpm':
-            raise thandy.FormatException()
-
-        handles = []
-        for entry in package['files']:
-            if len(entry) < 3:
-                continue
-            fn, h, extra = entry[:3]
-            name = os.path.split(fn)[1]
-
-            try:
-                version = extra['rpm_version']
-            except KeyError:
-                raise thandy.FormatException()
-
-            handles.append(RPMPackageHandle(name,
-                                            version,
-                                            fn,
-                                            self._repo.getFilename(fn)))
-
-        return handles
-
-    def getTransaction(self):
-        return RPMPackageTransaction()
-
 _CALLBACK_CODES = {}
 
 for name in dir(rpm):
@@ -50,34 +20,34 @@
         _CALLBACK_CODES[getattr(rpm, name)] = name[12:]
 del name
 
-class RPMPackageTransaction(thandy.packagesys.PackageSystem.PackageTransaction):
+class RPMPackageTransaction:
 
-    def _start(self):
-        thandy.packagesys.PackageSystem.PackageTransaction.__init__(self)
-        self._tset = rpm.TransactionSet()
+   def _start(self):
+       PS.PackageTransaction.__init__(self)
+       self._tset = rpm.TransactionSet()
 
-    def _commit(self):
-        self._tset.run(self._callback, "")
+   def _commit(self):
+       self._tset.run(self._callback, "")
+       
+   def _callback(self, what, amount, total, mydata, _):
+       if what == rpm.RPMCALLBACK_INST_OPEN_FILE:
+           hdr, path = mydata
+           logging.info("Installing RPM for %s [%s]", hdr['name'], path)
 
-    def _callback(self, what, amount, total, mydata, _):
-        if what == rpm.RPMCALLBACK_INST_OPEN_FILE:
-            hdr, path = mydata
-            logging.info("Installing RPM for %s [%s]", hdr['name'], path)
+       elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
+           hdr, path = mydata
+           logging.info("Done installing RPM for %s", path)
+           
+       elif what == rpm.RPMCALLBACK_INST_PROGRESS:
+           hdr, path = mydata
+           logging.info("%s: %.5s%% done", name, float(amount)/total*100)
 
-        elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
-            hdr, path = mydata
-            logging.info("Done installing RPM for %s", path)
+       else:
+           hdr, path = mydata
+           logging.info("RPM event %s on %s [%s/%s]",
+                        _CALLBACK_CODES.get(what,str(what)),
+                        hdr['name'], amount, total)
 
-        elif what == rpm.RPMCALLBACK_INST_PROGRESS:
-            hdr, path = mydata
-            logging.info("%s: %.5s%% done", name, float(amount)/total*100)
-
-        else:
-            hdr, path = mydata
-            logging.info("RPM event %s on %s [%s/%s]",
-                         _CALLBACK_CODES.get(what,str(what)),
-                         hdr['name'], amount, total)
-
 def addRPMInstall(ts, path):
     fd = os.open(path, os.O_RDONLY)
     try:
@@ -155,31 +125,36 @@
 
     return found and all_ok
 
-class RPMPackageHandle(thandy.packagesys.PackageSystem.PackageHandle):
-    def __init__(self, name, version, relativePath, filename):
-        self._name = name
-        self._version = version
-        self._relPath = relativePath
-        self._filename = filename
+class RPMChacker(PS.Checker):
+    def __init__(self, rpmname, rpmversion):
+        PS.Checker.__init__(self)
+        self._name = rpmname
+        self._version = rpmversion
 
-    def getRelativePath(self):
-        return self._relPath
+    def __repr__(self):
+        return "RPMChecker(%r, %r)"%(self._name, self._version)
 
-    def anyVersionInstalled(self, transaction=None):
-        return len(getInstalledRPMVersions(self.name, transaction)) > 1
+    def getInstalledVersions(self):
+        return getInstalledRPMVersions(self._name, self._transaction)
 
-    def getInstalledVersion(self, transaction=None):
-        s = max(getInstalledRPMVersions(self._name, transaction))
+    def isInstalled(self):
+        vers = getInstalledRPMVersions(self._name, self._transaction)
+        return self._version in vers
 
+#    def checkInstall(self):
+#        return checkRPMInstall(self._name, self._version)
+
+class RPMInstaller(PS.Installer):
+    def __init__(self, rpmname, relPath):
+        PS.Installer.__init__(self, relPath)
+        self._name = rpmname
+
+    def __repr__(self):
+        return "RPMInstaller(%r, %r)"%(self._name, self._relPath)
+
     def install(self, transaction):
-        addRPMInstall(transaction._trans, self._filename)
+        addRPMInstall(transaction._trans, self.getFilename())
 
     def remove(self, transaction):
         addRPMErase(transaction._trans, self._name)
 
-    def isInstalled(self, transaction=None):
-        return self._version in getInstalledRPMVersions(self._name,transaction)
-
-    def checkInstall(self, transaction=None):
-        return checkRPMInstall(self._name, self._version)
-

Modified: updater/trunk/lib/thandy/packagesys/__init__.py
===================================================================
--- updater/trunk/lib/thandy/packagesys/__init__.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/packagesys/__init__.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -2,3 +2,6 @@
 
 __all__ = [ ]
 
+
+
+

Modified: updater/trunk/lib/thandy/repository.py
===================================================================
--- updater/trunk/lib/thandy/repository.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/repository.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -2,6 +2,7 @@
 
 import thandy.formats
 import thandy.util
+import thandy.packagesys.PackageSystem
 
 try:
     import json
@@ -297,11 +298,11 @@
         return None
 
     def getFilesToUpdate(self, now=None, trackingBundles=(), hashDict=None,
-                         pkgSystems=None, installableDict=None):
+                         usePackageSystem=True, installableDict=None):
         """Return a set of relative paths for all files that we need
            to fetch.  Assumes that we care about the bundles
            'trackingBundles'.
-           DOCDOC pkgSystems, installableDict, hashDict
+           DOCDOC installableDict, hashDict, usePackageSystem
         """
 
         if now == None:
@@ -314,6 +315,8 @@
         if installableDict == None:
             installableDict = {}
 
+        pkgItems = None
+
         need = set()
 
         # Fetch missing metafiles.
@@ -468,26 +471,31 @@
         for pfile in packages.values():
             package = pfile.get()
 
-            alreadyInstalled = {}
-            allHandles = {}
-            if pkgSystems is not None:
-                psys = pkgSystems.getSysForPackage(package)
-                if psys is None:
-                    logging.info("No way to check whether a %s package is "
-                                 "up-to-date." % package['format'])
-                else:
-                    handles = psys.packageHandlesFromJSON(package)
+            alreadyInstalled = set()
+            pkgItems = {}
 
-                    for h in handles:
-                        allHandles[h.getRelativePath()] = h
-                        if h.isInstalled():
-                            alreadyInstalled[h.getRelativePath()] = h
+            if usePackageSystem:
+                pkgItems = thandy.packagesys.PackageSystem.getItemsFromPackage(
+                    package)
 
+                for f in package['files']:
+                    item = pkgItems[f[0]]
+                    if not item.canCheck():
+                        logging.info("No way to check whether %s is "
+                                     "up-to-date.", f[0])
+                    else:
+                        try:
+                            if item.getChecker().isInstalled():
+                                alreadyInstalled.add(item.getRelativePath())
+                        except thandy.CheckNotSupported, err:
+                            logging.warn("Can't check installed-ness of %s: %s",
+                                         f[0], err)
+
             pkg_rp = pfile.getRelativePath()
 
             for f in package['files']:
                 rp, h = f[:2]
-                if alreadyInstalled.has_key(rp):
+                if rp in alreadyInstalled:
                     logging.info("%s is already installed; no need to download",
                                  rp)
                     continue
@@ -506,8 +514,8 @@
                     logging.info("Hash for %s not as expected; must load.", rp)
                     need.add(rp)
                 else:
-                    if allHandles.has_key(rp):
-                        installableDict.setdefault(pkg_rp, {})[rp] = allHandles[rp]
+                    if pkgItems.has_key(rp):
+                        installableDict.setdefault(pkg_rp, {})[rp] = pkgItems[rp]
 
         # Okay; these are the files we need.
         return need

Modified: updater/trunk/lib/thandy/tests.py
===================================================================
--- updater/trunk/lib/thandy/tests.py	2008-11-30 05:43:48 UTC (rev 17413)
+++ updater/trunk/lib/thandy/tests.py	2008-11-30 06:19:04 UTC (rev 17414)
@@ -11,6 +11,11 @@
 import thandy.checkJson
 import thandy.encodeToXML
 import thandy.util
+import thandy.packagesys
+import thandy.packagesys.PackageSystem
+import thandy.packagesys.PackageDB
+import thandy.packagesys.RPMPackages
+import thandy.packagesys.ExePackages
 
 import thandy.tests