#!/usr/bin/python -tt # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright 2003 Duke University import mdUtils # consider making an XMLPackageObject # BasePackageObject - with just methods and the init'd storage dicts # XMLPackageobject will be used to build the rpmXMLPAckageObject - which is # formatnode stuff for rpm. class PackageObject: """Base Package Object - sets up the default storage dicts and the most common returns""" def __init__(self): self.simple = {} # simple things, name, arch, e,v,r, size, etc self.checksums = [] # (type, checksum, id(0,1) def __str__(self): return self.returnNevraPrintable() def returnSimple(self, varname): return self.simple[varname] def simpleItems(self): return self.simple.keys() def returnID(self): return self.returnSimple('id') def returnPackageTuple(self): return (self.returnSimple('name'), self.returnSimple('arch'), self.returnSimple('epoch'),self.returnSimple('version'), self.returnSimple('release')) def returnNevraTuple(self): return (self.returnSimple('name'), self.returnSimple('epoch'), self.returnSimple('version'),self.returnSimple('release'), self.returnSimple('arch')) def returnNevraPrintable(self): """return printable string for the pkgname/object name - epoch:ver-rel.arch""" if self.returnSimple('epoch') == '0': string = '%s - %s-%s.%s' % (self.returnSimple('name'), self.returnSimple('version'), self.returnSimple('release'), self.returnSimple('arch')) else: string = '%s - %s:%s-%s.%s' % (self.returnSimple('name'), self.returnSimple('epoch'), self.returnSimple('version'), self.returnSimple('release'), self.returnSimple('arch')) return string def returnEVR(self): """returns a tuple of epoch, ver, rel""" return (self.returnSimple('epoch'), self.returnSimple('version'), self.returnSimple('release')) return def returnChangelog(self): """return changelog entries""" return self.changelog class XMLPackageObject(PackageObject): """inherits from PackageObject, does the functions to parse an xml package format file to pull packages in""" def __init__(self): PackageObject.__init__(self) def parseVersion(self, node): """takes a version element, returns a tuple of (epoch, ver, rel)""" epoch = node.GetAttribute('epoch') ver = node.GetAttribute('ver') rel = node.GetAttribute('rel') return (epoch, ver, rel) def parseChecksum(self, node): """takes a checksum element, returns a tuple of (type, checksum, if it is the checksum to be used for the the package id)""" csumtype = node.GetAttribute('type') csumid = node.GetAttribute('pkgid') if csumid is None or csumid.upper() == 'NO': csumid = 0 elif csumid.upper() == 'YES': csumid = 1 else: #FIXME - raise an exception print 'broken csumid - invalid document' csumid = 0 node.Read() csum = node.Value() return (csumtype, csum, csumid) def parseSize(self, node): """takes a size element, returns package, installed and archive size""" pkg = node.GetAttribute('package') installed = node.GetAttribute('installed') archive = node.GetAttribute('archive') return pkg, installed, archive def parseTime(self, node): """takes a time element, returns buildtime, filetime(mtime)""" build = node.GetAttribute('build') mtime = node.GetAttribute('file') return build, mtime def parseLocation(self, node): """takes a location element, returnsbase url path, relative path to package""" base = node.GetAttribute('base') relative = node.GetAttribute('href') return base, relative def parseSimple(self, node): """takes a simple unattributed CDATA element and returns its value""" if node.IsEmptyElement(): return '' node.Read() # get the next node return node.Value() def readPkgNode(self, reader): """primary package node reading and dumping""" mydepth = reader.Depth() ret = reader.Read() while ret: if reader.NodeType() == 14: ret = reader.Read() continue if reader.NodeType() == 15 and reader.Depth() == mydepth: return if reader.NodeType() == 1: if reader.Depth() == mydepth: #print 'oh crap - we are outside - how did that happen??' return nodeName = reader.LocalName() if nodeName in ['name', 'arch', 'summary', 'description', 'url', 'packager', 'buildtime', 'filetime']: self.simple[nodeName] = self.parseSimple(reader) elif nodeName == 'version': (self.simple['epoch'], self.simple['version'], self.simple['release']) = self.parseVersion(reader) elif nodeName == 'size': self.simple['packagesize'], self.simple['installedsize'], \ self.simple['archivesize'] = self.parseSize(reader) elif nodeName == 'time': self.simple['buildtime'], self.simple['filetime'], = \ self.parseTime(reader) elif nodeName == 'location': self.simple['basepath'], self.simple['relativepath'] = \ self.parseLocation(reader) elif nodeName == 'checksum': (sumtype, sumdata, sumid) = self.parseChecksum(reader) self.checksums.append((sumtype, sumdata, sumid)) if sumid: self.simple['id'] = sumdata elif nodeName == 'format': try: self.readFormatNode(reader) except AttributeError: # FIXME - should raise an exception print 'No method to handle format element' else: pass # FIXME - should raise an exception print 'unknown element in package: %s' % nodeName ret = reader.Read() continue class RpmBase: """return functions and storage for rpm-specific data""" def __init__(self): self.prco = {} self.prco['obsoletes'] = [] # (name, flag, (e,v,r)) self.prco['conflicts'] = [] # (name, flag, (e,v,r)) self.prco['requires'] = [] # (name, flag, (e,v,r)) self.prco['provides'] = [] # (name, flag, (e,v,r)) self.files = {} self.files['file'] = [] self.files['dir'] = [] self.files['ghost'] = [] self.changelog = [] # (ctime, cname, ctext) self.licenses = [] def returnPrco(self, prcotype): """return list of provides, requires, conflicts or obsoletes""" if self.prco.has_key(prcotype): return self.prco[prcotype] else: return [] def checkPrco(self, prcotype, prcotuple): """returns 1 or 0 if the pkg contains the requested tuple/tuple range""" # get rid of simple cases - nothing if not self.prco.has_key(prcotype): return 0 # exact match if prcotuple in self.prco[prcotype]: return 1 else: # make us look it up and compare (reqn, reqf, (reqe, reqv ,reqr)) = prcotuple if reqf is not None: if self.inPrcoRange(prcotype, prcotuple): return 1 else: return 0 else: for (n, f, (e, v, r)) in self.returnPrco(prcotype): if reqn == n: return 1 return 0 def inPrcoRange(self, prcotype, reqtuple): """returns true if the package has a the prco that satisfies the reqtuple range, assume false. Takes: prcotype, requested prco tuple""" # we only ever get here if we have a versioned prco # nameonly shouldn't ever raise it (reqn, reqf, (reqe, reqv, reqr)) = reqtuple # find the named entry in pkgobj, do the comparsion for (n, f, (e, v, r)) in self.returnPrco(prcotype): if reqn == n: # found it if f != 'EQ': # isn't this odd, it's not 'EQ' - it really should be # use the pkgobj's evr for the comparison (e, v, r) = self.returnEVR() # and you thought we were done having fun # if the requested release is left out then we have # to remove release from the package prco to make sure the match # is a success - ie: if the request is EQ foo 1:3.0.0 and we have # foo 1:3.0.0-15 then we have to drop the 15 so we can match if reqr is None: r = None if reqe is None: e = None if reqv is None: # just for the record if ver is None then we're going to segfault v = None rc = mdUtils.compareEVR((e, v, r), (reqe, reqv, reqr)) if rc >= 1: if reqf in ['GT', 'GE', 4, 12]: return 1 if rc == 0: if reqf in ['GE', 'LE', 'EQ', 8, 10, 12]: return 1 if rc <= -1: if reqf in ['LT', 'LE', 2, 10]: return 1 return 0 def returnChangelog(self): """return changelog entries""" return self.changelog def returnFileEntries(self, ftype='file'): """return list of files based on type""" if self.files.has_key(ftype): return self.files[ftype] else: return [] def returnFileTypes(self): """return list of types of files in the package""" return self.files.keys() class RpmXMLPackageObject(XMLPackageObject, RpmBase): """used class - inherits from XMLPackageObject, which inherits from Package Object also inherits from RpmBase for return functions""" def __init__(self, node, repoid): XMLPackageObject.__init__(self) RpmBase.__init__(self) self.simple['repoid'] = repoid self.readPkgNode(node) self.repoid = repoid def dumpPkg(self): fconv = { 'EQ':'=', 'LT':'<', 'LE':'<=', 'GT':'>', 'GE':'>='} for item in self.simpleItems(): print '%s = %s' % (item, self.returnSimple(item)) for csum in self.checksums: print csum for thing in ['requires', 'provides', 'obsoletes', 'conflicts']: if len(self.prco[thing]) > 0: print '%s:' % thing for (n,f,(e,v,r)) in self.prco[thing]: if f is None: print '\t%s ' % n else: print '\t', print n, print fconv[f], print '%s:%s-%s' %(e,v,r) print '' def readFormatNode(self, reader): """reads the element and hands off the elements to be parsed elsewhere""" mydepth = reader.Depth() ret = reader.Read() while ret: if reader.NodeType() == 14: ret = reader.Read() continue if reader.NodeType() == 15 and reader.Depth() == mydepth: return if reader.NodeType() == 1: if reader.Depth() == mydepth: #print 'oh crap - we are outside - how did that happen??' return nodeName = reader.LocalName() if nodeName in ['vendor', 'group', 'buildhost', 'sourcerpm']: self.simple[nodeName] = self.parseSimple(reader) elif nodeName == 'license': self.licenses.append(self.parseSimple(reader)) elif nodeName == 'header-range': self.simple['hdrstart'], self.simple['hdrend'] = \ self.parseHdrRange(reader) elif nodeName in ['obsoletes', 'provides', 'requires', 'conflicts']: objlist = self.parsePrco(reader) self.prco[nodeName].extend(objlist) elif nodeName == 'file': self.loadFileEntry(reader) else: # FIXME - should raise an exception print 'unknown element in format: %s' % nodeName #pass ret = reader.Read() continue def parseHdrRange(self, node): """parse header-range, returns (start, end) tuple""" start = node.GetAttribute('start') end = node.GetAttribute('end') return start, end def parsePrco(self, reader): """parse a provides,requires,obsoletes,conflicts element""" objlist = [] mydepth = reader.Depth() ret = reader.Read() while ret: if reader.NodeType() == 14: ret = reader.Read() continue if reader.NodeType() == 15 and reader.Depth() == mydepth: return objlist if reader.NodeType() == 1: if reader.Depth() == mydepth: #print 'oh crap - we are outside - how did that happen??' return objlist prcoName = reader.LocalName() if prcoName == 'entry': name = reader.GetAttribute('name') flag = reader.GetAttribute('flags') e = reader.GetAttribute('epoch') v = reader.GetAttribute('ver') r = reader.GetAttribute('rel') pre = reader.GetAttribte('pre') objlist.append((name, flag, (e, v, r), pre)) ret = reader.Read() continue return objlist def loadFileEntry(self, node): """load a file/dir entry""" ftype = node.GetAttribute('type') node.Read() # content is file file = node.Value() if not ftype: ftype = 'file' if not self.files.has_key(ftype): self.files[ftype] = [] #if file not in self.files[ftype]: self.files[ftype].append(file) return (ftype, file) def loadChangeLogEntry(self, node): """load changelog data""" time = node.GetAttribute('date') author = node.GetAttribute('author') node.Read() content = node.Value() self.changelog.append((time, author, content))