summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2008-11-30 06:19:04 +0000
committerNick Mathewson <nickm@torproject.org>2008-11-30 06:19:04 +0000
commit3bef6462fa8117dde2f55a3ff06dbf1b1a8e02a8 (patch)
tree4dd9ef4282482e81422b28624fd1dbfb442e4f61 /lib
parentbd2a8dad04994c28086c7c30636273a40be01243 (diff)
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.
git-svn-id: file:///home/or/svnrepo/updater/trunk@17414 55e972cd-5a19-0410-ae62-a4d7a52db4cd
Diffstat (limited to 'lib')
-rw-r--r--lib/thandy/ClientCLI.py18
-rw-r--r--lib/thandy/__init__.py11
-rw-r--r--lib/thandy/checkJson.py106
-rw-r--r--lib/thandy/formats.py69
-rw-r--r--lib/thandy/packagesys/ExePackages.py145
-rw-r--r--lib/thandy/packagesys/PackageDB.py153
-rw-r--r--lib/thandy/packagesys/PackageSystem.py218
-rw-r--r--lib/thandy/packagesys/RPMPackages.py137
-rw-r--r--lib/thandy/packagesys/__init__.py3
-rw-r--r--lib/thandy/repository.py46
-rw-r--r--lib/thandy/tests.py5
11 files changed, 589 insertions, 322 deletions
diff --git a/lib/thandy/ClientCLI.py b/lib/thandy/ClientCLI.py
index ad9f220..fa73ab2 100644
--- a/lib/thandy/ClientCLI.py
+++ b/lib/thandy/ClientCLI.py
@@ -110,10 +110,6 @@ def update(args):
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 @@ def update(args):
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 @@ def update(args):
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 @@ def update(args):
# 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:
diff --git a/lib/thandy/__init__.py b/lib/thandy/__init__.py
index 5b53bfe..6b00c05 100644
--- a/lib/thandy/__init__.py
+++ b/lib/thandy/__init__.py
@@ -36,3 +36,14 @@ class UnknownMethod(CryptoError):
class DownloadError(Exception):
pass
+
+class CheckNotSupported(Exception):
+ pass
+
+class RemoveNotSupported(Exception):
+ pass
+
+class InstallFailed(Exception):
+ pass
+
+
diff --git a/lib/thandy/checkJson.py b/lib/thandy/checkJson.py
index 84ea9f2..e63a1fc 100644
--- a/lib/thandy/checkJson.py
+++ b/lib/thandy/checkJson.py
@@ -137,6 +137,16 @@ class OneOf(Schema):
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 @@ class Struct(Schema):
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 @@ class Obj(Schema):
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,6 +345,63 @@ class Obj(Schema):
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):
"""
@@ -359,3 +448,14 @@ class Bool(Schema):
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)
diff --git a/lib/thandy/formats.py b/lib/thandy/formats.py
index f0a4e30..168f267 100644
--- a/lib/thandy/formats.py
+++ b/lib/thandy/formats.py
@@ -443,18 +443,6 @@ BUNDLE_SCHEMA = S.Obj(
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 @@ def checkWinRegistryKeyname(keyname):
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 @@ def makePackageObj(config_fname, package_fname):
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 @@ def makePackageObj(config_fname, package_fname):
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 @@ def makeKeylistObj(keylist_fname, includePrivate=False):
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,
diff --git a/lib/thandy/packagesys/ExePackages.py b/lib/thandy/packagesys/ExePackages.py
index be0517a..59be63e 100644
--- a/lib/thandy/packagesys/ExePackages.py
+++ b/lib/thandy/packagesys/ExePackages.py
@@ -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 RegistryChecker(PS.Checker):
+ def __init__(self, key, version):
+ PS.Checker.__init__(self)
+ self._key = key
+ self._version = version
+
+ def __repr__(self):
+ return "RegistryChecker(%r, %r)"%(self._key, self._version)
+
+ def getInstalledVersions(self):
+ try:
+ return [ thandy.util.getRegistryValue(self._key) ]
+ except thandy.util.NoRegistry:
+ raise thandy.CheckNotSupported("This OS has no registry.")
+
+ def isInstalled(self):
+ return self._version in self.getInstalledVersions()
+
+class CommandInstaller(PS.Installer):
+ def __init__(self, relPath, installCommand, removeCommand=None):
+ PS.Installer.__init__(self, relPath)
+ self._installCommand = installCommand
+ self._removeCommand = removeCommand
+
+ 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 install(self):
+ self._runCommand(self._installCommand)
+
+ def remove(self):
+ if self._removeCommand:
+ raise thandy.RemoveNotSupported()
+ self._runCommand(self._removeCommand)
+
+ 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)
+
+ return_code = self._execute(c)
+ if return_code != 0:
+ raise thandy.InstallFailed("Return code %s from calling %s"%
+ (return_code, c))
+
+ 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)
-class ExePackageSystem(pdb.DBBackedPackageSystem):
- def __init__(self, repo):
- pdb.DBBackedPackageSystem.__init__(self)
- self._repo = repo
-
- def getName(self):
- return "exe"
-
- def packageHandlesFromJSON(self, pkg):
- if pkg['format'] != 'exe':
- raise thandy.FormatException()
-
- handles = []
- for entry in pkg['files']:
- if len(entry) < 3:
- continue
- rp, h, extra = entry[:3]
- version = pkg['version']
-
- 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
-
- def canBeAutomatic(self):
- return True
-
- def canHaveUI(self):
- return True
-
-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 getRelativePath(self):
- return self._relPath
-
- 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 pdb.DBBackedPackageHandle.getInstalledVersion(self, transaction)
-
- 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)
diff --git a/lib/thandy/packagesys/PackageDB.py b/lib/thandy/packagesys/PackageDB.py
index a81507d..94a5ad9 100644
--- a/lib/thandy/packagesys/PackageDB.py
+++ b/lib/thandy/packagesys/PackageDB.py
@@ -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 @@ class SimplePackageDB:
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 getDB(self):
- if self._packageDB is None:
- fname = thandy.util.userFilename("db/packages")
- self._packageDB = SimplePackageDB(fname)
- return self._packageDB
-
-class DBBackedPackageHandle(thandy.packagesys.PackageSystem.PackageHandle):
- def __init__(self, packageDB, name, version, filelist):
- thandy.packagesys.PackageSystem.PackageHandle.__init__(self)
- self._packageDB = packageDB
- self._name = name
- self._version = version
- self._filelist = filelist
+ 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
- self._metaData = 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
- def _getInstallBase(self):
- raise NotImplemented()
+class _DBMixin:
+ def setDB(self, db):
+ self._db = db
- def anyVersionInstalled(self, transaction=None):
- return self.getInstalledVersion(transaction) != None
+ def getDB(self):
+ if self._db is None:
+ self._db = getPackageDBInstance()
+ return self._db
+
+class DBChecker(PS.Checker, _DBMixin):
+ def __init__(self, name, version):
+ PS.Checker.__init__(self)
+ self._name = name
+ self._version = version
+ self._db = None
+
+ def __repr__(self):
+ return "DBChecker(%r, %r)"%(self._name, self._version)
+
+# 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 isInstalled(self):
+ return self._version in self.getInstalledVersions(transaction)
+
+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 getInstalledVersion(self, transaction=None):
- return self._packageDB.getCurVersion(self._name)
+ def __repr__(self):
+ return "DBInstaller(%r, %r, %r, %r)"%(self._name,
+ self._version,
+ self._relPath,
+ self._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 setTransaction(self, transaction):
+ self._installer.setTransaction(transaction)
- def _doInstall(self):
- raise NotImplemented()
+ def setCacheRoot(self, cacheRoot):
+ self._installer.setCacheRoot(cacheRoot)
- def isInstalled(self, transaction=None):
- return self.getInstalledVersion(transaction) == self._version
+ def install(self):
+ self._installer.install()
- def checkInstall(self, transaction=None):
- base = self._getInstallBase()
+ 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)
- 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
+ def remove(self):
+ self._installer.remove()
+ self.getDB().removeAll(self._name)
- return all_ok
diff --git a/lib/thandy/packagesys/PackageSystem.py b/lib/thandy/packagesys/PackageSystem.py
index fa0d28b..4a847e9 100644
--- a/lib/thandy/packagesys/PackageSystem.py
+++ b/lib/thandy/packagesys/PackageSystem.py
@@ -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 = {}
-
- def addPackageSystem(self, system):
- self._systems[system.getName()] = system
-
- def getSysForPackage(self, pkg):
- return self._systems.get(pkg['format'], None)
-
- @staticmethod
- def create(repository):
- r = PackageMetasystem(repository)
-
- try:
- import rpm
- except ImportError:
- pass
+import os
+
+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")
+
+ 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
+
+def getChecker(checkType, relPath, extra, defaultFormat, package):
+ if checkType == None:
+ #DOCDOC obsolete
+ if defaultFormat == 'rpm':
import thandy.packagesys.RPMPackages
- r.addPackageSystem(thandy.packagesys.RPMPackages.RPMPackageSystem(
- repository))
-
+ 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
- r.addPackageSystem(thandy.packagesys.ExePackages.ExePackageSystem(
- repository))
-
- return r
-
-class PackageSystem:
- def getName(self):
- raise NotImplemented()
-
- def packageHandlesFromJSON(self, json):
- raise NotImplemented()
-
- def canBeAutomatic(self):
- return True
-
- def canHaveUI(self):
- return False
-
- def getTransaction(self):
- return PackageTransaction()
-
-class PackageTransaction:
+ k,v=extra['registry_ent']
+ return thandy.packagesys.ExePackages.RegistryChecker(k,v)
+ else:
+ return None
+
+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:
+ 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
+ installer = thandy.packagesys.ExePackages.CommandInstaller(
+ relPath, extra['cmd_install'], extra.get['cmd_remove'])
+ else:
+ return None
+
+ if extra.get('check_type') == 'db':
+ import thandy.packagesys.PackageDB
+ installer = thandy.packagesys.PackageDB.DBInstaller(
+ extra['item_name'], extra['item_version'], installer)
+
+ return installer
+
+class PackageItem:
+ def __init__(self, relativePath, checker, installer):
+ self._relPath = relativePath
+ self._checker = checker
+ self._installer = installer
+
+ 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 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
+
+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()
diff --git a/lib/thandy/packagesys/RPMPackages.py b/lib/thandy/packagesys/RPMPackages.py
index 4d0f326..c710b8d 100644
--- a/lib/thandy/packagesys/RPMPackages.py
+++ b/lib/thandy/packagesys/RPMPackages.py
@@ -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,33 +20,33 @@ for name in dir(rpm):
_CALLBACK_CODES[getattr(rpm, name)] = name[12:]
del name
-class RPMPackageTransaction(thandy.packagesys.PackageSystem.PackageTransaction):
-
- def _start(self):
- thandy.packagesys.PackageSystem.PackageTransaction.__init__(self)
- self._tset = rpm.TransactionSet()
-
- 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)
-
- 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)
-
- else:
- hdr, path = mydata
- logging.info("RPM event %s on %s [%s/%s]",
- _CALLBACK_CODES.get(what,str(what)),
- hdr['name'], amount, total)
+class RPMPackageTransaction:
+
+ def _start(self):
+ PS.PackageTransaction.__init__(self)
+ self._tset = rpm.TransactionSet()
+
+ 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)
+
+ 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)
+
+ 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)
@@ -155,31 +125,36 @@ def checkRPMInstall(name, version, ts=None):
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 __repr__(self):
+ return "RPMChecker(%r, %r)"%(self._name, self._version)
- def getRelativePath(self):
- return self._relPath
+ def getInstalledVersions(self):
+ return getInstalledRPMVersions(self._name, self._transaction)
- def anyVersionInstalled(self, transaction=None):
- return len(getInstalledRPMVersions(self.name, transaction)) > 1
+ def isInstalled(self):
+ vers = getInstalledRPMVersions(self._name, self._transaction)
+ return self._version in vers
- def getInstalledVersion(self, transaction=None):
- s = max(getInstalledRPMVersions(self._name, transaction))
+# 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)
-
diff --git a/lib/thandy/packagesys/__init__.py b/lib/thandy/packagesys/__init__.py
index 4dd7019..dc0825b 100644
--- a/lib/thandy/packagesys/__init__.py
+++ b/lib/thandy/packagesys/__init__.py
@@ -2,3 +2,6 @@
__all__ = [ ]
+
+
+
diff --git a/lib/thandy/repository.py b/lib/thandy/repository.py
index 2dea1c4..a9b0d19 100644
--- a/lib/thandy/repository.py
+++ b/lib/thandy/repository.py
@@ -2,6 +2,7 @@
import thandy.formats
import thandy.util
+import thandy.packagesys.PackageSystem
try:
import json
@@ -297,11 +298,11 @@ class LocalRepository:
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 @@ class LocalRepository:
if installableDict == None:
installableDict = {}
+ pkgItems = None
+
need = set()
# Fetch missing metafiles.
@@ -468,26 +471,31 @@ class LocalRepository:
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)
-
- for h in handles:
- allHandles[h.getRelativePath()] = h
- if h.isInstalled():
- alreadyInstalled[h.getRelativePath()] = h
+ alreadyInstalled = set()
+ pkgItems = {}
+
+ 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 @@ class LocalRepository:
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
diff --git a/lib/thandy/tests.py b/lib/thandy/tests.py
index a064188..44cbc88 100644
--- a/lib/thandy/tests.py
+++ b/lib/thandy/tests.py
@@ -11,6 +11,11 @@ import thandy.repository
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