diff options
| -rw-r--r-- | apt/cache.py | 65 | ||||
| -rw-r--r-- | apt/package.py | 16 | ||||
| -rw-r--r-- | apt/progress/__init__.py | 42 | ||||
| -rw-r--r-- | aptsources/distro.py | 4 | ||||
| -rw-r--r-- | data/templates/Debian.info.in | 2 | ||||
| -rw-r--r-- | debian/changelog | 79 | ||||
| -rw-r--r-- | debian/control | 9 | ||||
| -rw-r--r-- | debian/python-apt.doc-base | 11 | ||||
| -rw-r--r-- | doc/examples/progress.py | 18 | ||||
| -rw-r--r-- | python/cache.cc | 2 | ||||
| -rw-r--r-- | python/generic.h | 3 | ||||
| -rw-r--r-- | python/progress.cc | 167 | ||||
| -rw-r--r-- | python/progress.h | 17 | ||||
| -rw-r--r-- | python/tag.cc | 24 |
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}, |
