diff options
| author | Michael Vogt <mvo@debian.org> | 2008-12-15 14:42:01 +0100 |
|---|---|---|
| committer | Michael Vogt <mvo@debian.org> | 2008-12-15 14:42:01 +0100 |
| commit | 12cf58d12b969010f3d98b2974d72bbb950b775f (patch) | |
| tree | dc7f0af010aefa0b76a256f594d6dc4ae3163aaa | |
| parent | be8fa1ee4581faeadfb6d067301ee9e7761dc1c7 (diff) | |
| parent | 481a90a820564dc12567348c16773e15cc129560 (diff) | |
| download | python-apt-12cf58d12b969010f3d98b2974d72bbb950b775f.tar.gz | |
merge mvo branch, upload to experimental
| -rw-r--r-- | apt/cache.py | 37 | ||||
| -rw-r--r-- | apt/debfile.py | 508 | ||||
| -rw-r--r-- | apt/gtk/__init__.py | 0 | ||||
| -rw-r--r-- | apt/gtk/widgets.py | 394 | ||||
| -rw-r--r-- | apt/package.py | 204 | ||||
| -rw-r--r-- | apt/progress.py | 58 | ||||
| -rw-r--r-- | debian/changelog | 33 | ||||
| -rw-r--r-- | debian/control | 2 | ||||
| -rw-r--r-- | debian/python-apt.docs | 1 | ||||
| -rwxr-xr-x | debian/rules | 3 | ||||
| -rwxr-xr-x | doc/examples/gui-inst.py | 130 | ||||
| -rw-r--r-- | po/POTFILES.in | 1 | ||||
| -rw-r--r-- | po/python-apt.pot | 145 | ||||
| -rwxr-xr-x | setup.py | 31 |
14 files changed, 1356 insertions, 191 deletions
diff --git a/apt/cache.py b/apt/cache.py index bbf2165b..79e58282 100644 --- a/apt/cache.py +++ b/apt/cache.py @@ -129,6 +129,17 @@ class Cache(object): self.cachePostChange() @property + def requiredDownload(self): + """ get the size of the packages that are required to download """ + pm = apt_pkg.GetPackageManager(self._depcache) + fetcher = apt_pkg.GetAcquire() + pm.GetArchives(fetcher, self._list, self._records) + return fetcher.FetchNeeded + @property + def additionalRequiredSpace(self): + """ get the size of the additional required space on the fs """ + return self._depcache.UsrSize + @property def reqReinstallPkgs(self): " return the packages not downloadable packages in reqreinst state " reqreinst = set() @@ -182,6 +193,28 @@ class Cache(object): finally: os.close(lock) + def getProvidingPackages(self, virtual): + """ + Return a list of packages which provide the virtual package of the + specified name + """ + providers = [] + try: + vp = self._cache[virtual] + if len(vp.VersionList) != 0: + return providers + except KeyError: + return providers + for pkg in self: + v = self._depcache.GetCandidateVer(pkg._pkg) + if v == None: + continue + for p in v.ProvidesList: + if virtual == p[0]: + # we found a pkg that provides this virtual pkg + providers.append(pkg) + return providers + def update(self, fetchProgress=None): " run the equivalent of apt-get update " lockfile = apt_pkg.Config.FindDir("Dir::State::Lists") + "lock" @@ -232,6 +265,10 @@ class Cache(object): fetcher.Shutdown() return (res == pm.ResultCompleted) + def clear(self): + """ Unmark all changes """ + self._depcache.Init() + # cache changes def cachePostChange(self): " called internally if the cache has changed, emit a signal then " diff --git a/apt/debfile.py b/apt/debfile.py index ddde5bf1..b1d436cd 100644 --- a/apt/debfile.py +++ b/apt/debfile.py @@ -1,9 +1,38 @@ -import apt_inst -import apt_pkg -from apt_inst import arCheckMember +# Copyright (c) 2005-2007 Canonical +# +# AUTHOR: +# Michael Vogt <mvo@ubuntu.com> +# +# This file is part of GDebi +# +# GDebi is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, or (at +# your option) any later version. +# +# GDebi is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GDebi; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +import warnings +warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning) +import apt_inst, apt_pkg +import sys +import os from gettext import gettext as _ +# Constants for comparing the local package file with the version in the cache +(VERSION_NONE, + VERSION_OUTDATED, + VERSION_SAME, + VERSION_NEWER) = range(4) + class NoDebArchiveException(IOError): pass @@ -11,15 +40,22 @@ class DebPackage(object): _supported_data_members = ("data.tar.gz", "data.tar.bz2", "data.tar.lzma") - def __init__(self, filename=None): - self._section = {} + debug = 0 + + def __init__(self, filename=None, cache=None): + self._cache = cache + self.file = filename + self._needPkgs = [] + self._sections = {} + self._installedConflicts = set() + self._failureString = "" if filename: self.open(filename) def open(self, filename): " open given debfile " self.filename = filename - if not arCheckMember(open(self.filename), "debian-binary"): + if not apt_inst.arCheckMember(open(self.filename), "debian-binary"): raise NoDebArchiveException, _("This is not a valid DEB archive, missing '%s' member" % "debian-binary") control = apt_inst.debExtractControl(open(self.filename)) self._sections = apt_pkg.ParseSection(control) @@ -36,7 +72,7 @@ class DebPackage(object): # % (What,Name,Link,Mode,UID,GID,Size, MTime, Major, Minor) files.append(Name) for member in self._supported_data_members: - if arCheckMember(open(self.filename), member): + if apt_inst.arCheckMember(open(self.filename), member): try: apt_inst.debExtract(open(self.filename), extract_cb, member) break @@ -45,14 +81,458 @@ class DebPackage(object): return files filelist = property(filelist) + def _isOrGroupSatisfied(self, or_group): + """ this function gets a 'or_group' and analyzes if + at least one dependency of this group is already satisfied """ + self._dbg(2,"_checkOrGroup(): %s " % (or_group)) + + for dep in or_group: + depname = dep[0] + ver = dep[1] + oper = dep[2] + + # check for virtual pkgs + if not self._cache.has_key(depname): + if self._cache.isVirtualPackage(depname): + self._dbg(3,"_isOrGroupSatisfied(): %s is virtual dep" % depname) + for pkg in self._cache.getProvidingPackages(depname): + if pkg.isInstalled: + return True + continue + + inst = self._cache[depname] + instver = inst.installedVersion + if instver != None and apt_pkg.CheckDep(instver,oper,ver) == True: + return True + return False + + + def _satisfyOrGroup(self, or_group): + """ try to satisfy the or_group """ + + or_found = False + virtual_pkg = None + + for dep in or_group: + depname = dep[0] + ver = dep[1] + oper = dep[2] + + # if we don't have it in the cache, it may be virtual + if not self._cache.has_key(depname): + if not self._cache.isVirtualPackage(depname): + continue + providers = self._cache.getProvidingPackages(depname) + # if a package just has a single virtual provider, we + # just pick that (just like apt) + if len(providers) != 1: + continue + depname = providers[0].name + + # now check if we can satisfy the deps with the candidate(s) + # in the cache + cand = self._cache[depname] + candver = self._cache._depcache.GetCandidateVer(cand._pkg) + if not candver: + continue + if not apt_pkg.CheckDep(candver.VerStr,oper,ver): + continue + + # check if we need to install it + self._dbg(2,"Need to get: %s" % depname) + self._needPkgs.append(depname) + return True + + # if we reach this point, we failed + or_str = "" + for dep in or_group: + or_str += dep[0] + if dep != or_group[len(or_group)-1]: + or_str += "|" + self._failureString += _("Dependency is not satisfiable: %s\n" % or_str) + return False + + def _checkSinglePkgConflict(self, pkgname, ver, oper): + """ returns true if a pkg conflicts with a real installed/marked + pkg """ + # FIXME: deal with conflicts against its own provides + # (e.g. Provides: ftp-server, Conflicts: ftp-server) + self._dbg(3, "_checkSinglePkgConflict() pkg='%s' ver='%s' oper='%s'" % (pkgname, ver, oper)) + pkgver = None + cand = self._cache[pkgname] + if cand.isInstalled: + pkgver = cand.installedVersion + elif cand.markedInstall: + pkgver = cand.candidateVersion + #print "pkg: %s" % pkgname + #print "ver: %s" % ver + #print "pkgver: %s " % pkgver + #print "oper: %s " % oper + if (pkgver and apt_pkg.CheckDep(pkgver,oper,ver) and + not self.replacesRealPkg(pkgname, oper, ver)): + self._failureString += _("Conflicts with the installed package '%s'" % cand.name) + return True + return False + + def _checkConflictsOrGroup(self, or_group): + """ check the or-group for conflicts with installed pkgs """ + self._dbg(2,"_checkConflictsOrGroup(): %s " % (or_group)) + + or_found = False + virtual_pkg = None + + for dep in or_group: + depname = dep[0] + ver = dep[1] + oper = dep[2] + + # check conflicts with virtual pkgs + if not self._cache.has_key(depname): + # FIXME: we have to check for virtual replaces here as + # well (to pass tests/gdebi-test8.deb) + if self._cache.isVirtualPackage(depname): + for pkg in self._cache.getProvidingPackages(depname): + self._dbg(3, "conflicts virtual check: %s" % pkg.name) + # P/C/R on virtal pkg, e.g. ftpd + if self.pkgName == pkg.name: + self._dbg(3, "conflict on self, ignoring") + continue + if self._checkSinglePkgConflict(pkg.name,ver,oper): + self._installedConflicts.add(pkg.name) + continue + if self._checkSinglePkgConflict(depname,ver,oper): + self._installedConflicts.add(depname) + return len(self._installedConflicts) != 0 + + def getConflicts(self): + """ + Return list of package names conflicting with this package. + + WARNING: This method will is deprecated. Please use the + attribute DebPackage.depends instead. + """ + return self.conflicts + + def conflicts(self): + """ + List of package names conflicting with this package + """ + conflicts = [] + key = "Conflicts" + if self._sections.has_key(key): + conflicts = apt_pkg.ParseDepends(self._sections[key]) + return conflicts + conflicts = property(conflicts) + + def getDepends(self): + """ + Return list of package names on which this package depends on. + + WARNING: This method will is deprecated. Please use the + attribute DebPackage.depends instead. + """ + return self.depends + + def depends(self): + """ + List of package names on which this package depends on + """ + depends = [] + # find depends + for key in ["Depends","PreDepends"]: + if self._sections.has_key(key): + depends.extend(apt_pkg.ParseDepends(self._sections[key])) + return depends + depends = property(depends) + + def getProvides(self): + """ + Return list of virtual packages which are provided by this package. + + WARNING: This method will is deprecated. Please use the + attribute DebPackage.provides instead. + """ + return self.provides + + def provides(self): + """ + List of virtual packages which are provided by this package + """ + provides = [] + key = "Provides" + if self._sections.has_key(key): + provides = apt_pkg.ParseDepends(self._sections[key]) + return provides + provides = property(provides) + + def getReplaces(self): + """ + Return list of packages which are replaced by this package. + + WARNING: This method will is deprecated. Please use the + attribute DebPackage.replaces instead. + """ + return self.replaces + + def replaces(self): + """ + List of packages which are replaced by this package + """ + replaces = [] + key = "Replaces" + if self._sections.has_key(key): + replaces = apt_pkg.ParseDepends(self._sections[key]) + return replaces + replaces = property(replaces) + def replacesRealPkg(self, pkgname, oper, ver): + """ + return True if the deb packages replaces a real (not virtual) + packages named pkgname, oper, ver + """ + self._dbg(3, "replacesPkg() %s %s %s" % (pkgname,oper,ver)) + pkgver = None + cand = self._cache[pkgname] + if cand.isInstalled: + pkgver = cand.installedVersion + elif cand.markedInstall: + pkgver = cand.candidateVersion + for or_group in self.getReplaces(): + for (name, ver, oper) in or_group: + if (name == pkgname and + apt_pkg.CheckDep(pkgver,oper,ver)): + self._dbg(3, "we have a replaces in our package for the conflict against '%s'" % (pkgname)) + return True + return False + + def checkConflicts(self): + """ check if the pkg conflicts with a existing or to be installed + package. Return True if the pkg is ok """ + res = True + for or_group in self.getConflicts(): + if self._checkConflictsOrGroup(or_group): + #print "Conflicts with a exisiting pkg!" + #self._failureString = "Conflicts with a exisiting pkg!" + res = False + return res + + + def compareToVersionInCache(self, useInstalled=True): + """ checks if the pkg is already installed or availabe in the cache + and if so in what version, returns if the version of the deb + is not available,older,same,newer + """ + self._dbg(3,"compareToVersionInCache") + pkgname = self._sections["Package"] + debver = self._sections["Version"] + self._dbg(1,"debver: %s" % debver) + if self._cache.has_key(pkgname): + if useInstalled: + cachever = self._cache[pkgname].installedVersion + else: + cachever = self._cache[pkgname].candidateVersion + if cachever != None: + cmp = apt_pkg.VersionCompare(cachever,debver) + self._dbg(1, "CompareVersion(debver,instver): %s" % cmp) + if cmp == 0: + return VERSION_SAME + elif cmp < 0: + return VERSION_NEWER + elif cmp > 0: + return VERSION_OUTDATED + return VERSION_NONE + + def checkDeb(self): + self._dbg(3,"checkDepends") + + # check arch + arch = self._sections["Architecture"] + if arch != "all" and arch != apt_pkg.Config.Find("APT::Architecture"): + self._dbg(1,"ERROR: Wrong architecture dude!") + self._failureString = _("Wrong architecture '%s'" % arch) + return False + + # check version + res = self.compareToVersionInCache() + if res == VERSION_OUTDATED: # the deb is older than the installed + self._failureString = _("A later version is already installed") + return False + + # FIXME: this sort of error handling sux + self._failureString = "" + + # check conflicts + if not self.checkConflicts(): + return False + + # try to satisfy the dependencies + res = self._satisfyDepends(self.getDepends()) + if not res: + return False + + # check for conflicts again (this time with the packages that are + # makeed for install) + if not self.checkConflicts(): + return False + + if self._cache._depcache.BrokenCount > 0: + self._failureString = _("Failed to satisfy all dependencies (broken cache)") + # clean the cache again + self._cache.clear() + return False + return True + + def satisfyDependsStr(self, dependsstr): + return self._satisfyDepends(apt_pkg.ParseDepends(dependsstr)) + + def _satisfyDepends(self, depends): + # turn off MarkAndSweep via a action group (if available) + try: + _actiongroup = apt_pkg.GetPkgActionGroup(self._cache._depcache) + except AttributeError, e: + pass + # check depends + for or_group in depends: + #print "or_group: %s" % or_group + #print "or_group satified: %s" % self._isOrGroupSatisfied(or_group) + if not self._isOrGroupSatisfied(or_group): + if not self._satisfyOrGroup(or_group): + return False + # now try it out in the cache + for pkg in self._needPkgs: + try: + self._cache[pkg].markInstall(fromUser=False) + except SystemError, e: + self._failureString = _("Cannot install '%s'" % pkg) + self._cache.clear() + return False + return True + + def missingDeps(self): + self._dbg(1, "Installing: %s" % self._needPkgs) + if self._needPkgs == None: + self.checkDeb() + return self._needPkgs + missingDeps = property(missingDeps) + + def requiredChanges(self): + """ gets the required changes to satisfy the depends. + returns a tuple with (install, remove, unauthenticated) + """ + install = [] + remove = [] + unauthenticated = [] + for pkg in self._cache: + if pkg.markedInstall or pkg.markedUpgrade: + install.append(pkg.name) + # check authentication, one authenticated origin is enough + # libapt will skip non-authenticated origins then + authenticated = False + for origin in pkg.candidateOrigin: + authenticated |= origin.trusted + if not authenticated: + unauthenticated.append(pkg.name) + if pkg.markedDelete: + remove.append(pkg.name) + return (install,remove, unauthenticated) + requiredChanges = property(requiredChanges) + + def _dbg(self, level, msg): + """Write debugging output to sys.stderr. + """ + if level <= self.debug: + print >> sys.stderr, msg + + def install(self, installProgress=None): + """ Install the package """ + if installProgress == None: + res = os.system("/usr/sbin/dpkg -i %s" % self.filename) + else: + installProgress.startUpdate() + res = installProgress.run(self.filename) + installProgress.finishUpdate() + return res + +class DscSrcPackage(DebPackage): + def __init__(self, filename=None, cache=None): + DebPackage.__init__(self, filename, cache) + self.depends = [] + self.conflicts = [] + self.binaries = [] + if filename != None: + self.open(filename) + def getConflicts(self): + return self.conflicts + def getDepends(self): + return self.depends + def open(self, file): + depends_tags = ["Build-Depends:", "Build-Depends-Indep:"] + conflicts_tags = ["Build-Conflicts:", "Build-Conflicts-Indep:"] + for line in open(file): + # check b-d and b-c + for tag in depends_tags: + if line.startswith(tag): + key = line[len(tag):].strip() + self.depends.extend(apt_pkg.ParseSrcDepends(key)) + for tag in conflicts_tags: + if line.startswith(tag): + key = line[len(tag):].strip() + self.conflicts.extend(apt_pkg.ParseSrcDepends(key)) + # check binary and source and version + if line.startswith("Source:"): + self.pkgName = line[len("Source:"):].strip() + if line.startswith("Binary:"): + self.binaries = [pkg.strip() for pkg in line[len("Binary:"):].split(",")] + if line.startswith("Version:"): + self._sections["Version"] = line[len("Version:"):].strip() + # we are at the end + if line.startswith("-----BEGIN PGP SIGNATURE-"): + break + s = _("Install Build-Dependencies for " + "source package '%s' that builds %s\n" + ) % (self.pkgName, " ".join(self.binaries)) + self._sections["Description"] = s + + def checkDeb(self): + if not self.checkConflicts(): + for pkgname in self._installedConflicts: + if self._cache[pkgname]._pkg.Essential: + raise Exception, _("A essential package would be removed") + self._cache[pkgname].markDelete() + # FIXME: a additional run of the checkConflicts() + # after _satisfyDepends() should probably be done + return self._satisfyDepends(self.depends) if __name__ == "__main__": - import sys - - d = DebPackage(sys.argv[1]) - print d["Section"] - print d["Maintainer"] - print "Files:" - print "\n".join(d.filelist) + from cache import Cache + from progress import DpkgInstallProgress + + cache = Cache() + + vp = "www-browser" + print "%s virtual: %s" % (vp,cache.isVirtualPackage(vp)) + providers = cache.getProvidingPackages(vp) + print "Providers for %s :" % vp + for pkg in providers: + print " %s" % pkg.name + d = DebPackage(sys.argv[1], cache) + print "Deb: %s" % d.pkgname + if not d.checkDeb(): + print "can't be satified" + print d._failureString + print "missing deps: %s" % d.missingDeps + print d.requiredChanges + + print "Installing ..." + ret = d.install(DpkgInstallProgress()) + print ret + + #s = DscSrcPackage(cache, "../tests/3ddesktop_0.2.9-6.dsc") + #s.checkDep() + #print "Missing deps: ",s.missingDeps + #print "Print required changes: ", s.requiredChanges + + s = DscSrcPackage(cache=cache) + d = "libc6 (>= 2.3.2), libaio (>= 0.3.96) | libaio1 (>= 0.3.96)" + print s._satisfyDepends(apt_pkg.ParseDepends(d)) diff --git a/apt/gtk/__init__.py b/apt/gtk/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/apt/gtk/__init__.py diff --git a/apt/gtk/widgets.py b/apt/gtk/widgets.py new file mode 100644 index 00000000..3a15258f --- /dev/null +++ b/apt/gtk/widgets.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +widgets - GTK widgets to show the progress and status of apt + +Copyright (c) 2004,2005 Canonical Ltd. + +Authors: Michael Vogt <mvo@ubuntu.com> + Sebastian Heinlein <glatzor@ubuntu.com> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +his program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +from gettext import gettext as _ +import os +import time + +import pygtk +pygtk.require('2.0') +import gtk +import glib +import gobject +import pango +import vte + +import apt +import apt_pkg + +class GOpProgress(gobject.GObject, apt.progress.OpProgress): + + __gsignals__ = {"status-changed":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_INT)), + "status-started":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()), + "status-finished":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ())} + + def __init__(self): + apt.progress.OpProgress.__init__(self) + gobject.GObject.__init__(self) + self._context = glib.main_context_default() + + def update(self, percent): + self.emit("status-changed", self.op, percent) + while self._context.pending(): + self._context.iteration() + + def done(self): + self.emit("status-finished") + +class GInstallProgress(gobject.GObject, apt.progress.InstallProgress): + + # Seconds until a maintainer script will be regarded as hanging + INSTALL_TIMEOUT = 5 * 60 + + __gsignals__ = {"status-changed":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_INT)), + "status-started":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()), + "status-timeout":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()), + "status-error":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()), + "status-conffile":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()), + "status-finished":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ())} + + def __init__(self, term): + apt.progress.InstallProgress.__init__(self) + gobject.GObject.__init__(self) + self.finished = False + self.time_last_update = time.time() + self.term = term + reaper = vte.reaper_get() + reaper.connect("child-exited", self.childExited) + self.env = ["VTE_PTY_KEEP_FD=%s"% self.writefd, + "DEBIAN_FRONTEND=gnome", + "APT_LISTCHANGES_FRONTEND=gtk"] + self._context = glib.main_context_default() + + def childExited(self, term, pid, status): + self.apt_status = os.WEXITSTATUS(status) + self.finished = True + + def error(self, pkg, errormsg): + self.emit("status-error") + + def conffile(self, current, new): + self.emit("status-conffile") + + def startUpdate(self): + self.emit("status-started") + + def finishUpdate(self): + self.emit("status-finished") + + def statusChange(self, pkg, percent, status): + self.time_last_update = time.time() + self.emit("status-changed", status, percent) + + def updateInterface(self): + apt.progress.InstallProgress.updateInterface(self) + while self._context.pending(): + self._context.iteration() + if self.time_last_update + self.INSTALL_TIMEOUT < time.time(): + self.emit("status-timeout") + + def fork(self): + return self.term.forkpty(envv=self.env) + + def waitChild(self): + while not self.finished: + self.updateInterface() + return self.apt_status + + +class GDpkgInstallProgress(apt.progress.DpkgInstallProgress,GInstallProgress): + + def run(self, debfile): + apt.progress.DpkgInstallProgress.run(self, debfile) + + def updateInterface(self): + apt.progress.DpkgInstallProgress.updateInterface(self) + if self.time_last_update + self.INSTALL_TIMEOUT < time.time(): + self.emit("status-timeout") + + +class GFetchProgress(gobject.GObject, apt.progress.FetchProgress): + + __gsignals__ = {"status-changed":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_INT)), + "status-started":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()), + "status-finished":(gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ())} + + def __init__(self): + apt.progress.FetchProgress.__init__(self) + gobject.GObject.__init__(self) + self._continue = True + self._context = glib.main_context_default() + + def start(self): + self.emit("status-started") + + def stop(self): + self.emit("status-finished") + + def cancel(self): + self._continue = False + + def pulse(self): + apt.progress.FetchProgress.pulse(self) + currentItem = self.currentItems + 1 + if currentItem > self.totalItems: + currentItem = self.totalItems + if self.currentCPS > 0: + text = (_("Downloading file %(current)li of %(total)li with " + "%(speed)s/s") % \ + {"current" : currentItem, + "total" : self.totalItems, + "speed" : humanize_size(self.currentCPS)}) + else: + text = (_("Downloading file %(current)li of %(total)li") % \ + {"current" : currentItem, + "total" : self.totalItems }) + self.emit("status-changed", text, self.percent) + while self._context.pending(): + self._context.iteration() + return self._continue + + +class GtkAptProgress(gtk.VBox): + """ + This widget provides a progress bar, a terminal and a status bar for + showing the progress of package manipulation tasks. + + A simple example code snippet to install/remove a package: + + import pygtk + pygtk.require('2.0') + import gtk + + import apt.widgets + + win = gtk.Window() + progress = apt.widgets.GtkAptProgress() + win.set_title("GtkAptProgress Demo") + win.add(progress) + progress.show() + win.show() + + cache = apt.cache.Cache(progress.open)) + cache["xterm"].markDelete() + progress.show_terminal(expanded=True) + cache.commit(progress.fetch), + progress.install) + + gtk.main() + """ + def __init__(self): + gtk.VBox.__init__(self) + self.set_spacing(6) + # Setup some child widgets + self._expander = gtk.Expander(_("Details")) + self._terminal = vte.Terminal() + #self._terminal.set_font_from_string("monospace 10") + self._expander.add(self._terminal) + self._progressbar = gtk.ProgressBar() + # Setup the always italic status label + self._label = gtk.Label() + attr_list = pango.AttrList() + attr_list.insert(pango.AttrStyle(pango.STYLE_ITALIC, 0, -1)) + self._label.set_attributes(attr_list) + self._label.set_ellipsize(pango.ELLIPSIZE_END) + self._label.set_alignment(0, 0) + # add child widgets + self.pack_start(self._progressbar, False) + self.pack_start(self._label, False) + self.pack_start(self._expander, False) + # Setup the internal progress handlers + self._progress_open = GOpProgress() + self._progress_open.connect("status-changed", self._on_status_changed) + self._progress_open.connect("status-started", self._on_status_started) + self._progress_open.connect("status-finished", self._on_status_finished) + self._progress_fetch = GFetchProgress() + self._progress_fetch.connect("status-changed", self._on_status_changed) + self._progress_fetch.connect("status-started", self._on_status_started) + self._progress_fetch.connect("status-finished", + self._on_status_finished) + self._progress_install = GInstallProgress(self._terminal) + self._progress_install.connect("status-changed", + self._on_status_changed) + self._progress_install.connect("status-started", + self._on_status_started) + self._progress_install.connect("status-finished", + self._on_status_finished) + self._progress_install.connect("status-timeout", + self._on_status_timeout) + self._progress_install.connect("status-error", + self._on_status_timeout) + self._progress_install.connect("status-conffile", + self._on_status_timeout) + self._progress_dpkg_install = GDpkgInstallProgress(self._terminal) + self._progress_dpkg_install.connect("status-changed", + self._on_status_changed) + self._progress_dpkg_install.connect("status-started", + self._on_status_started) + self._progress_dpkg_install.connect("status-finished", + self._on_status_finished) + self._progress_dpkg_install.connect("status-timeout", + self._on_status_timeout) + self._progress_dpkg_install.connect("status-error", + self._on_status_timeout) + self._progress_dpkg_install.connect("status-conffile", + self._on_status_timeout) + + def clear(self): + """ + Reset all status information + """ + self._label.set_label("") + self._progress.set_fraction(0) + self._expander.set_expanded(False) + + @property + def open(self): + """ + Return the cache opening progress handler. + """ + return self._progress_open + + @property + def install(self): + """ + Return the install progress handler + """ + return self._progress_install + + @property + def dpkg_install(self): + """ + Return the install progress handler for dpkg + """ + return self._dpkg_progress_install + + @property + def fetch(self): + """ + Return the fetch progress handler + """ + return self._progress_fetch + + def _on_status_started(self, progress): + self._on_status_changed(progress, _("Starting..."), 0) + while gtk.events_pending(): + gtk.main_iteration() + + def _on_status_finished(self, progress): + self._on_status_changed(progress, _("Complete"), 100) + while gtk.events_pending(): + gtk.main_iteration() + + def _on_status_changed(self, progress, status, percent): + self._label.set_text(status) + if percent is None: + self._progressbar.pulse() + else: + self._progressbar.set_fraction(percent/100.0) + while gtk.events_pending(): + gtk.main_iteration() + + def _on_status_timeout(self, progress): + selt._expander.set_expanded(True) + while gtk.events_pending(): + gtk.main_iteration() + + def cancel_download(self): + """ + Cancel a currently running download + """ + self._progress_fetch.cancel() + + def show_terminal(self, expanded=False): + """ + Show an expander with a terminal widget which provides a way + to interact with dpkg + """ + self._expander.show() + self._terminal.show() + self._expander.set_expanded(expanded) + while gtk.events_pending(): + gtk.main_iteration() + + def hide_terminal(self): + """ + Hide the expander with the terminal widget + """ + self._expander.hide() + while gtk.events_pending(): + gtk.main_iteration() + + def show(self): + gtk.HBox.show(self) + self._label.show() + self._progressbar.show() + while gtk.events_pending(): + gtk.main_iteration() + +if __name__ == "__main__": + import sys + import debfile + + win = gtk.Window() + apt_progress = GAptProgress() + win.set_title("GtkAptProgress Demo") + win.add(apt_progress) + apt_progress.show() + win.show() + cache = apt.cache.Cache(apt_progress.get_open_progress()) + pkg = cache["xterm"] + if pkg.isInstalled: + pkg.markDelete() + else: + pkg.markInstall() + apt_progress.show_terminal(True) + try: + cache.commit(apt_progress.get_fetch_progress(), + apt_progress.get_install_progress()) + except: + pass + if len(sys.argv) > 1: + deb = DebPackage(sys.argv[1], cache) + deb.install(apt_progress.get_dpkg_install_progress()) + gtk.main() + +# vim: ts=4 et sts=4 diff --git a/apt/package.py b/apt/package.py index c1c2b1e1..70ddbb1a 100644 --- a/apt/package.py +++ b/apt/package.py @@ -19,10 +19,18 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -import apt_pkg +import httplib import sys import random +import re +import socket import string +import urllib2 + +import apt_pkg + +# Set a timeout for the changelog download +socket.setdefaulttimeout(2) #from gettext import gettext as _ import gettext @@ -68,6 +76,7 @@ class Package(object): self._pkg = pkgiter self._list = sourcelist # sourcelist self._pcache = pcache # python cache in cache.py + self._changelog = "" # Cached changelog pass # helper @@ -235,8 +244,15 @@ class Package(object): return self._records.ShortDesc summary = property(summary) - def description(self, format=True): - """ Return the formated long description """ + def description(self, format=True, useDots=False): + """ + Return the formated long description according to the Debian policy + (Chapter 5.6.13). + See http://www.debian.org/doc/debian-policy/ch-controlfields.html + for more information. + """ + if not format: + return self.rawDescription if not self._lookupRecord(): return "" # get the translated description @@ -249,12 +265,36 @@ class Package(object): except UnicodeDecodeError,e: s = _("Invalid unicode in description for '%s' (%s). " "Please report.") % (self.name,e) - for line in string.split(s,"\n"): - tmp = string.strip(line) - if tmp == ".": + lines = string.split(s, "\n") + for i in range(len(lines)): + # Skip the first line, since its a duplication of the summary + if i == 0: continue + raw_line = lines[i] + if raw_line.strip() == ".": + # The line is just line break + if not desc.endswith("\n"): desc += "\n" + continue + elif raw_line.startswith(" "): + # The line should be displayed verbatim without word wrapping + if not desc.endswith("\n"): + line = "\n%s\n" % raw_line[2:] + else: + line = "%s\n" % raw_line[2:] + elif raw_line.startswith(" "): + # The line is part of a paragraph. + if desc.endswith("\n") or desc == "": + # Skip the leading white space + line = raw_line[1:] else: - desc += tmp + "\n" + line = raw_line + else: + line = raw_line + # Use dots for lists + if useDots: + line = re.sub(r"^(\s*)(\*|0|o|-) ", ur"\1\u2022 ", line, 1) + # Add current line to the description + desc += line return desc description = property(description) @@ -354,6 +394,156 @@ class Package(object): return ver.InstalledSize installedSize = property(installedSize) + def installedFiles(self): + """ + Return the list of unicode names of the files which have + been installed by this package + """ + path = "/var/lib/dpkg/info/%s.list" % self.name + try: + list = open(path) + files = list.read().decode().split("\n") + list.close() + except: + return [] + return files + installedFiles = property(installedFiles) + + def getChangelog(self, uri=None, cancel_lock=None): + """ + Download the changelog of the package and return it as unicode + string + + uri: Is the uri to the changelog file. The following named variables + will be substituted: src_section, prefix, src_pkg and src_ver + For example the Ubuntu changelog: + uri = "http://changelogs.ubuntu.com/changelogs/pool" \\ + "/%(src_section)s/%(prefix)s/%(src_pkg)s" \\ + "/%(src_pkg)s_%(src_ver)s/changelog" + cancel_lock: If this threading.Lock() is set, the download will be + canceled + """ + # Return a cached changelog if available + if self._changelog != "": + return self._changelog + + if uri == None: + if self.candidateOrigin[0].origin == "Debian": + uri = "http://packages.debian.org/changelogs/pool" \ + "/%(src_section)s/%(prefix)s/%(src_pkg)s" \ + "/%(src_pkg)s_%(src_ver)s/changelog" + elif self.candidateOrigin[0].origin == "Ubuntu": + uri = "http://changelogs.ubuntu.com/changelogs/pool" \ + "/%(src_section)s/%(prefix)s/%(src_pkg)s" \ + "/%(src_pkg)s_%(src_ver)s/changelog" + else: + return _("The list of changes is not available") + + # get the src package name + src_pkg = self.sourcePackageName + + # assume "main" section + src_section = "main" + # use the section of the candidate as a starting point + section = self._depcache.GetCandidateVer(self._pkg).Section + + # get the source version, start with the binaries version + bin_ver = self.candidateVersion + src_ver = self.candidateVersion + #print "bin: %s" % binver + try: + # try to get the source version of the pkg, this differs + # for some (e.g. libnspr4 on ubuntu) + # this feature only works if the correct deb-src are in the + # sources.list + # otherwise we fall back to the binary version number + src_records = apt_pkg.GetPkgSrcRecords() + src_rec = src_records.Lookup(src_pkg) + if src_rec: + src_ver = src_records.Version + #if apt_pkg.VersionCompare(binver, srcver) > 0: + # srcver = binver + if not src_ver: + src_ver = bin_ver + #print "srcver: %s" % src_ver + section = src_records.Section + #print "srcsect: %s" % section + else: + # fail into the error handler + raise SystemError + except SystemError, e: + src_ver = bin_ver + + l = section.split("/") + if len(l) > 1: + src_section = l[0] + + # lib is handled special + prefix = src_pkg[0] + if src_pkg.startswith("lib"): + prefix = "lib" + src_pkg[3] + + # stip epoch + l = string.split(src_ver,":") + if len(l) > 1: + src_ver = "".join(l[1:]) + + uri = uri % {"src_section" : src_section, + "prefix" : prefix, + "src_pkg" : src_pkg, + "src_ver" : src_ver} + try: + # Check if the download was canceled + if cancel_lock and cancel_lock.isSet(): return "" + changelog_file = urllib2.urlopen(uri) + # do only get the lines that are new + changelog = "" + regexp = "^%s \((.*)\)(.*)$" % (re.escape(src_pkg)) + + i=0 + while True: + # Check if the download was canceled + if cancel_lock and cancel_lock.isSet(): return "" + # Read changelog line by line + line_raw = changelog_file.readline() + if line_raw == "": + break + # The changelog is encoded in utf-8, but since there isn't any + # http header, urllib2 seems to treat it as ascii + line = line_raw.decode("utf-8") + + #print line.encode('utf-8') + match = re.match(regexp, line) + if match: + # strip epoch from installed version + # and from changelog too + installed = self.installedVersion + if installed and ":" in installed: + installed = installed.split(":",1)[1] + changelog_ver = match.group(1) + if changelog_ver and ":" in changelog_ver: + changelog_ver = changelog_ver.split(":", 1)[1] + if installed and \ + apt_pkg.VersionCompare(changelog_ver, installed) <= 0: + break + # EOF (shouldn't really happen) + changelog += line + + # Print an error if we failed to extract a changelog + if len(changelog) == 0: + changelog = _("The list of changes is not available") + self._changelog = changelog + except urllib2.HTTPError,e: + return _("The list of changes is not available yet.\n\n" + "Please use http://launchpad.net/ubuntu/+source/%s/%s/" + "+changelog\n" + "until the changes become available or try again " + "later.") % (srcpkg, srcver), + except IOError, httplib.BadStatusLine: + return _("Failed to download the list of changes. \nPlease " + "check your Internet connection.") + return self._changelog + # canidate origin class Origin: def __init__(self, pkg, VerFileIter): diff --git a/apt/progress.py b/apt/progress.py index bb1bce35..a8ab76b6 100644 --- a/apt/progress.py +++ b/apt/progress.py @@ -208,7 +208,7 @@ class InstallProgress(DumbInstallProgress): (pid, res) = os.waitpid(self.child_pid,os.WNOHANG) if pid == self.child_pid: break - return os.WEXITSTATUS(res) + return res def run(self, pm): pid = self.fork() if pid == 0: @@ -217,7 +217,7 @@ class InstallProgress(DumbInstallProgress): os._exit(res) self.child_pid = pid res = self.waitChild() - return res + return os.WEXITSTATUS(res) class CdromProgress: """ Report the cdrom add progress @@ -233,6 +233,60 @@ class CdromProgress: def changeCdrom(self): pass + +class DpkgInstallProgress(InstallProgress): + """ + Progress handler for a local Debian package installation + """ + def run(self, debfile): + """ + Start installing the given Debian package + """ + self.debfile = debfile + self.debname = os.path.basename(debfile).split("_")[0] + pid = self.fork() + if pid == 0: + # child + res = os.system("/usr/bin/dpkg --status-fd %s -i %s" % \ + (self.writefd, self.debfile)) + os._exit(os.WEXITSTATUS(res)) + self.child_pid = pid + res = self.waitChild() + return res + + def updateInterface(self): + """ + Process status messages from dpkg + """ + if self.statusfd != None: + while True: + try: + self.read += os.read(self.statusfd.fileno(),1) + except OSError, (errno,errstr): + # resource temporarly unavailable is ignored + if errno != 11: + print errstr + break + if self.read.endswith("\n"): + statusl = string.split(self.read, ":") + if len(statusl) < 3: + print "got garbage from dpkg: '%s'" % read + self.read = "" + break + status = statusl[2].strip() + #print status + if status == "error": + self.error(self.debname, status) + elif status == "conffile-prompt": + # 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)) + else: + self.status = status + self.read = "" + # module test code if __name__ == "__main__": import apt_pkg diff --git a/debian/changelog b/debian/changelog index f51cb07a..a80a9b64 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,36 @@ +python-apt (0.7.9~exp1) experimental; urgency=low + + * Merged python-apt consolidation branch by Sebastian + Heinlein (many thanks) + * apt/cache.py: + - new method "isVirtualPackage()" + - new method "getProvidingPackages()" + - new method "getRequiredDownload()" + - new method "additionalRequiredSpace()" + * apt/debfile.py: + - move a lot of the gdebi code into this file, this + provides interfaces for querrying and installing + .deb files and .dsc files + * apt/package.py: + - better description parsing + - new method "installedFiles()" + - new method "getChangelog()" + * apt/gtk/widgets.py: + - new gobject GOpProgress + - new gobject GFetchProgress + - new gobject GInstallProgress + - new gobject GDpkgInstallProgress + - new widget GtkAptProgress + * doc/examples/gui-inst.py: + - updated to use the new widgets + * debian/control: + - add suggests for python-gtk2 and python-vte + * setup.py: + - build html/ help of the apt and aptsources modules + into /usr/share/doc/python-apt/html + + -- Michael Vogt <mvo@debian.org> Mon, 15 Dec 2008 14:29:47 +0100 + python-apt (0.7.8) unstable; urgency=low [ Michael Vogt ] diff --git a/debian/control b/debian/control index 1303dc6c..b738231f 100644 --- a/debian/control +++ b/debian/control @@ -15,7 +15,7 @@ Priority: optional Replaces: python2.3-apt (<< 0.6.18), python2.4-apt (<< 0.6.18) Conflicts: python2.3-apt (<< 0.6.18), python2.4-apt (<< 0.6.18) Provides: ${python:Provides} -Suggests: python-apt-dbg +Suggests: python-apt-dbg, python-gtk2, python-vte XB-Python-Version: ${python:Versions} Description: Python interface to libapt-pkg The apt_pkg Python interface will provide full access to the internal diff --git a/debian/python-apt.docs b/debian/python-apt.docs index 208050c5..f3c87fdc 100644 --- a/debian/python-apt.docs +++ b/debian/python-apt.docs @@ -1,3 +1,4 @@ README apt/README.apt data/templates/README.templates +html/ diff --git a/debian/rules b/debian/rules index c8aff2f9..adaad2a8 100755 --- a/debian/rules +++ b/debian/rules @@ -21,6 +21,9 @@ build/python-apt-dbg:: python$$i-dbg ./setup.py build; \ done +build/python-apt:: + pydoc -w + install/python-apt-dbg:: for i in $(cdbs_python_build_versions); do \ python$$i-dbg ./setup.py install --root $(CURDIR)/debian/python-apt-dbg; \ diff --git a/doc/examples/gui-inst.py b/doc/examples/gui-inst.py index c2555134..feefd6ed 100755 --- a/doc/examples/gui-inst.py +++ b/doc/examples/gui-inst.py @@ -12,113 +12,25 @@ import fcntl import pygtk pygtk.require('2.0') import gtk -import vte -import time -import posix - -from apt.progress import OpProgress, FetchProgress, InstallProgress - -class GuiFetchProgress(gtk.Window, FetchProgress): - def __init__(self): - gtk.Window.__init__(self) - self.vbox = gtk.VBox() - self.vbox.show() - self.add(self.vbox) - self.progress = gtk.ProgressBar() - self.progress.show() - self.label = gtk.Label() - self.label.show() - self.vbox.pack_start(self.progress) - self.vbox.pack_start(self.label) - self.resize(300,100) - def start(self): - print "start" - self.progress.set_fraction(0.0) - self.show() - def stop(self): - self.hide() - def pulse(self): - FetchProgress.pulse(self) - self.label.set_text("Speed: %s/s" % apt_pkg.SizeToStr(self.currentCPS)) - #self.progressbar.set_fraction(self.currentBytes/self.totalBytes) - while gtk.events_pending(): - gtk.main_iteration() - return True - -class TermInstallProgress(InstallProgress, gtk.Window): - def __init__(self): - gtk.Window.__init__(self) - InstallProgress.__init__(self) - self.show() - box = gtk.VBox() - box.show() - self.add(box) - self.term = vte.Terminal() - self.term.show() - # check for the child - self.reaper = vte.reaper_get() - self.reaper.connect("child-exited",self.child_exited) - self.finished = False - box.pack_start(self.term) - self.progressbar = gtk.ProgressBar() - self.progressbar.show() - box.pack_start(self.progressbar) - def child_exited(self,term, pid, status): - print "child_exited: %s %s %s %s" % (self,term,pid,status) - self.apt_status = posix.WEXITSTATUS(status) - self.finished = True - def startUpdate(self): - print "start" - self.show() - def waitChild(self): - while not self.finished: - self.updateInterface() - while gtk.events_pending(): - gtk.main_iteration() - time.sleep(0.001) - sys.stdin.readline() - return self.apt_status - def statusChange(self, pkg, percent, status): - print "statusChange", pkg, percent - self.progressbar.set_fraction(float(percent)/100.0) - self.progressbar.set_text(string.strip(status)) - def fork(self): - env = ["VTE_PTY_KEEP_FD=%s"%self.writefd] - return self.term.forkpty(envv=env) - -cache = apt.Cache() -print "Available packages: %s " % cache._cache.PackageCount - - -# update the cache -fprogress = GuiFetchProgress() -iprogress = TermInstallProgress() - -# update the cache -#cache.Update(fprogress) -#cache = apt_pkg.GetCache(progress) -#depcache = apt_pkg.GetDepCache(cache) -#depcache.ReadPinFile() -#depcache.Init(progress) - - -# show the interface -while gtk.events_pending(): - gtk.main_iteration() - - -pkg = cache["3dchess"] -print "\n%s"%pkg.name - -# install or remove, the importend thing is to keep us busy :) -if pkg.isInstalled: - pkg.markDelete() -else: - pkg.markInstall() -cache.commit(fprogress, iprogress) - -print "Exiting" -sys.exit(0) - - +import apt.gtk.widgets + +if __name__ == "__main__": + + win = gtk.Window() + progress = apt.gtk.widgets.GtkAptProgress() + win.set_title("GtkAptProgress Demo") + win.add(progress) + progress.show() + win.show() + + cache = apt.cache.Cache(progress.open) + if cache["2vcard"].isInstalled: + cache["2vcard"].markDelete() + else: + cache["2vcard"].markInstall() + progress.show_terminal(expanded=True) + cache.commit(progress.fetch, + progress.install) + + gtk.main() diff --git a/po/POTFILES.in b/po/POTFILES.in index aa82c8cb..0cf708e1 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -2,3 +2,4 @@ [type: gettext/rfc822deb] data/templates/Ubuntu.info.in [type: gettext/rfc822deb] data/templates/Debian.info.in aptsources/distro.py +apt/gtk/widgets.py
\ No newline at end of file diff --git a/po/python-apt.pot b/po/python-apt.pot index 3d513b7b..7a1002fb 100644 --- a/po/python-apt.pot +++ b/po/python-apt.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-08-15 09:59+0200\n" +"POT-Creation-Date: 2008-11-24 14:45+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -23,232 +23,242 @@ msgid "http://changelogs.ubuntu.com/changelogs/pool/%s/%s/%s/%s_%s/changelog" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:11 +#: ../data/templates/Ubuntu.info.in:13 +msgid "Ubuntu 9.04 'Jaunty Jackalope'" +msgstr "" + +#. Description +#: ../data/templates/Ubuntu.info.in:31 +msgid "Cdrom with Ubuntu 9.04 'Jaunty Jackalope'" +msgstr "" + +#. Description +#: ../data/templates/Ubuntu.info.in:74 msgid "Ubuntu 8.10 'Intrepid Ibex'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:28 +#: ../data/templates/Ubuntu.info.in:92 msgid "Cdrom with Ubuntu 8.10 'Intrepid Ibex'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:66 +#: ../data/templates/Ubuntu.info.in:136 msgid "Ubuntu 8.04 'Hardy Heron'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:83 +#: ../data/templates/Ubuntu.info.in:154 msgid "Cdrom with Ubuntu 8.04 'Hardy Heron'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:120 +#: ../data/templates/Ubuntu.info.in:191 msgid "Ubuntu 7.10 'Gutsy Gibbon'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:137 +#: ../data/templates/Ubuntu.info.in:209 msgid "Cdrom with Ubuntu 7.10 'Gutsy Gibbon'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:172 +#: ../data/templates/Ubuntu.info.in:244 msgid "Ubuntu 7.04 'Feisty Fawn'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:189 +#: ../data/templates/Ubuntu.info.in:262 msgid "Cdrom with Ubuntu 7.04 'Feisty Fawn'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:223 +#: ../data/templates/Ubuntu.info.in:296 msgid "Ubuntu 6.10 'Edgy Eft'" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:228 +#: ../data/templates/Ubuntu.info.in:301 msgid "Community-maintained" msgstr "" #. CompDescriptionLong -#: ../data/templates/Ubuntu.info.in:232 +#: ../data/templates/Ubuntu.info.in:305 msgid "Proprietary drivers for devices" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:234 +#: ../data/templates/Ubuntu.info.in:307 msgid "Restricted software" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:240 +#: ../data/templates/Ubuntu.info.in:314 msgid "Cdrom with Ubuntu 6.10 'Edgy Eft'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:274 +#: ../data/templates/Ubuntu.info.in:348 msgid "Ubuntu 6.06 LTS 'Dapper Drake'" msgstr "" #. CompDescriptionLong -#: ../data/templates/Ubuntu.info.in:277 +#: ../data/templates/Ubuntu.info.in:351 msgid "Canonical-supported Open Source software" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:279 +#: ../data/templates/Ubuntu.info.in:353 msgid "Community-maintained (universe)" msgstr "" #. CompDescriptionLong -#: ../data/templates/Ubuntu.info.in:280 +#: ../data/templates/Ubuntu.info.in:354 msgid "Community-maintained Open Source software" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:282 +#: ../data/templates/Ubuntu.info.in:356 msgid "Non-free drivers" msgstr "" #. CompDescriptionLong -#: ../data/templates/Ubuntu.info.in:283 +#: ../data/templates/Ubuntu.info.in:357 msgid "Proprietary drivers for devices " msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:285 +#: ../data/templates/Ubuntu.info.in:359 msgid "Restricted software (Multiverse)" msgstr "" #. CompDescriptionLong -#: ../data/templates/Ubuntu.info.in:286 +#: ../data/templates/Ubuntu.info.in:360 msgid "Software restricted by copyright or legal issues" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:291 +#: ../data/templates/Ubuntu.info.in:366 msgid "Cdrom with Ubuntu 6.06 LTS 'Dapper Drake'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:303 +#: ../data/templates/Ubuntu.info.in:378 msgid "Important security updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:308 +#: ../data/templates/Ubuntu.info.in:383 msgid "Recommended updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:313 +#: ../data/templates/Ubuntu.info.in:388 msgid "Pre-released updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:318 +#: ../data/templates/Ubuntu.info.in:393 msgid "Unsupported updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:325 +#: ../data/templates/Ubuntu.info.in:400 msgid "Ubuntu 5.10 'Breezy Badger'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:338 +#: ../data/templates/Ubuntu.info.in:414 msgid "Cdrom with Ubuntu 5.10 'Breezy Badger'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:350 +#: ../data/templates/Ubuntu.info.in:426 msgid "Ubuntu 5.10 Security Updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:355 +#: ../data/templates/Ubuntu.info.in:431 msgid "Ubuntu 5.10 Updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:360 +#: ../data/templates/Ubuntu.info.in:436 msgid "Ubuntu 5.10 Backports" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:367 +#: ../data/templates/Ubuntu.info.in:443 msgid "Ubuntu 5.04 'Hoary Hedgehog'" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:380 +#: ../data/templates/Ubuntu.info.in:457 msgid "Cdrom with Ubuntu 5.04 'Hoary Hedgehog'" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:383 ../data/templates/Debian.info.in:117 +#: ../data/templates/Ubuntu.info.in:460 ../data/templates/Debian.info.in:123 msgid "Officially supported" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:392 +#: ../data/templates/Ubuntu.info.in:469 msgid "Ubuntu 5.04 Security Updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:397 +#: ../data/templates/Ubuntu.info.in:474 msgid "Ubuntu 5.04 Updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:402 +#: ../data/templates/Ubuntu.info.in:479 msgid "Ubuntu 5.04 Backports" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:408 +#: ../data/templates/Ubuntu.info.in:485 msgid "Ubuntu 4.10 'Warty Warthog'" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:414 +#: ../data/templates/Ubuntu.info.in:491 msgid "Community-maintained (Universe)" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:416 +#: ../data/templates/Ubuntu.info.in:493 msgid "Non-free (Multiverse)" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:421 +#: ../data/templates/Ubuntu.info.in:499 msgid "Cdrom with Ubuntu 4.10 'Warty Warthog'" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:424 +#: ../data/templates/Ubuntu.info.in:502 msgid "No longer officially supported" msgstr "" #. CompDescription -#: ../data/templates/Ubuntu.info.in:426 +#: ../data/templates/Ubuntu.info.in:504 msgid "Restricted copyright" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:433 +#: ../data/templates/Ubuntu.info.in:511 msgid "Ubuntu 4.10 Security Updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:438 +#: ../data/templates/Ubuntu.info.in:516 msgid "Ubuntu 4.10 Updates" msgstr "" #. Description -#: ../data/templates/Ubuntu.info.in:443 +#: ../data/templates/Ubuntu.info.in:521 msgid "Ubuntu 4.10 Backports" msgstr "" @@ -264,47 +274,47 @@ msgid "Debian 5.0 'Lenny' " msgstr "" #. Description -#: ../data/templates/Debian.info.in:31 +#: ../data/templates/Debian.info.in:33 msgid "Debian 4.0 'Etch' " msgstr "" #. Description -#: ../data/templates/Debian.info.in:54 +#: ../data/templates/Debian.info.in:58 msgid "Debian 3.1 'Sarge'" msgstr "" #. Description -#: ../data/templates/Debian.info.in:65 +#: ../data/templates/Debian.info.in:69 msgid "Proposed updates" msgstr "" #. Description -#: ../data/templates/Debian.info.in:70 +#: ../data/templates/Debian.info.in:76 msgid "Security updates" msgstr "" #. Description -#: ../data/templates/Debian.info.in:77 +#: ../data/templates/Debian.info.in:83 msgid "Debian current stable release" msgstr "" #. Description -#: ../data/templates/Debian.info.in:90 +#: ../data/templates/Debian.info.in:96 msgid "Debian testing" msgstr "" #. Description -#: ../data/templates/Debian.info.in:115 +#: ../data/templates/Debian.info.in:121 msgid "Debian 'Sid' (unstable)" msgstr "" #. CompDescription -#: ../data/templates/Debian.info.in:119 +#: ../data/templates/Debian.info.in:125 msgid "DFSG-compatible Software with Non-Free Dependencies" msgstr "" #. CompDescription -#: ../data/templates/Debian.info.in:121 +#: ../data/templates/Debian.info.in:127 msgid "Non-DFSG-compatible Software" msgstr "" @@ -325,3 +335,26 @@ msgstr "" #: ../aptsources/distro.py:235 msgid "Custom servers" msgstr "" + +#: ../apt/gtk/widgets.py:173 +#, python-format +msgid "Downloading file %(current)li of %(total)li with %(speed)s/s" +msgstr "" + +#: ../apt/gtk/widgets.py:179 +#, python-format +msgid "Downloading file %(current)li of %(total)li" +msgstr "" + +#. Setup some child widgets +#: ../apt/gtk/widgets.py:220 +msgid "Details" +msgstr "" + +#: ../apt/gtk/widgets.py:310 +msgid "Starting..." +msgstr "" + +#: ../apt/gtk/widgets.py:313 +msgid "Complete" +msgstr "" @@ -4,7 +4,30 @@ from distutils.core import setup, Extension from distutils.sysconfig import parse_makefile from DistUtilsExtra.command import * -import glob, os, string +import glob +import os +import os.path +import pydoc +import shutil +import string +import sys + +def build_docs(dir="html", modules=["apt","aptsources"]): + htmldir = os.path.join(os.getcwd(), dir) + for d in modules: + for (dirpath, dirnames, filenames) in os.walk(d): + pydoc.writedoc(dirpath.replace("/",".")) + for file in filenames: + if not file.endswith(".py"): + continue + if file in ["__init__.py"]: + continue + pydoc.writedoc(dirpath.replace("/",".")+"."+file.split(".py")[0]) + if not os.path.exists(htmldir): + os.mkdir(htmldir) + for f in glob.glob("*.html"): + shutil.move(f, htmldir) + return True # The apt_pkg module files = map(lambda source: "python/"+source, @@ -29,13 +52,17 @@ for template in glob.glob('data/templates/*.info.in'): source.close() build.close() +# build doc +if sys.argv[1] == "build": + build_docs() + setup(name="python-apt", version="0.6.17", description="Python bindings for APT", author="APT Development Team", author_email="deity@lists.debian.org", ext_modules=[apt_pkg,apt_inst], - packages=['apt', 'aptsources'], + packages=['apt', 'apt.gtk', 'aptsources'], data_files = [('share/python-apt/templates', glob.glob('build/data/templates/*.info')), ('share/python-apt/templates', |
