summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apt/cache.py65
-rw-r--r--apt/package.py16
-rw-r--r--apt/progress/__init__.py42
-rw-r--r--aptsources/distro.py4
-rw-r--r--data/templates/Debian.info.in2
-rw-r--r--debian/changelog79
-rw-r--r--debian/control9
-rw-r--r--debian/python-apt.doc-base11
-rw-r--r--doc/examples/progress.py18
-rw-r--r--python/cache.cc2
-rw-r--r--python/generic.h3
-rw-r--r--python/progress.cc167
-rw-r--r--python/progress.h17
-rw-r--r--python/tag.cc24
14 files changed, 408 insertions, 51 deletions
diff --git a/apt/cache.py b/apt/cache.py
index 94a77fd8..c1e2428c 100644
--- a/apt/cache.py
+++ b/apt/cache.py
@@ -20,6 +20,7 @@
# USA
import os
+import weakref
import apt_pkg
from apt import Package
@@ -47,6 +48,8 @@ class Cache(object):
def __init__(self, progress=None, rootdir=None, memonly=False):
self._callbacks = {}
+ self._weakref = weakref.WeakValueDictionary()
+ self._set = set()
if memonly:
# force apt to build its caches in memory
apt_pkg.Config.Set("Dir::Cache::pkgcache", "")
@@ -63,6 +66,9 @@ class Cache(object):
# create required dirs/files when run with special rootdir
# automatically
self._check_and_create_required_dirs(rootdir)
+ # Call InitSystem so the change to Dir::State::Status is actually
+ # recognized (LP: #320665)
+ apt_pkg.InitSystem()
self.open(progress)
def _check_and_create_required_dirs(self, rootdir):
@@ -93,7 +99,7 @@ class Cache(object):
for callback in self._callbacks[name]:
callback()
- def open(self, progress):
+ def open(self, progress=None):
""" Open the package cache, after that it can be used like
a dictionary
"""
@@ -105,7 +111,8 @@ class Cache(object):
self._records = apt_pkg.GetPkgRecords(self._cache)
self._list = apt_pkg.GetPkgSourceList()
self._list.ReadMainList()
- self._dict = {}
+ self._set.clear()
+ self._weakref.clear()
progress.Op = "Building data structures"
i=last=0
@@ -116,7 +123,7 @@ class Cache(object):
last=i
# drop stuff with no versions (cruft)
if len(pkg.VersionList) > 0:
- self._dict[pkg.Name] = Package(self, pkg)
+ self._set.add(pkg.Name)
i += 1
@@ -125,30 +132,36 @@ class Cache(object):
def __getitem__(self, key):
""" look like a dictionary (get key) """
- return self._dict[key]
+ try:
+ return self._weakref[key]
+ except KeyError:
+ if key in self._set:
+ pkg = self._weakref[key] = Package(self, self._cache[key])
+ return pkg
+ else:
+ raise KeyError('The cache has no package named %r' % key)
def __iter__(self):
- for pkgname in self._dict.keys():
- yield self._dict[pkgname]
+ for pkgname in self._set:
+ yield self[pkgname]
raise StopIteration
def has_key(self, key):
- return (key in self._dict)
+ return (key in self._set)
def __contains__(self, key):
- return (key in self._dict)
+ return (key in self._set)
def __len__(self):
- return len(self._dict)
+ return len(self._set)
def keys(self):
- return self._dict.keys()
+ return list(self._set)
def getChanges(self):
""" Get the marked changes """
changes = []
- for name in self._dict.keys():
- p = self._dict[name]
+ for p in self:
if p.markedUpgrade or p.markedInstall or p.markedDelete or \
p.markedDowngrade or p.markedReinstall:
changes.append(p)
@@ -329,6 +342,26 @@ class Cache(object):
self._callbacks[name] = []
self._callbacks[name].append(callback)
+ @property
+ def broken_count(self):
+ """Return the number of packages with broken dependencies."""
+ return self._depcache.broken_count
+
+ @property
+ def delete_count(self):
+ """Return the number of packages marked for deletion."""
+ return self._depcache.del_count
+
+ @property
+ def install_count(self):
+ """Return the number of packages marked for installation."""
+ return self._depcache.inst_count
+
+ @property
+ def keep_count(self):
+ """Return the number of packages marked as keep."""
+ return self._depcache.keep_count
+
# ----------------------------- experimental interface
@@ -373,7 +406,7 @@ class FilteredCache(object):
return len(self._filtered)
def __getitem__(self, key):
- return self.cache._dict[key]
+ return self.cache[key]
def __iter__(self):
for pkgname in self._filtered:
@@ -391,10 +424,10 @@ class FilteredCache(object):
def _reapplyFilter(self):
" internal helper to refilter "
self._filtered = {}
- for pkg in self.cache._dict.keys():
+ for pkg in self.cache:
for f in self._filters:
- if f.apply(self.cache._dict[pkg]):
- self._filtered[pkg] = 1
+ if f.apply(pkg):
+ self._filtered[pkg.name] = 1
break
def setFilter(self, filter):
diff --git a/apt/package.py b/apt/package.py
index ec88a456..f5bdc47d 100644
--- a/apt/package.py
+++ b/apt/package.py
@@ -486,15 +486,25 @@ class Package(object):
def __repr__(self):
return '<Package: name:%r id:%r>' % (self._pkg.Name, self._pkg.ID)
- @property
def candidate(self):
"""Return the candidate version of the package.
-
- :since: 0.7.9"""
+
+ This property is writeable to allow you to set the candidate version
+ of the package. Just assign a Version() object, and it will be set as
+ the candidate version.
+ """
cand = self._pcache._depcache.GetCandidateVer(self._pkg)
if cand is not None:
return Version(self, cand)
+ def __set_candidate(self, version):
+ """Set the candidate version of the package."""
+ self._pcache.cachePreChange()
+ self._pcache._depcache.SetCandidateVer(self._pkg, version._cand)
+ self._pcache.cachePostChange()
+
+ candidate = property(candidate, __set_candidate)
+
@property
def installed(self):
"""Return the currently installed version of the package.
diff --git a/apt/progress/__init__.py b/apt/progress/__init__.py
index 769942ce..b9288c2c 100644
--- a/apt/progress/__init__.py
+++ b/apt/progress/__init__.py
@@ -112,6 +112,13 @@ class FetchProgress(object):
This happens eg. when the downloads fails or is completed.
"""
+ def update_status_full(self, uri, descr, short_descr, status, file_size,
+ partial_size):
+ """Called when the status of an item changes.
+
+ This happens eg. when the downloads fails or is completed. This
+ version include information on current filesize and partial size
+ """
def pulse(self):
"""Called periodically to update the user interface.
@@ -125,6 +132,19 @@ class FetchProgress(object):
float(self.currentCPS))
return True
+ def pulse_items(self, items):
+ """Called periodically to update the user interface.
+ This function includes details about the items being fetched
+ Return True to continue or False to cancel.
+
+ """
+ self.percent = (((self.currentBytes + self.currentItems) * 100.0) /
+ float(self.totalBytes + self.totalItems))
+ if self.currentCPS > 0:
+ self.eta = ((self.totalBytes - self.currentBytes) /
+ float(self.currentCPS))
+ return True
+
def mediaChange(self, medium, drive):
"""react to media change events."""
@@ -266,11 +286,20 @@ class InstallProgress(DumbInstallProgress):
def waitChild(self):
"""Wait for child progress to exit."""
while True:
- select.select([self.statusfd], [], [], self.selectTimeout)
- self.updateInterface()
- (pid, res) = os.waitpid(self.child_pid, os.WNOHANG)
- if pid == self.child_pid:
+ try:
+ select.select([self.statusfd], [], [], self.selectTimeout)
+ except select.error, (errno_, errstr):
+ if errno_ != errno.EINTR:
+ raise
break
+ self.updateInterface()
+ try:
+ (pid, res) = os.waitpid(self.child_pid, os.WNOHANG)
+ if pid == self.child_pid:
+ break
+ except OSError, (errno_, errstr):
+ if errno_ != errno.EINTR:
+ raise
return res
def run(self, pm):
@@ -315,7 +344,7 @@ class DpkgInstallProgress(InstallProgress):
if pid == 0:
# child
res = os.system("/usr/bin/dpkg --status-fd %s -i %s" % \
- (self.writefd, self.debfile))
+ (self.writefd, debfile))
os._exit(os.WEXITSTATUS(res))
self.child_pid = pid
res = self.waitChild()
@@ -341,10 +370,11 @@ class DpkgInstallProgress(InstallProgress):
print "got garbage from dpkg: '%s'" % self.read
self.read = ""
break
+ pkg_name = statusl[1].strip()
status = statusl[2].strip()
#print status
if status == "error":
- self.error(self.debname, status)
+ self.error(pkg_name, status)
elif status == "conffile-prompt":
# we get a string like this:
# 'current-conffile' 'new-conffile' useredited distedited
diff --git a/aptsources/distro.py b/aptsources/distro.py
index bbb8ba50..5398d4a3 100644
--- a/aptsources/distro.py
+++ b/aptsources/distro.py
@@ -319,12 +319,12 @@ class Distribution:
if s.type == self.binary_type:
if s.dist not in comps_per_dist:
comps_per_dist[s.dist] = set()
- map(comps_per_dist[s.dist].add, s.comps)
+ map(comps_per_dist[s.dist].add, s.comps)
for s in self.source_code_sources:
if s.type == self.source_type:
if s.dist not in comps_per_sdist:
comps_per_sdist[s.dist] = set()
- map(comps_per_sdist[s.dist].add, s.comps)
+ map(comps_per_sdist[s.dist].add, s.comps)
# check if there is a main source at all
if len(self.main_sources) < 1:
diff --git a/data/templates/Debian.info.in b/data/templates/Debian.info.in
index e80f0f6c..e5a1b424 100644
--- a/data/templates/Debian.info.in
+++ b/data/templates/Debian.info.in
@@ -5,7 +5,7 @@ RepositoryType: deb
BaseURI: http://http.us.debian.org/debian/
MatchURI: ftp[0-9]*\.[a-z]\.debian\.org
MirrorsFile: /usr/share/python-apt/templates/Debian.mirrors
-_Description: Debian 5.1 'Squeeze'
+_Description: Debian 6.0 'Squeeze'
Component: main
_CompDescription: Officially supported
Component: contrib
diff --git a/debian/changelog b/debian/changelog
index da376bc1..d63f01a0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,82 @@
-python-apt (0.7.10.4ubuntu2) UNRELEASED; urgency=low
+python-apt (0.7.11.2ubuntu1) UNRELEASED; urgency=low
+ [ Loïc Minier ]
* Revert addition of gcc and gcc_s to python-apt libs as the toolchain has
been fixed; LP: #375334.
+
+ [ Michael Vogt ]
+ * merged with debian/unstable
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Thu, 30 Jul 2009 15:51:27 +0200
+
+python-apt (0.7.11.2) unstable; urgency=low
+
+ [ Julian Andres Klode ]
+ * python/cache.cc:
+ - Support Breaks, Enhances dependency types (Closes: #416247)
+ * debian/control:
+ - Only recommend libjs-jquery (Closes: #527543)
+ - Build-depend on libapt-pkg-dev (>= 0.7.22~)
+ - Update Standards-Version to 3.8.2
+ * apt/cache.py:
+ - Correctly handle rootdir on second and later invocations of
+ open(), by calling InitSystem again. (LP: #320665).
+ - Provide broken_count, delete_count, install_count, keep_count
+ properties (Closes: #532338)
+ - Only create Package objects when they are requested, do not keep them in
+ a dict. Saves 10MB for 25,000 packages on my machine.
+ * apt/package.py:
+ - Allow to set the candidate of a package (Closes: #523997)
+ + Support assignments to the 'candidate' property of Package objects.
+ + Initial patch by Sebastian Heinlein
+
+ [ Stefano Zacchiroli ]
+ * debian/python-apt.doc-base: register the documentation with the
+ doc-base system (Closes: #525134)
+
+ [ Sebastian Heinlein ]
+ * apt/progress.py: Extract the package name from the status message
+ (Closes: #532660)
+
+ -- Julian Andres Klode <jak@debian.org> Thu, 30 Jul 2009 14:08:30 +0200
+
+python-apt (0.7.11.1) unstable; urgency=low
- -- Loïc Minier <loic.minier@ubuntu.com> Mon, 29 Jun 2009 10:30:58 +0200
+ [ Stephan Peijnik ]
+ * apt/progress/__init__.py:
+ - Exception handling fixes in InstallProgress class.
+
+ [ Michael Vogt ]
+ * python/tag.cc:
+ - merge patch from John Wright that adds FindRaw method
+ (closes: #538723)
+
+ -- Michael Vogt <mvo@debian.org> Wed, 29 Jul 2009 19:15:56 +0200
+
+python-apt (0.7.11.0) unstable; urgency=low
+
+ [ Julian Andres Klode ]
+ * data/templates/Debian.info.in: Squeeze will be 6.0, not 5.1
+
+ [ Stephan Peijnik ]
+ * apt/progress/__init__.py:
+ - add update_status_full() that takes file_size/partial_size as
+ additional callback arguments
+ - add pulse_items() that takes a addtional "items" tuple that
+ gives the user full access to the individual items that are
+ fetched
+ * python/progress.cc:
+ - low level code for update_status_full and pulse_items()
+ - better threading support
+
+ [ Michael Vogt ]
+ * aptsources/distro.py:
+ - fix indent error that causes incorrect sources.list additons
+ (LP: #372224)
+ * python/progress.cc:
+ - fix crash in RunSimpleCallback()
+
+ -- Michael Vogt <mvo@debian.org> Mon, 20 Jul 2009 15:35:27 +0200
python-apt (0.7.10.4ubuntu1) karmic; urgency=low
@@ -14,7 +87,7 @@ python-apt (0.7.10.4ubuntu1) karmic; urgency=low
required dirs/files automatically
* python/progress.cc:
- fix crash in RunSimpleCallback()
-
+
[ Loic Minier ]
* Merge changes below from Michael Casadevall; note that these changes were
concurrently uploaded in a different form in 0.7.10.3ubuntu2 which wasn't
diff --git a/debian/control b/debian/control
index 57a95cd4..99f77927 100644
--- a/debian/control
+++ b/debian/control
@@ -4,12 +4,12 @@ Priority: optional
Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com>
XSBC-Original-Maintainer: APT Development Team <deity@lists.debian.org>
Uploaders: Michael Vogt <mvo@debian.org>, Julian Andres Klode <jak@debian.org>
-Standards-Version: 3.8.1
+Standards-Version: 3.8.2
XS-Python-Version: all
Build-Depends: apt-utils,
cdbs,
debhelper (>= 5.0.37.1),
- libapt-pkg-dev (>= 0.7.10),
+ libapt-pkg-dev (>= 0.7.22~),
python-all-dbg,
python-all-dev,
python2.4-dev,
@@ -25,9 +25,8 @@ Vcs-Browser: http://bzr.debian.org/loggerhead/apt/python-apt/debian-sid/changes
Package: python-apt
Architecture: any
-Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}, lsb-release,
- libjs-jquery
-Recommends: iso-codes
+Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}, lsb-release
+Recommends: iso-codes, libjs-jquery
Breaks: debdelta (<< 0.28~)
Provides: ${python:Provides}
Suggests: python-apt-dbg, python-gtk2, python-vte
diff --git a/debian/python-apt.doc-base b/debian/python-apt.doc-base
new file mode 100644
index 00000000..d25926b7
--- /dev/null
+++ b/debian/python-apt.doc-base
@@ -0,0 +1,11 @@
+Document: python-apt-api-reference
+Title: Python APT: API reference manual
+Abstract: API reference manual for Python bindings to libapt-pkg
+Section: Programming/Python
+
+Format: HTML
+Index: /usr/share/doc/python-apt/html/index.html
+Files: /usr/share/doc/python-apt/html/*
+
+Format: Text
+Files: /usr/share/doc/python-apt/text/*
diff --git a/doc/examples/progress.py b/doc/examples/progress.py
index 2231001f..c007681f 100644
--- a/doc/examples/progress.py
+++ b/doc/examples/progress.py
@@ -35,15 +35,31 @@ class TextFetchProgress(apt.FetchProgress):
pass
def updateStatus(self, uri, descr, shortDescr, status):
- print "UpdateStatus: '%s' '%s' '%s' '%i'" % (
+ print "UpdateStatus: '%s' '%s' '%s' '%i' " % (
uri, descr, shortDescr, status)
+ def update_status_full(self, uri, descr, shortDescr, status, fileSize,
+ partialSize):
+ print "update_status_full: '%s' '%s' '%s' '%i' '%d/%d'" % (
+ uri, descr, shortDescr, status, partialSize, fileSize)
+
def pulse(self):
print "Pulse: CPS: %s/s; Bytes: %s/%s; Item: %s/%s" % (
apt.SizeToStr(self.currentCPS), apt.SizeToStr(self.currentBytes),
apt.SizeToStr(self.totalBytes), self.currentItems, self.totalItems)
return True
+ def pulse_items(self, items):
+ print "Pulse: CPS: %s/s; Bytes: %s/%s; Item: %s/%s" % (
+ apt.SizeToStr(self.currentCPS), apt.SizeToStr(self.currentBytes),
+ apt.SizeToStr(self.totalBytes), self.currentItems, self.totalItems)
+ print "Pulse-Items: "
+ for itm in items:
+ uri, descr, shortDescr, fileSize, partialSize = itm
+ print " - '%s' '%s' '%s' '%d/%d'" % (
+ uri, descr, shortDescr, partialSize, fileSize)
+ return True
+
def mediaChange(self, medium, drive):
print "Please insert medium %s in drive %s" % (medium, drive)
sys.stdin.readline()
diff --git a/python/cache.cc b/python/cache.cc
index 0c59f561..c7e5e76e 100644
--- a/python/cache.cc
+++ b/python/cache.cc
@@ -502,7 +502,7 @@ static PyObject *MakeDepends(PyObject *Owner,pkgCache::VerIterator &Ver,
{
"", "Depends","PreDepends","Suggests",
"Recommends","Conflicts","Replaces",
- "Obsoletes"
+ "Obsoletes", "Breaks", "Enhances"
};
PyObject *Dep = PyString_FromString(Types[Start->Type]);
LastDepType = Start->Type;
diff --git a/python/generic.h b/python/generic.h
index ce79a54c..d2fcf42a 100644
--- a/python/generic.h
+++ b/python/generic.h
@@ -121,8 +121,9 @@ void CppOwnedDealloc(PyObject *iObj)
{
CppOwnedPyObject<T> *Obj = (CppOwnedPyObject<T> *)iObj;
Obj->Object.~T();
- if (Obj->Owner != 0)
+ if (Obj->Owner != 0) {
Py_DECREF(Obj->Owner);
+ }
PyObject_DEL(Obj);
}
diff --git a/python/progress.cc b/python/progress.cc
index ef114e89..b3e06b87 100644
--- a/python/progress.cc
+++ b/python/progress.cc
@@ -9,10 +9,13 @@
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
+#include <map>
+#include <utility>
#include <apt-pkg/acquire-item.h>
+#include <apt-pkg/acquire-worker.h>
+#include "generic.h"
#include "progress.h"
-
// generic
bool PyCallbackObj::RunSimpleCallback(const char* method_name,
PyObject *arglist,
@@ -34,14 +37,16 @@ bool PyCallbackObj::RunSimpleCallback(const char* method_name,
return false;
}
PyObject *result = PyEval_CallObject(method, arglist);
+
Py_XDECREF(arglist);
if(result == NULL) {
// exception happend
std::cerr << "Error in function " << method_name << std::endl;
PyErr_Print();
+ PyErr_Clear();
- return NULL;
+ return false;
}
if(res != NULL)
*res = result;
@@ -67,8 +72,10 @@ void PyOpProgress::Update()
PyObject_SetAttrString(callbackInst, "majorChange", o);
Py_XDECREF(o);
- // Build up the argument list...
- if(CheckChange(0.05))
+ // CheckChange takes a time delta argument how often we
+ // should run update - for interactive UIs it makes sense
+ // to run ~25/sec
+ if(CheckChange(0.04))
{
PyObject *arglist = Py_BuildValue("(f)", Percent);
RunSimpleCallback("update", arglist);
@@ -87,8 +94,10 @@ void PyOpProgress::Done()
// apt interface
+
bool PyFetchProgress::MediaChange(string Media, string Drive)
{
+ PyCbObj_END_ALLOW_THREADS
//std::cout << "MediaChange" << std::endl;
PyObject *arglist = Py_BuildValue("(ss)", Media.c_str(), Drive.c_str());
PyObject *result;
@@ -101,14 +110,35 @@ bool PyFetchProgress::MediaChange(string Media, string Drive)
// FIXME: find out what it should return usually
//std::cerr << "res is: " << res << std::endl;
+ PyCbObj_BEGIN_ALLOW_THREADS
return res;
}
void PyFetchProgress::UpdateStatus(pkgAcquire::ItemDesc &Itm, int status)
{
//std::cout << "UpdateStatus: " << Itm.URI << " " << status << std::endl;
- PyObject *arglist = Py_BuildValue("(sssi)", Itm.URI.c_str(), Itm.Description.c_str(), Itm.ShortDesc.c_str(), status);
+
+ // Added object file size and object partial size to
+ // parameters that are passed to updateStatus.
+ // -- Stephan
+ PyCbObj_END_ALLOW_THREADS
+ PyObject *arglist = Py_BuildValue("(sssikk)", Itm.URI.c_str(),
+ Itm.Description.c_str(),
+ Itm.ShortDesc.c_str(),
+ status,
+ Itm.Owner->FileSize,
+ Itm.Owner->PartialSize);
+
+ RunSimpleCallback("update_status_full", arglist);
+
+ // legacy version of the interface
+ arglist = Py_BuildValue("(sssi)", Itm.URI.c_str(),
+ Itm.Description.c_str(),
+ Itm.ShortDesc.c_str(),
+ status);
RunSimpleCallback("updateStatus", arglist);
+ PyCbObj_BEGIN_ALLOW_THREADS
+
}
void PyFetchProgress::IMSHit(pkgAcquire::ItemDesc &Itm)
@@ -144,12 +174,44 @@ void PyFetchProgress::Start()
{
//std::cout << "Start" << std::endl;
pkgAcquireStatus::Start();
+
+ // These attributes should be initialized before the first callback (start)
+ // is invoked.
+ // -- Stephan
+ PyObject *o;
+
+ o = Py_BuildValue("f", 0.0f);
+ PyObject_SetAttrString(callbackInst, "currentCPS", o);
+ Py_XDECREF(o);
+ o = Py_BuildValue("f", 0.0f);
+ PyObject_SetAttrString(callbackInst, "currentBytes", o);
+ Py_XDECREF(o);
+ o = Py_BuildValue("i", 0);
+ PyObject_SetAttrString(callbackInst, "currentItems", o);
+ Py_XDECREF(o);
+ o = Py_BuildValue("i", 0);
+ PyObject_SetAttrString(callbackInst, "totalItems", o);
+ Py_XDECREF(o);
+ o = Py_BuildValue("f", 0.0f);
+ PyObject_SetAttrString(callbackInst, "totalBytes", o);
+ Py_XDECREF(o);
+
RunSimpleCallback("start");
+ /* After calling the start method we can safely allow
+ * other Python threads to do their work for now.
+ */
+ PyCbObj_BEGIN_ALLOW_THREADS
}
void PyFetchProgress::Stop()
{
+ /* After the stop operation occured no other threads
+ * are allowed. This is done so we have a matching
+ * PyCbObj_END_ALLOW_THREADS to our previous
+ * PyCbObj_BEGIN_ALLOW_THREADS (Python requires this!).
+ */
+ PyCbObj_END_ALLOW_THREADS
//std::cout << "Stop" << std::endl;
pkgAcquireStatus::Stop();
RunSimpleCallback("stop");
@@ -157,6 +219,7 @@ void PyFetchProgress::Stop()
bool PyFetchProgress::Pulse(pkgAcquire * Owner)
{
+ PyCbObj_END_ALLOW_THREADS
pkgAcquireStatus::Pulse(Owner);
//std::cout << "Pulse" << std::endl;
@@ -181,19 +244,90 @@ bool PyFetchProgress::Pulse(pkgAcquire * Owner)
PyObject_SetAttrString(callbackInst, "totalBytes", o);
Py_XDECREF(o);
- PyObject *arglist = Py_BuildValue("()");
- PyObject *result;
- RunSimpleCallback("pulse", arglist, &result);
+ // Go through the list of items and add active items to the
+ // activeItems vector.
+ map<pkgAcquire::Worker *, pkgAcquire::ItemDesc *> activeItemMap;
+
+ for(pkgAcquire::Worker *Worker = Owner->WorkersBegin();
+ Worker != 0; Worker = Owner->WorkerStep(Worker)) {
+
+ if (Worker->CurrentItem == 0) {
+ // Ignore workers with no item running
+ continue;
+ }
+ activeItemMap.insert(std::make_pair(Worker, Worker->CurrentItem));
+ }
+
+ // Create the tuple that is passed as argument to pulse().
+ // This tuple contains activeItemMap.size() item tuples.
+ PyObject *arglist;
+
+ if (((int)activeItemMap.size()) > 0) {
+ PyObject *itemsTuple = PyTuple_New((Py_ssize_t) activeItemMap.size());
+
+ // Go through activeItems, create an item tuple in the form
+ // (URI, Description, ShortDesc, FileSize, PartialSize) and
+ // add that tuple to itemsTuple.
+ map<pkgAcquire::Worker *, pkgAcquire::ItemDesc *>::iterator iter;
+ int tuplePos;
+
+ for(tuplePos = 0, iter = activeItemMap.begin();
+ iter != activeItemMap.end(); ++iter, tuplePos++) {
+ pkgAcquire::Worker *worker = iter->first;
+ pkgAcquire::ItemDesc *itm = iter->second;
+
+ PyObject *itmTuple = Py_BuildValue("(ssskk)", itm->URI.c_str(),
+ itm->Description.c_str(),
+ itm->ShortDesc.c_str(),
+ worker->TotalSize,
+ worker->CurrentSize);
+ PyTuple_SetItem(itemsTuple, tuplePos, itmTuple);
+ }
+
+ // Now our itemsTuple is ready for being passed to pulse().
+ // pulse() is going to receive a single argument, being the
+ // tuple of items, which again contains one tuple with item
+ // information per item.
+ //
+ // Python Example:
+ //
+ // class MyFetchProgress(FetchProgress):
+ // def pulse(self, items):
+ // for itm in items:
+ // uri, desc, shortdesc, filesize, partialsize = itm
+ //
+ arglist = PyTuple_Pack(1, itemsTuple);
+ }
+ else {
+ arglist = Py_BuildValue("(())");
+ }
+ PyObject *result;
bool res = true;
- if(!PyArg_Parse(result, "b", &res))
+
+ RunSimpleCallback("pulse_items", arglist, &result);
+ if (result != NULL && PyArg_Parse(result, "b", &res) && res == false) {
+ // the user returned a explicit false here, stop
+ PyCbObj_BEGIN_ALLOW_THREADS
+ return false;
+ }
+
+ arglist = Py_BuildValue("()");
+ if (!RunSimpleCallback("pulse", arglist, &result)) {
+ PyCbObj_BEGIN_ALLOW_THREADS
+ return true;
+ }
+
+ if((result == NULL) || (!PyArg_Parse(result, "b", &res)))
{
// most of the time the user who subclasses the pulse()
// method forgot to add a return {True,False} so we just
// assume he wants a True
+ PyCbObj_BEGIN_ALLOW_THREADS
return true;
}
+ PyCbObj_BEGIN_ALLOW_THREADS
// fetching can be canceld by returning false
return res;
}
@@ -205,15 +339,19 @@ bool PyFetchProgress::Pulse(pkgAcquire * Owner)
void PyInstallProgress::StartUpdate()
{
RunSimpleCallback("startUpdate");
+ PyCbObj_BEGIN_ALLOW_THREADS
}
void PyInstallProgress::UpdateInterface()
{
+ PyCbObj_END_ALLOW_THREADS
RunSimpleCallback("updateInterface");
+ PyCbObj_BEGIN_ALLOW_THREADS
}
void PyInstallProgress::FinishUpdate()
{
+ PyCbObj_END_ALLOW_THREADS
RunSimpleCallback("finishUpdate");
}
@@ -272,9 +410,9 @@ pkgPackageManager::OrderResult PyInstallProgress::Run(pkgPackageManager *pm)
_exit(res);
}
-
StartUpdate();
+ PyCbObj_END_ALLOW_THREADS
if(PyObject_HasAttrString(callbackInst, "waitChild")) {
PyObject *method = PyObject_GetAttrString(callbackInst, "waitChild");
//std::cerr << "custom waitChild found" << std::endl;
@@ -289,14 +427,19 @@ pkgPackageManager::OrderResult PyInstallProgress::Run(pkgPackageManager *pm)
int child_res;
if(!PyArg_Parse(result, "i", &res) ) {
std::cerr << "custom waitChild() result could not be parsed?"<< std::endl;
+ PyCbObj_BEGIN_ALLOW_THREADS
return pkgPackageManager::Failed;
}
+ PyCbObj_BEGIN_ALLOW_THREADS
//std::cerr << "got child_res: " << res << std::endl;
} else {
//std::cerr << "using build-in waitpid()" << std::endl;
-
- while (waitpid(child_id, &ret, WNOHANG) == 0)
+ PyCbObj_BEGIN_ALLOW_THREADS
+ while (waitpid(child_id, &ret, WNOHANG) == 0) {
+ PyCbObj_END_ALLOW_THREADS
UpdateInterface();
+ PyCbObj_BEGIN_ALLOW_THREADS
+ }
res = (pkgPackageManager::OrderResult) WEXITSTATUS(ret);
//std::cerr << "build-in waitpid() got: " << res << std::endl;
diff --git a/python/progress.h b/python/progress.h
index 5ac67b1c..29243bfc 100644
--- a/python/progress.h
+++ b/python/progress.h
@@ -15,10 +15,27 @@
#include <apt-pkg/cdrom.h>
#include <Python.h>
+/* PyCbObj_BEGIN_ALLOW_THREADS and PyCbObj_END_ALLOW_THREADS are sligthly
+ * modified versions of Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS.
+ * Instead of storing the thread state in a function-local variable these
+ * use a class attribute (with the same) name, allowing blocking and
+ * unblocking from different class methods.
+ * Py_BLOCK_THREADS and Py_UNBLOCK_THREADS do not define their own
+ * local variable but use the one provided by PyCbObj_BEGIN_ALLOW_THREADS
+ * and thus are the same as Py_BLOCK_THREADS and Py_UNBLOCK_THREADS.
+ */
+#define PyCbObj_BEGIN_ALLOW_THREADS \
+ _save = PyEval_SaveThread();
+#define PyCbObj_END_ALLOW_THREADS \
+ PyEval_RestoreThread(_save); \
+ _save = NULL;
+#define PyCbObj_BLOCK_THREADS Py_BLOCK_THREADS
+#define PyCbObj_UNBLOCK_THREADS Py_UNBLOCK_THREADS
class PyCallbackObj {
protected:
PyObject *callbackInst;
+ PyThreadState *_save;
public:
void setCallbackInst(PyObject *o) {
diff --git a/python/tag.cc b/python/tag.cc
index 217be290..6fe97ed5 100644
--- a/python/tag.cc
+++ b/python/tag.cc
@@ -92,6 +92,29 @@ static PyObject *TagSecFind(PyObject *Self,PyObject *Args)
return PyString_FromStringAndSize(Start,Stop-Start);
}
+static char *doc_FindRaw = "FindRaw(Name) -> String/None";
+static PyObject *TagSecFindRaw(PyObject *Self,PyObject *Args)
+{
+ char *Name = 0;
+ char *Default = 0;
+ if (PyArg_ParseTuple(Args,"s|z",&Name,&Default) == 0)
+ return 0;
+
+ unsigned Pos;
+ if (GetCpp<pkgTagSection>(Self).Find(Name,Pos) == false)
+ {
+ if (Default == 0)
+ Py_RETURN_NONE;
+ return PyString_FromString(Default);
+ }
+
+ const char *Start;
+ const char *Stop;
+ GetCpp<pkgTagSection>(Self).Get(Start,Stop,Pos);
+
+ return PyString_FromStringAndSize(Start,Stop-Start);
+}
+
static char *doc_FindFlag = "FindFlag(Name) -> integer/none";
static PyObject *TagSecFindFlag(PyObject *Self,PyObject *Args)
{
@@ -355,6 +378,7 @@ static PyMethodDef TagSecMethods[] =
{
// Query
{"Find",TagSecFind,METH_VARARGS,doc_Find},
+ {"FindRaw",TagSecFindRaw,METH_VARARGS,doc_FindRaw},
{"FindFlag",TagSecFindFlag,METH_VARARGS,doc_FindFlag},
{"Bytes",TagSecBytes,METH_VARARGS,doc_Bytes},