961516c9817dbd69ebde60cf3110463102ed17a4
[yum.git] / yum / __init__.py
1 #!/usr/bin/python -tt
2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation; either version 2 of the License, or
5 # (at your option) any later version.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 # GNU Library General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # Copyright 2005 Duke University
16
17 """
18 The Yum RPM software updater.
19 """
20
21 import os
22 import os.path
23 import rpm
24 import re
25 import types
26 import errno
27 import time
28 import glob
29 import fnmatch
30 import logging
31 import logging.config
32 import operator
33 import gzip
34
35 import yum.i18n
36 _ = yum.i18n._
37 P_ = yum.i18n.P_
38
39 import config
40 from config import ParsingError, ConfigParser
41 import Errors
42 import rpmsack
43 import rpmUtils.updates
44 from rpmUtils.arch import canCoinstall, ArchStorage, isMultiLibArch
45 import rpmUtils.transaction
46 import comps
47 from repos import RepoStorage
48 import misc
49 from parser import ConfigPreProcessor, varReplace
50 import transactioninfo
51 import urlgrabber
52 from urlgrabber.grabber import URLGrabber, URLGrabError
53 from urlgrabber.progress import format_number
54 from packageSack import packagesNewestByNameArch, ListPackageSack
55 import depsolve
56 import plugins
57 import logginglevels
58 import yumRepo
59 import callbacks
60
61 import warnings
62 warnings.simplefilter("ignore", Errors.YumFutureDeprecationWarning)
63
64 from packages import parsePackages, YumAvailablePackage, YumLocalPackage, YumInstalledPackage, comparePoEVR
65 from constants import *
66 from yum.rpmtrans import RPMTransaction,SimpleCliCallBack
67 from yum.i18n import to_unicode
68
69 import string
70
71 from urlgrabber.grabber import default_grabber
72
73 __version__ = '3.2.23'
74 __version_info__ = tuple([ int(num) for num in __version__.split('.')])
75
76 #  Setup a default_grabber UA here that says we are yum, done using the global
77 # so that other API users can easily add to it if they want.
78 #  Don't do it at init time, or we'll get multiple additions if you create
79 # multiple YumBase() objects.
80 default_grabber.opts.user_agent += " yum/" + __version__
81
82 class _YumPreBaseConf:
83     """This is the configuration interface for the YumBase configuration.
84        So if you want to change if plugins are on/off, or debuglevel/etc.
85        you tweak it here, and when yb.conf does it's thing ... it happens. """
86
87     def __init__(self):
88         self.fn = '/etc/yum/yum.conf'
89         self.root = '/'
90         self.init_plugins = True
91         self.plugin_types = (plugins.TYPE_CORE,)
92         self.optparser = None
93         self.debuglevel = None
94         self.errorlevel = None
95         self.disabled_plugins = None
96         self.enabled_plugins = None
97         self.syslog_ident = None
98         self.syslog_facility = None
99         self.syslog_device = '/dev/log'
100         self.arch = None
101         self.releasever = None
102
103 class YumBase(depsolve.Depsolve):
104     """This is a primary structure and base class. It houses the objects and
105        methods needed to perform most things in yum. It is almost an abstract
106        class in that you will need to add your own class above it for most
107        real use."""
108     
109     def __init__(self):
110         depsolve.Depsolve.__init__(self)
111         self._conf = None
112         self._tsInfo = None
113         self._rpmdb = None
114         self._up = None
115         self._comps = None
116         self._pkgSack = None
117         self._lockfile = None
118         self.skipped_packages = []   # packages skip by the skip-broken code
119         self.logger = logging.getLogger("yum.YumBase")
120         self.verbose_logger = logging.getLogger("yum.verbose.YumBase")
121         self._repos = RepoStorage(self)
122
123         # Start with plugins disabled
124         self.disablePlugins()
125
126         self.localPackages = [] # for local package handling
127
128         self.mediagrabber = None
129         self.arch = ArchStorage()
130         self.preconf = _YumPreBaseConf()
131
132
133     def __del__(self):
134         self.close()
135         self.closeRpmDB()
136         self.doUnlock()
137
138     def close(self):
139         if self._repos:
140             self._repos.close()
141
142     def _transactionDataFactory(self):
143         """Factory method returning TransactionData object"""
144         return transactioninfo.TransactionData()
145
146     def doGenericSetup(self, cache=0):
147         """do a default setup for all the normal/necessary yum components,
148            really just a shorthand for testing"""
149
150         self.preconf.init_plugins = False
151         self.conf.cache = cache
152
153     def doConfigSetup(self, fn='/etc/yum/yum.conf', root='/', init_plugins=True,
154             plugin_types=(plugins.TYPE_CORE,), optparser=None, debuglevel=None,
155             errorlevel=None):
156         warnings.warn(_('doConfigSetup() will go away in a future version of Yum.\n'),
157                 Errors.YumFutureDeprecationWarning, stacklevel=2)
158
159         if hasattr(self, 'preconf'):
160             self.preconf.fn = fn
161             self.preconf.root = root
162             self.preconf.init_plugins = init_plugins
163             self.preconf.plugin_types = plugin_types
164             self.preconf.optparser = optparser
165             self.preconf.debuglevel = debuglevel
166             self.preconf.errorlevel = errorlevel
167
168         return self.conf
169         
170     def _getConfig(self, **kwargs):
171         '''
172         Parse and load Yum's configuration files and call hooks initialise
173         plugins and logging. Uses self.preconf for pre-configuration,
174         configuration. '''
175
176         # ' xemacs syntax hack
177
178         if kwargs:
179             warnings.warn('Use .preconf instead of passing args to _getConfig')
180
181         if self._conf:
182             return self._conf
183         conf_st = time.time()            
184
185         if kwargs:
186             for arg in ('fn', 'root', 'init_plugins', 'plugin_types',
187                         'optparser', 'debuglevel', 'errorlevel',
188                         'disabled_plugins', 'enabled_plugins'):
189                 if arg in kwargs:
190                     setattr(self.preconf, arg, kwargs[arg])
191
192         fn = self.preconf.fn
193         root = self.preconf.root
194         init_plugins = self.preconf.init_plugins
195         plugin_types = self.preconf.plugin_types
196         optparser = self.preconf.optparser
197         debuglevel = self.preconf.debuglevel
198         errorlevel = self.preconf.errorlevel
199         disabled_plugins = self.preconf.disabled_plugins
200         enabled_plugins = self.preconf.enabled_plugins
201         syslog_ident    = self.preconf.syslog_ident
202         syslog_facility = self.preconf.syslog_facility
203         syslog_device   = self.preconf.syslog_device
204         releasever = self.preconf.releasever
205         arch = self.preconf.arch
206
207         if arch: # if preconf is setting an arch we need to pass that up
208             self.arch.setup_arch(arch)
209         else:
210             arch = self.arch.canonarch
211
212         # TODO: Remove this block when we no longer support configs outside
213         # of /etc/yum/
214         if fn == '/etc/yum/yum.conf' and not os.path.exists(fn):
215             # Try the old default
216             fn = '/etc/yum.conf'
217
218         startupconf = config.readStartupConfig(fn, root)
219         startupconf.arch = arch
220         startupconf.basearch = self.arch.basearch
221
222         if startupconf.gaftonmode:
223             global _
224             _ = yum.i18n.dummy_wrapper
225
226         if debuglevel != None:
227             startupconf.debuglevel = debuglevel
228         if errorlevel != None:
229             startupconf.errorlevel = errorlevel
230         if syslog_ident != None:
231             startupconf.syslog_ident = syslog_ident
232         if syslog_facility != None:
233             startupconf.syslog_facility = syslog_facility
234         if releasever != None:
235             startupconf.releasever = releasever
236
237         self.doLoggingSetup(startupconf.debuglevel, startupconf.errorlevel,
238                             startupconf.syslog_ident,
239                             startupconf.syslog_facility, syslog_device)
240
241         if init_plugins and startupconf.plugins:
242             self.doPluginSetup(optparser, plugin_types, startupconf.pluginpath,
243                     startupconf.pluginconfpath,disabled_plugins,enabled_plugins)
244
245         self._conf = config.readMainConfig(startupconf)
246
247         #  We don't want people accessing/altering preconf after it becomes
248         # worthless. So we delete it, and thus. it'll raise AttributeError
249         del self.preconf
250
251         # run the postconfig plugin hook
252         self.plugins.run('postconfig')
253         self.yumvar = self.conf.yumvar
254
255         self.getReposFromConfig()
256
257         # who are we:
258         self.conf.uid = os.geteuid()
259         
260         
261         self.doFileLogSetup(self.conf.uid, self.conf.logfile)
262         self.verbose_logger.debug('Config time: %0.3f' % (time.time() - conf_st))
263         self.plugins.run('init')
264         return self._conf
265         
266
267     def doLoggingSetup(self, debuglevel, errorlevel,
268                        syslog_ident=None, syslog_facility=None,
269                        syslog_device='/dev/log'):
270         '''
271         Perform logging related setup.
272
273         @param debuglevel: Debug logging level to use.
274         @param errorlevel: Error logging level to use.
275         '''
276         logginglevels.doLoggingSetup(debuglevel, errorlevel,
277                                      syslog_ident, syslog_facility,
278                                      syslog_device)
279
280     def doFileLogSetup(self, uid, logfile):
281         logginglevels.setFileLog(uid, logfile)
282
283     def getReposFromConfigFile(self, repofn, repo_age=None, validate=None):
284         """read in repositories from a config .repo file"""
285
286         if repo_age is None:
287             repo_age = os.stat(repofn)[8]
288         
289         confpp_obj = ConfigPreProcessor(repofn, vars=self.yumvar)
290         parser = ConfigParser()
291         try:
292             parser.readfp(confpp_obj)
293         except ParsingError, e:
294             msg = str(e)
295             raise Errors.ConfigError, msg
296
297         # Check sections in the .repo file that was just slurped up
298         for section in parser.sections():
299
300             if section in ['main', 'installed']:
301                 continue
302
303             # Check the repo.id against the valid chars
304             bad = None
305             for byte in section:
306                 if byte in string.ascii_letters:
307                     continue
308                 if byte in string.digits:
309                     continue
310                 if byte in "-_.:":
311                     continue
312                 
313                 bad = byte
314                 break
315
316             if bad:
317                 self.logger.warning("Bad id for repo: %s, byte = %s %d" %
318                                     (section, bad, section.find(bad)))
319                 continue
320
321             try:
322                 thisrepo = self.readRepoConfig(parser, section)
323             except (Errors.RepoError, Errors.ConfigError), e:
324                 self.logger.warning(e)
325                 continue
326             else:
327                 thisrepo.repo_config_age = repo_age
328                 thisrepo.repofile = repofn
329
330             if validate and not validate(thisrepo):
331                 continue
332                     
333             # Got our list of repo objects, add them to the repos
334             # collection
335             try:
336                 self._repos.add(thisrepo)
337             except Errors.RepoError, e:
338                 self.logger.warning(e)
339         
340     def getReposFromConfig(self):
341         """read in repositories from config main and .repo files"""
342
343         # Read .repo files from directories specified by the reposdir option
344         # (typically /etc/yum/repos.d)
345         repo_config_age = self.conf.config_file_age
346         
347         # Get the repos from the main yum.conf file
348         self.getReposFromConfigFile(self.conf.config_file_path, repo_config_age)
349
350         for reposdir in self.conf.reposdir:
351             # this check makes sure that our dirs exist properly.
352             # if they aren't in the installroot then don't prepent the installroot path
353             # if we don't do this then anaconda likes to not  work.
354             if os.path.exists(self.conf.installroot+'/'+reposdir):
355                 reposdir = self.conf.installroot + '/' + reposdir
356
357             if os.path.isdir(reposdir):
358                 for repofn in sorted(glob.glob('%s/*.repo' % reposdir)):
359                     thisrepo_age = os.stat(repofn)[8]
360                     if thisrepo_age < repo_config_age:
361                         thisrepo_age = repo_config_age
362                     self.getReposFromConfigFile(repofn, repo_age=thisrepo_age)
363
364     def readRepoConfig(self, parser, section):
365         '''Parse an INI file section for a repository.
366
367         @param parser: ConfParser or similar to read INI file values from.
368         @param section: INI file section to read.
369         @return: YumRepository instance.
370         '''
371         repo = yumRepo.YumRepository(section)
372         repo.populate(parser, section, self.conf)
373
374         # Ensure that the repo name is set
375         if not repo.name:
376             repo.name = section
377             self.logger.error(_('Repository %r is missing name in configuration, '
378                     'using id') % section)
379         repo.name = to_unicode(repo.name)
380
381         # Set attributes not from the config file
382         repo.basecachedir = self.conf.cachedir
383         repo.yumvar.update(self.conf.yumvar)
384         repo.cfg = parser
385
386         return repo
387
388     def disablePlugins(self):
389         '''Disable yum plugins
390         '''
391         self.plugins = plugins.DummyYumPlugins()
392     
393     def doPluginSetup(self, optparser=None, plugin_types=None, searchpath=None,
394             confpath=None,disabled_plugins=None,enabled_plugins=None):
395         '''Initialise and enable yum plugins. 
396
397         Note: _getConfig() will initialise plugins if instructed to. Only
398         call this method directly if not calling _getConfig() or calling
399         doConfigSetup(init_plugins=False).
400
401         @param optparser: The OptionParser instance for this run (optional)
402         @param plugin_types: A sequence specifying the types of plugins to load.
403             This should be sequnce containing one or more of the
404             yum.plugins.TYPE_...  constants. If None (the default), all plugins
405             will be loaded.
406         @param searchpath: A list of directories to look in for plugins. A
407             default will be used if no value is specified.
408         @param confpath: A list of directories to look in for plugin
409             configuration files. A default will be used if no value is
410             specified.
411         @param disabled_plugins: Plugins to be disabled    
412         @param enabled_plugins: Plugins to be enabled
413         '''
414         if isinstance(self.plugins, plugins.YumPlugins):
415             raise RuntimeError(_("plugins already initialised"))
416
417         self.plugins = plugins.YumPlugins(self, searchpath, optparser,
418                 plugin_types, confpath, disabled_plugins, enabled_plugins)
419
420     
421     def doRpmDBSetup(self):
422         warnings.warn(_('doRpmDBSetup() will go away in a future version of Yum.\n'),
423                 Errors.YumFutureDeprecationWarning, stacklevel=2)
424
425         return self._getRpmDB()
426     
427     def _getRpmDB(self):
428         """sets up a holder object for important information from the rpmdb"""
429
430         if self._rpmdb is None:
431             rpmdb_st = time.time()
432             self.verbose_logger.log(logginglevels.DEBUG_4,
433                                     _('Reading Local RPMDB'))
434             self._rpmdb = rpmsack.RPMDBPackageSack(root=self.conf.installroot)
435             self.verbose_logger.debug('rpmdb time: %0.3f' % (time.time() - rpmdb_st))
436         return self._rpmdb
437
438     def closeRpmDB(self):
439         """closes down the instances of the rpmdb we have wangling around"""
440         if self._rpmdb is not None:
441             self._rpmdb.ts = None
442             self._rpmdb.dropCachedData()
443         self._rpmdb = None
444         self._ts = None
445         self._tsInfo = None
446         self._up = None
447         self.comps = None
448     
449     def _deleteTs(self):
450         del self._ts
451         self._ts = None
452
453     def doRepoSetup(self, thisrepo=None):
454         warnings.warn(_('doRepoSetup() will go away in a future version of Yum.\n'),
455                 Errors.YumFutureDeprecationWarning, stacklevel=2)
456
457         return self._getRepos(thisrepo, True)
458
459     def _getRepos(self, thisrepo=None, doSetup = False):
460         """ For each enabled repository set up the basics of the repository. """
461         self.conf # touch the config class first
462
463         if doSetup:
464             repo_st = time.time()        
465             self._repos.doSetup(thisrepo)
466             self.verbose_logger.debug('repo time: %0.3f' % (time.time() - repo_st))        
467         return self._repos
468
469     def _delRepos(self):
470         del self._repos
471         self._repos = RepoStorage(self)
472     
473     def doSackSetup(self, archlist=None, thisrepo=None):
474         warnings.warn(_('doSackSetup() will go away in a future version of Yum.\n'),
475                 Errors.YumFutureDeprecationWarning, stacklevel=2)
476
477         return self._getSacks(archlist=archlist, thisrepo=thisrepo)
478         
479     def _getSacks(self, archlist=None, thisrepo=None):
480         """populates the package sacks for information from our repositories,
481            takes optional archlist for archs to include"""
482
483         # FIXME: Fist of death ... normally we'd do either:
484         #
485         # 1. use self._pkgSack is not None, and only init. once.
486         # 2. auto. correctly re-init each time a repo is added/removed
487         #
488         # ...we should probably just smeg it and do #2, but it's hard and will
489         # probably break something (but it'll "fix" excludes).
490         #  #1 can't be done atm. because we did self._pkgSack and external
491         # tools now rely on being able to create an empty sack and then have it
492         # auto. re-init when they add some stuff. So we add a bit more "clever"
493         # and don't setup the pkgSack to not be None when it's empty. This means
494         # we skip excludes/includes/etc. ... but there's no packages, so
495         # hopefully that's ok.
496         if self._pkgSack is not None and thisrepo is None:
497             return self._pkgSack
498         
499         if thisrepo is None:
500             repos = 'enabled'
501         else:
502             repos = self.repos.findRepos(thisrepo)
503         
504         self.verbose_logger.debug(_('Setting up Package Sacks'))
505         sack_st = time.time()
506         if not archlist:
507             archlist = self.arch.archlist
508         
509         archdict = {}
510         for arch in archlist:
511             archdict[arch] = 1
512         
513         self.repos.getPackageSack().setCompatArchs(archdict)
514         self.repos.populateSack(which=repos)
515         if not self.repos.getPackageSack():
516             return self.repos.getPackageSack() # ha ha, see above
517         self._pkgSack = self.repos.getPackageSack()
518         
519         self.excludePackages()
520         self._pkgSack.excludeArchs(archlist)
521         
522         #FIXME - this could be faster, too.
523         if repos == 'enabled':
524             repos = self.repos.listEnabled()
525         for repo in repos:
526             self.excludePackages(repo)
527             self.includePackages(repo)
528         self.plugins.run('exclude')
529         self._pkgSack.buildIndexes()
530
531         # now go through and kill pkgs based on pkg.repo.cost()
532         self.costExcludePackages()
533         self.verbose_logger.debug('pkgsack time: %0.3f' % (time.time() - sack_st))
534         return self._pkgSack
535     
536     
537     def _delSacks(self):
538         """reset the package sacks back to zero - making sure to nuke the ones
539            in the repo objects, too - where it matters"""
540            
541         # nuke the top layer
542         
543         self._pkgSack = None
544            
545         for repo in self.repos.repos.values():
546             if hasattr(repo, '_resetSack'):
547                 repo._resetSack()
548             else:
549                 warnings.warn(_('repo object for repo %s lacks a _resetSack method\n') +
550                         _('therefore this repo cannot be reset.\n'),
551                         Errors.YumFutureDeprecationWarning, stacklevel=2)
552             
553            
554     def doUpdateSetup(self):
555         warnings.warn(_('doUpdateSetup() will go away in a future version of Yum.\n'),
556                 Errors.YumFutureDeprecationWarning, stacklevel=2)
557
558         return self._getUpdates()
559         
560     def _getUpdates(self):
561         """setups up the update object in the base class and fills out the
562            updates, obsoletes and others lists"""
563         
564         if self._up:
565             return self._up
566         
567         self.verbose_logger.debug(_('Building updates object'))
568
569         up_st = time.time()
570
571         self._up = rpmUtils.updates.Updates(self.rpmdb.simplePkgList(), self.pkgSack.simplePkgList())
572         if self.conf.debuglevel >= 7:
573             self._up.debug = 1
574         
575         if self.conf.obsoletes:
576             obs_init = time.time()    
577             self._up.rawobsoletes = self.pkgSack.returnObsoletes()
578             self.verbose_logger.debug('up:Obs Init time: %0.3f' % (time.time() - obs_init))
579
580         self._up.myarch = self.arch.canonarch
581         self._up._is_multilib = self.arch.multilib
582         self._up._archlist = self.arch.archlist
583         self._up._multilib_compat_arches = self.arch.compatarches
584         self._up.exactarch = self.conf.exactarch
585         self._up.exactarchlist = self.conf.exactarchlist
586         up_pr_st = time.time()
587         self._up.doUpdates()
588         self.verbose_logger.debug('up:simple updates time: %0.3f' % (time.time() - up_pr_st))
589
590         if self.conf.obsoletes:
591             obs_st = time.time()
592             self._up.doObsoletes()
593             self.verbose_logger.debug('up:obs time: %0.3f' % (time.time() - obs_st))
594
595         cond_up_st = time.time()                    
596         self._up.condenseUpdates()
597         self.verbose_logger.debug('up:condense time: %0.3f' % (time.time() - cond_up_st))
598         self.verbose_logger.debug('updates time: %0.3f' % (time.time() - up_st))        
599         return self._up
600     
601     def doGroupSetup(self):
602         warnings.warn(_('doGroupSetup() will go away in a future version of Yum.\n'),
603                 Errors.YumFutureDeprecationWarning, stacklevel=2)
604
605         self.comps = None
606         return self._getGroups()
607
608     def _setGroups(self, val):
609         if val is None:
610             # if we unset the comps object, we need to undo which repos have
611             # been added to the group file as well
612             if self._repos:
613                 for repo in self._repos.listGroupsEnabled():
614                     repo.groups_added = False
615         self._comps = val
616     
617     def _getGroups(self):
618         """create the groups object that will store the comps metadata
619            finds the repos with groups, gets their comps data and merge it
620            into the group object"""
621         
622         if self._comps:
623             return self._comps
624
625         group_st = time.time()            
626         self.verbose_logger.log(logginglevels.DEBUG_4,
627                                 _('Getting group metadata'))
628         reposWithGroups = []
629         self.repos.doSetup()
630         for repo in self.repos.listGroupsEnabled():
631             if repo.groups_added: # already added the groups from this repo
632                 reposWithGroups.append(repo)
633                 continue
634                 
635             if not repo.ready():
636                 raise Errors.RepoError, "Repository '%s' not yet setup" % repo
637             try:
638                 groupremote = repo.getGroupLocation()
639             except Errors.RepoMDError, e:
640                 pass
641             else:
642                 reposWithGroups.append(repo)
643         
644         # now we know which repos actually have groups files.
645         overwrite = self.conf.overwrite_groups
646         self._comps = comps.Comps(overwrite_groups = overwrite)
647
648         for repo in reposWithGroups:
649             if repo.groups_added: # already added the groups from this repo
650                 continue
651                 
652             self.verbose_logger.log(logginglevels.DEBUG_4,
653                 _('Adding group file from repository: %s'), repo)
654             groupfile = repo.getGroups()
655             # open it up as a file object so iterparse can cope with our gz file
656             if groupfile is not None and groupfile.endswith('.gz'):
657                 groupfile = gzip.open(groupfile)
658                 
659             try:
660                 self._comps.add(groupfile)
661             except (Errors.GroupsError,Errors.CompsException), e:
662                 msg = _('Failed to add groups file for repository: %s - %s') % (repo, str(e))
663                 self.logger.critical(msg)
664             else:
665                 repo.groups_added = True
666
667         if self._comps.compscount == 0:
668             raise Errors.GroupsError, _('No Groups Available in any repository')
669
670         self._comps.compile(self.rpmdb.simplePkgList())
671         self.verbose_logger.debug('group time: %0.3f' % (time.time() - group_st))                
672         return self._comps
673     
674     # properties so they auto-create themselves with defaults
675     repos = property(fget=lambda self: self._getRepos(),
676                      fset=lambda self, value: setattr(self, "_repos", value),
677                      fdel=lambda self: self._delRepos())
678     pkgSack = property(fget=lambda self: self._getSacks(),
679                        fset=lambda self, value: setattr(self, "_pkgSack", value),
680                        fdel=lambda self: self._delSacks())
681     conf = property(fget=lambda self: self._getConfig(),
682                     fset=lambda self, value: setattr(self, "_conf", value),
683                     fdel=lambda self: setattr(self, "_conf", None))
684     rpmdb = property(fget=lambda self: self._getRpmDB(),
685                      fset=lambda self, value: setattr(self, "_rpmdb", value),
686                      fdel=lambda self: setattr(self, "_rpmdb", None))
687     tsInfo = property(fget=lambda self: self._getTsInfo(), 
688                       fset=lambda self,value: self._setTsInfo(value), 
689                       fdel=lambda self: self._delTsInfo())
690     ts = property(fget=lambda self: self._getActionTs(), fdel=lambda self: self._deleteTs())
691     up = property(fget=lambda self: self._getUpdates(),
692                   fset=lambda self, value: setattr(self, "_up", value),
693                   fdel=lambda self: setattr(self, "_up", None))
694     comps = property(fget=lambda self: self._getGroups(),
695                      fset=lambda self, value: self._setGroups(value),
696                      fdel=lambda self: setattr(self, "_comps", None))
697     
698     
699     def doSackFilelistPopulate(self):
700         """convenience function to populate the repos with the filelist metadata
701            it also is simply to only emit a log if anything actually gets populated"""
702         
703         necessary = False
704         
705         # I can't think of a nice way of doing this, we have to have the sack here
706         # first or the below does nothing so...
707         if self.pkgSack:
708             for repo in self.repos.listEnabled():
709                 if repo in repo.sack.added:
710                     if 'filelists' in repo.sack.added[repo]:
711                         continue
712                     else:
713                         necessary = True
714                 else:
715                     necessary = True
716
717         if necessary:
718             msg = _('Importing additional filelist information')
719             self.verbose_logger.log(logginglevels.INFO_2, msg)
720             self.repos.populateSack(mdtype='filelists')
721            
722     def yumUtilsMsg(self, func, prog):
723         """ Output a message that the tool requires the yum-utils package,
724             if not installed. """
725         if self.rpmdb.contains(name="yum-utils"):
726             return
727
728         hibeg, hiend = "", ""
729         if hasattr(self, 'term'):
730             hibeg, hiend = self.term.MODE['bold'], self.term.MODE['normal']
731
732         func(_("The program %s%s%s is found in the yum-utils package.") %
733              (hibeg, prog, hiend))
734
735     def buildTransaction(self, unfinished_transactions_check=True):
736         """go through the packages in the transaction set, find them in the
737            packageSack or rpmdb, and pack up the ts accordingly"""
738         if (unfinished_transactions_check and
739             misc.find_unfinished_transactions(yumlibpath=self.conf.persistdir)):
740             msg = _('There are unfinished transactions remaining. You might ' \
741                     'consider running yum-complete-transaction first to finish them.' )
742             self.logger.critical(msg)
743             self.yumUtilsMsg(self.logger.critical, "yum-complete-transaction")
744             time.sleep(3)
745
746         self.plugins.run('preresolve')
747         ds_st = time.time()
748
749         (rescode, restring) = self.resolveDeps()
750         self._limit_installonly_pkgs()
751         
752         #  We _must_ get rid of all the used tses before we go on, so that C-c
753         # works for downloads / mirror failover etc.
754         self.rpmdb.ts = None
755
756         # if depsolve failed and skipbroken is enabled
757         # The remove the broken packages from the transactions and
758         # Try another depsolve
759         if self.conf.skip_broken and rescode==1:
760             self.skipped_packages = []    # reset the public list of skipped packages.
761             sb_st = time.time()
762             rescode, restring = self._skipPackagesWithProblems(rescode, restring)
763             self._printTransaction()        
764             self.verbose_logger.debug('Skip-Broken time: %0.3f' % (time.time() - sb_st))
765
766         self.plugins.run('postresolve', rescode=rescode, restring=restring)
767
768         if self.tsInfo.changed:
769             (rescode, restring) = self.resolveDeps(rescode == 1)
770         if self.tsInfo.pkgSack is not None: # rm Transactions don't have pkgSack
771             self.tsInfo.pkgSack.dropCachedData()
772         self.rpmdb.dropCachedData()
773
774         self.verbose_logger.debug('Depsolve time: %0.3f' % (time.time() - ds_st))
775         return rescode, restring
776
777     def _skipPackagesWithProblems(self, rescode, restring):
778         ''' Remove the packages with depsolve errors and depsolve again '''
779
780         def _remove(po, depTree, toRemove):
781             if not po:
782                 return
783             self._getPackagesToRemove(po, depTree, toRemove)
784             # Only remove non installed packages from pkgSack
785             _remove_from_sack(po)
786
787         def _remove_from_sack(po):
788             # get all compatible arch packages from pkgSack
789             # we need to remove them to so a i386 paqckages is not 
790             # dragged in when a x86_64 is skipped.
791             pkgs = self._getPackagesToRemoveAllArch(po)
792             for pkg in pkgs:
793                 if not po.repoid == 'installed' and pkg not in removed_from_sack:             
794                     self.verbose_logger.debug('SKIPBROKEN: removing %s from pkgSack & updates' % str(po))
795                     self.pkgSack.delPackage(pkg)
796                     self.up.delPackage(pkg.pkgtup)
797                     removed_from_sack.add(pkg)
798
799         # Keep removing packages & Depsolve until all errors is gone
800         # or the transaction is empty
801         count = 0
802         skipped_po = set()
803         removed_from_sack = set()
804         orig_restring = restring    # Keep the old error messages 
805         looping = 0 
806         while (len(self.po_with_problems) > 0 and rescode == 1):
807             count += 1
808             self.verbose_logger.debug(_("Skip-broken round %i"), count)
809             self._printTransaction()        
810             depTree = self._buildDepTree()
811             startTs = set(self.tsInfo)
812             toRemove = set()
813             for po,wpo,err in self.po_with_problems:
814                 # check if the problem is caused by a package in the transaction
815                 if not self.tsInfo.exists(po.pkgtup):
816                     _remove(wpo, depTree, toRemove)
817                 else:
818                     _remove(po,  depTree, toRemove)
819             for po in toRemove:
820                 skipped = self._skipFromTransaction(po)
821                 for skip in skipped:
822                     skipped_po.add(skip)
823                     # make sure we get the compat arch packages skip from pkgSack and up too.
824                     if skip not in removed_from_sack and skip.repoid == 'installed':
825                         _remove_from_sack(skip)
826             # Nothing was removed, so we still got a problem
827              # the first time we get here we reset the resolved members of
828              # tsInfo and takes a new run all members in the current transaction
829             if not toRemove: 
830                 looping += 1
831                 if looping > 2:
832                     break # Bail out
833                 else:
834                     self.verbose_logger.debug('SKIPBROKEN: resetting already resovled packages (no packages to skip)' )
835                     self.tsInfo.resetResolved(hard=True)
836             rescode, restring = self.resolveDeps(True)
837             endTs = set(self.tsInfo)
838              # Check if tsInfo has changes since we started to skip packages
839              # if there is no changes then we got a loop.
840              # the first time we get here we reset the resolved members of
841              # tsInfo and takes a new run all members in the current transaction
842             if startTs-endTs == set():
843                 looping += 1
844                 if looping > 2:
845                     break # Bail out
846                 else:
847                     self.verbose_logger.debug('SKIPBROKEN: resetting already resovled packages (transaction not changed)' )
848                     self.tsInfo.resetResolved(hard=True)
849                     
850             # if we are alel clear, then we have to check that the whole current transaction 
851             # can complete the depsolve without error, because the packages skipped
852             # can have broken something that passed the tests earliere.
853             # FIXME: We need do this in a better way.
854             if rescode != 1:
855                 self.verbose_logger.debug('SKIPBROKEN: sanity check the current transaction' )
856                 self.tsInfo.resetResolved(hard=True)
857                 self._checkMissingObsoleted() # This is totally insane, but needed :(
858                 rescode, restring = self.resolveDeps()
859         if rescode != 1:
860             self.verbose_logger.debug(_("Skip-broken took %i rounds "), count)
861             self.verbose_logger.info(_('\nPackages skipped because of dependency problems:'))
862             skipped_list = [p for p in skipped_po]
863             skipped_list.sort()
864             for po in skipped_list:
865                 msg = _("    %s from %s") % (str(po),po.repo.id)
866                 self.verbose_logger.info(msg)
867             self.skipped_packages = skipped_list    # make the skipped packages public
868         else:
869             # If we cant solve the problems the show the original error messages.
870             self.verbose_logger.info("Skip-broken could not solve problems")
871             return 1, orig_restring
872         return rescode, restring
873
874     def _checkMissingObsoleted(self):
875         """ 
876         If multiple packages is obsoleting the same package
877         then the TS_OBSOLETED can get removed from the transaction
878         so we must make sure that they, exist and else create them
879         """
880         for txmbr in self.tsInfo:
881             for pkg in txmbr.obsoletes:
882                 if not self.tsInfo.exists(pkg.pkgtup):
883                     obs = self.tsInfo.addObsoleted(pkg,txmbr.po)
884                     self.verbose_logger.debug('SKIPBROKEN: Added missing obsoleted %s (%s)' % (pkg,txmbr.po) )
885             for pkg in txmbr.obsoleted_by:
886                 # check if the obsoleting txmbr is in the transaction
887                 # else remove the obsoleted txmbr
888                 # it clean out some really wierd cases
889                 if not self.tsInfo.exists(pkg.pkgtup):
890                     self.verbose_logger.debug('SKIPBROKEN: Remove extra obsoleted %s (%s)' % (txmbr.po,pkg) )
891                     self.tsInfo.remove(txmbr.po.pkgtup)
892
893     def _getPackagesToRemoveAllArch(self,po):
894         ''' get all compatible arch packages in pkgSack'''
895         pkgs = []
896         if self.arch.multilib:
897             n,a,e,v,r = po.pkgtup
898             # skip for all compat archs
899             for a in self.arch.archlist:
900                 pkgtup = (n,a,e,v,r)
901                 matched = self.pkgSack.searchNevra(n,e,v,r,a) 
902                 pkgs.extend(matched)
903         else:
904             pkgs.append(po)
905         return pkgs   
906         
907                 
908                 
909         
910
911     def _skipFromTransaction(self,po):
912         skipped =  []
913         n,a,e,v,r = po.pkgtup
914         # skip for all compat archs
915         for a in self.arch.archlist:
916             pkgtup = (n,a,e,v,r)
917             if self.tsInfo.exists(pkgtup):
918                 for txmbr in self.tsInfo.getMembers(pkgtup):
919                     pkg = txmbr.po
920                     skip = self._removePoFromTransaction(pkg)
921                     skipped.extend(skip)
922         return skipped
923
924     def _removePoFromTransaction(self,po):
925         skip =  []
926         if self.tsInfo.exists(po.pkgtup):
927             self.verbose_logger.debug('SKIPBROKEN: removing %s from transaction' % str(po))
928             self.tsInfo.remove(po.pkgtup)
929             if not po.repoid == 'installed':
930                 skip.append(po)
931         return skip 
932               
933     def _buildDepTree(self):
934         ''' create a dictionary with po and deps '''
935         depTree = { }
936         for txmbr in self.tsInfo:
937             for dep in txmbr.depends_on:
938                 depTree.setdefault(dep, []).append(txmbr.po)
939         # self._printDepTree(depTree)
940         return depTree
941
942     def _printDepTree(self, tree):
943         for pkg, l in tree.iteritems():
944             print pkg
945             for p in l:
946                 print "\t", p
947
948     def _printTransaction(self):
949         #transaction set states
950         state = { TS_UPDATE     : "update",
951                   TS_INSTALL    : "install",
952                   TS_TRUEINSTALL: "trueinstall",
953                   TS_ERASE      : "erase",
954                   TS_OBSOLETED  : "obsoleted",
955                   TS_OBSOLETING : "obsoleting",
956                   TS_AVAILABLE  : "available",
957                   TS_UPDATED    : "updated"}
958
959         self.verbose_logger.log(logginglevels.DEBUG_2,"TSINFO: Current Transaction : %i member(s) " % len(self.tsInfo))
960         for txmbr in self.tsInfo:
961             msg = "  %-11s : %s " % (state[txmbr.output_state],txmbr.po)
962             self.verbose_logger.log(logginglevels.DEBUG_2, msg)
963             for po,rel in txmbr.relatedto:
964                 msg = "                   %s : %s" % (rel,po)
965                 self.verbose_logger.log(logginglevels.DEBUG_2, msg)
966                 
967                                     
968     def _getPackagesToRemove(self,po,deptree,toRemove):
969         '''
970         get the (related) pos to remove.
971         '''
972         toRemove.add(po)
973         for txmbr in self.tsInfo.getMembers(po.pkgtup):
974             for pkg in (txmbr.updates + txmbr.obsoletes):
975                 toRemove.add(pkg)
976                 self._getDepsToRemove(pkg, deptree, toRemove)
977         self._getDepsToRemove(po, deptree, toRemove)
978
979     def _getDepsToRemove(self,po, deptree, toRemove):
980         for dep in deptree.get(po, []): # Loop trough all deps of po
981             for txmbr in self.tsInfo.getMembers(dep.pkgtup):
982                 for pkg in (txmbr.updates + txmbr.obsoletes):
983                     toRemove.add(pkg)
984             toRemove.add(dep)
985             self._getDepsToRemove(dep, deptree, toRemove)
986
987     def runTransaction(self, cb):
988         """takes an rpm callback object, performs the transaction"""
989
990         self.plugins.run('pretrans')
991
992         errors = self.ts.run(cb.callback, '')
993         # ts.run() exit codes are, hmm, "creative": None means all ok, empty 
994         # list means some errors happened in the transaction and non-empty 
995         # list that there were errors preventing the ts from starting...
996         
997         # make resultobject - just a plain yumgenericholder object
998         resultobject = misc.GenericHolder()
999         resultobject.return_code = 0
1000         if errors is None:
1001             pass
1002         elif len(errors) == 0:
1003             errstring = _('Warning: scriptlet or other non-fatal errors occurred during transaction.')
1004             self.verbose_logger.debug(errstring)
1005             resultobject.return_code = 1
1006         else:
1007             raise Errors.YumBaseError, errors
1008                           
1009         if not self.conf.keepcache:
1010             self.cleanUsedHeadersPackages()
1011         
1012         for i in ('ts_all_fn', 'ts_done_fn'):
1013             if hasattr(cb, i):
1014                 fn = getattr(cb, i)
1015                 try:
1016                     misc.unlink_f(fn)
1017                 except (IOError, OSError), e:
1018                     self.logger.critical(_('Failed to remove transaction file %s') % fn)
1019
1020         self.rpmdb.dropCachedData() # drop out the rpm cache so we don't step on bad hdr indexes
1021         self.plugins.run('posttrans')
1022         # sync up what just happened versus what is in the rpmdb
1023         self.verifyTransaction()
1024         return resultobject
1025
1026     def verifyTransaction(self):
1027         """checks that the transaction did what we expected it to do. Also 
1028            propagates our external yumdb info"""
1029         
1030         # check to see that the rpmdb and the tsInfo roughly matches
1031         # push package object metadata outside of rpmdb into yumdb
1032         # delete old yumdb metadata entries
1033         
1034         # for each pkg in the tsInfo
1035         # if it is an install - see that the pkg is installed
1036         # if it is a remove - see that the pkg is no longer installed, provided
1037         #    that there is not also an install of this pkg in the tsInfo (reinstall)
1038         # for any kind of install add from_repo to the yumdb, and the cmdline
1039         # and the install reason
1040
1041         self.rpmdb.dropCachedData()
1042         for txmbr in self.tsInfo:
1043             if txmbr.output_state in TS_INSTALL_STATES:
1044                 if not self.rpmdb.contains(po=txmbr.po):
1045                     # maybe a file log here, too
1046                     # but raising an exception is not going to do any good
1047                     self.logger.critical(_('%s was supposed to be installed' \
1048                                            ' but is not!' % txmbr.po))
1049                     continue
1050                 po = self.rpmdb.searchPkgTuple(txmbr.pkgtup)[0]
1051                 rpo = txmbr.po
1052                 po.yumdb_info.from_repo = rpo.repoid
1053                 po.yumdb_info.reason = txmbr.reason
1054                 po.yumdb_info.releasever = self.yumvar['releasever']
1055                 if hasattr(self, 'cmds') and self.cmds:
1056                     po.yumdb_info.command_line = ' '.join(self.cmds)
1057                 csum = rpo.returnIdSum()
1058                 if csum is not None:
1059                     po.yumdb_info.checksum_type = str(csum[0])
1060                     po.yumdb_info.checksum_data = str(csum[1])
1061
1062                 if isinstance(rpo, YumLocalPackage):
1063                     try:
1064                         st = os.stat(rpo.localPkg())
1065                         lp_ctime = str(int(st.st_ctime))
1066                         lp_mtime = str(int(st.st_mtime))
1067                         po.yumdb_info.from_repo_revision  = lp_ctime
1068                         po.yumdb_info.from_repo_timestamp = lp_mtime
1069                     except: pass
1070
1071                 if not hasattr(rpo.repo, 'repoXML'):
1072                     continue
1073
1074                 md = rpo.repo.repoXML
1075                 if md and md.revision is not None:
1076                     po.yumdb_info.from_repo_revision  = str(md.revision)
1077                 if md:
1078                     po.yumdb_info.from_repo_timestamp = str(md.timestamp)
1079             
1080             elif txmbr.output_state in TS_REMOVE_STATES:
1081                 if self.rpmdb.contains(po=txmbr.po):
1082                     if not self.tsInfo.getMembersWithState(pkgtup=txmbr.pkgtup,
1083                                 output_states=TS_INSTALL_STATES):
1084                         # maybe a file log here, too
1085                         # but raising an exception is not going to do any good
1086                         self.logger.critical(_('%s was supposed to be removed' \
1087                                                ' but is not!' % txmbr.po))
1088                         continue
1089                 yumdb_item = self.rpmdb.yumdb.get_package(po=txmbr.po)
1090                 yumdb_item.clean()
1091             else:
1092                 self.verbose_logger.log(logginglevels.DEBUG_2, 'What is this? %s' % txmbr.po)
1093
1094         self.rpmdb.dropCachedData()
1095
1096     def costExcludePackages(self):
1097         """exclude packages if they have an identical package in another repo
1098         and their repo.cost value is the greater one"""
1099         
1100         # check to see if the cost per repo is anything other than equal
1101         # if all the repo.costs are equal then don't bother running things
1102         costs = {}
1103         for r in self.repos.listEnabled():
1104             costs[r.cost] = 1
1105
1106         if len(costs) <= 1: # if all of our costs are the same then return
1107             return
1108             
1109         def _sort_by_cost(a, b):
1110             if a.repo.cost < b.repo.cost:
1111                 return -1
1112             if a.repo.cost == b.repo.cost:
1113                 return 0
1114             if a.repo.cost > b.repo.cost:
1115                 return 1
1116                 
1117         pkgdict = {}
1118         for po in self.pkgSack:
1119             if not pkgdict.has_key(po.pkgtup):
1120                 pkgdict[po.pkgtup] = []
1121             pkgdict[po.pkgtup].append(po)
1122         
1123         for pkgs in pkgdict.values():
1124             if len(pkgs) == 1:
1125                 continue
1126                 
1127             pkgs.sort(_sort_by_cost)
1128             lowcost = pkgs[0].repo.cost
1129             #print '%s : %s : %s' % (pkgs[0], pkgs[0].repo, pkgs[0].repo.cost)
1130             for pkg in pkgs[1:]:
1131                 if pkg.repo.cost > lowcost:
1132                     msg = _('excluding for cost: %s from %s') % (pkg, pkg.repo.id)
1133                     self.verbose_logger.log(logginglevels.DEBUG_3, msg)
1134                     pkg.repo.sack.delPackage(pkg)
1135             
1136
1137     def excludePackages(self, repo=None):
1138         """removes packages from packageSacks based on global exclude lists,
1139            command line excludes and per-repository excludes, takes optional 
1140            repo object to use."""
1141
1142         if "all" in self.conf.disable_excludes:
1143             return
1144         
1145         # if not repo: then assume global excludes, only
1146         # if repo: then do only that repos' packages and excludes
1147         
1148         if not repo: # global only
1149             if "main" in self.conf.disable_excludes:
1150                 return
1151             excludelist = self.conf.exclude
1152             repoid = None
1153         else:
1154             if repo.id in self.conf.disable_excludes:
1155                 return
1156             excludelist = repo.getExcludePkgList()
1157             repoid = repo.id
1158
1159         for match in excludelist:
1160             self.pkgSack.addPackageExcluder(repoid, 'exclude.match', match)
1161
1162     def includePackages(self, repo):
1163         """removes packages from packageSacks based on list of packages, to include.
1164            takes repoid as a mandatory argument."""
1165         
1166         includelist = repo.getIncludePkgList()
1167         
1168         if len(includelist) == 0:
1169             return
1170         
1171         for match in includelist:
1172             self.pkgSack.addPackageExcluder(repo.id, 'include.match', match)
1173         self.pkgSack.addPackageExcluder(repo.id, 'exclude.*')
1174         
1175     def doLock(self, lockfile = YUM_PID_FILE):
1176         """perform the yum locking, raise yum-based exceptions, not OSErrors"""
1177         
1178         # if we're not root then we don't lock - just return nicely
1179         if self.conf.uid != 0:
1180             return
1181             
1182         root = self.conf.installroot
1183         lockfile = root + '/' + lockfile # lock in the chroot
1184         lockfile = os.path.normpath(lockfile) # get rid of silly preceding extra /
1185         
1186         mypid=str(os.getpid())    
1187         while not self._lock(lockfile, mypid, 0644):
1188             fd = open(lockfile, 'r')
1189             try: oldpid = int(fd.readline())
1190             except ValueError:
1191                 # bogus data in the pid file. Throw away.
1192                 self._unlock(lockfile)
1193             else:
1194                 if oldpid == os.getpid(): # if we own the lock, we're fine
1195                     break
1196                 try: os.kill(oldpid, 0)
1197                 except OSError, e:
1198                     if e[0] == errno.ESRCH:
1199                         # The pid doesn't exist
1200                         self._unlock(lockfile)
1201                     else:
1202                         # Whoa. What the heck happened?
1203                         msg = _('Unable to check if PID %s is active') % oldpid
1204                         raise Errors.LockError(1, msg, oldpid)
1205                 else:
1206                     # Another copy seems to be running.
1207                     msg = _('Existing lock %s: another copy is running as pid %s.') % (lockfile, oldpid)
1208                     raise Errors.LockError(0, msg, oldpid)
1209         # We've got the lock, store it so we can auto-unlock on __del__...
1210         self._lockfile = lockfile
1211     
1212     def doUnlock(self, lockfile=None):
1213         """do the unlock for yum"""
1214         
1215         # if we're not root then we don't lock - just return nicely
1216         #  Note that we can get here from __del__, so if we haven't created
1217         # YumBase.conf we don't want to do so here as creating stuff inside
1218         # __del__ is bad.
1219         if hasattr(self, 'preconf') or self.conf.uid != 0:
1220             return
1221         
1222         if lockfile is not None:
1223             root = self.conf.installroot
1224             lockfile = root + '/' + lockfile # lock in the chroot
1225         elif self._lockfile is None:
1226             return # Don't delete other people's lock files on __del__
1227         else:
1228             lockfile = self._lockfile # Get the value we locked with
1229         
1230         self._unlock(lockfile)
1231         self._lockfile = None
1232         
1233     def _lock(self, filename, contents='', mode=0777):
1234         lockdir = os.path.dirname(filename)
1235         try:
1236             if not os.path.exists(lockdir):
1237                 os.makedirs(lockdir, mode=0755)
1238             fd = os.open(filename, os.O_EXCL|os.O_CREAT|os.O_WRONLY, mode)    
1239         except OSError, msg:
1240             if not msg.errno == errno.EEXIST: raise msg
1241             return 0
1242         else:
1243             os.write(fd, contents)
1244             os.close(fd)
1245             return 1
1246     
1247     def _unlock(self, filename):
1248         misc.unlink_f(filename)
1249
1250     def verifyPkg(self, fo, po, raiseError):
1251         """verifies the package is what we expect it to be
1252            raiseError  = defaults to 0 - if 1 then will raise
1253            a URLGrabError if the file does not check out.
1254            otherwise it returns false for a failure, true for success"""
1255         failed = False
1256
1257         if type(fo) is types.InstanceType:
1258             fo = fo.filename
1259         
1260         if fo != po.localPkg():
1261             po.localpath = fo
1262
1263         if not po.verifyLocalPkg():
1264             failed = True
1265         else:
1266             ylp = YumLocalPackage(self.rpmdb.readOnlyTS(), fo)
1267             if ylp.pkgtup != po.pkgtup:
1268                 failed = True
1269
1270
1271         if failed:            
1272             # if the file is wrong AND it is >= what we expected then it
1273             # can't be redeemed. If we can, kill it and start over fresh
1274             cursize = os.stat(fo)[6]
1275             totsize = long(po.size)
1276             if cursize >= totsize and not po.repo.cache:
1277                 # if the path to the file is NOT inside the cachedir then don't
1278                 # unlink it b/c it is probably a file:// url and possibly
1279                 # unlinkable
1280                 if fo.startswith(po.repo.cachedir):
1281                     os.unlink(fo)
1282
1283             if raiseError:
1284                 raise URLGrabError(-1, _('Package does not match intended download'))
1285             else:
1286                 return False
1287
1288         
1289         return True
1290         
1291         
1292     def verifyChecksum(self, fo, checksumType, csum):
1293         """Verify the checksum of the file versus the 
1294            provided checksum"""
1295
1296         try:
1297             filesum = misc.checksum(checksumType, fo)
1298         except Errors.MiscError, e:
1299             raise URLGrabError(-3, _('Could not perform checksum'))
1300             
1301         if filesum != csum:
1302             raise URLGrabError(-1, _('Package does not match checksum'))
1303         
1304         return 0
1305
1306     def downloadPkgs(self, pkglist, callback=None, callback_total=None):
1307         def mediasort(apo, bpo):
1308             # FIXME: we should probably also use the mediaid; else we
1309             # could conceivably ping-pong between different disc1's
1310             a = apo.getDiscNum()
1311             b = bpo.getDiscNum()
1312             if a is None and b is None:
1313                 return cmp(apo, bpo)
1314             if a is None:
1315                 return -1
1316             if b is None:
1317                 return 1
1318             if a < b:
1319                 return -1
1320             elif a > b:
1321                 return 1
1322             return 0
1323         
1324         """download list of package objects handed to you, output based on
1325            callback, raise yum.Errors.YumBaseError on problems"""
1326
1327         errors = {}
1328         def adderror(po, msg):
1329             errors.setdefault(po, []).append(msg)
1330
1331         self.plugins.run('predownload', pkglist=pkglist)
1332         repo_cached = False
1333         remote_pkgs = []
1334         remote_size = 0
1335         for po in pkglist:
1336             if hasattr(po, 'pkgtype') and po.pkgtype == 'local':
1337                 continue
1338                     
1339             local = po.localPkg()
1340             if os.path.exists(local):
1341                 if not self.verifyPkg(local, po, False):
1342                     if po.repo.cache:
1343                         repo_cached = True
1344                         adderror(po, _('package fails checksum but caching is '
1345                             'enabled for %s') % po.repo.id)
1346                 else:
1347                     self.verbose_logger.debug(_("using local copy of %s") %(po,))
1348                     continue
1349                         
1350             remote_pkgs.append(po)
1351             remote_size += po.size
1352             
1353             # caching is enabled and the package 
1354             # just failed to check out there's no 
1355             # way to save this, report the error and return
1356             if (self.conf.cache or repo_cached) and errors:
1357                 return errors
1358                 
1359
1360         remote_pkgs.sort(mediasort)
1361         #  This is kind of a hack and does nothing in non-Fedora versions,
1362         # we'll fix it one way or anther soon.
1363         if (hasattr(urlgrabber.progress, 'text_meter_total_size') and
1364             len(remote_pkgs) > 1):
1365             urlgrabber.progress.text_meter_total_size(remote_size)
1366         beg_download = time.time()
1367         i = 0
1368         local_size = 0
1369         for po in remote_pkgs:
1370             #  Recheck if the file is there, works around a couple of weird
1371             # edge cases.
1372             local = po.localPkg()
1373             i += 1
1374             if os.path.exists(local):
1375                 if self.verifyPkg(local, po, False):
1376                     self.verbose_logger.debug(_("using local copy of %s") %(po,))
1377                     remote_size -= po.size
1378                     if hasattr(urlgrabber.progress, 'text_meter_total_size'):
1379                         urlgrabber.progress.text_meter_total_size(remote_size,
1380                                                                   local_size)
1381                     continue
1382                 if os.path.getsize(local) >= po.size:
1383                     os.unlink(local)
1384
1385             checkfunc = (self.verifyPkg, (po, 1), {})
1386             dirstat = os.statvfs(po.repo.pkgdir)
1387             if (dirstat.f_bavail * dirstat.f_bsize) <= long(po.size):
1388                 adderror(po, _('Insufficient space in download directory %s\n'
1389                         "    * free   %s\n"
1390                         "    * needed %s") %
1391                          (po.repo.pkgdir,
1392                           format_number(dirstat.f_bavail * dirstat.f_bsize),
1393                           format_number(po.size)))
1394                 continue
1395             
1396             try:
1397                 if i == 1 and not local_size and remote_size == po.size:
1398                     text = os.path.basename(po.relativepath)
1399                 else:
1400                     text = '(%s/%s): %s' % (i, len(remote_pkgs),
1401                                             os.path.basename(po.relativepath))
1402                 mylocal = po.repo.getPackage(po,
1403                                    checkfunc=checkfunc,
1404                                    text=text,
1405                                    cache=po.repo.http_caching != 'none',
1406                                    )
1407                 local_size += po.size
1408                 if hasattr(urlgrabber.progress, 'text_meter_total_size'):
1409                     urlgrabber.progress.text_meter_total_size(remote_size,
1410                                                               local_size)
1411             except Errors.RepoError, e:
1412                 adderror(po, str(e))
1413             else:
1414                 po.localpath = mylocal
1415                 if errors.has_key(po):
1416                     del errors[po]
1417
1418         if hasattr(urlgrabber.progress, 'text_meter_total_size'):
1419             urlgrabber.progress.text_meter_total_size(0)
1420         if callback_total is not None and not errors:
1421             callback_total(remote_pkgs, remote_size, beg_download)
1422
1423         self.plugins.run('postdownload', pkglist=pkglist, errors=errors)
1424
1425         return errors
1426
1427     def verifyHeader(self, fo, po, raiseError):
1428         """check the header out via it's naevr, internally"""
1429         if type(fo) is types.InstanceType:
1430             fo = fo.filename
1431             
1432         try:
1433             hlist = rpm.readHeaderListFromFile(fo)
1434             hdr = hlist[0]
1435         except (rpm.error, IndexError):
1436             if raiseError:
1437                 raise URLGrabError(-1, _('Header is not complete.'))
1438             else:
1439                 return 0
1440                 
1441         yip = YumInstalledPackage(hdr) # we're using YumInstalledPackage b/c
1442                                        # it takes headers <shrug>
1443         if yip.pkgtup != po.pkgtup:
1444             if raiseError:
1445                 raise URLGrabError(-1, 'Header does not match intended download')
1446             else:
1447                 return 0
1448         
1449         return 1
1450         
1451     def downloadHeader(self, po):
1452         """download a header from a package object.
1453            output based on callback, raise yum.Errors.YumBaseError on problems"""
1454
1455         if hasattr(po, 'pkgtype') and po.pkgtype == 'local':
1456             return
1457                 
1458         errors = {}
1459         local =  po.localHdr()
1460         repo = self.repos.getRepo(po.repoid)
1461         if os.path.exists(local):
1462             try:
1463                 result = self.verifyHeader(local, po, raiseError=1)
1464             except URLGrabError, e:
1465                 # might add a check for length of file - if it is < 
1466                 # required doing a reget
1467                 misc.unlink_f(local)
1468             else:
1469                 po.hdrpath = local
1470                 return
1471         else:
1472             if self.conf.cache:
1473                 raise Errors.RepoError, \
1474                 _('Header not in local cache and caching-only mode enabled. Cannot download %s') % po.hdrpath
1475         
1476         if self.dsCallback: self.dsCallback.downloadHeader(po.name)
1477         
1478         try:
1479             if not os.path.exists(repo.hdrdir):
1480                 os.makedirs(repo.hdrdir)
1481             checkfunc = (self.verifyHeader, (po, 1), {})
1482             hdrpath = repo.getHeader(po, checkfunc=checkfunc,
1483                     cache=repo.http_caching != 'none',
1484                     )
1485         except Errors.RepoError, e:
1486             saved_repo_error = e
1487             try:
1488                 misc.unlink_f(local)
1489             except OSError, e:
1490                 raise Errors.RepoError, saved_repo_error
1491             else:
1492                 raise Errors.RepoError, saved_repo_error
1493         else:
1494             po.hdrpath = hdrpath
1495             return
1496
1497     def sigCheckPkg(self, po):
1498         '''
1499         Take a package object and attempt to verify GPG signature if required
1500
1501         Returns (result, error_string) where result is:
1502             - 0 - GPG signature verifies ok or verification is not required.
1503             - 1 - GPG verification failed but installation of the right GPG key
1504                   might help.
1505             - 2 - Fatal GPG verifcation error, give up.
1506         '''
1507         if hasattr(po, 'pkgtype') and po.pkgtype == 'local':
1508             check = self.conf.gpgcheck
1509             hasgpgkey = 0
1510         else:
1511             repo = self.repos.getRepo(po.repoid)
1512             check = repo.gpgcheck
1513             hasgpgkey = not not repo.gpgkey 
1514         
1515         if check:
1516             ts = self.rpmdb.readOnlyTS()
1517             sigresult = rpmUtils.miscutils.checkSig(ts, po.localPkg())
1518             localfn = os.path.basename(po.localPkg())
1519             
1520             if sigresult == 0:
1521                 result = 0
1522                 msg = ''
1523
1524             elif sigresult == 1:
1525                 if hasgpgkey:
1526                     result = 1
1527                 else:
1528                     result = 2
1529                 msg = _('Public key for %s is not installed') % localfn
1530
1531             elif sigresult == 2:
1532                 result = 2
1533                 msg = _('Problem opening package %s') % localfn
1534
1535             elif sigresult == 3:
1536                 if hasgpgkey:
1537                     result = 1
1538                 else:
1539                     result = 2
1540                 result = 1
1541                 msg = _('Public key for %s is not trusted') % localfn
1542
1543             elif sigresult == 4:
1544                 result = 2 
1545                 msg = _('Package %s is not signed') % localfn
1546             
1547         else:
1548             result =0
1549             msg = ''
1550
1551         return result, msg
1552
1553     def cleanUsedHeadersPackages(self):
1554         filelist = []
1555         for txmbr in self.tsInfo:
1556             if txmbr.po.state not in TS_INSTALL_STATES:
1557                 continue
1558             if txmbr.po.repoid == "installed":
1559                 continue
1560             if not self.repos.repos.has_key(txmbr.po.repoid):
1561                 continue
1562             
1563             # make sure it's not a local file
1564             repo = self.repos.repos[txmbr.po.repoid]
1565             local = False
1566             for u in repo.baseurl:
1567                 if u.startswith("file:"):
1568                     local = True
1569                     break
1570                 
1571             if local:
1572                 filelist.extend([txmbr.po.localHdr()])
1573             else:
1574                 filelist.extend([txmbr.po.localPkg(), txmbr.po.localHdr()])
1575
1576         # now remove them
1577         for fn in filelist:
1578             if not os.path.exists(fn):
1579                 continue
1580             try:
1581                 misc.unlink_f(fn)
1582             except OSError, e:
1583                 self.logger.warning(_('Cannot remove %s'), fn)
1584                 continue
1585             else:
1586                 self.verbose_logger.log(logginglevels.DEBUG_4,
1587                     _('%s removed'), fn)
1588         
1589     def cleanHeaders(self):
1590         exts = ['hdr']
1591         return self._cleanFiles(exts, 'hdrdir', 'header')
1592
1593     def cleanPackages(self):
1594         exts = ['rpm']
1595         return self._cleanFiles(exts, 'pkgdir', 'package')
1596
1597     def cleanSqlite(self):
1598         exts = ['sqlite', 'sqlite.bz2']
1599         return self._cleanFiles(exts, 'cachedir', 'sqlite')
1600
1601     def cleanMetadata(self):
1602         exts = ['xml.gz', 'xml', 'cachecookie', 'mirrorlist.txt', 'asc']
1603         # Metalink is also here, but is a *.xml file
1604         return self._cleanFiles(exts, 'cachedir', 'metadata') 
1605
1606     def cleanExpireCache(self):
1607         exts = ['cachecookie', 'mirrorlist.txt']
1608         return self._cleanFiles(exts, 'cachedir', 'metadata')
1609
1610     def _cleanFiles(self, exts, pathattr, filetype):
1611         filelist = []
1612         removed = 0
1613         for ext in exts:
1614             for repo in self.repos.listEnabled():
1615                 path = getattr(repo, pathattr)
1616                 if os.path.exists(path) and os.path.isdir(path):
1617                     filelist = misc.getFileList(path, ext, filelist)
1618
1619         for item in filelist:
1620             try:
1621                 misc.unlink_f(item)
1622             except OSError, e:
1623                 self.logger.critical(_('Cannot remove %s file %s'), filetype, item)
1624                 continue
1625             else:
1626                 self.verbose_logger.log(logginglevels.DEBUG_4,
1627                     _('%s file %s removed'), filetype, item)
1628                 removed+=1
1629         msg = _('%d %s files removed') % (removed, filetype)
1630         return 0, [msg]
1631
1632     def doPackageLists(self, pkgnarrow='all', patterns=None, showdups=None,
1633                        ignore_case=False):
1634         """generates lists of packages, un-reduced, based on pkgnarrow option"""
1635
1636         if showdups is None:
1637             showdups = self.conf.showdupesfromrepos
1638         ygh = misc.GenericHolder(iter=pkgnarrow)
1639         
1640         installed = []
1641         available = []
1642         reinstall_available = []
1643         old_available = []
1644         updates = []
1645         obsoletes = []
1646         obsoletesTuples = []
1647         recent = []
1648         extras = []
1649
1650         ic = ignore_case
1651         # list all packages - those installed and available, don't 'think about it'
1652         if pkgnarrow == 'all': 
1653             dinst = {}
1654             ndinst = {} # Newest versions by name.arch
1655             for po in self.rpmdb.returnPackages(patterns=patterns,
1656                                                 ignore_case=ic):
1657                 dinst[po.pkgtup] = po
1658                 if showdups:
1659                     continue
1660                 key = (po.name, po.arch)
1661                 if key not in ndinst or po.verGT(ndinst[key]):
1662                     ndinst[key] = po
1663             installed = dinst.values()
1664                         
1665             if showdups:
1666                 avail = self.pkgSack.returnPackages(patterns=patterns,
1667                                                     ignore_case=ic)
1668             else:
1669                 try:
1670                     avail = self.pkgSack.returnNewestByNameArch(patterns=patterns,
1671                                                               ignore_case=ic)
1672                 except Errors.PackageSackError:
1673                     avail = []
1674             
1675             for pkg in avail:
1676                 if showdups:
1677                     if pkg.pkgtup in dinst:
1678                         reinstall_available.append(pkg)
1679                     else:
1680                         available.append(pkg)
1681                 else:
1682                     key = (pkg.name, pkg.arch)
1683                     if pkg.pkgtup in dinst:
1684                         reinstall_available.append(pkg)
1685                     elif key not in ndinst or pkg.verGT(ndinst[key]):
1686                         available.append(pkg)
1687                     else:
1688                         old_available.append(pkg)
1689
1690         # produce the updates list of tuples
1691         elif pkgnarrow == 'updates':
1692             for (n,a,e,v,r) in self.up.getUpdatesList():
1693                 matches = self.pkgSack.searchNevra(name=n, arch=a, epoch=e, 
1694                                                    ver=v, rel=r)
1695                 if len(matches) > 1:
1696                     updates.append(matches[0])
1697                     self.verbose_logger.log(logginglevels.DEBUG_1,
1698                         _('More than one identical match in sack for %s'), 
1699                         matches[0])
1700                 elif len(matches) == 1:
1701                     updates.append(matches[0])
1702                 else:
1703                     self.verbose_logger.log(logginglevels.DEBUG_1,
1704                         _('Nothing matches %s.%s %s:%s-%s from update'), n,a,e,v,r)
1705             if patterns:
1706                 exactmatch, matched, unmatched = \
1707                    parsePackages(updates, patterns, casematch=not ignore_case)
1708                 updates = exactmatch + matched
1709
1710         # installed only
1711         elif pkgnarrow == 'installed':
1712             installed = self.rpmdb.returnPackages(patterns=patterns,
1713                                                   ignore_case=ic)
1714         
1715         # available in a repository
1716         elif pkgnarrow == 'available':
1717
1718             if showdups:
1719                 avail = self.pkgSack.returnPackages(patterns=patterns,
1720                                                     ignore_case=ic)
1721             else:
1722                 try:
1723                     avail = self.pkgSack.returnNewestByNameArch(patterns=patterns,
1724                                                               ignore_case=ic)
1725                 except Errors.PackageSackError:
1726                     avail = []
1727             
1728             for pkg in avail:
1729                 if showdups:
1730                     if self.rpmdb.contains(po=pkg):
1731                         reinstall_available.append(pkg)
1732                     else:
1733                         available.append(pkg)
1734                 else:
1735                     ipkgs = self.rpmdb.searchNevra(pkg.name, arch=pkg.arch)
1736                     if ipkgs:
1737                         latest = sorted(ipkgs, reverse=True)[0]
1738                     if not ipkgs or pkg.verGT(latest):
1739                         available.append(pkg)
1740                     elif pkg.verEQ(latest):
1741                         reinstall_available.append(pkg)
1742                     else:
1743                         old_available.append(pkg)
1744
1745         # not in a repo but installed
1746         elif pkgnarrow == 'extras':
1747             # we must compare the installed set versus the repo set
1748             # anything installed but not in a repo is an extra
1749             avail = self.pkgSack.simplePkgList(patterns=patterns,
1750                                                ignore_case=ic)
1751             avail = set(avail)
1752             for po in self.rpmdb.returnPackages(patterns=patterns,
1753                                                 ignore_case=ic):
1754                 if po.pkgtup not in avail:
1755                     extras.append(po)
1756
1757         # obsoleting packages (and what they obsolete)
1758         elif pkgnarrow == 'obsoletes':
1759             self.conf.obsoletes = 1
1760
1761             for (pkgtup, instTup) in self.up.getObsoletesTuples():
1762                 (n,a,e,v,r) = pkgtup
1763                 pkgs = self.pkgSack.searchNevra(name=n, arch=a, ver=v, rel=r, epoch=e)
1764                 instpo = self.rpmdb.searchPkgTuple(instTup)[0] # the first one
1765                 for po in pkgs:
1766                     obsoletes.append(po)
1767                     obsoletesTuples.append((po, instpo))
1768             if patterns:
1769                 exactmatch, matched, unmatched = \
1770                    parsePackages(obsoletes, patterns, casematch=not ignore_case)
1771                 obsoletes = exactmatch + matched
1772                 matched_obsoletes = set(obsoletes)
1773                 nobsoletesTuples = []
1774                 for po, instpo in obsoletesTuples:
1775                     if po not in matched_obsoletes:
1776                         continue
1777                     nobsoletesTuples.append((po, instpo))
1778                 obsoletesTuples = nobsoletesTuples
1779         
1780         # packages recently added to the repositories
1781         elif pkgnarrow == 'recent':
1782             now = time.time()
1783             recentlimit = now-(self.conf.recent*86400)
1784             ftimehash = {}
1785             if showdups:
1786                 avail = self.pkgSack.returnPackages(patterns=patterns,
1787                                                     ignore_case=ic)
1788             else:
1789                 try:
1790                     avail = self.pkgSack.returnNewestByNameArch(patterns=patterns,
1791                                                               ignore_case=ic)
1792                 except Errors.PackageSackError:
1793                     avail = []
1794             
1795             for po in avail:
1796                 ftime = int(po.filetime)
1797                 if ftime > recentlimit:
1798                     if not ftimehash.has_key(ftime):
1799                         ftimehash[ftime] = [po]
1800                     else:
1801                         ftimehash[ftime].append(po)
1802
1803             for sometime in ftimehash:
1804                 for po in ftimehash[sometime]:
1805                     recent.append(po)
1806         
1807         
1808         ygh.installed = installed
1809         ygh.available = available
1810         ygh.reinstall_available = reinstall_available
1811         ygh.old_available = old_available
1812         ygh.updates = updates
1813         ygh.obsoletes = obsoletes
1814         ygh.obsoletesTuples = obsoletesTuples
1815         ygh.recent = recent
1816         ygh.extras = extras
1817
1818         return ygh
1819
1820
1821         
1822     def findDeps(self, pkgs):
1823         """
1824         Return the dependencies for a given package object list, as well
1825         possible solutions for those dependencies.
1826            
1827         Returns the deps as a dict of dicts::
1828             packageobject = [reqs] = [list of satisfying pkgs]
1829         """
1830         
1831         results = {}
1832
1833         for pkg in pkgs:
1834             results[pkg] = {} 
1835             reqs = pkg.requires
1836             reqs.sort()
1837             pkgresults = results[pkg] # shorthand so we don't have to do the
1838                                       # double bracket thing
1839             
1840             for req in reqs:
1841                 (r,f,v) = req
1842                 if r.startswith('rpmlib('):
1843                     continue
1844                 
1845                 satisfiers = []
1846
1847                 for po in self.whatProvides(r, f, v):
1848                     satisfiers.append(po)
1849
1850                 pkgresults[req] = satisfiers
1851         
1852         return results
1853     
1854     # pre 3.2.10 API used to always showdups, so that's the default atm.
1855     def searchGenerator(self, fields, criteria, showdups=True, keys=False):
1856         """Generator method to lighten memory load for some searches.
1857            This is the preferred search function to use. Setting keys to True
1858            will use the search keys that matched in the sorting, and return
1859            the search keys in the results. """
1860         sql_fields = []
1861         for f in fields:
1862             if RPM_TO_SQLITE.has_key(f):
1863                 sql_fields.append(RPM_TO_SQLITE[f])
1864             else:
1865                 sql_fields.append(f)
1866
1867         matched_values = {}
1868
1869         # yield the results in order of most terms matched first
1870         sorted_lists = {}
1871         tmpres = []
1872         real_crit = []
1873         for s in criteria:
1874             real_crit.append(s)
1875         real_crit_lower = [] # Take the s.lower()'s out of the loop
1876         rcl2c = {}
1877         for s in criteria:
1878             real_crit_lower.append(s.lower())
1879             rcl2c[s.lower()] = s
1880
1881         for sack in self.pkgSack.sacks.values():
1882             tmpres.extend(sack.searchPrimaryFieldsMultipleStrings(sql_fields, real_crit))
1883
1884         def results2sorted_lists(tmpres, sorted_lists):
1885             for (po, count) in tmpres:
1886                 # check the pkg for sanity
1887                 # pop it into the sorted lists
1888                 tmpkeys   = set()
1889                 tmpvalues = []
1890                 if count not in sorted_lists: sorted_lists[count] = []
1891                 for s in real_crit_lower:
1892                     for field in fields:
1893                         value = to_unicode(getattr(po, field))
1894                         if value and value.lower().find(s) != -1:
1895                             tmpvalues.append(value)
1896                             tmpkeys.add(rcl2c[s])
1897
1898                 if len(tmpvalues) > 0:
1899                     sorted_lists[count].append((po, tmpkeys, tmpvalues))
1900         results2sorted_lists(tmpres, sorted_lists)
1901
1902         tmpres = self.rpmdb.searchPrimaryFieldsMultipleStrings(fields,
1903                                                                real_crit_lower,
1904                                                                lowered=True)
1905         # close our rpmdb connection so we can ctrl-c, kthxbai
1906         self.closeRpmDB()
1907
1908         results2sorted_lists(tmpres, sorted_lists)
1909         del tmpres
1910
1911         # By default just sort using package sorting
1912         sort_func = operator.itemgetter(0)
1913         if keys:
1914             # Take into account the keys found, as well
1915             sort_func = lambda x: "%s%s" % ("\0".join(sorted(x[1])), str(x[0]))
1916         yielded = {}
1917         for val in reversed(sorted(sorted_lists)):
1918             for (po, ks, vs) in sorted(sorted_lists[val], key=sort_func):
1919                 if not showdups and (po.name, po.arch) in yielded:
1920                     continue
1921
1922                 if keys:
1923                     yield (po, ks, vs)
1924                 else:
1925                     yield (po, vs)
1926
1927                 if not showdups:
1928                     yielded[(po.name, po.arch)] = 1
1929
1930
1931     def searchPackages(self, fields, criteria, callback=None):
1932         """Search specified fields for matches to criteria
1933            optional callback specified to print out results
1934            as you go. Callback is a simple function of:
1935            callback(po, matched values list). It will 
1936            just return a dict of dict[po]=matched values list"""
1937         warnings.warn(_('searchPackages() will go away in a future version of Yum.\
1938                       Use searchGenerator() instead. \n'),
1939                 Errors.YumFutureDeprecationWarning, stacklevel=2)           
1940         matches = {}
1941         match_gen = self.searchGenerator(fields, criteria)
1942         
1943         for (po, matched_strings) in match_gen:
1944             if callback:
1945                 callback(po, matched_strings)
1946             if not matches.has_key(po):
1947                 matches[po] = []
1948             
1949             matches[po].extend(matched_strings)
1950         
1951         return matches
1952     
1953     def searchPackageProvides(self, args, callback=None,
1954                               callback_has_matchfor=False):
1955         
1956         matches = {}
1957         for arg in args:
1958             arg = to_unicode(arg)
1959             if not misc.re_glob(arg):
1960                 isglob = False
1961                 if arg[0] != '/':
1962                     canBeFile = False
1963                 else:
1964                     canBeFile = True
1965             else:
1966                 isglob = True
1967                 if arg[0] != '/' and not misc.re_glob(arg[0]):
1968                     canBeFile = False
1969                 else:
1970                     canBeFile = True
1971                 
1972             if not isglob:
1973                 usedDepString = True
1974                 where = self.returnPackagesByDep(arg)
1975             else:
1976                 usedDepString = False
1977                 where = self.pkgSack.searchAll(arg, False)
1978             self.verbose_logger.log(logginglevels.DEBUG_1,
1979                 _('Searching %d packages'), len(where))
1980             
1981             for po in where:
1982                 self.verbose_logger.log(logginglevels.DEBUG_2,
1983                     _('searching package %s'), po)
1984                 tmpvalues = []
1985                 
1986                 if usedDepString:
1987                     tmpvalues.append(arg)
1988
1989                 if not isglob and canBeFile:
1990                     # then it is not a globbed file we have matched it precisely
1991                     tmpvalues.append(arg)
1992                     
1993                 if isglob and canBeFile:
1994                     self.verbose_logger.log(logginglevels.DEBUG_2,
1995                         _('searching in file entries'))
1996                     for thisfile in po.dirlist + po.filelist + po.ghostlist:
1997                         if fnmatch.fnmatch(thisfile, arg):
1998                             tmpvalues.append(thisfile)
1999                 
2000
2001                 self.verbose_logger.log(logginglevels.DEBUG_2,
2002                     _('searching in provides entries'))
2003                 for (p_name, p_flag, (p_e, p_v, p_r)) in po.provides:
2004                     prov = misc.prco_tuple_to_string((p_name, p_flag, (p_e, p_v, p_r)))
2005                     if not usedDepString:
2006                         if fnmatch.fnmatch(p_name, arg) or fnmatch.fnmatch(prov, arg):
2007                             tmpvalues.append(prov)
2008
2009                 if len(tmpvalues) > 0:
2010                     if callback: # No matchfor, on globs
2011                         if not isglob and callback_has_matchfor:
2012                             callback(po, tmpvalues, args)
2013                         else:
2014                             callback(po, tmpvalues)
2015                     matches[po] = tmpvalues
2016         
2017         # installed rpms, too
2018         taglist = ['filelist', 'dirnames', 'provides_names']
2019         for arg in args:
2020             if not misc.re_glob(arg):
2021                 isglob = False
2022                 if arg[0] != '/':
2023                     canBeFile = False
2024                 else:
2025                     canBeFile = True
2026             else:
2027                 isglob = True
2028                 canBeFile = True
2029             
2030             if not isglob:
2031                 where = self.returnInstalledPackagesByDep(arg)
2032                 usedDepString = True
2033                 for po in where:
2034                     tmpvalues = []
2035                     msg = _('Provides-match: %s') % to_unicode(arg)
2036                     tmpvalues.append(msg)
2037
2038                     if len(tmpvalues) > 0:
2039                         if callback:
2040                             if callback_has_matchfor:
2041                                 callback(po, tmpvalues, args)
2042                             else:
2043                                 callback(po, tmpvalues)
2044                         matches[po] = tmpvalues
2045
2046             else:
2047                 usedDepString = False
2048                 where = self.rpmdb
2049                 
2050                 for po in where:
2051                     searchlist = []
2052                     tmpvalues = []
2053                     for tag in taglist:
2054                         tagdata = getattr(po, tag)
2055                         if tagdata is None:
2056                             continue
2057                         if type(tagdata) is types.ListType:
2058                             searchlist.extend(tagdata)
2059                         else:
2060                             searchlist.append(tagdata)
2061                     
2062                     for item in searchlist:
2063                         if fnmatch.fnmatch(item, arg):
2064                             tmpvalues.append(item)
2065                 
2066                     if len(tmpvalues) > 0:
2067                         if callback: # No matchfor, on globs
2068                             callback(po, tmpvalues)
2069                         matches[po] = tmpvalues
2070             
2071             
2072         return matches
2073
2074     def doGroupLists(self, uservisible=0, patterns=None, ignore_case=True):
2075         """returns two lists of groups, installed groups and available groups
2076            optional 'uservisible' bool to tell it whether or not to return
2077            only groups marked as uservisible"""
2078         
2079         
2080         installed = []
2081         available = []
2082
2083         if self.comps.compscount == 0:
2084             raise Errors.GroupsError, _('No group data available for configured repositories')
2085         
2086         if patterns is None:
2087             grps = self.comps.groups
2088         else:
2089             grps = self.comps.return_groups(",".join(patterns),
2090                                             case_sensitive=not ignore_case)
2091         for grp in grps:
2092             if grp.installed:
2093                 if uservisible:
2094                     if grp.user_visible:
2095                         installed.append(grp)
2096                 else:
2097                     installed.append(grp)
2098             else:
2099                 if uservisible:
2100                     if grp.user_visible:
2101                         available.append(grp)
2102                 else:
2103                     available.append(grp)
2104             
2105         return sorted(installed), sorted(available)
2106     
2107     
2108     def groupRemove(self, grpid):
2109         """mark all the packages in this group to be removed"""
2110         
2111         txmbrs_used = []
2112         
2113         thesegroups = self.comps.return_groups(grpid)
2114         if not thesegroups:
2115             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2116
2117         for thisgroup in thesegroups:
2118             thisgroup.toremove = True
2119             pkgs = thisgroup.packages
2120             for pkg in thisgroup.packages:
2121                 txmbrs = self.remove(name=pkg, silence_warnings=True)
2122                 txmbrs_used.extend(txmbrs)
2123                 for txmbr in txmbrs:
2124                     txmbr.groups.append(thisgroup.groupid)
2125             
2126         return txmbrs_used
2127
2128     def groupUnremove(self, grpid):
2129         """unmark any packages in the group from being removed"""
2130         
2131
2132         thesegroups = self.comps.return_groups(grpid)
2133         if not thesegroups:
2134             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2135
2136         for thisgroup in thesegroups:
2137             thisgroup.toremove = False
2138             pkgs = thisgroup.packages
2139             for pkg in thisgroup.packages:
2140                 for txmbr in self.tsInfo:
2141                     if txmbr.po.name == pkg and txmbr.po.state in TS_INSTALL_STATES:
2142                         try:
2143                             txmbr.groups.remove(grpid)
2144                         except ValueError:
2145                             self.verbose_logger.log(logginglevels.DEBUG_1,
2146                                _("package %s was not marked in group %s"), txmbr.po,
2147                                 grpid)
2148                             continue
2149                         
2150                         # if there aren't any other groups mentioned then remove the pkg
2151                         if len(txmbr.groups) == 0:
2152                             self.tsInfo.remove(txmbr.po.pkgtup)
2153         
2154         
2155     def selectGroup(self, grpid, group_package_types=[], enable_group_conditionals=None):
2156         """mark all the packages in the group to be installed
2157            returns a list of transaction members it added to the transaction 
2158            set
2159            Optionally take:
2160            group_package_types=List - overrides self.conf.group_package_types
2161            enable_group_conditionals=Bool - overrides self.conf.enable_group_conditionals
2162         """
2163
2164         if not self.comps.has_group(grpid):
2165             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2166         
2167         txmbrs_used = []
2168         thesegroups = self.comps.return_groups(grpid)
2169      
2170         if not thesegroups:
2171             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2172
2173         package_types = self.conf.group_package_types
2174         if group_package_types:
2175             package_types = group_package_types
2176
2177         for thisgroup in thesegroups:
2178             if thisgroup.selected:
2179                 continue
2180             
2181             thisgroup.selected = True
2182             
2183             pkgs = []
2184             if 'mandatory' in package_types:
2185                 pkgs.extend(thisgroup.mandatory_packages)
2186             if 'default' in package_types:
2187                 pkgs.extend(thisgroup.default_packages)
2188             if 'optional' in package_types:
2189                 pkgs.extend(thisgroup.optional_packages)
2190
2191             for pkg in pkgs:
2192                 self.verbose_logger.log(logginglevels.DEBUG_2,
2193                     _('Adding package %s from group %s'), pkg, thisgroup.groupid)
2194                 try:
2195                     txmbrs = self.install(name = pkg)
2196                 except Errors.InstallError, e:
2197                     self.verbose_logger.debug(_('No package named %s available to be installed'),
2198                         pkg)
2199                 else:
2200                     txmbrs_used.extend(txmbrs)
2201                     for txmbr in txmbrs:
2202                         txmbr.groups.append(thisgroup.groupid)
2203             
2204             group_conditionals = self.conf.enable_group_conditionals
2205             if enable_group_conditionals is not None: # has to be this way so we can set it to False
2206                 group_conditionals = enable_group_conditionals
2207
2208             if group_conditionals:
2209                 for condreq, cond in thisgroup.conditional_packages.iteritems():
2210                     if self.isPackageInstalled(cond):
2211                         try:
2212                             txmbrs = self.install(name = condreq)
2213                         except Errors.InstallError:
2214                             # we don't care if the package doesn't exist
2215                             continue
2216                         else:
2217                             if cond not in self.tsInfo.conditionals:
2218                                 self.tsInfo.conditionals[cond]=[]
2219
2220                         txmbrs_used.extend(txmbrs)
2221                         for txmbr in txmbrs:
2222                             txmbr.groups.append(thisgroup.groupid)
2223                             self.tsInfo.conditionals[cond].append(txmbr.po)
2224                         continue
2225                     # Otherwise we hook into tsInfo.add to make 
2226                     # sure we'll catch it if its added later in this transaction
2227                     pkgs = self.pkgSack.searchNevra(name=condreq)
2228                     if pkgs:
2229                         if self.arch.multilib:
2230                             if self.conf.multilib_policy == 'best':
2231                                 use = []
2232                                 best = self.arch.legit_multi_arches
2233                                 best.append('noarch')
2234                                 for pkg in pkgs:
2235                                     if pkg.arch in best:
2236                                         use.append(pkg)
2237                                 pkgs = use
2238                                
2239                         pkgs = packagesNewestByNameArch(pkgs)
2240
2241                         if not self.tsInfo.conditionals.has_key(cond):
2242                             self.tsInfo.conditionals[cond] = []
2243                         self.tsInfo.conditionals[cond].extend(pkgs)
2244         return txmbrs_used
2245
2246     def deselectGroup(self, grpid):
2247         """de-mark all the packages in the group for install"""
2248         
2249         if not self.comps.has_group(grpid):
2250             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2251             
2252         thesegroups = self.comps.return_groups(grpid)
2253         if not thesegroups:
2254             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2255         
2256         for thisgroup in thesegroups:
2257             thisgroup.selected = False
2258             
2259             for pkgname in thisgroup.packages:
2260             
2261                 for txmbr in self.tsInfo:
2262                     if txmbr.po.name == pkgname and txmbr.po.state in TS_INSTALL_STATES:
2263                         try: 
2264                             txmbr.groups.remove(grpid)
2265                         except ValueError:
2266                             self.verbose_logger.log(logginglevels.DEBUG_1,
2267                                _("package %s was not marked in group %s"), txmbr.po,
2268                                 grpid)
2269                             continue
2270                         
2271                         # if there aren't any other groups mentioned then remove the pkg
2272                         if len(txmbr.groups) == 0:
2273                             self.tsInfo.remove(txmbr.po.pkgtup)
2274                             
2275                             for pkg in self.tsInfo.conditionals.get(txmbr.name, []):
2276                                 self.tsInfo.remove(pkg.pkgtup)
2277
2278         
2279     def getPackageObject(self, pkgtup):
2280         """retrieves a packageObject from a pkgtuple - if we need
2281            to pick and choose which one is best we better call out
2282            to some method from here to pick the best pkgobj if there are
2283            more than one response - right now it's more rudimentary."""
2284            
2285         
2286         # look it up in the self.localPackages first:
2287         for po in self.localPackages:
2288             if po.pkgtup == pkgtup:
2289                 return po
2290                 
2291         pkgs = self.pkgSack.searchPkgTuple(pkgtup)
2292
2293         if len(pkgs) == 0:
2294             raise Errors.DepError, _('Package tuple %s could not be found in packagesack') % str(pkgtup)
2295             
2296         if len(pkgs) > 1: # boy it'd be nice to do something smarter here FIXME
2297             result = pkgs[0]
2298         else:
2299             result = pkgs[0] # which should be the only
2300         
2301             # this is where we could do something to figure out which repository
2302             # is the best one to pull from
2303         
2304         return result
2305
2306     def getInstalledPackageObject(self, pkgtup):
2307         """returns a YumInstallPackage object for the pkgtup specified"""
2308         warnings.warn(_('getInstalledPackageObject() will go away, use self.rpmdb.searchPkgTuple().\n'),
2309                 Errors.YumFutureDeprecationWarning, stacklevel=2)
2310         
2311         po = self.rpmdb.searchPkgTuple(pkgtup)[0] # take the first one
2312         return po
2313         
2314     def gpgKeyCheck(self):
2315         """checks for the presence of gpg keys in the rpmdb
2316            returns 0 if no keys returns 1 if keys"""
2317
2318         gpgkeyschecked = self.conf.cachedir + '/.gpgkeyschecked.yum'
2319         if os.path.exists(gpgkeyschecked):
2320             return 1
2321             
2322         myts = rpmUtils.transaction.initReadOnlyTransaction(root=self.conf.installroot)
2323         myts.pushVSFlags(~(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS))
2324         idx = myts.dbMatch('name', 'gpg-pubkey')
2325         keys = idx.count()
2326         del idx
2327         del myts
2328         
2329         if keys == 0:
2330             return 0
2331         else:
2332             mydir = os.path.dirname(gpgkeyschecked)
2333             if not os.path.exists(mydir):
2334                 os.makedirs(mydir)
2335                 
2336             fo = open(gpgkeyschecked, 'w')
2337             fo.close()
2338             del fo
2339             return 1
2340
2341     def returnPackagesByDep(self, depstring):
2342         """Pass in a generic [build]require string and this function will 
2343            pass back the packages it finds providing that dep."""
2344         
2345         results = []
2346         # parse the string out
2347         #  either it is 'dep (some operator) e:v-r'
2348         #  or /file/dep
2349         #  or packagename
2350         # or a full dep tuple
2351         if type(depstring) == types.TupleType:
2352             (depname, depflags, depver) = depstring
2353         else:
2354             depname = depstring
2355             depflags = None
2356             depver = None
2357         
2358             if depstring[0] != '/':
2359                 # not a file dep - look at it for being versioned
2360                 dep_split = depstring.split()
2361                 if len(dep_split) == 3:
2362                     depname, flagsymbol, depver = dep_split
2363                     if not flagsymbol in SYMBOLFLAGS:
2364                         raise Errors.YumBaseError, _('Invalid version flag')
2365                     depflags = SYMBOLFLAGS[flagsymbol]
2366                 
2367         sack = self.whatProvides(depname, depflags, depver)
2368         results = sack.returnPackages()
2369         return results
2370         
2371
2372     def returnPackageByDep(self, depstring):
2373         """Pass in a generic [build]require string and this function will 
2374            pass back the best(or first) package it finds providing that dep."""
2375         
2376         try:
2377             pkglist = self.returnPackagesByDep(depstring)
2378         except Errors.YumBaseError:
2379             raise Errors.YumBaseError, _('No Package found for %s') % depstring
2380         
2381         ps = ListPackageSack(pkglist)
2382         result = self._bestPackageFromList(ps.returnNewestByNameArch())
2383         if result is None:
2384             raise Errors.YumBaseError, _('No Package found for %s') % depstring
2385         
2386         return result
2387
2388     def returnInstalledPackagesByDep(self, depstring):
2389         """Pass in a generic [build]require string and this function will 
2390            pass back the installed packages it finds providing that dep."""
2391         
2392         # parse the string out
2393         #  either it is 'dep (some operator) e:v-r'
2394         #  or /file/dep
2395         #  or packagename
2396         if type(depstring) == types.TupleType:
2397             (depname, depflags, depver) = depstring
2398         else:
2399             depname = depstring
2400             depflags = None
2401             depver = None
2402             
2403             if depstring[0] != '/':
2404                 # not a file dep - look at it for being versioned
2405                 dep_split = depstring.split()
2406                 if len(dep_split) == 3:
2407                     depname, flagsymbol, depver = dep_split
2408                     if not flagsymbol in SYMBOLFLAGS:
2409                         raise Errors.YumBaseError, _('Invalid version flag')
2410                     depflags = SYMBOLFLAGS[flagsymbol]
2411
2412         return self.rpmdb.getProvides(depname, depflags, depver).keys()
2413
2414     def _bestPackageFromList(self, pkglist):
2415         """take list of package objects and return the best package object.
2416            If the list is empty, return None. 
2417            
2418            Note: this is not aware of multilib so make sure you're only
2419            passing it packages of a single arch group."""
2420         
2421         
2422         if len(pkglist) == 0:
2423             return None
2424             
2425         if len(pkglist) == 1:
2426             return pkglist[0]
2427
2428         bestlist = self._compare_providers(pkglist, None)
2429         return bestlist[0][0]
2430
2431     def bestPackagesFromList(self, pkglist, arch=None, single_name=False):
2432         """Takes a list of packages, returns the best packages.
2433            This function is multilib aware so that it will not compare
2434            multilib to singlelib packages""" 
2435     
2436         returnlist = []
2437         compatArchList = self.arch.get_arch_list(arch)
2438         multiLib = []
2439         singleLib = []
2440         noarch = []
2441         for po in pkglist:
2442             if po.arch not in compatArchList:
2443                 continue
2444             elif po.arch in ("noarch"):
2445                 noarch.append(po)
2446             elif isMultiLibArch(arch=po.arch):
2447                 multiLib.append(po)
2448             else:
2449                 singleLib.append(po)
2450                 
2451         # we now have three lists.  find the best package(s) of each
2452         multi = self._bestPackageFromList(multiLib)
2453         single = self._bestPackageFromList(singleLib)
2454         no = self._bestPackageFromList(noarch)
2455
2456         if single_name and multi and single and multi.name != single.name:
2457             # Sinlge _must_ match multi, if we want a single package name
2458             single = None
2459
2460         # now, to figure out which arches we actually want
2461         # if there aren't noarch packages, it's easy. multi + single
2462         if no is None:
2463             if multi: returnlist.append(multi)
2464             if single: returnlist.append(single)
2465         # if there's a noarch and it's newer than the multilib, we want
2466         # just the noarch.  otherwise, we want multi + single
2467         elif multi:
2468             best = self._bestPackageFromList([multi,no])
2469             if best.arch == "noarch":
2470                 returnlist.append(no)
2471             else:
2472                 if multi: returnlist.append(multi)
2473                 if single: returnlist.append(single)
2474         # similar for the non-multilib case
2475         elif single:
2476             best = self._bestPackageFromList([single,no])
2477             if best.arch == "noarch":
2478                 returnlist.append(no)
2479             else:
2480                 returnlist.append(single)
2481         # if there's not a multi or single lib, then we want the noarch
2482         else:
2483             returnlist.append(no)
2484
2485         return returnlist
2486
2487     def _pkg2obspkg(self, po):
2488         """ Given a package return the package it's obsoleted by and so
2489             we should install instead. Or None if there isn't one. """
2490         thispkgobsdict = self.up.checkForObsolete([po.pkgtup])
2491         if thispkgobsdict.has_key(po.pkgtup):
2492             obsoleting = thispkgobsdict[po.pkgtup][0]
2493             obsoleting_pkg = self.getPackageObject(obsoleting)
2494             return obsoleting_pkg
2495         return None
2496
2497     def _test_loop(self, node, next_func):
2498         """ Generic comp. sci. test for looping, walk the list with two pointers
2499             moving one twice as fast as the other. If they are ever == you have
2500             a loop. If loop we return None, if no loop the last element. """
2501         slow = node
2502         done = False
2503         while True:
2504             next = next_func(node)
2505             if next is None and not done: return None
2506             if next is None: return node
2507             node = next_func(next)
2508             if node is None: return next
2509             done = True
2510
2511             slow = next_func(slow)
2512             if next == slow:
2513                 return None
2514
2515     def _at_groupinstall(self, pattern):
2516         " Do groupinstall via. leading @ on the cmd line, for install/update."
2517         assert pattern[0] == '@'
2518         group_string = pattern[1:]
2519         tx_return = []
2520         for group in self.comps.return_groups(group_string):
2521             try:
2522                 txmbrs = self.selectGroup(group.groupid)
2523                 tx_return.extend(txmbrs)
2524             except yum.Errors.GroupsError:
2525                 self.logger.critical(_('Warning: Group %s does not exist.'), group_string)
2526                 continue
2527         return tx_return
2528         
2529     def _at_groupremove(self, pattern):
2530         " Do groupremove via. leading @ on the cmd line, for remove."
2531         assert pattern[0] == '@'
2532         group_string = pattern[1:]
2533         tx_return = []
2534         try:
2535             txmbrs = self.groupRemove(group_string)
2536         except yum.Errors.GroupsError:
2537             self.logger.critical(_('No group named %s exists'), group_string)
2538         else:
2539             tx_return.extend(txmbrs)
2540         return tx_return
2541
2542     #  Note that this returns available pkgs, and not txmbrs like the other
2543     # _at_group* functions.
2544     def _at_groupdowngrade(self, pattern):
2545         " Do downgrade of a group via. leading @ on the cmd line."
2546         assert pattern[0] == '@'
2547         grpid = pattern[1:]
2548
2549         thesegroups = self.comps.return_groups(grpid)
2550         if not thesegroups:
2551             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2552         pkgnames = set()
2553         for thisgroup in thesegroups:
2554             pkgnames.update(thisgroup.packages)
2555         return self.pkgSack.searchNames(pkgnames)
2556
2557     def _find_obsoletees(self, po):
2558         """ Return the pkgs. that are obsoleted by the po we pass in. """
2559         for (obstup, inst_tup) in self.up.getObsoletersTuples(name=po.name):
2560             if po.pkgtup == obstup:
2561                 installed_pkg =  self.rpmdb.searchPkgTuple(inst_tup)[0]
2562                 yield installed_pkg
2563
2564     def _add_prob_flags(self, *flags):
2565         """ Add all of the passed flags to the tsInfo.probFilterFlags array. """
2566         for flag in flags:
2567             if flag not in self.tsInfo.probFilterFlags:
2568                 self.tsInfo.probFilterFlags.append(flag)
2569
2570     def install(self, po=None, **kwargs):
2571         """try to mark for install the item specified. Uses provided package 
2572            object, if available. If not it uses the kwargs and gets the best
2573            packages from the keyword options provided 
2574            returns the list of txmbr of the items it installs
2575            
2576            """
2577         
2578         pkgs = []
2579         was_pattern = False
2580         if po:
2581             if isinstance(po, YumAvailablePackage) or isinstance(po, YumLocalPackage):
2582                 pkgs.append(po)
2583             else:
2584                 raise Errors.InstallError, _('Package Object was not a package object instance')
2585             
2586         else:
2587             if not kwargs:
2588                 raise Errors.InstallError, _('Nothing specified to install')
2589
2590             if kwargs.has_key('pattern'):
2591                 if kwargs['pattern'][0] == '@':
2592                     return self._at_groupinstall(kwargs['pattern'])
2593
2594                 was_pattern = True
2595                 pats = [kwargs['pattern']]
2596                 mypkgs = self.pkgSack.returnPackages(patterns=pats,
2597                                                       ignore_case=False)
2598                 pkgs.extend(mypkgs)
2599                 # if we have anything left unmatched, let's take a look for it
2600                 # being a dep like glibc.so.2 or /foo/bar/baz
2601                 
2602                 if not mypkgs:
2603                     arg = kwargs['pattern']
2604                     self.verbose_logger.debug(_('Checking for virtual provide or file-provide for %s'), 
2605                         arg)
2606
2607                     try:
2608                         mypkgs = self.returnPackagesByDep(arg)
2609                     except yum.Errors.YumBaseError, e:
2610                         self.logger.critical(_('No Match for argument: %s') % arg)
2611                     else:
2612                         # install MTA* == fail, because provides don't do globs
2613                         # install /usr/kerberos/bin/* == success (and we want
2614                         #                                all of the pkgs)
2615                         if mypkgs and not misc.re_glob(arg):
2616                             mypkgs = self.bestPackagesFromList(mypkgs,
2617                                                                single_name=True)
2618                         if mypkgs:
2619                             pkgs.extend(mypkgs)
2620                         
2621             else:
2622                 nevra_dict = self._nevra_kwarg_parse(kwargs)
2623
2624                 pkgs = self.pkgSack.searchNevra(name=nevra_dict['name'],
2625                      epoch=nevra_dict['epoch'], arch=nevra_dict['arch'],
2626                      ver=nevra_dict['version'], rel=nevra_dict['release'])
2627                 
2628             if pkgs:
2629                 # if was_pattern or nevra-dict['arch'] is none, take the list
2630                 # of arches based on our multilib_compat config and 
2631                 # toss out any pkgs of any arch NOT in that arch list
2632
2633                 
2634                 # only do these things if we're multilib
2635                 if self.arch.multilib:
2636                     if was_pattern or not nevra_dict['arch']: # and only if they
2637                                                               # they didn't specify an arch
2638                         if self.conf.multilib_policy == 'best':
2639                             pkgs_by_name = {}
2640                             use = []
2641                             not_added = []
2642                             best = self.arch.legit_multi_arches
2643                             best.append('noarch')
2644                             for pkg in pkgs:
2645                                 if pkg.arch in best:
2646                                     pkgs_by_name[pkg.name] = 1    
2647                                     use.append(pkg)  
2648                                 else:
2649                                     not_added.append(pkg)
2650                             for pkg in not_added:
2651                                 if not pkg.name in pkgs_by_name:
2652                                     use.append(pkg)
2653                            
2654                             pkgs = use
2655                            
2656                 pkgs = packagesNewestByNameArch(pkgs)
2657
2658                 pkgbyname = {}
2659                 for pkg in pkgs:
2660                     if not pkgbyname.has_key(pkg.name):
2661                         pkgbyname[pkg.name] = [ pkg ]
2662                     else:
2663                         pkgbyname[pkg.name].append(pkg)
2664
2665                 lst = []
2666                 for pkgs in pkgbyname.values():
2667                     lst.extend(self.bestPackagesFromList(pkgs))
2668                 pkgs = lst
2669
2670
2671         if not pkgs:
2672             # Do we still want to return errors here?
2673             # We don't in the cases below, so I didn't here...
2674             if 'pattern' in kwargs:
2675                 pkgs = self.rpmdb.returnPackages(patterns=[kwargs['pattern']],
2676                                                  ignore_case=False)
2677             if 'name' in kwargs:
2678                 pkgs = self.rpmdb.searchNevra(name=kwargs['name'])
2679             # Warning here does "weird" things when doing:
2680             # yum --disablerepo='*' install '*'
2681             # etc. ... see RHBZ#480402
2682             if False:
2683                 for pkg in pkgs:
2684                     self.verbose_logger.warning(_('Package %s installed and not available'), pkg)
2685             if pkgs:
2686                 return []
2687             raise Errors.InstallError, _('No package(s) available to install')
2688         
2689         # FIXME - lots more checking here
2690         #  - install instead of erase
2691         #  - better error handling/reporting
2692
2693
2694         tx_return = []
2695         for po in pkgs:
2696             if self.tsInfo.exists(pkgtup=po.pkgtup):
2697                 if self.tsInfo.getMembersWithState(po.pkgtup, TS_INSTALL_STATES):
2698                     self.verbose_logger.log(logginglevels.DEBUG_1,
2699                         _('Package: %s  - already in transaction set'), po)
2700                     tx_return.extend(self.tsInfo.getMembers(pkgtup=po.pkgtup))
2701                     continue
2702             
2703             # make sure this shouldn't be passed to update:
2704             if self.up.updating_dict.has_key(po.pkgtup):
2705                 txmbrs = self.update(po=po)
2706                 tx_return.extend(txmbrs)
2707                 continue
2708             
2709             #  Make sure we're not installing a package which is obsoleted by
2710             # something else in the repo. Unless there is a obsoletion loop,
2711             # at which point ignore everything.
2712             obsoleting_pkg = self._test_loop(po, self._pkg2obspkg)
2713             if obsoleting_pkg is not None:
2714                 # this is not a definitive check but it'll make sure we don't
2715                 # pull in foo.i586 when foo.x86_64 already obsoletes the pkg and
2716                 # is already installed
2717                 already_obs = None
2718                 poprovtup = (po.name, 'EQ', (po.epoch, po.ver, po.release))
2719                 for pkg in self.rpmdb.searchNevra(name=obsoleting_pkg.name):
2720                     if pkg.inPrcoRange('obsoletes', poprovtup):
2721                         already_obs = pkg
2722                         continue
2723
2724                 if already_obs:
2725                     self.verbose_logger.warning(_('Package %s is obsoleted by %s which is already installed'), 
2726                                                 po, already_obs)
2727                 else:
2728                     self.verbose_logger.warning(_('Package %s is obsoleted by %s, trying to install %s instead'),
2729                         po.name, obsoleting_pkg.name, obsoleting_pkg)               
2730                     self.install(po=obsoleting_pkg)
2731                 continue
2732             
2733             # make sure it's not already installed
2734             if self.rpmdb.contains(po=po):
2735                 if not self.tsInfo.getMembersWithState(po.pkgtup, TS_REMOVE_STATES):
2736                     self.verbose_logger.warning(_('Package %s already installed and latest version'), po)
2737                     continue
2738
2739             # make sure we don't have a name.arch of this already installed
2740             # if so pass it to update b/c it should be able to figure it out
2741             # if self.rpmdb.contains(name=po.name, arch=po.arch) and not self.allowedMultipleInstalls(po):
2742             if not self.allowedMultipleInstalls(po):
2743                 found = True
2744                 for ipkg in self.rpmdb.searchNevra(name=po.name, arch=po.arch):
2745                     found = False
2746                     if self.tsInfo.getMembersWithState(ipkg.pkgtup, TS_REMOVE_STATES):
2747                         found = True
2748                         break
2749                 if not found:
2750                     self.verbose_logger.warning(_('Package matching %s already installed. Checking for update.'), po)            
2751                     txmbrs = self.update(po=po)
2752                     tx_return.extend(txmbrs)
2753                     continue
2754
2755                 
2756             # at this point we are going to mark the pkg to be installed, make sure
2757             # it's not an older package that is allowed in due to multiple installs
2758             # or some other oddity. If it is - then modify the problem filter to cope
2759             
2760             for ipkg in self.rpmdb.searchNevra(name=po.name, arch=po.arch):
2761                 if ipkg.verEQ(po):
2762                     self._add_prob_flags(rpm.RPMPROB_FILTER_REPLACEPKG,
2763                                          rpm.RPMPROB_FILTER_REPLACENEWFILES,
2764                                          rpm.RPMPROB_FILTER_REPLACEOLDFILES)
2765                     break
2766                 if ipkg.verGT(po):
2767                     self._add_prob_flags(rpm.RPMPROB_FILTER_OLDPACKAGE)
2768                     break
2769             
2770             # it doesn't obsolete anything. If it does, mark that in the tsInfo, too
2771             if po.pkgtup in self.up.getObsoletesList(name=po.name):
2772                 for obsoletee in self._find_obsoletees(po):
2773                     txmbr = self.tsInfo.addObsoleting(po, obsoletee)
2774                     self.tsInfo.addObsoleted(obsoletee, po)
2775                     tx_return.append(txmbr)
2776             else:
2777                 txmbr = self.tsInfo.addInstall(po)
2778                 tx_return.append(txmbr)
2779         
2780         return tx_return
2781
2782     def _check_new_update_provides(self, opkg, npkg):
2783         """ Check for any difference in the provides of the old and new update
2784             that is needed by the transaction. If so we "update" those pkgs
2785             too, to the latest version. """
2786         oprovs = set(opkg.returnPrco('provides'))
2787         nprovs = set(npkg.returnPrco('provides'))
2788         for prov in oprovs.difference(nprovs):
2789             reqs = self.tsInfo.getRequires(*prov)
2790             for pkg in reqs:
2791                 for req in reqs[pkg]:
2792                     if not npkg.inPrcoRange('provides', req):
2793                         naTup = (pkg.name, pkg.arch)
2794                         for pkg in self.pkgSack.returnNewestByNameArch(naTup):
2795                             self.update(po=pkg)
2796                         break
2797
2798     def _newer_update_in_trans(self, pkgtup, available_pkg):
2799         """ We return True if there is a newer package already in the
2800             transaction. If there is an older one, we remove it (and update any
2801             deps. that aren't satisfied by the newer pkg) and return False so
2802             we'll update to this newer pkg. """
2803         found = False
2804         for txmbr in self.tsInfo.getMembersWithState(pkgtup, [TS_UPDATED]):
2805             count = 0
2806             for po in txmbr.updated_by:
2807                 if available_pkg.verLE(po):
2808                     count += 1
2809                 else:
2810                     for ntxmbr in self.tsInfo.getMembers(po.pkgtup):
2811                         self.tsInfo.remove(ntxmbr.po.pkgtup)
2812                         self._check_new_update_provides(ntxmbr.po,
2813                                                         available_pkg)
2814             if count:
2815                 found = True
2816             else:
2817                 self.tsInfo.remove(txmbr.po.pkgtup)
2818         return found
2819
2820     def update(self, po=None, requiringPo=None, **kwargs):
2821         """try to mark for update the item(s) specified. 
2822             po is a package object - if that is there, mark it for update,
2823             if possible
2824             else use **kwargs to match the package needing update
2825             if nothing is specified at all then attempt to update everything
2826             
2827             returns the list of txmbr of the items it marked for update"""
2828         
2829         # check for args - if no po nor kwargs, do them all
2830         # if po, do it, ignore all else
2831         # if no po do kwargs
2832         # uninstalled pkgs called for update get returned with errors in a list, maybe?
2833
2834         tx_return = []
2835         if not po and not kwargs: # update everything (the easy case)
2836             self.verbose_logger.log(logginglevels.DEBUG_2, _('Updating Everything'))
2837             updates = self.up.getUpdatesTuples()
2838             if self.conf.obsoletes:
2839                 obsoletes = self.up.getObsoletesTuples(newest=1)
2840             else:
2841                 obsoletes = []
2842
2843             for (obsoleting, installed) in obsoletes:
2844                 obsoleting_pkg = self.getPackageObject(obsoleting)
2845                 topkg = self._test_loop(obsoleting_pkg, self._pkg2obspkg)
2846                 if topkg is not None:
2847                     obsoleting_pkg = topkg
2848                 installed_pkg =  self.rpmdb.searchPkgTuple(installed)[0]
2849                 txmbr = self.tsInfo.addObsoleting(obsoleting_pkg, installed_pkg)
2850                 self.tsInfo.addObsoleted(installed_pkg, obsoleting_pkg)
2851                 if requiringPo:
2852                     txmbr.setAsDep(requiringPo)
2853                 tx_return.append(txmbr)
2854                 
2855             for (new, old) in updates:
2856                 if self.tsInfo.isObsoleted(pkgtup=old):
2857                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
2858                         old)
2859                 else:
2860                     tx_return.extend(self.update(po=self.getPackageObject(new)))
2861             
2862             return tx_return
2863
2864         # complications
2865         # the user has given us something - either a package object to be
2866         # added to the transaction as an update or they've given us a pattern 
2867         # of some kind
2868         
2869         instpkgs = []
2870         availpkgs = []
2871         if po: # just a po
2872             if po.repoid == 'installed':
2873                 instpkgs.append(po)
2874             else:
2875                 availpkgs.append(po)
2876                 
2877                 
2878         elif kwargs.has_key('pattern'):
2879             if kwargs['pattern'][0] == '@':
2880                 return self._at_groupinstall(kwargs['pattern'])
2881
2882             (e, m, u) = self.rpmdb.matchPackageNames([kwargs['pattern']])
2883             instpkgs.extend(e)
2884             instpkgs.extend(m)
2885
2886             if u:
2887                 depmatches = []
2888                 arg = u[0]
2889                 try:
2890                     depmatches = self.returnInstalledPackagesByDep(arg)
2891                 except yum.Errors.YumBaseError, e:
2892                     self.logger.critical(_('%s') % e)
2893                 
2894                 instpkgs.extend(depmatches)
2895
2896             #  Always look for available packages, it doesn't seem to do any
2897             # harm (apart from some time). And it fixes weird edge cases where
2898             # "update a" (which requires a new b) is different from "update b"
2899             m =self.pkgSack.returnNewestByNameArch(patterns=[kwargs['pattern']])
2900             availpkgs.extend(m)
2901
2902             if not availpkgs and not instpkgs:
2903                 self.logger.critical(_('No Match for argument: %s') % arg)
2904         
2905         else: # we have kwargs, sort them out.
2906             nevra_dict = self._nevra_kwarg_parse(kwargs)
2907
2908             instpkgs = self.rpmdb.searchNevra(name=nevra_dict['name'], 
2909                         epoch=nevra_dict['epoch'], arch=nevra_dict['arch'], 
2910                         ver=nevra_dict['version'], rel=nevra_dict['release'])
2911
2912             if not instpkgs:
2913                 availpkgs = self.pkgSack.searchNevra(name=nevra_dict['name'],
2914                             epoch=nevra_dict['epoch'], arch=nevra_dict['arch'],
2915                             ver=nevra_dict['version'], rel=nevra_dict['release'])
2916                 if len(availpkgs) > 1:
2917                     availpkgs = self._compare_providers(availpkgs, requiringPo)
2918                     availpkgs = map(lambda x: x[0], availpkgs)
2919
2920        
2921         # for any thing specified
2922         # get the list of available pkgs matching it (or take the po)
2923         # get the list of installed pkgs matching it (or take the po)
2924         # go through each list and look for:
2925            # things obsoleting it if it is an installed pkg
2926            # things it updates if it is an available pkg
2927            # things updating it if it is an installed pkg
2928            # in that order
2929            # all along checking to make sure we:
2930             # don't update something that's already been obsoleted
2931             # don't update something that's already been updated
2932             
2933         # if there are more than one package that matches an update from
2934         # a pattern/kwarg then:
2935             # if it is a valid update and we'
2936         
2937         # TODO: we should search the updates and obsoletes list and
2938         # mark the package being updated or obsoleted away appropriately
2939         # and the package relationship in the tsInfo
2940         
2941
2942         # check for obsoletes first
2943         if self.conf.obsoletes:
2944             for installed_pkg in instpkgs:
2945                 obs_tups = self.up.obsoleted_dict.get(installed_pkg.pkgtup, [])
2946                 # This is done so we don't have to returnObsoletes(newest=True)
2947                 # It's a minor UI problem for RHEL, but might as well dtrt.
2948                 obs_pkgs = [self.getPackageObject(tup) for tup in obs_tups]
2949                 for obsoleting_pkg in packagesNewestByNameArch(obs_pkgs):
2950                     tx_return.extend(self.install(po=obsoleting_pkg))
2951             for available_pkg in availpkgs:
2952                 for obsoleted in self.up.obsoleting_dict.get(available_pkg.pkgtup, []):
2953                     obsoleted_pkg = self.getInstalledPackageObject(obsoleted)
2954                     txmbr = self.tsInfo.addObsoleting(available_pkg, obsoleted_pkg)
2955                     if requiringPo:
2956                         txmbr.setAsDep(requiringPo)
2957                     tx_return.append(txmbr)
2958                     if self.tsInfo.isObsoleted(obsoleted):
2959                         self.verbose_logger.log(logginglevels.DEBUG_2, _('Package is already obsoleted: %s.%s %s:%s-%s'), obsoleted)
2960                     else:
2961                         txmbr = self.tsInfo.addObsoleted(obsoleted_pkg, available_pkg)
2962                         tx_return.append(txmbr)
2963
2964         for installed_pkg in instpkgs:
2965             for updating in self.up.updatesdict.get(installed_pkg.pkgtup, []):
2966                 po = self.getPackageObject(updating)
2967                 if self.tsInfo.isObsoleted(installed_pkg.pkgtup):
2968                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
2969                                             installed_pkg.pkgtup)                                               
2970                 # at this point we are going to mark the pkg to be installed, make sure
2971                 # it doesn't obsolete anything. If it does, mark that in the tsInfo, too
2972                 elif po.pkgtup in self.up.getObsoletesList(name=po.name):
2973                     for obsoletee in self._find_obsoletees(po):
2974                         txmbr = self.tsInfo.addUpdate(po, installed_pkg)
2975                         if requiringPo:
2976                             txmbr.setAsDep(requiringPo)
2977                         self.tsInfo.addObsoleting(po, obsoletee)
2978                         self.tsInfo.addObsoleted(obsoletee, po)
2979                         tx_return.append(txmbr)
2980                 else:
2981                     txmbr = self.tsInfo.addUpdate(po, installed_pkg)
2982                     if requiringPo:
2983                         txmbr.setAsDep(requiringPo)
2984                     tx_return.append(txmbr)
2985                         
2986         for available_pkg in availpkgs:
2987             for updated in self.up.updating_dict.get(available_pkg.pkgtup, []):
2988                 if self.tsInfo.isObsoleted(updated):
2989                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
2990                                             updated)
2991                 elif self._newer_update_in_trans(updated, available_pkg):
2992                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already updated: %s.%s %s:%s-%s'), 
2993                                             updated)
2994                 
2995                 else:
2996                     updated_pkg =  self.rpmdb.searchPkgTuple(updated)[0]
2997                     txmbr = self.tsInfo.addUpdate(available_pkg, updated_pkg)
2998                     if requiringPo:
2999                         txmbr.setAsDep(requiringPo)
3000                     tx_return.append(txmbr)
3001                     
3002             # check to see if the pkg we want to install is not _quite_ the newest
3003             # one but still technically an update over what is installed.
3004             #FIXME - potentially do the comparables thing from what used to
3005             #        be in cli.installPkgs() to see what we should be comparing
3006             #        it to of what is installed. in the meantime name.arch is
3007             #        most likely correct
3008             pot_updated = self.rpmdb.searchNevra(name=available_pkg.name, arch=available_pkg.arch)
3009             if pot_updated and self.allowedMultipleInstalls(available_pkg):
3010                 # only compare against the newest of what's installed for kernel
3011                 pot_updated = sorted(pot_updated)[-1:]
3012
3013             for ipkg in pot_updated:
3014                 if self.tsInfo.isObsoleted(ipkg.pkgtup):
3015                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
3016                                             ipkg.pkgtup)
3017                 elif self._newer_update_in_trans(ipkg.pkgtup, available_pkg):
3018                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already updated: %s.%s %s:%s-%s'), 
3019                                             ipkg.pkgtup)
3020                 elif ipkg.verLT(available_pkg):
3021                     txmbr = self.tsInfo.addUpdate(available_pkg, ipkg)
3022                     if requiringPo:
3023                         txmbr.setAsDep(requiringPo)
3024                     tx_return.append(txmbr)
3025                 
3026                 #else:
3027                     #magically make allowdowngrade work here
3028                     # yum --allow-downgrade update something-specific here
3029                     # could work but we will need to be careful with it
3030                     # maybe a downgrade command is necessary
3031
3032         return tx_return
3033         
3034     def remove(self, po=None, **kwargs):
3035         """try to find and mark for remove the specified package(s) -
3036             if po is specified then that package object (if it is installed) 
3037             will be marked for removal.
3038             if no po then look at kwargs, if neither then raise an exception"""
3039
3040         if not po and not kwargs:
3041             raise Errors.RemoveError, 'Nothing specified to remove'
3042         
3043         tx_return = []
3044         pkgs = []
3045         
3046         
3047         if po:
3048             pkgs = [po]  
3049         else:
3050             if kwargs.has_key('pattern'):
3051                 if kwargs['pattern'][0] == '@':
3052                     return self._at_groupremove(kwargs['pattern'])
3053
3054                 (e,m,u) = self.rpmdb.matchPackageNames([kwargs['pattern']])
3055                 pkgs.extend(e)
3056                 pkgs.extend(m)
3057                 if u:
3058                     depmatches = []
3059                     arg = u[0]
3060                     try:
3061                         depmatches = self.returnInstalledPackagesByDep(arg)
3062                     except yum.Errors.YumBaseError, e:
3063                         self.logger.critical(_('%s') % e)
3064                     
3065                     if not depmatches:
3066                         self.logger.critical(_('No Match for argument: %s') % arg)
3067                     else:
3068                         pkgs.extend(depmatches)
3069                 
3070             else:    
3071                 nevra_dict = self._nevra_kwarg_parse(kwargs)
3072
3073                 pkgs = self.rpmdb.searchNevra(name=nevra_dict['name'], 
3074                             epoch=nevra_dict['epoch'], arch=nevra_dict['arch'], 
3075                             ver=nevra_dict['version'], rel=nevra_dict['release'])
3076
3077                 if len(pkgs) == 0:
3078                     if not kwargs.get('silence_warnings', False):
3079                         self.logger.warning(_("No package matched to remove"))
3080
3081         for po in pkgs:
3082             txmbr = self.tsInfo.addErase(po)
3083             tx_return.append(txmbr)
3084         
3085         return tx_return
3086
3087     def installLocal(self, pkg, po=None, updateonly=False):
3088         """
3089         handles installs/updates of rpms provided on the filesystem in a
3090         local dir (ie: not from a repo)
3091
3092         Return the added transaction members.
3093
3094         @param pkg: a path to an rpm file on disk.
3095         @param po: A YumLocalPackage
3096         @param updateonly: Whether or not true installs are valid.
3097         """
3098
3099         # read in the package into a YumLocalPackage Object
3100         # append it to self.localPackages
3101         # check if it can be installed or updated based on nevra versus rpmdb
3102         # don't import the repos until we absolutely need them for depsolving
3103
3104         tx_return = []
3105         installpkgs = []
3106         updatepkgs = []
3107         donothingpkgs = []
3108
3109         if not po:
3110             try:
3111                 po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg)
3112             except Errors.MiscError:
3113                 self.logger.critical(_('Cannot open file: %s. Skipping.'), pkg)
3114                 return tx_return
3115             self.verbose_logger.log(logginglevels.INFO_2,
3116                 _('Examining %s: %s'), po.localpath, po)
3117
3118         # if by any chance we're a noncompat arch rpm - bail and throw out an error
3119         # FIXME -our archlist should be stored somewhere so we don't have to
3120         # do this: but it's not a config file sort of thing
3121         # FIXME: Should add noarch, yum localinstall works ...
3122         # just rm this method?
3123         if po.arch not in self.arch.archlist:
3124             self.logger.critical(_('Cannot add package %s to transaction. Not a compatible architecture: %s'), pkg, po.arch)
3125             return tx_return
3126         
3127         # everything installed that matches the name
3128         installedByKey = self.rpmdb.searchNevra(name=po.name)
3129         # go through each package
3130         if len(installedByKey) == 0: # nothing installed by that name
3131             if updateonly:
3132                 self.logger.warning(_('Package %s not installed, cannot update it. Run yum install to install it instead.'), po.name)
3133                 return tx_return
3134             else:
3135                 installpkgs.append(po)
3136
3137         for installed_pkg in installedByKey:
3138             if po.verGT(installed_pkg): # we're newer - this is an update, pass to them
3139                 if installed_pkg.name in self.conf.exactarchlist:
3140                     if po.arch == installed_pkg.arch:
3141                         updatepkgs.append((po, installed_pkg))
3142                     else:
3143                         donothingpkgs.append(po)
3144                 else:
3145                     updatepkgs.append((po, installed_pkg))
3146             elif po.verEQ(installed_pkg):
3147                 if (po.arch != installed_pkg.arch and
3148                     (isMultiLibArch(po.arch) or
3149                      isMultiLibArch(installed_pkg.arch))):
3150                     installpkgs.append(po)
3151                 else:
3152                     donothingpkgs.append(po)
3153             elif self.allowedMultipleInstalls(po):
3154                 installpkgs.append(po)
3155             else:
3156                 donothingpkgs.append(po)
3157
3158         # handle excludes for a localinstall
3159         toexc = []
3160         if len(self.conf.exclude) > 0:
3161             exactmatch, matched, unmatched = \
3162                    parsePackages(installpkgs + map(lambda x: x[0], updatepkgs),
3163                                  self.conf.exclude, casematch=1)
3164             toexc = exactmatch + matched
3165
3166         if po in toexc:
3167             self.verbose_logger.debug(_('Excluding %s'), po)
3168             return tx_return
3169
3170         for po in installpkgs:
3171             self.verbose_logger.log(logginglevels.INFO_2,
3172                 _('Marking %s to be installed'), po.localpath)
3173             self.localPackages.append(po)
3174             tx_return.extend(self.install(po=po))
3175
3176         for (po, oldpo) in updatepkgs:
3177             self.verbose_logger.log(logginglevels.INFO_2,
3178                 _('Marking %s as an update to %s'), po.localpath, oldpo)
3179             self.localPackages.append(po)
3180             txmbrs = self.update(po=po)
3181             tx_return.extend(txmbrs)
3182
3183         for po in donothingpkgs:
3184             self.verbose_logger.log(logginglevels.INFO_2,
3185                 _('%s: does not update installed package.'), po.localpath)
3186
3187         return tx_return
3188
3189     def reinstallLocal(self, pkg, po=None):
3190         """
3191         handles reinstall of rpms provided on the filesystem in a
3192         local dir (ie: not from a repo)
3193
3194         Return the added transaction members.
3195
3196         @param pkg: a path to an rpm file on disk.
3197         @param po: A YumLocalPackage
3198         """
3199
3200         if not po:
3201             try:
3202                 po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg)
3203             except Errors.MiscError:
3204                 self.logger.critical(_('Cannot open file: %s. Skipping.'), pkg)
3205                 return []
3206             self.verbose_logger.log(logginglevels.INFO_2,
3207                 _('Examining %s: %s'), po.localpath, po)
3208
3209         if po.arch not in self.arch.archlist:
3210             self.logger.critical(_('Cannot add package %s to transaction. Not a compatible architecture: %s'), pkg, po.arch)
3211             return []
3212
3213         # handle excludes for a local reinstall
3214         toexc = []
3215         if len(self.conf.exclude) > 0:
3216             exactmatch, matched, unmatched = \
3217                    parsePackages([po], self.conf.exclude, casematch=1)
3218             toexc = exactmatch + matched
3219
3220         if po in toexc:
3221             self.verbose_logger.debug(_('Excluding %s'), po)
3222             return []
3223
3224         return self.reinstall(po=po)
3225
3226     def reinstall(self, po=None, **kwargs):
3227         """Setup the problem filters to allow a reinstall to work, then
3228            pass everything off to install"""
3229            
3230         self._add_prob_flags(rpm.RPMPROB_FILTER_REPLACEPKG,
3231                              rpm.RPMPROB_FILTER_REPLACENEWFILES,
3232                              rpm.RPMPROB_FILTER_REPLACEOLDFILES)
3233
3234         tx_mbrs = []
3235         if po: # The po, is the "available" po ... we want the installed po
3236             tx_mbrs.extend(self.remove(pkgtup=po.pkgtup))
3237         else:
3238             tx_mbrs.extend(self.remove(**kwargs))
3239         if not tx_mbrs:
3240             raise Errors.ReinstallRemoveError, _("Problem in reinstall: no package matched to remove")
3241         templen = len(tx_mbrs)
3242         # this is a reinstall, so if we can't reinstall exactly what we uninstalled
3243         # then we really shouldn't go on
3244         new_members = []
3245         for item in tx_mbrs:
3246             #FIXME future - if things in the rpm transaction handling get
3247             # a bit finer-grained, then we should allow reinstalls of kernels
3248             # for now, banned and dropped.
3249             if self.allowedMultipleInstalls(item.po):
3250                 self.tsInfo.remove(item.pkgtup)
3251                 tx_mbrs.remove(item)
3252                 msg = _("Package %s is allowed multiple installs, skipping") % item.po
3253                 self.verbose_logger.log(logginglevels.INFO_2, msg)
3254                 continue
3255             
3256             #  Make sure obsoletes processing is off, so we can reinstall()
3257             # pkgs that are obsolete.
3258             old_conf_obs = self.conf.obsoletes
3259             self.conf.obsoletes = False
3260             if isinstance(po, YumLocalPackage):
3261                 members = self.install(po=po)
3262             else:
3263                 members = self.install(name=item.name, arch=item.arch,
3264                                        ver=item.version, release=item.release,
3265                                        epoch=item.epoch)
3266             self.conf.obsoletes = old_conf_obs
3267             if len(members) == 0:
3268                 self.tsInfo.remove(item.pkgtup)
3269                 tx_mbrs.remove(item)
3270                 raise Errors.ReinstallInstallError, _("Problem in reinstall: no package %s matched to install") % item.po
3271             new_members.extend(members)
3272
3273         tx_mbrs.extend(new_members)
3274         return tx_mbrs
3275         
3276     def downgradeLocal(self, pkg, po=None):
3277         """
3278         handles downgrades of rpms provided on the filesystem in a
3279         local dir (ie: not from a repo)
3280
3281         Return the added transaction members.
3282
3283         @param pkg: a path to an rpm file on disk.
3284         @param po: A YumLocalPackage
3285         """
3286
3287         if not po:
3288             try:
3289                 po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg)
3290             except Errors.MiscError:
3291                 self.logger.critical(_('Cannot open file: %s. Skipping.'), pkg)
3292                 return []
3293             self.verbose_logger.log(logginglevels.INFO_2,
3294                 _('Examining %s: %s'), po.localpath, po)
3295
3296         if po.arch not in self.arch.archlist:
3297             self.logger.critical(_('Cannot add package %s to transaction. Not a compatible architecture: %s'), pkg, po.arch)
3298             return []
3299
3300         # handle excludes for a local downgrade
3301         toexc = []
3302         if len(self.conf.exclude) > 0:
3303             exactmatch, matched, unmatched = \
3304                    parsePackages([po], self.conf.exclude, casematch=1)
3305             toexc = exactmatch + matched
3306
3307         if po in toexc:
3308             self.verbose_logger.debug(_('Excluding %s'), po)
3309             return []
3310
3311         return self.downgrade(po=po)
3312
3313     def downgrade(self, po=None, **kwargs):
3314         """ Try to downgrade a package. Works like:
3315             % yum shell <<EOL
3316             remove  abcd
3317             install abcd-<old-version>
3318             run
3319             EOL """
3320
3321         if not po and not kwargs:
3322             raise Errors.DowngradeError, 'Nothing specified to remove'
3323
3324         doing_group_pkgs = False
3325         if po:
3326             apkgs = [po]
3327         elif 'pattern' in kwargs:
3328             if kwargs['pattern'][0] == '@':
3329                 apkgs = self._at_groupdowngrade(kwargs['pattern'])
3330                 doing_group_pkgs = True # Don't warn. about some things
3331             else:
3332                 apkgs = self.pkgSack.returnPackages(patterns=[kwargs['pattern']],
3333                                                    ignore_case=False)
3334                 if not apkgs:
3335                     arg = kwargs['pattern']
3336                     self.verbose_logger.debug(_('Checking for virtual provide or file-provide for %s'), 
3337                         arg)
3338
3339                     try:
3340                         apkgs = self.returnPackagesByDep(arg)
3341                     except yum.Errors.YumBaseError, e:
3342                         self.logger.critical(_('No Match for argument: %s') % arg)
3343
3344         else:
3345             nevra_dict = self._nevra_kwarg_parse(kwargs)
3346             apkgs = self.pkgSack.searchNevra(name=nevra_dict['name'], 
3347                                              epoch=nevra_dict['epoch'],
3348                                              arch=nevra_dict['arch'], 
3349                                              ver=nevra_dict['version'],
3350                                              rel=nevra_dict['release'])
3351         if not apkgs:
3352             # Do we still want to return errors here?
3353             # We don't in the cases below, so I didn't here...
3354             if 'pattern' in kwargs:
3355                 pkgs = self.rpmdb.returnPackages(patterns=[kwargs['pattern']],
3356                                                  ignore_case=False)
3357             if 'name' in kwargs:
3358                 pkgs = self.rpmdb.searchNevra(name=kwargs['name'])
3359             if pkgs:
3360                 return []
3361             raise Errors.DowngradeError, _('No package(s) available to downgrade')
3362
3363         warned_nas = set()
3364         # Skip kernel etc.
3365         tapkgs = []
3366         for pkg in apkgs:
3367             if self.allowedMultipleInstalls(pkg):
3368                 if (pkg.name, pkg.arch) not in warned_nas:
3369                     msg = _("Package %s is allowed multiple installs, skipping") % pkg
3370                     self.verbose_logger.log(logginglevels.INFO_2, msg)
3371                 warned_nas.add((pkg.name, pkg.arch))
3372                 continue
3373             tapkgs.append(pkg)
3374         apkgs = tapkgs
3375
3376         # Find installed versions of "to downgrade pkgs"
3377         apkg_names = set()
3378         for pkg in apkgs:
3379             apkg_names.add(pkg.name)
3380         ipkgs = self.rpmdb.searchNames(list(apkg_names))
3381
3382         latest_installed_na = {}
3383         latest_installed_n  = {}
3384         for pkg in ipkgs:
3385             latest_installed_n[pkg.name] = pkg
3386             latest_installed_na[(pkg.name, pkg.arch)] = pkg
3387
3388         #  Find "latest downgrade", ie. latest available pkg before
3389         # installed version.
3390         downgrade_apkgs = {}
3391         for pkg in sorted(apkgs):
3392             na  = (pkg.name, pkg.arch)
3393
3394             # Here we allow downgrades from .i386 => .noarch, or .i586 => .i386
3395             # but not .i386 => .x86_64 (similar to update).
3396             key = na
3397             latest_installed = latest_installed_na
3398             if pkg.name in latest_installed_n and na not in latest_installed_na:
3399                 if not canCoinstall(pkg.arch,latest_installed_n[pkg.name].arch):
3400                     key = pkg.name
3401                     latest_installed = latest_installed_n
3402
3403             if key not in latest_installed:
3404                 if na not in warned_nas and not doing_group_pkgs:
3405                     msg = _('No Match for available package: %s') % pkg
3406                     self.logger.critical(msg)
3407                 warned_nas.add(na)
3408                 continue
3409             if pkg.verGE(latest_installed[key]):
3410                 if na not in warned_nas:
3411                     msg = _('Only Upgrade available on package: %s') % pkg
3412                     self.logger.critical(msg)
3413                 warned_nas.add(na)
3414                 continue
3415             warned_nas.add(na)
3416             if (na in downgrade_apkgs and
3417                 pkg.verLE(downgrade_apkgs[na])):
3418                 continue # Skip older than "latest downgrade"
3419             downgrade_apkgs[na] = pkg
3420
3421         tx_return = []
3422         for po in ipkgs:
3423             na = (po.name, po.arch)
3424             if na not in downgrade_apkgs:
3425                 continue
3426             txmbrs = self.tsInfo.addDowngrade(downgrade_apkgs[na], po)
3427             if not txmbrs: # Fail?
3428                 continue
3429             self._add_prob_flags(rpm.RPMPROB_FILTER_OLDPACKAGE)
3430             tx_return.extend(txmbrs)
3431
3432         return tx_return
3433         
3434     def _nevra_kwarg_parse(self, kwargs):
3435             
3436         returndict = {}
3437         
3438         if 'pkgtup' in kwargs:
3439             (n, a, e, v, r) = kwargs['pkgtup']
3440             returndict['name'] = n
3441             returndict['epoch'] = e
3442             returndict['arch'] = a
3443             returndict['version'] = v
3444             returndict['release'] = r
3445             return returndict
3446
3447         returndict['name'] = kwargs.get('name')
3448         returndict['epoch'] = kwargs.get('epoch')
3449         returndict['arch'] = kwargs.get('arch')
3450         # get them as ver, version and rel, release - if someone
3451         # specifies one of each then that's kinda silly.
3452         returndict['version'] = kwargs.get('version')
3453         if returndict['version'] is None:
3454             returndict['version'] = kwargs.get('ver')
3455
3456         returndict['release'] = kwargs.get('release')
3457         if returndict['release'] is None:
3458             returndict['release'] = kwargs.get('rel')
3459
3460         return returndict
3461
3462     def _retrievePublicKey(self, keyurl, repo=None):
3463         """
3464         Retrieve a key file
3465         @param keyurl: url to the key to retrieve
3466         Returns a list of dicts with all the keyinfo
3467         """
3468         key_installed = False
3469
3470         self.logger.info(_('Retrieving GPG key from %s') % keyurl)
3471
3472         # Go get the GPG key from the given URL
3473         try:
3474             url = misc.to_utf8(keyurl)
3475             if repo is None:
3476                 rawkey = urlgrabber.urlread(url, limit=9999)
3477             else:
3478                 #  If we have a repo. use the proxy etc. configuration for it.
3479                 # In theory we have a global proxy config. too, but meh...
3480                 # external callers should just update.
3481                 ug = URLGrabber(bandwidth = repo.bandwidth,
3482                                 retry = repo.retries,
3483                                 throttle = repo.throttle,
3484                                 progress_obj = repo.callback,
3485                                 proxies=repo.proxy_dict)
3486                 ug.opts.user_agent = default_grabber.opts.user_agent
3487                 rawkey = ug.urlread(url, text=repo.id + "/gpgkey")
3488
3489         except urlgrabber.grabber.URLGrabError, e:
3490             raise Errors.YumBaseError(_('GPG key retrieval failed: ') +
3491                                       to_unicode(str(e)))
3492         # Parse the key
3493         keys_info = misc.getgpgkeyinfo(rawkey, multiple=True)
3494         keys = []
3495         for keyinfo in keys_info:
3496             thiskey = {}
3497             for info in ('keyid', 'timestamp', 'userid', 
3498                          'fingerprint', 'raw_key'):
3499                 if not keyinfo.has_key(info):
3500                     raise Errors.YumBaseError, \
3501                       _('GPG key parsing failed: key does not have value %s') + info
3502                 thiskey[info] = keyinfo[info]
3503             thiskey['hexkeyid'] = misc.keyIdToRPMVer(keyinfo['keyid']).upper()
3504             keys.append(thiskey)
3505         
3506         return keys
3507
3508     def getKeyForPackage(self, po, askcb = None, fullaskcb = None):
3509         """
3510         Retrieve a key for a package. If needed, prompt for if the key should
3511         be imported using askcb.
3512         
3513         @param po: Package object to retrieve the key of.
3514         @param askcb: Callback function to use for asking for verification.
3515                       Takes arguments of the po, the userid for the key, and
3516                       the keyid.
3517         @param fullaskcb: Callback function to use for asking for verification
3518                           of a key. Differs from askcb in that it gets passed
3519                           a dictionary so that we can expand the values passed.
3520         """
3521         repo = self.repos.getRepo(po.repoid)
3522         keyurls = repo.gpgkey
3523         key_installed = False
3524
3525         ts = self.rpmdb.readOnlyTS()
3526
3527         for keyurl in keyurls:
3528             keys = self._retrievePublicKey(keyurl, repo)
3529
3530             for info in keys:
3531                 # Check if key is already installed
3532                 if misc.keyInstalled(ts, info['keyid'], info['timestamp']) >= 0:
3533                     self.logger.info(_('GPG key at %s (0x%s) is already installed') % (
3534                         keyurl, info['hexkeyid']))
3535                     continue
3536
3537                 # Try installing/updating GPG key
3538                 self.logger.critical(_('Importing GPG key 0x%s "%s" from %s') %
3539                                      (info['hexkeyid'], 
3540                                       to_unicode(info['userid']),
3541                                       keyurl.replace("file://","")))
3542                 rc = False
3543                 if self.conf.assumeyes:
3544                     rc = True
3545                 elif fullaskcb:
3546                     rc = fullaskcb({"po": po, "userid": info['userid'],
3547                                     "hexkeyid": info['hexkeyid'], 
3548                                     "keyurl": keyurl,
3549                                     "fingerprint": info['fingerprint'],
3550                                     "timestamp": info['timestamp']})
3551                 elif askcb:
3552                     rc = askcb(po, info['userid'], info['hexkeyid'])
3553
3554                 if not rc:
3555                     raise Errors.YumBaseError, _("Not installing key")
3556                 
3557                 # Import the key
3558                 result = ts.pgpImportPubkey(misc.procgpgkey(info['raw_key']))
3559                 if result != 0:
3560                     raise Errors.YumBaseError, \
3561                           _('Key import failed (code %d)') % result
3562                 self.logger.info(_('Key imported successfully'))
3563                 key_installed = True
3564
3565                 if not key_installed:
3566                     raise Errors.YumBaseError, \
3567                           _('The GPG keys listed for the "%s" repository are ' \
3568                           'already installed but they are not correct for this ' \
3569                           'package.\n' \
3570                           'Check that the correct key URLs are configured for ' \
3571                           'this repository.') % (repo.name)
3572
3573         # Check if the newly installed keys helped
3574         result, errmsg = self.sigCheckPkg(po)
3575         if result != 0:
3576             self.logger.info(_("Import of key(s) didn't help, wrong key(s)?"))
3577             raise Errors.YumBaseError, errmsg
3578     
3579     def getKeyForRepo(self, repo, callback=None):
3580         """
3581         Retrieve a key for a repository If needed, prompt for if the key should
3582         be imported using callback
3583         
3584         @param repo: Repository object to retrieve the key of.
3585         @param callback: Callback function to use for asking for verification
3586                           of a key. Takes a dictionary of key info.
3587         """
3588         keyurls = repo.gpgkey
3589         key_installed = False
3590         for keyurl in keyurls:
3591             keys = self._retrievePublicKey(keyurl, repo)
3592             for info in keys:
3593                 # Check if key is already installed
3594                 if info['keyid'] in misc.return_keyids_from_pubring(repo.gpgdir):
3595                     self.logger.info(_('GPG key at %s (0x%s) is already imported') % (
3596                         keyurl, info['hexkeyid']))
3597                     continue
3598
3599                 # Try installing/updating GPG key
3600                 self.logger.critical(_('Importing GPG key 0x%s "%s" from %s') %
3601                                      (info['hexkeyid'], 
3602                                      to_unicode(info['userid']),
3603                                      keyurl.replace("file://","")))
3604                 rc = False
3605                 if self.conf.assumeyes:
3606                     rc = True
3607                 elif callback:
3608                     rc = callback({"repo": repo, "userid": info['userid'],
3609                                     "hexkeyid": info['hexkeyid'], "keyurl": keyurl,
3610                                     "fingerprint": info['fingerprint'],
3611                                     "timestamp": info['timestamp']})
3612
3613
3614                 if not rc:
3615                     raise Errors.YumBaseError, _("Not installing key for repo %s") % repo
3616                 
3617                 # Import the key
3618                 result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=repo.gpgdir)
3619                 if not result:
3620                     raise Errors.YumBaseError, _('Key import failed')
3621                 self.logger.info(_('Key imported successfully'))
3622                 key_installed = True
3623
3624                 if not key_installed:
3625                     raise Errors.YumBaseError, \
3626                           _('The GPG keys listed for the "%s" repository are ' \
3627                           'already installed but they are not correct for this ' \
3628                           'package.\n' \
3629                           'Check that the correct key URLs are configured for ' \
3630                           'this repository.') % (repo.name)
3631
3632
3633     def _limit_installonly_pkgs(self):
3634         """ Limit packages based on conf.installonly_limit, if any of the
3635             packages being installed have a provide in conf.installonlypkgs.
3636             New in 3.2.24: Obey yumdb_info.installonly data. """
3637
3638         def _sort_and_filter_installonly(pkgs):
3639             """ Allow the admin to specify some overrides fo installonly pkgs.
3640                 using the yumdb. """
3641             ret_beg = []
3642             ret_mid = []
3643             ret_end = []
3644             for pkg in sorted(pkgs):
3645                 if 'installonly' not in pkg.yumdb_info:
3646                     ret_mid.append(pkg)
3647                     continue
3648
3649                 if pkg.yumdb_info.installonly == 'keep':
3650                     continue
3651
3652                 if True: # Don't to magic sorting, yet
3653                     ret_mid.append(pkg)
3654
3655                 if pkg.yumdb_info.installonly == 'remove-first':
3656                     ret_beg.append(pkg)
3657                 elif pkg.yumdb_info.installonly == 'remove-last':
3658                     ret_end.append(pkg)
3659                 else:
3660                     ret_mid.append(pkg)
3661
3662             return ret_beg + ret_mid + ret_end
3663
3664         if self.conf.installonly_limit < 1 :
3665             return 
3666             
3667         toremove = []
3668         #  We "probably" want to use either self.ts or self.rpmdb.ts if either
3669         # is available. However each ts takes a ref. on signals generally, and
3670         # SIGINT specifically, so we _must_ have got rid of all of the used tses
3671         # before we try downloading. This is called from buildTransaction()
3672         # so self.rpmdb.ts should be valid.
3673         ts = self.rpmdb.readOnlyTS()
3674         (cur_kernel_v, cur_kernel_r) = misc.get_running_kernel_version_release(ts)
3675         for instpkg in self.conf.installonlypkgs:
3676             for m in self.tsInfo.getMembers():
3677                 if (m.name == instpkg or instpkg in m.po.provides_names) \
3678                        and m.ts_state in ('i', 'u'):
3679                     installed = self.rpmdb.searchNevra(name=m.name)
3680                     installed = _sort_and_filter_installonly(installed)
3681                     if len(installed) >= self.conf.installonly_limit - 1: # since we're adding one
3682                         numleft = len(installed) - self.conf.installonly_limit + 1
3683                         for po in installed:
3684                             if (po.version, po.release) == (cur_kernel_v, cur_kernel_r): 
3685                                 # don't remove running
3686                                 continue
3687                             if numleft == 0:
3688                                 break
3689                             toremove.append(po)
3690                             numleft -= 1
3691                         
3692         map(lambda x: self.tsInfo.addErase(x), toremove)
3693
3694     def processTransaction(self, callback=None,rpmTestDisplay=None, rpmDisplay=None):
3695         '''
3696         Process the current Transaction
3697         - Download Packages
3698         - Check GPG Signatures.
3699         - Run Test RPM Transaction
3700         - Run RPM Transaction
3701         
3702         callback.event method is called at start/end of each process.
3703         
3704         @param callback: callback object (must have an event method)
3705         @param rpmTestDisplay: Name of display class to use in RPM Test Transaction 
3706         @param rpmDisplay: Name of display class to use in RPM Transaction 
3707         '''
3708         
3709         if not callback:
3710             callback = callbacks.ProcessTransNoOutputCallback()
3711         
3712         # Download Packages
3713         callback.event(callbacks.PT_DOWNLOAD)
3714         pkgs = self._downloadPackages(callback)
3715         # Check Package Signatures
3716         if pkgs != None:
3717             callback.event(callbacks.PT_GPGCHECK)
3718             self._checkSignatures(pkgs,callback)
3719         # Run Test Transaction
3720         callback.event(callbacks.PT_TEST_TRANS)
3721         self._doTestTransaction(callback,display=rpmTestDisplay)
3722         # Run Transaction
3723         callback.event(callbacks.PT_TRANSACTION)
3724         self._doTransaction(callback,display=rpmDisplay)
3725     
3726     def _downloadPackages(self,callback):
3727         ''' Download the need packages in the Transaction '''
3728         # This can be overloaded by a subclass.    
3729         dlpkgs = map(lambda x: x.po, filter(lambda txmbr:
3730                                             txmbr.ts_state in ("i", "u"),
3731                                             self.tsInfo.getMembers()))
3732         # Check if there is something to do
3733         if len(dlpkgs) == 0:
3734             return None
3735         # make callback with packages to download                                    
3736         callback.event(callbacks.PT_DOWNLOAD_PKGS,dlpkgs)
3737         try:
3738             probs = self.downloadPkgs(dlpkgs)
3739
3740         except IndexError:
3741             raise Errors.YumBaseError, [_("Unable to find a suitable mirror.")]
3742         if len(probs) > 0:
3743             errstr = [_("Errors were encountered while downloading packages.")]
3744             for key in probs:
3745                 errors = misc.unique(probs[key])
3746                 for error in errors:
3747                     errstr.append("%s: %s" % (key, error))
3748
3749             raise Errors.YumDownloadError, errstr
3750         return dlpkgs
3751
3752     def _checkSignatures(self,pkgs,callback):
3753         ''' The the signatures of the downloaded packages '''
3754         # This can be overloaded by a subclass.    
3755         for po in pkgs:
3756             result, errmsg = self.sigCheckPkg(po)
3757             if result == 0:
3758                 # Verified ok, or verify not req'd
3759                 continue            
3760             elif result == 1:
3761                 self.getKeyForPackage(po, self._askForGPGKeyImport)
3762             else:
3763                 raise Errors.YumGPGCheckError, errmsg
3764
3765         return 0
3766         
3767     def _askForGPGKeyImport(self, po, userid, hexkeyid):
3768         ''' 
3769         Ask for GPGKeyImport 
3770         This need to be overloaded in a subclass to make GPG Key import work
3771         '''
3772         return False
3773     
3774     def _doTestTransaction(self,callback,display=None):
3775         ''' Do the RPM test transaction '''
3776         # This can be overloaded by a subclass.    
3777         if self.conf.rpm_check_debug:
3778             self.verbose_logger.log(logginglevels.INFO_2, 
3779                  _('Running rpm_check_debug'))
3780             msgs = self._run_rpm_check_debug()
3781             if msgs:
3782                 retmsgs = [_('ERROR with rpm_check_debug vs depsolve:')]
3783                 retmsgs.extend(msgs) 
3784                 retmsgs.append(_('Please report this error at %s') 
3785                                              % self.conf.bugtracker_url)