#!/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 2004 Duke University # Written by Seth Vidal """ Classes and functions dealing with rpm package representations. """ import rpm import os import os.path import misc import i18n import re import fnmatch import stat import warnings from subprocess import Popen, PIPE from rpmUtils import RpmUtilsError import rpmUtils.miscutils from rpmUtils.miscutils import flagToString, stringToVersion, compareVerOnly import Errors import errno import struct from constants import * from operator import itemgetter import urllib import urlparse urlparse.uses_fragment.append("media") from urlgrabber.grabber import URLGrabber, URLGrabError try: import xattr if not hasattr(xattr, 'get'): xattr = None # This is a "newer" API. except ImportError: xattr = None # For verify import pwd import grp def comparePoEVR(po1, po2): """ Compare two Package or PackageEVR objects. """ (e1, v1, r1) = (po1.epoch, po1.version, po1.release) (e2, v2, r2) = (po2.epoch, po2.version, po2.release) return rpmUtils.miscutils.compareEVR((e1, v1, r1), (e2, v2, r2)) def comparePoEVREQ(po1, po2): """ Compare two Package or PackageEVR objects for equality. """ (e1, v1, r1) = (po1.epoch, po1.version, po1.release) (e2, v2, r2) = (po2.epoch, po2.version, po2.release) if r1 != r2: return False if v1 != v2: return False if e1 != e2: return False return True def buildPkgRefDict(pkgs, casematch=True): """take a list of pkg objects and return a dict the contains all the possible naming conventions for them eg: for (name,i386,0,1,1) dict[name] = (name, i386, 0, 1, 1) dict[name.i386] = (name, i386, 0, 1, 1) dict[name-1-1.i386] = (name, i386, 0, 1, 1) dict[name-1] = (name, i386, 0, 1, 1) dict[name-1-1] = (name, i386, 0, 1, 1) dict[0:name-1-1.i386] = (name, i386, 0, 1, 1) dict[name-0:1-1.i386] = (name, i386, 0, 1, 1) """ pkgdict = {} for pkg in pkgs: (n, a, e, v, r) = pkg.pkgtup if not casematch: n = n.lower() a = a.lower() e = e.lower() v = v.lower() r = r.lower() name = n nameArch = '%s.%s' % (n, a) nameVerRelArch = '%s-%s-%s.%s' % (n, v, r, a) nameVer = '%s-%s' % (n, v) nameVerRel = '%s-%s-%s' % (n, v, r) envra = '%s:%s-%s-%s.%s' % (e, n, v, r, a) nevra = '%s-%s:%s-%s.%s' % (n, e, v, r, a) for item in [name, nameArch, nameVerRelArch, nameVer, nameVerRel, envra, nevra]: if item not in pkgdict: pkgdict[item] = [] pkgdict[item].append(pkg) return pkgdict def parsePackages(pkgs, usercommands, casematch=0, unique='repo-epoch-name-version-release-arch', pkgdict=None): """matches up the user request versus a pkg list: for installs/updates available pkgs should be the 'others list' for removes it should be the installed list of pkgs takes an optional casematch option to determine if case should be matched exactly. Defaults to not matching.""" if pkgdict is None: pkgdict = buildPkgRefDict(pkgs, bool(casematch)) exactmatch = [] matched = [] unmatched = [] for command in usercommands: if not casematch: command = command.lower() if command in pkgdict: exactmatch.extend(pkgdict[command]) del pkgdict[command] else: # anything we couldn't find a match for # could mean it's not there, could mean it's a wildcard if misc.re_glob(command): trylist = pkgdict.keys() # command and pkgdict are already lowered if not casematch # so case sensitive is always fine regex = misc.compile_pattern(command) foundit = 0 for item in trylist: if regex(item): matched.extend(pkgdict[item]) del pkgdict[item] foundit = 1 if not foundit: unmatched.append(command) else: unmatched.append(command) unmatched = misc.unique(unmatched) if unique == 'repo-epoch-name-version-release-arch': # pkg.__hash__ matched = misc.unique(matched) exactmatch = misc.unique(exactmatch) elif unique == 'repo-pkgkey': # So we get all pkg entries from a repo def pkgunique(pkgs): u = {} for pkg in pkgs: mark = "%s%s" % (pkg.repo.id, pkg.pkgKey) u[mark] = pkg return u.values() matched = pkgunique(matched) exactmatch = pkgunique(exactmatch) else: raise ValueError, "Bad value for unique: %s" % unique return exactmatch, matched, unmatched class FakeSack: """ Fake PackageSack to use with FakeRepository""" def __init__(self): pass # This is fake, so do nothing def have_fastReturnFileEntries(self): """ Is calling pkg.returnFileEntries(primary_only=True) faster than using searchFiles(). """ return True def delPackage(self, obj): """delete a pkgobject, do nothing, but make localpackages work with --skip-broken""" pass # This is fake, so do nothing class FakeRepository: """Fake repository class for use in rpmsack package objects""" def _set_cleanup_repoid(self, repoid): """ Set the repoid, but because it can be random ... clean it up. """ # We don't want repoids to contain random bytes that can be # in the FS directories. It's also nice if they aren't "huge". So # just chop to the rpm name. pathbased = False if '/' in repoid: repoid = os.path.basename(repoid) pathbased = True if repoid.endswith(".rpm"): repoid = repoid[:-4] pathbased = True bytes = [] # Just in case someone uses mv to be evil: if pathbased: bytes.append('/') for byte in repoid: if ord(byte) >= 128: byte = '?' bytes.append(byte) self.id = "".join(bytes) def __init__(self, repoid): self._set_cleanup_repoid(repoid) self.name = self.id self.sack = FakeSack() def __cmp__(self, other): if self.id > other.id: return 1 elif self.id < other.id: return -1 else: return 0 def __hash__(self): return hash(self.id) def __str__(self): return self.id def _ui_id(self): return self.id ui_id = property(fget=lambda self: self._ui_id()) # Goal for the below is to have a packageobject that can be used by generic # functions independent of the type of package - ie: installed or available # Note that this is also used to history etc. ... so it's more a nevra+checksum # holder than a base for things which are actual packages. class PackageObject(object): """Base Package Object - sets up the default storage dicts and the most common returns""" def __init__(self): self.name = None self.version = None self.release = None self.epoch = None self.arch = None # self.pkgtup = (self.name, self.arch, self.epoch, self.version, self.release) self._checksums = [] # (type, checksum, id(0,1) def _ui_envra(self): if self.epoch == '0': return self.nvra else: return self.envra ui_envra = property(fget=lambda self: self._ui_envra()) def _ui_nevra(self): if self.epoch == '0': return self.nvra else: return self.nevra ui_nevra = property(fget=lambda self: self._ui_nevra()) def _ui_evr(self): if self.epoch == '0': return self.vr else: return self.evr ui_evr = property(fget=lambda self: self._ui_evr()) def _ui_evra(self): if self.epoch == '0': return self.vra else: return self.evra ui_evra = property(fget=lambda self: self._ui_evra()) def _ui_nevr(self): if self.epoch == '0': return self.nvr else: return self.nevr ui_nevr = property(fget=lambda self: self._ui_nevr()) def _na(self): return '%s.%s' % (self.name, self.arch) na = property(fget=lambda self: self._na()) def _vr(self): return '%s-%s' % (self.version, self.release) vr = property(fget=lambda self: self._vr()) def _vra(self): return '%s-%s.%s' % (self.version, self.release, self.arch) vra = property(fget=lambda self: self._vra()) def _evr(self): return '%s:%s-%s' % (self.epoch, self.version, self.release) evr = property(fget=lambda self: self._evr()) def _evra(self): return '%s:%s-%s.%s' % (self.epoch,self.version,self.release, self.arch) evra = property(fget=lambda self: self._evra()) def _nvr(self): return '%s-%s-%s' % (self.name, self.version, self.release) nvr = property(fget=lambda self: self._nvr()) def _nvra(self): return '%s-%s-%s.%s' % (self.name, self.version,self.release, self.arch) nvra = property(fget=lambda self: self._nvra()) def _nevr(self): return '%s-%s:%s-%s' % (self.name, self.epoch,self.version,self.release) nevr = property(fget=lambda self: self._nevr()) def _nevra(self): return '%s-%s:%s-%s.%s' % (self.name, self.epoch, self.version, self.release, self.arch) nevra = property(fget=lambda self: self._nevra()) def _envr(self): return '%s:%s-%s-%s' % (self.epoch,self.name, self.version,self.release) envr = property(fget=lambda self: self._envr()) def _envra(self): return '%s:%s-%s-%s.%s' % (self.epoch, self.name, self.version, self.release, self.arch) envra = property(fget=lambda self: self._envra()) def __str__(self): return self.ui_envra def printVer(self): """returns a printable version string - including epoch, if it's set""" if self.epoch != '0': ver = '%s:%s-%s' % (self.epoch, self.version, self.release) else: ver = '%s-%s' % (self.version, self.release) return ver def verCMP(self, other): """ Compare package to another one, only rpm-version ordering. """ if not other: return 1 ret = cmp(self.name, other.name) if ret == 0: ret = comparePoEVR(self, other) return ret def __cmp__(self, other): """ Compare packages, this is just for UI/consistency. """ ret = self.verCMP(other) if ret == 0: ret = cmp(self.arch, other.arch) if ret == 0 and hasattr(self, 'repoid') and hasattr(other, 'repoid'): ret = cmp(self.repoid, other.repoid) # We want 'installed' to appear over 'abcd' and 'xyz', so boost that if ret and self.repoid == 'installed': return 1 if ret and other.repoid == 'installed': return -1 return ret def __eq__(self, other): """ Compare packages for yes/no equality, includes everything in the UI package comparison. """ if not other: return False if self.pkgtup != other.pkgtup: return False if hasattr(self, 'repoid') and hasattr(other, 'repoid'): if self.repoid != other.repoid: return False return True def __ne__(self, other): if not (self == other): return True return False def __getitem__(self, key): return getattr(self, key) def verEQ(self, other): """ Compare package to another one, only rpm-version equality. """ if not other: return None ret = cmp(self.name, other.name) if ret != 0: return False return comparePoEVREQ(self, other) def verNE(self, other): """ Compare package to another one, only rpm-version inequality. """ if not other: return None return not self.verEQ(other) def verLT(self, other): """ Uses verCMP, tests if the other _rpm-version_ is < ours. """ return self.verCMP(other) < 0 def verLE(self, other): """ Uses verCMP, tests if the other _rpm-version_ is <= ours. """ return self.verCMP(other) <= 0 def verGT(self, other): """ Uses verCMP, tests if the other _rpm-version_ is > ours. """ return self.verCMP(other) > 0 def verGE(self, other): """ Uses verCMP, tests if the other _rpm-version_ is >= ours. """ return self.verCMP(other) >= 0 def __repr__(self): return "<%s : %s (%s)>" % (self.__class__.__name__, str(self),hex(id(self))) def returnSimple(self, varname): warnings.warn("returnSimple() will go away in a future version of Yum.\n", Errors.YumFutureDeprecationWarning, stacklevel=2) return getattr(self, varname) def returnChecksums(self): return self._checksums checksums = property(fget=lambda self: self.returnChecksums()) def returnIdSum(self): for (csumtype, csum, csumid) in self.checksums: if csumid: return (csumtype, csum) _not_found_repo = FakeRepository('-') _not_found_repo.cost = 0 class YumNotFoundPackage(PackageObject): def __init__(self, pkgtup): self.name = pkgtup[0] self.arch = pkgtup[1] self.epoch = pkgtup[2] self.version = pkgtup[3] self.release = pkgtup[4] self.pkgtup = pkgtup self.size = 0 self._checksums = [] # (type, checksum, id(0,1) self.repo = _not_found_repo self.repoid = _not_found_repo.id # Fakeout output.py that it's a real pkg. ... def _ui_from_repo(self): """ This just returns '-' """ return self.repoid ui_from_repo = property(fget=lambda self: self._ui_from_repo()) def verifyLocalPkg(self): """check the package checksum vs the localPkg return True if pkg is good, False if not""" return False # This is the virtual base class of actual packages, it basically requires a # repo. even though it doesn't set one up in it's __init__. It also doesn't have # PackageObject methods ... so is basically unusable on it's own # see: YumAvailablePackage. class RpmBase(object): """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 = [] self._hash = None # FIXME: This is identical to PackageObject.__eq__ and __ne__, should be # remove (is .repoid fine here? ... we need it, maybe .repo.id). def __eq__(self, other): if not other: # check if other not is a package object. return False if self.pkgtup != other.pkgtup: return False if self.repoid != other.repoid: return False return True def __ne__(self, other): if not (self == other): return True return False def returnEVR(self): return PackageEVR(self.epoch, self.version, self.release) def __hash__(self): if self._hash is None: mystr = '%s - %s:%s-%s-%s.%s' % (self.repo.id, self.epoch, self.name, self.version, self.release, self.arch) self._hash = hash(mystr) return self._hash def returnPrco(self, prcotype, printable=False): """return list of provides, requires, conflicts or obsoletes""" prcos = self.prco.get(prcotype, []) if printable: results = [] for prco in prcos: if not prco[0]: # empty or none or whatever, doesn't matter continue results.append(misc.prco_tuple_to_string(prco)) return results return prcos 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 prcotype not in self.prco: return 0 # First try and exact match, then search # Make it faster, if it's "big". if len(self.prco[prcotype]) <= 8: if prcotuple in self.prco[prcotype]: return 1 else: if not hasattr(self, '_prco_lookup'): self._prco_lookup = {'obsoletes' : None, 'conflicts' : None, 'requires' : None, 'provides' : None} if self._prco_lookup[prcotype] is None: self._prco_lookup[prcotype] = set(self.prco[prcotype]) if prcotuple in self._prco_lookup[prcotype]: return 1 # make us look it up and compare (reqn, reqf, (reqe, reqv ,reqr)) = prcotuple if reqf is not None: return self.inPrcoRange(prcotype, prcotuple) else: for (n, f, (e, v, r)) in self.returnPrco(prcotype): if i18n.str_eq(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""" return bool(self.matchingPrcos(prcotype, reqtuple)) def matchingPrcos(self, prcotype, reqtuple): (reqn, reqf, (reqe, reqv, reqr)) = reqtuple # find the named entry in pkgobj, do the comparsion result = [] for (n, f, (e, v, r)) in self.returnPrco(prcotype): if not i18n.str_eq(reqn, n): continue if f == '=': f = 'EQ' if f != 'EQ' and prcotype == 'provides': # isn't this odd, it's not 'EQ' and it is a provides # - it really should be EQ # use the pkgobj's evr for the comparison if e is None: e = self.epoch if v is None: v = self.ver if r is None: r = self.rel #(e, v, r) = (self.epoch, self.ver, self.rel) matched = rpmUtils.miscutils.rangeCompare( reqtuple, (n, f, (e, v, r))) if matched: result.append((n, f, (e, v, r))) return result def provides_for(self, reqtuple): """check to see if the package object provides for the requirement passed, including searching filelists if the requirement is a file dep""" if self.checkPrco('provides', reqtuple): return True if reqtuple[0].startswith('/'): if misc.re_primary_filename(reqtuple[0]): pri_only = True else: pri_only = False for ftype in ('file', 'dir', 'ghost'): if reqtuple[0] in self.returnFileEntries(ftype, pri_only): return True return False def returnChangelog(self): """return changelog entries""" return self._changelog def returnFileEntries(self, ftype='file', primary_only=False): """return list of files based on type, you can pass primary_only=True to limit to those files in the primary repodata""" if self.files: if ftype in self.files: if primary_only: if ftype == 'dir': match = misc.re_primary_dirname else: match = misc.re_primary_filename return [fn for fn in self.files[ftype] if match(fn)] return self.files[ftype] return [] def returnFileTypes(self, primary_only=False): """return list of types of files in the package, you can pass primary_only=True to limit to those files in the primary repodata""" if primary_only: ret = [] # We only return the types for the primary files. for ftype in self.files.keys(): if ftype == 'dir': match = misc.re_primary_dirname else: match = misc.re_primary_filename # As soon as we find a primary file of this type, we can # return it. for fn in self.files[ftype]: if match(fn): break else: continue ret.append(ftype) return ret return self.files.keys() def returnPrcoNames(self, prcotype): if not hasattr(self, '_cache_prco_names_' + prcotype): data = [n for (n, f, v) in self.returnPrco(prcotype)] setattr(self, '_cache_prco_names_' + prcotype, data) return getattr(self, '_cache_prco_names_' + prcotype) def getProvidesNames(self): warnings.warn('getProvidesNames() will go away in a future version of Yum.\n', Errors.YumDeprecationWarning, stacklevel=2) return self.provides_names def simpleFiles(self, ftype='files'): warnings.warn('simpleFiles() will go away in a future version of Yum.' 'Use returnFileEntries(primary_only=True)\n', Errors.YumDeprecationWarning, stacklevel=2) if self.files and ftype in self.files: return self.files[ftype] return [] filelist = property(fget=lambda self: self.returnFileEntries(ftype='file')) dirlist = property(fget=lambda self: self.returnFileEntries(ftype='dir')) ghostlist = property(fget=lambda self: self.returnFileEntries(ftype='ghost')) requires = property(fget=lambda self: self.returnPrco('requires')) strong_requires = property(fget=lambda self: self.returnPrco('strong_requires')) provides = property(fget=lambda self: self.returnPrco('provides')) obsoletes = property(fget=lambda self: self.returnPrco('obsoletes')) conflicts = property(fget=lambda self: self.returnPrco('conflicts')) provides_names = property(fget=lambda self: self.returnPrcoNames('provides')) requires_names = property(fget=lambda self: self.returnPrcoNames('requires')) strong_requires_names = property(fget=lambda self: self.returnPrcoNames('strong_requires')) conflicts_names = property(fget=lambda self: self.returnPrcoNames('conflicts')) obsoletes_names = property(fget=lambda self: self.returnPrcoNames('obsoletes')) provides_print = property(fget=lambda self: self.returnPrco('provides', True)) requires_print = property(fget=lambda self: self.returnPrco('requires', True)) strong_requires_print = property(fget=lambda self: self.returnPrco('strong_requires', True)) conflicts_print = property(fget=lambda self: self.returnPrco('conflicts', True)) obsoletes_print = property(fget=lambda self: self.returnPrco('obsoletes', True)) changelog = property(fget=lambda self: self.returnChangelog()) EVR = property(fget=lambda self: self.returnEVR()) def _getBaseName(self): """ Return the "base name" of the package, atm. we can only look at the sourcerpm. """ if hasattr(self, '_base_package_name_ret'): return self._base_package_name_ret if hasattr(self, 'sourcerpm') and self.sourcerpm: (n, v, r, e, a) = rpmUtils.miscutils.splitFilename(self.sourcerpm) if n != self.name: self._base_package_name_ret = n return n # If there is no sourcerpm, or sourcerpm == us, use .name self._base_package_name_ret = self.name return self._base_package_name_ret base_package_name = property(fget=lambda self: self._getBaseName()) def have_fastReturnFileEntries(self): """ Is calling pkg.returnFileEntries(primary_only=True) faster than using searchFiles(). """ return self.repo.sack.have_fastReturnFileEntries() def obsoletedBy(self, obsoleters, limit=0): """ Returns list of obsoleters that obsolete this package. Note that we don't do obsoleting loops. If limit is != 0, then we stop after finding that many. """ provtup = (self.name, 'EQ', (self.epoch, self.version, self.release)) ret = [] for obspo in obsoleters: if obspo.inPrcoRange('obsoletes', provtup): ret.append(obspo) if limit and len(ret) > limit: break return ret # This is kind of deprecated class PackageEVR: """ A comparable epoch, version, and release representation. Note that you almost certainly want to use pkg.verEQ() or pkg.verGT() etc. instead. """ def __init__(self,e,v,r): self.epoch = e self.ver = v self.version = v self.rel = r self.release = r def compare(self,other): return rpmUtils.miscutils.compareEVR((self.epoch, self.ver, self.rel), (other.epoch, other.ver, other.rel)) def __lt__(self, other): if self.compare(other) < 0: return True return False def __gt__(self, other): if self.compare(other) > 0: return True return False def __le__(self, other): if self.compare(other) <= 0: return True return False def __ge__(self, other): if self.compare(other) >= 0: return True return False def __eq__(self, other): return comparePoEVREQ(self, other) def __ne__(self, other): if not (self == other): return True return False # This is the real base class of actual packages, it has a repo. and is # usable on it's own, in theory (but in practise see sqlitesack). class YumAvailablePackage(PackageObject, RpmBase): """derived class for the packageobject and RpmBase packageobject yum uses this for dealing with packages in a repository""" def __init__(self, repo, pkgdict = None): PackageObject.__init__(self) RpmBase.__init__(self) self.repoid = repo.id self.repo = repo self.state = None self._loadedfiles = False self._verify_local_pkg_cache = None if pkgdict != None: self.importFromDict(pkgdict) self.ver = self.version self.rel = self.release self.pkgtup = (self.name, self.arch, self.epoch, self.version, self.release) def _ui_from_repo(self): """ This reports the repo the package is from, we integrate YUMDB info. for RPM packages so a package from "fedora" that is installed has a ui_from_repo of "@fedora". Note that, esp. with the --releasever option, "fedora" or "rawhide" isn't authoritative. So we also check against the current releasever and if it is different we also print the YUMDB releasever. This means that installing from F12 fedora, while running F12, would report as "@fedora/13". """ if self.repoid == 'installed' and 'from_repo' in self.yumdb_info: end = '' if (self.rpmdb.releasever is not None and 'releasever' in self.yumdb_info and self.yumdb_info.releasever != self.rpmdb.releasever): end = '/' + self.yumdb_info.releasever return '@' + self.yumdb_info.from_repo + end return self.repoid ui_from_repo = property(fget=lambda self: self._ui_from_repo()) def exclude(self): """remove self from package sack""" self.repo.sack.delPackage(self) def printVer(self): """returns a printable version string - including epoch, if it's set""" if self.epoch != '0': ver = '%s:%s-%s' % (self.epoch, self.version, self.release) else: ver = '%s-%s' % (self.version, self.release) return ver def compactPrint(self): ver = self.printVer() return "%s.%s %s" % (self.name, self.arch, ver) def _size(self): return self.packagesize def _remote_path(self): return self.relativepath def _remote_url(self): """returns a URL that can be used for downloading the package. Note that if you're going to download the package in your tool, you should use self.repo.getPackage.""" base = self.basepath if base: # urljoin sucks in the reverse way that os.path.join sucks :) if base[-1] != '/': base = base + '/' return urlparse.urljoin(base, self.remote_path) return urlparse.urljoin(self.repo.urls[0], self.remote_path) size = property(fget=lambda self: self._size()) remote_path = property(_remote_path) remote_url = property(lambda self: self._remote_url()) def _committer(self): "Returns the name of the last person to do a commit to the changelog." if hasattr(self, '_committer_ret'): return self._committer_ret if not len(self.changelog): # Empty changelog is _possible_ I guess self._committer_ret = self.packager return self._committer_ret val = self.changelog[0][1] # Chagnelog data is in multiple locale's, so we convert to ascii # ignoring "bad" chars. val = misc.to_unicode(val, errors='replace') val = val.encode('ascii', 'replace') # Hacky way to get rid of version numbers... ix = val.find('> ') if ix != -1: val = val[0:ix+1] self._committer_ret = val return self._committer_ret committer = property(_committer) def _committime(self): "Returns the time of the last commit to the changelog." if hasattr(self, '_committime_ret'): return self._committime_ret if not len(self.changelog): # Empty changelog is _possible_ I guess self._committime_ret = self.buildtime return self._committime_ret self._committime_ret = self.changelog[0][0] return self._committime_ret committime = property(_committime) # FIXME test this to see if it causes hell elsewhere def _checksum(self): "Returns the 'default' checksum" return self.checksums[0][1] checksum = property(_checksum) def getDiscNum(self): if self.basepath is None: return None (scheme, netloc, path, query, fragid) = urlparse.urlsplit(self.basepath) if scheme == "media": if len(fragid) == 0: return 0 return int(fragid) return None def returnHeaderFromPackage(self): rpmfile = self.localPkg() ts = rpmUtils.transaction.initReadOnlyTransaction() try: hdr = rpmUtils.miscutils.hdrFromPackage(ts, rpmfile) except rpmUtils.RpmUtilsError: raise Errors.RepoError, 'Package Header %s: RPM Cannot open' % self return hdr def returnLocalHeader(self): """returns an rpm header object from the package object's local header cache""" if os.path.exists(self.localHdr()): try: hlist = rpm.readHeaderListFromFile(self.localHdr()) hdr = hlist[0] except (rpm.error, IndexError): raise Errors.RepoError, 'Package Header %s: Cannot open' % self else: raise Errors.RepoError, 'Package Header %s: Not Available' % self return hdr def localPkg(self): """return path to local package (whether it is present there, or not)""" if not hasattr(self, 'localpath'): rpmfn = os.path.basename(self.remote_path) self.localpath = self.repo.pkgdir + '/' + rpmfn return self.localpath def localHdr(self): """return path to local cached Header file downloaded from package byte ranges""" if not hasattr(self, 'hdrpath'): pkgname = os.path.basename(self.remote_path) hdrname = pkgname[:-4] + '.hdr' self.hdrpath = self.repo.hdrdir + '/' + hdrname return self.hdrpath def verifyLocalPkg(self): """check the package checksum vs the localPkg return True if pkg is good, False if not""" # This is called a few times now, so we want some way to not have to # read+checksum "large" datasets multiple times per. transaction. try: nst = os.stat(self.localPkg()) except OSError, e: return False if (hasattr(self, '_verify_local_pkg_cache') and self._verify_local_pkg_cache): ost = self._verify_local_pkg_cache if (ost.st_ino == nst.st_ino and ost.st_dev == nst.st_dev and ost.st_mtime == nst.st_mtime and ost.st_size == nst.st_size): return True (csum_type, csum) = self.returnIdSum() try: filesum = misc.checksum(csum_type, self.localPkg(), datasize=self.packagesize) except Errors.MiscError: return False if filesum != csum: return False self._verify_local_pkg_cache = nst return True # See: http://www.freedesktop.org/wiki/CommonExtendedAttributes def _localXattrUrl(self): """ Get the user.xdg.origin.url value from the local pkg. ... if it's present. We cache this so we can access it after the file has been deleted (keepcache=False). """ if xattr is None: return None if hasattr(self, '__cached_localXattrUrl'): return getattr(self, '__cached_localXattrUrl') if not self.verifyLocalPkg(): return None try: ret = xattr.get(self.localPkg(), 'user.xdg.origin.url') except: # Documented to be "EnvironmentError", but make sure return None setattr(self, '__cached_localXattrUrl', ret) return ret xattr_origin_url = property(lambda x: x._localXattrUrl()) def prcoPrintable(self, prcoTuple): """convert the prco tuples into a nicer human string""" warnings.warn('prcoPrintable() will go away in a future version of Yum.\n', Errors.YumDeprecationWarning, stacklevel=2) return misc.prco_tuple_to_string(prcoTuple) def requiresList(self): """return a list of requires in normal rpm format""" return self.requires_print def returnChecksums(self): return [(self.checksum_type, self.pkgId, 1)] def importFromDict(self, pkgdict): """handles an mdCache package dictionary item to populate out the package information""" # translates from the pkgdict, populating out the information for the # packageObject if hasattr(pkgdict, 'nevra'): (n, e, v, r, a) = pkgdict.nevra self.name = n self.epoch = e self.version = v self.arch = a self.release = r if hasattr(pkgdict, 'time'): self.buildtime = pkgdict.time['build'] self.filetime = pkgdict.time['file'] if hasattr(pkgdict, 'size'): self.packagesize = pkgdict.size['package'] self.archivesize = pkgdict.size['archive'] self.installedsize = pkgdict.size['installed'] if hasattr(pkgdict, 'location'): url = pkgdict.location.get('base') if url == '': url = None self.basepath = url self.relativepath = pkgdict.location['href'] if hasattr(pkgdict, 'hdrange'): self.hdrstart = pkgdict.hdrange['start'] self.hdrend = pkgdict.hdrange['end'] if hasattr(pkgdict, 'info'): for item in ['summary', 'description', 'packager', 'group', 'buildhost', 'sourcerpm', 'url', 'vendor']: setattr(self, item, pkgdict.info[item]) self.summary = self.summary.replace('\n', '') self.licenses.append(pkgdict.info['license']) if hasattr(pkgdict, 'files'): for fn in pkgdict.files: ftype = pkgdict.files[fn] if ftype not in self.files: self.files[ftype] = [] self.files[ftype].append(fn) if hasattr(pkgdict, 'prco'): for rtype in pkgdict.prco: for rdict in pkgdict.prco[rtype]: name = rdict['name'] f = rdict.get('flags') e = rdict.get('epoch') v = rdict.get('ver') r = rdict.get('rel') self.prco[rtype].append((name, f, (e,v,r))) if hasattr(pkgdict, 'changelog'): for cdict in pkgdict.changelog: date = cdict.get('date') text = cdict.get('value') author = cdict.get('author') self._changelog.append((date, author, text)) if hasattr(pkgdict, 'checksum'): ctype = pkgdict.checksum['type'] csum = pkgdict.checksum['value'] csumid = pkgdict.checksum['pkgid'] if csumid is None or csumid.upper() == 'NO': csumid = 0 elif csumid.upper() == 'YES': csumid = 1 else: csumid = 0 self._checksums.append((ctype, csum, csumid)) # from here down this is for dumping a package object back out to metadata def _return_remote_location(self): # break self.remote_url up into smaller pieces base = os.path.dirname(self.remote_url) href = os.path.basename(self.remote_url) msg = """\n""" % ( misc.to_xml(base,attrib=True), misc.to_xml(href, attrib=True)) return msg def _dump_base_items(self): packager = url = '' if self.packager: packager = misc.to_xml(self.packager) if self.url: url = misc.to_xml(self.url) (csum_type, csum, csumid) = self.checksums[0] msg = """ %s %s %s %s %s %s %s