summaryrefslogtreecommitdiff
path: root/apt
diff options
context:
space:
mode:
Diffstat (limited to 'apt')
-rw-r--r--apt/cache.py144
-rw-r--r--apt/package.py97
-rw-r--r--apt/progress.py84
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