diff options
Diffstat (limited to 'apt')
| -rw-r--r-- | apt/cache.py | 144 | ||||
| -rw-r--r-- | apt/package.py | 97 | ||||
| -rw-r--r-- | apt/progress.py | 84 |
3 files changed, 276 insertions, 49 deletions
diff --git a/apt/cache.py b/apt/cache.py index 973291c0..690510e3 100644 --- a/apt/cache.py +++ b/apt/cache.py @@ -21,8 +21,9 @@ import apt_pkg from apt import Package -from apt.progress import OpTextProgress -from UserDict import UserDict +import apt.progress +import os +import sys class Cache(object): """ Dictionary-like package cache @@ -30,10 +31,14 @@ class Cache(object): dictionary """ - def __init__(self, progress=None): + def __init__(self, progress=None, rootdir=None): self._callbacks = {} self.open(progress) + if rootdir: + apt_pkg.Config.Set("Dir", rootdir) + apt_pkg.Config.Set("Dir::State::status", rootdir + "/var/lib/dpkg/status") + def _runCallbacks(self, name): """ internal helper to run a callback """ if self._callbacks.has_key(name): @@ -48,6 +53,8 @@ class Cache(object): self._cache = apt_pkg.GetCache(progress) self._depcache = apt_pkg.GetDepCache(self._cache) self._records = apt_pkg.GetPkgRecords(self._cache) + self._list = apt_pkg.GetPkgSourceList() + self._list.ReadMainList() self._dict = {} # build the packages dict @@ -62,7 +69,8 @@ class Cache(object): # drop stuff with no versions (cruft) if len(pkg.VersionList) > 0: self._dict[pkg.Name] = Package(self._cache, self._depcache, - self._records, self, pkg) + self._records, self._list, + self, pkg) i += 1 if progress != None: @@ -79,11 +87,7 @@ class Cache(object): raise StopIteration def has_key(self, key): - try: - self._dict[key] - except KeyError: - return False - return True + return self._dict.has_key(key) def __len__(self): return len(self._dict) @@ -109,15 +113,102 @@ class Cache(object): self._depcache.Upgrade(distUpgrade) self.cachePostChange() - def update(self, fetchProgress=None, opProgress=None): - if(opProgress != None): - self._cache.Update(fetchProgress, opProgress); - else: - self._cache.Update(fetchProgress); + def _runFetcher(self, fetcher): + # do the actual fetching + res = fetcher.Run() + + # now check the result (this is the code from apt-get.cc) + failed = False + transient = False + errMsg = "" + for item in fetcher.Items: + if item.Status == item.StatDone: + continue + if item.StatIdle: + transient = True + continue + errMsg += "Failed to fetch %s %s\n" % (item.DescURI,item.ErrorText) + failed = True + + # we raise a exception if the download failed + if failed: + raise IOError, errMsg + return res + + def _fetchArchives(self, fetcher, pm): + """ fetch the needed archives """ + + # get lock + lockfile = apt_pkg.Config.FindDir("Dir::Cache::Archives") + "lock" + lock = apt_pkg.GetLock(lockfile) + if lock < 0: + raise IOError, "Failed to lock %s" % lockfile + + try: + # this may as well throw a SystemError exception + if not pm.GetArchives(fetcher, self._list, self._records): + return False + # now run the fetcher, throw exception if something fails to be + # fetched + return self._runFetcher(fetcher) + finally: + os.close(lock) + + def update(self, fetchProgress=None): + lockfile = apt_pkg.Config.FindDir("Dir::State::Lists") + "lock" + lock = apt_pkg.GetLock(lockfile) + if lock < 0: + raise IOError, "Failed to lock %s" % lockfile - def commit(self, fprogress, iprogress): + try: + if fetchProgress == None: + fetchProgress = apt.progress.FetchProgress() + fetcher = apt_pkg.GetAcquire(fetchProgress) + # this can throw a exception + self._list.GetIndexes(fetcher) + # now run the fetcher, throw exception if something fails to be + # fetched + if self._runFetcher(fetcher) == fetcher.ResultContinue: + return True + return False + finally: + os.close(lock) + + def installArchives(self, pm, installProgress): + installProgress.startUpdate() + res = installProgress.run(pm) + installProgress.finishUpdate() + return res + + def commit(self, fetchProgress=None, installProgress=None): """ Apply the marked changes to the cache """ - self._depcache.Commit(fprogress, iprogress) + # FIXME: + # use the new acquire/pkgmanager interface here, + # raise exceptions when a download or install fails + # and send proper error strings to the application. + # Current a failed download will just display "error" + # which is less than optimal! + + if fetchProgress == None: + fetchProgress = apt.progress.FetchProgress() + if installProgress == None: + installProgress = apt.progress.InstallProgress() + + pm = apt_pkg.GetPackageManager(self._depcache) + fetcher = apt_pkg.GetAcquire(fetchProgress) + while True: + # fetch archives first + res = self._fetchArchives(fetcher, pm) + + # then install + res = self.installArchives(pm, installProgress) + if res == pm.ResultCompleted: + break + if res == pm.ResultFailed: + raise SystemError, "installArchives() failed" + # reload the fetcher for media swaping + fetcher.Shutdown() + return (res == pm.ResultCompleted) # cache changes def cachePostChange(self): @@ -131,7 +222,7 @@ class Cache(object): def connect(self, name, callback): """ connect to a signal, currently only used for - cache_{post,pre}_changed """ + cache_{post,pre}_{changed,open} """ if not self._callbacks.has_key(name): self._callbacks[name] = [] self._callbacks[name].append(callback) @@ -177,11 +268,7 @@ class FilteredCache(object): return self._filtered.keys() def has_key(self, key): - try: - self._filtered[key] - except KeyError: - return False - return True + return self._filtered.has_key(key) def _reapplyFilter(self): " internal helper to refilter " @@ -228,7 +315,7 @@ def cache_post_changed(): if __name__ == "__main__": print "Cache self test" apt_pkg.init() - c = Cache(OpTextProgress()) + c = Cache(apt.progress.OpTextProgress()) c.connect("cache_pre_change", cache_pre_changed) c.connect("cache_post_change", cache_post_changed) print c.has_key("aptitude") @@ -246,6 +333,17 @@ if __name__ == "__main__": #print p.name x = p.name + + # see if fetching works + for d in ["/tmp/pytest", "/tmp/pytest/partial"]: + if not os.path.exists(d): + os.mkdir(d) + apt_pkg.Config.Set("Dir::Cache::Archives","/tmp/pytest") + pm = apt_pkg.GetPackageManager(c._depcache) + fetcher = apt_pkg.GetAcquire(apt.progress.TextFetchProgress()) + c._fetchArchives(fetcher, pm) + #sys.exit(1) + print "Testing filtered cache (argument is old cache)" f = FilteredCache(c) f.cache.connect("cache_pre_change", cache_pre_changed) diff --git a/apt/package.py b/apt/package.py index 9749da52..0d1145ea 100644 --- a/apt/package.py +++ b/apt/package.py @@ -19,18 +19,22 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -import apt_pkg, string +import apt_pkg +import sys import random +import string + class Package(object): """ This class represents a package in the cache """ - def __init__(self, cache, depcache, records, pcache, pkgiter): + def __init__(self, cache, depcache, records, sourcelist, pcache, pkgiter): """ Init the Package object """ self._cache = cache # low level cache self._depcache = depcache self._records = records self._pkg = pkgiter + self._list = sourcelist # sourcelist self._pcache = pcache # python cache in cache.py pass @@ -45,14 +49,14 @@ class Package(object): # check if we found a version if ver == None: - print "No version for: %s (Candiate: %s)" % (self._pkg.Name, UseCandidate) + #print "No version for: %s (Candidate: %s)" % (self._pkg.Name, UseCandidate) return False if ver.FileList == None: print "No FileList for: %s " % self._pkg.Name() return False - file, index = ver.FileList.pop(0) - self._records.Lookup((file,index)) + f, index = ver.FileList.pop(0) + self._records.Lookup((f,index)) return True @@ -90,9 +94,29 @@ class Package(object): return None candidateVersion = property(candidateVersion) + def _downloadable(self, useCandidate=True): + """ helper, return if the version is downloadable """ + if useCandidate: + ver = self._depcache.GetCandidateVer(self._pkg) + else: + ver = self._pkg.CurrentVer + if ver == None: + return False + return ver.Downloadable + def candidateDownloadable(self): + " returns if the canidate is downloadable " + return self._downloadable(useCandidate=True) + candidateDownloadable = property(candidateDownloadable) + + def installedDownloadable(self): + " returns if the installed version is downloadable " + return self._downloadable(useCandidate=False) + installedDownloadable = property(installedDownloadable) + def sourcePackageName(self): """ Return the source package name as string """ - self._lookupRecord() + if not self._lookupRecord(): + return None src = self._records.SourcePkg if src != "": return src @@ -125,13 +149,15 @@ class Package(object): def summary(self): """ Return the short description (one line summary) """ - self._lookupRecord() + if not self._lookupRecord(): + return "" return self._records.ShortDesc summary = property(summary) def description(self, format=True): """ Return the formated long description """ - self._lookupRecord() + if not self._lookupRecord(): + return "" desc = "" for line in string.split(self._records.LongDesc, "\n"): tmp = string.strip(line) @@ -144,7 +170,8 @@ class Package(object): def rawDescription(self): """ return the long description (raw)""" - self._lookupRecord() + if not self._lookupRecord(): + return "" return self._records.LongDesc rawDescription = property(rawDescription) @@ -215,12 +242,33 @@ class Package(object): installedSize = property(installedSize) # canidate origin + class Origin: + def __init__(self, pkg, VerFileIter): + self.component = VerFileIter.Component + self.archive = VerFileIter.Archive + self.origin = VerFileIter.Origin + self.label = VerFileIter.Label + self.site = VerFileIter.Site + # check the trust + indexfile = pkg._list.FindIndex(VerFileIter) + if indexfile and indexfile.IsTrusted: + self.trusted = True + else: + self.trusted = False + def __repr__(self): + return "component: '%s' archive: '%s' origin: '%s' label: '%s' " \ + "site '%s' isTrusted: '%s'"% (self.component, self.archive, + self.origin, self.label, + self.site, self.trusted) + def candidateOrigin(self): ver = self._depcache.GetCandidateVer(self._pkg) - (VerFileIter,index) = ver.FileList.pop() - print len(VerFileIter) - print VerFileIter - return VerFileIter.Component + if not ver: + return None + origins = [] + for (verFileIter,index) in ver.FileList: + origins.append(self.Origin(self, verFileIter)) + return origins candidateOrigin = property(candidateOrigin) # depcache actions @@ -242,10 +290,12 @@ class Package(object): Fix.InstallProtect() Fix.Resolve() self._pcache.cachePostChange() - def markInstall(self, autoFix=True): - """ mark a package for install. Run the resolver if autoFix is set """ + def markInstall(self, autoFix=True, autoInst=True): + """ mark a package for install. Run the resolver if autoFix is set, + automatically install required dependencies if autoInst is set + """ self._pcache.cachePreChange() - self._depcache.MarkInstall(self._pkg) + self._depcache.MarkInstall(self._pkg, autoInst) # try to fix broken stuff if autoFix and self._depcache.BrokenCount > 0: fixer = apt_pkg.GetPkgProblemResolver(self._depcache) @@ -256,9 +306,10 @@ class Package(object): def markUpgrade(self): """ mark a package for upgrade """ if self.isUpgradable: - self.MarkInstall() - # FIXME: we may want to throw a exception here - sys.stderr.write("MarkUpgrade() called on a non-upgrable pkg") + self.markInstall() + else: + # FIXME: we may want to throw a exception here + sys.stderr.write("MarkUpgrade() called on a non-upgrable pkg: '%s'\n" %self._pkg.Name) def commit(self, fprogress, iprogress): """ commit the changes, need a FetchProgress and InstallProgress @@ -274,16 +325,18 @@ if __name__ == "__main__": cache = apt_pkg.GetCache() depcache = apt_pkg.GetDepCache(cache) records = apt_pkg.GetPkgRecords(cache) + sourcelist = apt_pkg.GetPkgSourceList() - iter = cache["apt-utils"] - pkg = Package(cache, depcache, records, None, iter) + pkgiter = cache["apt-utils"] + pkg = Package(cache, depcache, records, sourcelist, None, pkgiter) print "Name: %s " % pkg.name print "ID: %s " % pkg.id print "Priority (Candidate): %s " % pkg.priority print "Priority (Installed): %s " % pkg.installedPriority print "Installed: %s " % pkg.installedVersion print "Candidate: %s " % pkg.candidateVersion - print "CandiateOrigin: %s" % pkg.candidateOrigin + print "CandidateDownloadable: %s" % pkg.candidateDownloadable + print "CandidateOrigins: %s" % pkg.candidateOrigin print "SourcePkg: %s " % pkg.sourcePackageName print "Section: %s " % pkg.section print "Summary: %s" % pkg.summary diff --git a/apt/progress.py b/apt/progress.py index 86cd6594..4119067c 100644 --- a/apt/progress.py +++ b/apt/progress.py @@ -19,9 +19,9 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -import sys +import sys, apt_pkg, os, fcntl, string, re -class OpProgress: +class OpProgress(object): """ Abstract class to implement reporting on cache opening Subclass this class to implement simple Operation progress reporting """ @@ -62,6 +62,8 @@ class FetchProgress(object): dlIgnored : "Ignored"} def __init__(self): + self.eta = 0.0 + self.percent = 0.0 pass def start(self): @@ -104,7 +106,7 @@ class TextFetchProgress(FetchProgress): sys.stdout.flush() return True def stop(self): - print "\rDone " + print "\rDone downloading " def mediaChange(self, medium, drive): """ react to media change events """ res = True; @@ -115,7 +117,7 @@ class TextFetchProgress(FetchProgress): res = false; return res -class InstallProgress: +class DumbInstallProgress(object): """ Report the install progress Subclass this class to implement install progress reporting """ @@ -123,11 +125,85 @@ class InstallProgress: pass def startUpdate(self): pass + def run(self, pm): + return pm.DoInstall() def finishUpdate(self): pass def updateInterface(self): pass +class InstallProgress(DumbInstallProgress): + """ A InstallProgress that is pretty useful. + It supports the attributes 'percent' 'status' and callbacks + for the dpkg errors and conffiles and status changes + """ + def __init__(self): + DumbInstallProgress.__init__(self) + (read, write) = os.pipe() + self.writefd=write + self.statusfd = os.fdopen(read, "r") + fcntl.fcntl(self.statusfd.fileno(), fcntl.F_SETFL,os.O_NONBLOCK) + self.read = "" + self.percent = 0.0 + self.status = "" + def error(self, pkg, errormsg): + " called when a error is detected during the install " + pass + def conffile(self,current,new): + " called when a conffile question from dpkg is detected " + pass + def statusChange(self, pkg, percent, status): + " called when the status changed " + pass + def updateInterface(self): + if self.statusfd != None: + try: + while not self.read.endswith("\n"): + self.read += os.read(self.statusfd.fileno(),1) + except OSError, (errno,errstr): + # resource temporarly unavailable is ignored + if errno != 11: + print errstr + if self.read.endswith("\n"): + s = self.read + #print s + (status, pkg, percent, status_str) = string.split(s, ":") + #print "percent: %s %s" % (pkg, float(percent)/100.0) + if status == "pmerror": + self.error(pkg,status_str) + elif status == "pmconffile": + # we get a string like this: + # 'current-conffile' 'new-conffile' useredited distedited + match = re.compile("\s*\'(.*)\'\s*\'(.*)\'.*").match(status_str) + if match: + self.conffile(match.group(1), match.group(2)) + elif status == "pmstatus": + if float(percent) != self.percent or \ + status_str != self.status: + self.statusChange(pkg, float(percent), status_str.strip()) + self.percent = float(percent) + self.status = string.strip(status_str) + self.read = "" + + def fork(self): + return os.fork() + def waitChild(self): + while True: + (pid, res) = os.waitpid(self.child_pid,os.WNOHANG) + if pid == self.child_pid: + break + self.updateInterface() + return os.WEXITSTATUS(res) + def run(self, pm): + pid = self.fork() + if pid == 0: + # child + res = pm.DoInstall(self.writefd) + sys.exit(res) + self.child_pid = pid + res = self.waitChild() + return res + class CdromProgress: """ Report the cdrom add progress Subclass this class to implement cdrom add progress reporting |
