summaryrefslogtreecommitdiff
path: root/ept/apt
diff options
context:
space:
mode:
authorEnrico Zini <enrico@enricozini.org>2009-09-29 15:08:02 +0100
committerEnrico Zini <enrico@enricozini.org>2009-09-29 15:08:02 +0100
commit006621455482edd8abf46a05292120745f99f7ec (patch)
treeff1318ccc409a3dd0519035ce2949ac8a48e969e /ept/apt
downloadlibept-006621455482edd8abf46a05292120745f99f7ec.tar.gz
Initial import
Diffstat (limited to 'ept/apt')
-rw-r--r--ept/apt/apt.cc654
-rw-r--r--ept/apt/apt.h284
-rw-r--r--ept/apt/apt.test.h192
-rw-r--r--ept/apt/packagerecord.cc118
-rw-r--r--ept/apt/packagerecord.h176
-rw-r--r--ept/apt/packagerecord.test.h138
-rw-r--r--ept/apt/recordparser.cc170
-rw-r--r--ept/apt/recordparser.h95
-rw-r--r--ept/apt/recordparser.test.h228
-rw-r--r--ept/apt/version.cc87
-rw-r--r--ept/apt/version.h94
-rw-r--r--ept/apt/version.test.h136
12 files changed, 2372 insertions, 0 deletions
diff --git a/ept/apt/apt.cc b/ept/apt/apt.cc
new file mode 100644
index 0000000..159b83a
--- /dev/null
+++ b/ept/apt/apt.cc
@@ -0,0 +1,654 @@
+/** \file
+ * High-level front-end to libapt-pkg, as a data provider for the ept framework.
+ */
+
+/*
+ * Copyright (C) 2007,2008 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/apt/apt.h>
+
+#include <apt-pkg/error.h>
+#include <apt-pkg/init.h>
+#include <apt-pkg/progress.h>
+#include <apt-pkg/sourcelist.h>
+#include <apt-pkg/pkgcachegen.h>
+#include <apt-pkg/policy.h>
+#include <apt-pkg/cachefile.h>
+
+#include <wibble/sys/fs.h>
+#include <sys/stat.h>
+
+#include <vector>
+#include <algorithm>
+
+#include <iostream>
+
+using namespace std;
+
+namespace ept {
+namespace apt {
+
+static time_t aptTimestamp()
+{
+ namespace wfs = wibble::sys::fs;
+
+ std::auto_ptr<struct stat> st = wfs::stat(
+ _config->FindFile( "Dir::Cache::pkgcache" ) );
+ time_t t1 = st.get() == NULL ? 0 : st->st_mtime;
+
+ std::auto_ptr<struct stat> st1 = wfs::stat(
+ _config->FindFile( "Dir::State::status" ) );
+ time_t t2 = st1.get() == NULL ? 0 : st1->st_mtime;
+
+ return t1 > t2 ? t1 : t2;
+}
+
+Exception::Exception(const std::string& context) throw ()
+ : Generic(context)
+{
+ // Concatenate all errors and warnings found
+ string err;
+ while (!_error->empty())
+ {
+ bool type = _error->PopMessage(err);
+ if (type)
+ m_message += "E: " + err + "\n";
+ else
+ m_message += "W: " + err + "\n";
+ }
+}
+
+static void aptInit ()
+{
+ if (_config->FindB("Initialized"))
+ return;
+
+ if (!pkgInitConfig (*_config))
+ throw Exception("initialising apt configuration");
+
+ _config->Set("Initialized", 1);
+
+ /*
+ _config->Set("Dir", CACHE_DIR);
+ _config->Set("Dir::Cache", "cache");
+ _config->Set("Dir::State", "state");
+ _config->Set("Dir::Etc", "etc");
+ _config->Set("Dir::State::status", CACHE_DIR "dpkg-status");
+ */
+ if (!pkgInitSystem (*_config, _system))
+ throw Exception("initialising apt system");
+}
+
+struct AptImplementation
+{
+ pkgSourceList* m_list;
+ MMap *m;
+ OpProgress progress;
+ pkgCache* m_cache;
+ pkgPolicy* m_policy;
+ pkgCacheFile* m_depcache;
+ time_t m_open_timestamp;
+
+ AptImplementation() : m_list(0), m(0), m_cache(0), m_policy(0), m_depcache(0), m_open_timestamp(0)
+ {
+ // Init the apt library if needed
+ aptInit();
+
+ m_open_timestamp = aptTimestamp();
+
+ m_list = new pkgSourceList;
+ if (!m_list->ReadMainList())
+ throw Exception("reading list of sources");
+
+ bool res = pkgMakeStatusCache(*m_list, progress, &m, true);
+ progress.Done();
+ if (!res)
+ throw Exception("Reading the package lists or status file");
+
+ m_cache = new pkgCache(m);
+ m_policy = new pkgPolicy(m_cache);
+ if (!ReadPinFile(*m_policy))
+ throw Exception("Reading the policy pin file");
+ }
+
+ ~AptImplementation()
+ {
+ if (m_depcache) delete m_depcache;
+ if (m_policy) delete m_policy;
+ if (m_cache) delete m_cache;
+ if (m) delete m;
+ if (m_list) delete m_list;
+ }
+
+ pkgCache& cache()
+ {
+ return *m_cache;
+ }
+
+ pkgPolicy& policy()
+ {
+ return *m_policy;
+ }
+
+ pkgCacheFile& depcache()
+ {
+ if (!m_depcache)
+ {
+ m_depcache = new pkgCacheFile;
+ if (!m_depcache->Open(progress, false))
+ throw Exception("Opening the cache file");
+ }
+ return *m_depcache;
+ }
+};
+
+// Sort a version list by package file locality
+bool localityCompare(const pkgCache::VerFile* a, const pkgCache::VerFile* b)
+{
+ if (a == 0 && b == 0)
+ return false;
+ if (a == 0)
+ return true;
+ if (b == 0)
+ return false;
+
+ if (a->File == b->File)
+ return a->Offset < b->Offset;
+ return a->File < b->File;
+}
+
+// Iterate records using the algorithm used by apt-cache dumpavail
+struct RecordIteratorImpl
+{
+ mutable int _ref;
+ AptImplementation& apt;
+ vector<pkgCache::VerFile*> vflist;
+ pkgCache::PkgFileIterator lastFile;
+ FileFd file;
+ size_t lastOffset;
+
+ RecordIteratorImpl(AptImplementation& apt) : _ref(0), apt(apt), vflist(0)
+ {
+ // We already have an estimate of how many versions we're about to find
+ vflist.reserve(apt.cache().HeaderP->PackageCount + 1);
+
+ // Populate the vector of versions to print
+ for (pkgCache::PkgIterator pi = apt.cache().PkgBegin(); !pi.end(); ++pi)
+ {
+ if (pi->VersionList == 0)
+ continue;
+
+ /* Get the candidate version or fallback on the installed version,
+ * as usual */
+ pkgCache::VerIterator vi = apt.policy().GetCandidateVer(pi);
+ if (vi.end() == true)
+ {
+ if (pi->CurrentVer == 0)
+ continue;
+ vi = pi.CurrentVer();
+ }
+
+ // Choose a valid file that contains the record for this version
+ pkgCache::VerFileIterator vfi = vi.FileList();
+ for ( ; !vfi.end(); ++vfi)
+ if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0)
+ break;
+
+ // Handle packages whose candidate version is currently installed
+ // from outside the archives (like from a locally built .deb
+ if (vfi.end() == true)
+ {
+ for (pkgCache::VerIterator cur = pi.VersionList(); cur.end() != true; cur++)
+ {
+ for (vfi = cur.FileList(); vfi.end() == false; vfi++)
+ {
+ if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0)
+ {
+ vfi = vi.FileList();
+ break;
+ }
+ }
+
+ if (vfi.end() == false)
+ break;
+ }
+ }
+ if (!vfi.end())
+ vflist.push_back(vfi);
+ }
+
+ //cerr << vflist.size() << " versions found" << endl;
+
+ sort(vflist.begin(), vflist.end(), localityCompare);
+
+ //for (size_t i = 0; i < vflist.size(); ++i)
+ //{
+ // pkgCache::PkgFileIterator fi(apt.cache(), vflist[i]->File + apt.cache().PkgFileP);
+ // cerr << i << ": " << fi.FileName() << ":" << vflist[i]->Offset << "-" << vflist[i]->Size << endl;
+ //}
+ //cerr << "Done indexing." << endl;
+ }
+
+ ~RecordIteratorImpl()
+ {
+ if (file.IsOpen())
+ file.Close();
+ }
+
+ void ref() { ++_ref; }
+ bool unref() { return --_ref == 0; }
+
+ size_t size() { return vflist.size(); }
+
+ string record(size_t idx)
+ {
+ //cerr << "Access record " << idx << endl;
+ //cerr << "lastfile: " << (lastFile.Cache() != 0) << endl;
+ // We can't reuse the file that was already open: open the new one
+ if ((lastFile.Cache() == 0) || vflist[idx]->File + apt.cache().PkgFileP != lastFile)
+ {
+ //cerr << "Needs open/reopen" << endl;
+ lastFile = pkgCache::PkgFileIterator(apt.cache(), vflist[idx]->File + apt.cache().PkgFileP);
+ if (!lastFile.IsOk())
+ throw Exception(string("Reading the data record for a package from file ") + lastFile.FileName());
+ //cerr << "Ok for " << lastFile.FileName() << endl;
+ if (file.IsOpen())
+ file.Close();
+ if (!file.Open(lastFile.FileName(), FileFd::ReadOnly))
+ throw Exception(string("Opening file ") + lastFile.FileName());
+ //cerr << "Opened " << lastFile.FileName() << endl;
+ lastOffset = 0;
+ }
+
+ //cerr << "Reading from " << lastFile.FileName() << ":" << vflist[idx]->Offset << "-" << vflist[idx]->Size << " (lastOffset: " << lastOffset << ")" << endl;
+
+ // If we start near were we ended, avoid a seek and enlarge the read a bit
+ size_t slack = vflist[idx]->Offset - lastOffset;
+ //cerr << "Slack: " << slack << endl;
+ if (slack > 8)
+ {
+ //cerr << "Slack too big: seek to " << vflist[idx]->Offset << endl;
+ slack = 0;
+ if (!file.Seek(vflist[idx]->Offset))
+ throw Exception(string("Cannot seek to package record in file ") + lastFile.FileName());
+ }
+
+ char buffer[vflist[idx]->Size + slack + 1];
+ if (!file.Read(buffer, vflist[idx]->Size + slack))
+ throw Exception(string("Cannot read package record in file ") + lastFile.FileName());
+ buffer[vflist[idx]->Size + slack] = '\n';
+ //cerr << "Data read (slack: " << slack << ")" << endl;
+
+ lastOffset = vflist[idx]->Offset + vflist[idx]->Size;
+
+ return string(buffer+slack);
+ }
+};
+
+Apt::Iterator::Iterator(const Iterator& i)
+{
+ if (i.cur)
+ {
+ pkgCache::PkgIterator* p = new pkgCache::PkgIterator;
+ *p = *static_cast<pkgCache::PkgIterator*>(i.cur);
+ cur = p;
+ } else
+ cur = 0;
+}
+
+Apt::Iterator& Apt::Iterator::operator=(const Iterator& i)
+{
+ if (cur != i.cur)
+ {
+ if (cur) delete static_cast<pkgCache::PkgIterator*>(cur);
+ if (i.cur)
+ {
+ pkgCache::PkgIterator* p = new pkgCache::PkgIterator;
+ *p = *static_cast<pkgCache::PkgIterator*>(i.cur);
+ cur = p;
+ } else
+ cur = 0;
+ }
+ return *this;
+}
+
+Apt::Iterator::~Iterator()
+{
+ if (cur) delete static_cast<pkgCache::PkgIterator*>(cur);
+}
+std::string Apt::Iterator::operator*()
+{
+ return static_cast<pkgCache::PkgIterator*>(cur)->Name();
+}
+Apt::Iterator& Apt::Iterator::operator++()
+{
+ pkgCache::PkgIterator* iter = static_cast<pkgCache::PkgIterator*>(cur);
+ ++*iter;
+ while (!iter->end() && (*iter)->VersionList == 0)
+ ++*iter;
+ if (iter->end())
+ {
+ delete iter;
+ cur = 0;
+ }
+ return *this;
+}
+bool Apt::Iterator::operator==(const Iterator& i) const
+{
+ if (cur == 0 && i.cur == 0)
+ return true;
+ if (cur == 0 || i.cur == 0)
+ return false;
+ pkgCache::PkgIterator* iter1 = static_cast<pkgCache::PkgIterator*>(cur);
+ pkgCache::PkgIterator* iter2 = static_cast<pkgCache::PkgIterator*>(i.cur);
+ return *iter1 == *iter2;
+}
+bool Apt::Iterator::operator!=(const Iterator& i) const
+{
+ if (cur == 0 && i.cur == 0)
+ return false;
+ if (cur == 0 || i.cur == 0)
+ return true;
+ pkgCache::PkgIterator* iter1 = static_cast<pkgCache::PkgIterator*>(cur);
+ pkgCache::PkgIterator* iter2 = static_cast<pkgCache::PkgIterator*>(i.cur);
+ return *iter1 != *iter2;
+}
+
+
+Apt::RecordIterator::RecordIterator(RecordIteratorImpl* impl, size_t pos)
+ : impl(impl), pos(pos), cur_pos(pos)
+{
+ if (impl)
+ {
+ impl->ref();
+ cur = impl->record(pos);
+ cur_pos = pos;
+ }
+}
+Apt::RecordIterator::RecordIterator(const RecordIterator& r)
+ : impl(r.impl), pos(r.pos), cur(r.cur), cur_pos(r.cur_pos)
+{
+ if (impl)
+ impl->ref();
+}
+Apt::RecordIterator::~RecordIterator()
+{
+ if (impl && impl->unref())
+ delete impl;
+}
+std::string Apt::RecordIterator::operator*()
+{
+ if (cur_pos != pos)
+ {
+ cur = impl->record(pos);
+ cur_pos = pos;
+ }
+ return cur;
+}
+std::string* Apt::RecordIterator::operator->()
+{
+ if (cur_pos != pos)
+ {
+ cur = impl->record(pos);
+ cur_pos = pos;
+ }
+ return &cur;
+}
+Apt::RecordIterator& Apt::RecordIterator::operator++()
+{
+ ++pos;
+ if (pos >= impl->size())
+ {
+ // If we reach the end, we become an end iterator
+ if (impl && impl->unref())
+ delete impl;
+ impl = 0;
+ pos = 0;
+ }
+ return *this;
+}
+Apt::RecordIterator& Apt::RecordIterator::operator=(const RecordIterator& r)
+{
+ // Increment first, to avoid it reaching zero on assignment to self
+ if (r.impl) r.impl->ref();
+ if (impl && impl->unref())
+ delete impl;
+ impl = r.impl;
+ pos = r.pos;
+ cur = r.cur;
+ cur_pos = r.cur_pos;
+ return *this;
+}
+bool Apt::RecordIterator::operator==(const RecordIterator& ri) const
+{
+ return impl == ri.impl && pos == ri.pos;
+}
+bool Apt::RecordIterator::operator!=(const RecordIterator& ri) const
+{
+ return impl != ri.impl || pos != ri.pos;
+}
+
+
+Apt::Apt() : impl(new AptImplementation()) {}
+Apt::~Apt() { delete impl; }
+
+Apt::iterator Apt::begin() const
+{
+ pkgCache::PkgIterator* p = new pkgCache::PkgIterator;
+ *p = impl->cache().PkgBegin();
+ return Apt::Iterator(p);
+}
+
+Apt::iterator Apt::end() const
+{
+ return Apt::Iterator();
+}
+
+Apt::record_iterator Apt::recordBegin() const
+{
+ return Apt::RecordIterator(new RecordIteratorImpl(*impl));
+}
+
+Apt::record_iterator Apt::recordEnd() const
+{
+ return Apt::RecordIterator();
+}
+
+size_t Apt::size() const
+{
+ return impl->cache().HeaderP->PackageCount;
+}
+
+time_t Apt::timestamp()
+{
+ return aptTimestamp();
+}
+
+bool Apt::isValid(const std::string& pkg) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg);
+ return !pi.end();
+}
+
+Version Apt::validate(const Version& ver) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(ver.name());
+ if (pi.end()) return Version();
+ for (pkgCache::VerIterator vi = pi.VersionList(); !vi.end(); vi++)
+ {
+ const char* v = vi.VerStr();
+ if (v == 0) continue;
+ if (ver.version() == v)
+ return ver;
+ }
+ return Version();
+}
+
+Version Apt::candidateVersion(const std::string& pkg) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg);
+ if (pi.end()) return Version();
+ pkgCache::VerIterator vi = impl->policy().GetCandidateVer(pi);
+ if (vi.end()) return Version();
+ return Version(pkg, vi.VerStr());
+}
+
+Version Apt::installedVersion(const std::string& pkg) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg);
+ if (pi.end()) return Version();
+ if (pi->CurrentVer == 0) return Version();
+ pkgCache::VerIterator vi = pi.CurrentVer();
+ if (vi.end()) return Version();
+ return Version(pkg, vi.VerStr());
+}
+
+Version Apt::anyVersion(const std::string& pkg) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg);
+ if (pi.end()) return Version();
+
+ pkgCache::VerIterator vi = impl->policy().GetCandidateVer(pi);
+ if (vi.end())
+ {
+ if (pi->CurrentVer == 0) return Version();
+ vi = pi.CurrentVer();
+ if (vi.end()) return Version();
+ }
+ return Version(pkg, vi.VerStr());
+}
+
+PackageState Apt::state(const std::string& pkg) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg);
+ if (pi.end()) return PackageState();
+ pkgDepCache::StateCache sc = impl->depcache()[pi];
+
+ unsigned int flags = PackageState::Valid;
+
+ // Check if the package is installed
+ if (pi->CurrentState != pkgCache::State::ConfigFiles &&
+ pi->CurrentState != pkgCache::State::NotInstalled &&
+ pi->CurrentVer != 0)
+ {
+ // Try to get a VerIterator to the installed version
+ pkgCache::VerIterator inst = pi.CurrentVer();
+ if (!inst.end())
+ {
+ // If we made it so far, it is installed
+ flags |= PackageState::Installed;
+
+ // Now check if it is upgradable
+ pkgCache::VerIterator cand = impl->policy().GetCandidateVer(pi);
+
+ // If the candidate version is different than the installed one, then
+ // it is installable
+ if (!cand.end() && inst != cand)
+ flags |= PackageState::Upgradable;
+ }
+ }
+ if (sc.Install())
+ flags |= PackageState::Install;
+ if ((sc.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
+ flags |= PackageState::ReInstall;
+ if (sc.Keep())
+ flags |= PackageState::Keep;
+ if (sc.Delete())
+ flags |= PackageState::Remove;
+ if ((sc.iFlags & pkgDepCache::Purge) == pkgDepCache::Purge)
+ flags |= PackageState::Purge;
+ if (sc.NowBroken())
+ flags |= PackageState::NowBroken;
+ if (sc.InstBroken())
+ flags |= PackageState::WillBreak;
+
+ return PackageState(flags);
+}
+
+std::string Apt::rawRecord(const std::string& ver) const
+{
+ // TODO: possibly reimplement using a single lump of apt code, to avoid
+ // repeating lookups
+ return rawRecord(anyVersion(ver));
+}
+
+std::string Apt::rawRecord(const Version& ver) const
+{
+ pkgCache::PkgIterator pi = impl->cache().FindPkg(ver.name());
+ if (pi.end()) return std::string();
+ for (pkgCache::VerIterator vi = pi.VersionList(); !vi.end(); vi++)
+ {
+ const char* v = vi.VerStr();
+ if (v == 0) continue;
+ if (ver.version() == v)
+ {
+ // Code taken and adapted from apt-cache's DisplayRecord
+
+ // Find an appropriate file
+ pkgCache::VerFileIterator vfi = vi.FileList();
+ for (; !vfi.end(); vfi++)
+ if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0)
+ break;
+ if (vfi.end())
+ vfi = vi.FileList();
+
+ // Check and load the package list file
+ pkgCache::PkgFileIterator pfi = vfi.File();
+ if (!pfi.IsOk())
+ throw Exception(string("Reading the data record for a package version from file ") + pfi.FileName());
+
+ FileFd pkgf(pfi.FileName(), FileFd::ReadOnly);
+ if (_error->PendingError() == true)
+ return std::string();
+
+ // Read the record and then write it out again.
+ char* buffer = new char[vfi->Size+1];
+ buffer[vfi->Size] = '\n';
+ if (!pkgf.Seek(vfi->Offset) || !pkgf.Read(buffer, vfi->Size))
+ {
+ delete[] buffer;
+ return std::string();
+ }
+
+ std::string res(buffer, vfi->Size);
+ delete[] buffer;
+ return res;
+ }
+ }
+ return std::string();
+}
+
+void Apt::checkCacheUpdates()
+{
+ if (impl->m_open_timestamp < timestamp())
+ {
+ // Crudely reopen everything
+ delete impl;
+ impl = new AptImplementation;
+ }
+}
+
+void Apt::invalidateTimestamp()
+{
+ impl->m_open_timestamp = 0;
+}
+
+}
+}
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/apt.h b/ept/apt/apt.h
new file mode 100644
index 0000000..0cfff60
--- /dev/null
+++ b/ept/apt/apt.h
@@ -0,0 +1,284 @@
+// -*- C++ -*-
+#ifndef EPT_APT_APT_H
+#define EPT_APT_APT_H
+
+/** \file
+ * High-level front-end to libapt-pkg, as a data provider for the ept framework.
+ */
+
+/*
+ * Copyright (C) 2007,2008 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <wibble/exception.h>
+#include <ept/apt/version.h>
+
+#include <iterator>
+
+namespace ept {
+namespace apt {
+
+class Exception : public wibble::exception::Generic
+{
+protected:
+ std::string m_message;
+
+public:
+ Exception(const std::string& context) throw ();
+ ~Exception() throw () {}
+
+ virtual const char* type() const throw () { return "Apt"; }
+ virtual std::string desc() const throw () { return m_message; }
+};
+
+class Apt;
+class AptImplementation;
+class RecordIteratorImpl;
+
+struct PackageState {
+ enum Query {
+ Install = 1 << 0,
+ Upgrade = 1 << 1,
+ Keep = 1 << 2,
+ Remove = 1 << 3,
+ Installed = 1 << 4,
+ Upgradable = 1 << 5,
+ NowBroken = 1 << 6,
+ WillBreak = 1 << 7,
+ ReInstall = 1 << 8,
+ Purge = 1 << 9,
+ Hold = 1 << 10,
+ Valid = 1 << 11
+ };
+
+ typedef unsigned state;
+
+ operator unsigned() { return m_state; };
+
+ PackageState &operator=( unsigned i ) {
+ m_state = i;
+ return *this;
+ }
+
+ PackageState &operator|=( const PackageState &s ) {
+ m_state |= s.m_state;
+ return *this;
+ }
+
+ PackageState( unsigned a ) {
+ m_state = a;
+ }
+
+ PackageState() : m_state( 0 ) {}
+
+ // FIXME this probably needs to be used consistently in core and out of core
+ bool isValid() const { return m_state & Valid; }
+ // FIXME compatibility API for non-core apt
+ bool isInstalled() const { return installed(); }
+
+ bool install() const { return m_state & Install; }
+ // reinstall() implies install()
+ bool reinstall() const { return m_state & ReInstall; }
+ bool remove() const { return m_state & Remove; }
+ // purge() implies remove()
+ bool purge() const { return m_state & Purge; }
+ bool keep() const { return m_state & Keep; }
+ bool willBreak() const { return m_state & WillBreak; }
+ // upgrade() implies install()
+ bool upgrade() const { return hasNewVersion() && install(); }
+ // newInsstal() implies install()
+ bool newInstall() const { return !installed() && install(); }
+ bool hold() const { return m_state & Hold; }
+
+ bool installed() const { return m_state & Installed; }
+ bool hasNewVersion() const { return m_state & Upgradable; }
+ bool upgradable() const { return hasNewVersion() && !hold(); }
+ bool held() const { return hasNewVersion() && hold(); }
+ bool nowBroken() const { return m_state & NowBroken; }
+
+ bool modify() const { return install() || remove(); }
+
+protected:
+ unsigned m_state;
+};
+
+/**
+ * High-level access to the Apt cache, as a data provider for the ept
+ * framework.
+ *
+ * This class wraps the Apt cache and allows to query it in various ways.
+ */
+class Apt
+{
+protected:
+ AptImplementation* impl;
+
+public:
+ // Iterate Packages in the Apt cache
+ class Iterator : public std::iterator<std::input_iterator_tag, std::string, void, void, void>
+ {
+ void* cur;
+
+ protected:
+ // Construct a valid iterator
+ Iterator(void* cur) : cur(cur) {}
+
+ // Construct and end iterator
+ Iterator() : cur(0) {}
+
+ public:
+ // Copy constructor
+ Iterator(const Iterator&);
+ ~Iterator();
+ std::string operator*();
+ Iterator& operator++();
+ Iterator& operator=(const Iterator&);
+ bool operator==(const Iterator&) const;
+ bool operator!=(const Iterator&) const;
+
+ // FIXME: Iterator operator++(int); cannot be easily implemented
+ // because of how Apt's pkgIterator works
+
+ friend class Apt;
+ };
+
+ // Iterate Package records in the Apt cache
+ class RecordIterator : public std::iterator<std::input_iterator_tag, std::string, void, void, void>
+ {
+ RecordIteratorImpl* impl;
+ size_t pos;
+ std::string cur;
+ size_t cur_pos;
+
+ protected:
+ // Construct a valid iterator
+ RecordIterator(RecordIteratorImpl* cur, size_t pos = 0);
+
+ // Construct and end iterator
+ RecordIterator() : impl(0), pos(0), cur_pos(0) {}
+
+ public:
+ // Copy constructor
+ RecordIterator(const RecordIterator& r);
+
+ ~RecordIterator();
+ std::string operator*();
+ std::string* operator->();
+ RecordIterator& operator++();
+ RecordIterator& operator=(const RecordIterator& r);
+ bool operator==(const RecordIterator&) const;
+ bool operator!=(const RecordIterator&) const;
+
+ // FIXME: Iterator operator++(int); cannot be easily implemented
+ // because of how Apt's pkgIterator works
+
+ friend class Apt;
+ };
+
+ typedef Iterator iterator;
+ typedef RecordIterator record_iterator;
+
+ /**
+ * Create the Apt data provider
+ */
+ Apt();
+ ~Apt();
+
+ iterator begin() const;
+ iterator end() const;
+
+ record_iterator recordBegin() const;
+ record_iterator recordEnd() const;
+
+
+ /// Return the number of packages in the archive
+ size_t size() const;
+
+ /**
+ * Validate a package name, returning trye if it exists in the APT database,
+ * or false if it does not.
+ */
+ bool isValid(const std::string& pkg) const;
+
+ /// Validate a package name, returning it if it exists in the APT database,
+ /// or returning the empty string if it does not.
+ std::string validate(const std::string& pkg) const
+ {
+ if (isValid(pkg))
+ return pkg;
+ return std::string();
+ }
+
+ /// Validate a Version, returning it if it exists in the APT database, or
+ /// returning the invalid version if it does not.
+ Version validate(const Version& ver) const;
+
+ /// Return the installed version for a package
+ Version installedVersion(const std::string& pkg) const;
+
+ /// Return the candidate version for a package
+ Version candidateVersion(const std::string& pkg) const;
+
+ /**
+ * Return the candidate version for a package, if available, or the
+ * installed version otherwise
+ */
+ Version anyVersion(const std::string& pkg) const;
+
+ /// Return state information on a package
+ PackageState state(const std::string& pkg) const;
+
+ /**
+ * Perform a package search.
+ *
+ * All packages for which the functor filter returns true, are passed to
+ * the functor out.
+ */
+ //template<typename FILTER, typename OUT>
+ //void search(const FILTER& filter, OUT& out);
+
+ /// Get the raw package record for the given Version
+ std::string rawRecord(const std::string& pkg) const;
+
+ /// Get the raw package record for the given Version
+ std::string rawRecord(const Version& ver) const;
+
+ /// Timestamp of when the apt index was last modified
+ time_t timestamp();
+
+ /**
+ * Check if the cache has been changed by another process, and reopen it if
+ * that is the case.
+ *
+ * Note that this method can invalidate all existing iterators.
+ */
+ void checkCacheUpdates();
+
+ /**
+ * Invalidate the cache timestamp used to track cache updates.
+ *
+ * @warning Do not use this method: it is here only to support the test
+ * cases, and may disappear in any future version.
+ */
+ void invalidateTimestamp();
+};
+
+}
+}
+
+// vim:set ts=4 sw=4:
+#endif
diff --git a/ept/apt/apt.test.h b/ept/apt/apt.test.h
new file mode 100644
index 0000000..cc9602e
--- /dev/null
+++ b/ept/apt/apt.test.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/test.h>
+#include <ept/apt/apt.h>
+#include <set>
+#include <algorithm>
+
+using namespace std;
+using namespace ept;
+using namespace ept::apt;
+
+struct TestApt : AptTestEnvironment {
+ Apt apt;
+
+ // Check that iterations iterates among some packages
+ Test iterators()
+ {
+ Apt::iterator i = apt.begin();
+ assert(i != apt.end());
+
+ size_t count = 0;
+ for (; i != apt.end(); ++i)
+ ++count;
+
+ assert(count > 100);
+ }
+
+ // Check that iteration gives some well-known packages
+ Test aptExists()
+ {
+ set<string> packages;
+
+ std::copy(apt.begin(), apt.end(), inserter(packages, packages.begin()));
+
+ assert(packages.find("libsp1") != packages.end());
+ // TODO this exposes a bug somewhere... sp definitely is among
+ // the packages
+ // assert(packages.find("sp") != packages.end());
+ assert(packages.find("") == packages.end());
+ }
+
+ // Check that timestamp gives some meaningful timestamp
+ Test timestamp()
+ {
+ time_t ts = apt.timestamp();
+ assert(ts > 1000000);
+ }
+
+ // Check the package validator
+ Test validity()
+ {
+ assert(apt.isValid("apt"));
+ assert(!apt.isValid("this-package-does-not-really-exists"));
+ }
+
+ // Check the version instantiators
+ Test versions()
+ {
+ std::string pkg("apt");
+ Version ver = apt.candidateVersion(pkg);
+ assert(ver.isValid());
+
+ ver = apt.installedVersion(pkg);
+ assert(ver.isValid());
+
+ ver = apt.anyVersion(pkg);
+ assert(ver.isValid());
+
+ std::string pkg1("this-package-does-not-really-exists");
+ ver = apt.candidateVersion(pkg1);
+ assert(!ver.isValid());
+
+ ver = apt.installedVersion(pkg1);
+ assert(!ver.isValid());
+
+ ver = apt.anyVersion(pkg1);
+ assert(!ver.isValid());
+ }
+
+ // Check the version validator
+ Test versionValidity()
+ {
+ Version ver = apt.candidateVersion("apt");
+ assert(apt.validate(ver) == ver);
+
+ ver = Version("this-package-does-not-really-exists", "0.1");
+ assert(!apt.validate(ver).isValid());
+
+ ver = Version("apt", "0.31415");
+ assert(!apt.validate(ver).isValid());
+ }
+
+ // Check the raw record accessor
+ Test rawRecord()
+ {
+ string pkg("sp");
+ Version ver = apt.candidateVersion(pkg);
+ assert(apt.validate(ver) == ver);
+
+ string record = apt.rawRecord(ver);
+ assert(record.find("Package: sp") != string::npos);
+ assert(record.find("Section: text") != string::npos);
+
+ record = apt.rawRecord(Version("sp", "0.31415"));
+ assert_eq(record, string());
+
+ assert_eq(apt.rawRecord(pkg), apt.rawRecord(apt.anyVersion(pkg)));
+ }
+
+ // Check the package state accessor
+ Test state()
+ {
+ PackageState s = apt.state("kdenetwork");
+ assert(s.isValid());
+ assert(s.isInstalled());
+
+ s = apt.state("this-package-does-not-really-exists");
+ assert(!s.isValid());
+ }
+
+ // Check the record iterator (accessing with *)
+ Test recordIteration()
+ {
+ size_t count = 0;
+ for (Apt::record_iterator i = apt.recordBegin();
+ i != apt.recordEnd(); ++i)
+ {
+ assert((*i).size() > 8);
+ assert_eq((*i).substr(0, 8), "Package:");
+ ++count;
+ }
+ assert(count > 200);
+ }
+
+ // Check the record iterator (accessing with ->)
+ Test recordIteration2()
+ {
+ size_t count = 0;
+ for (Apt::record_iterator i = apt.recordBegin();
+ i != apt.recordEnd(); ++i)
+ {
+ assert(i->size() > 8);
+ assert_eq(i->substr(0, 8), "Package:");
+ ++count;
+ }
+ assert(count > 200);
+ }
+
+ // Check that the iterators can be used with the algorithms
+ Test stlIteration()
+ {
+ vector<string> out;
+ std::copy(apt.begin(), apt.end(), back_inserter(out));
+ }
+
+ // Check that the iterators can be used with the algorithms
+ Test stlRecordIteration()
+ {
+ vector<string> out;
+ std::copy(apt.recordBegin(), apt.recordEnd(), back_inserter(out));
+ }
+
+ // Check that checkUpdates will keep a working Apt object
+ Test checkUpdates()
+ {
+ assert(apt.isValid("apt"));
+ apt.checkCacheUpdates();
+ assert(apt.isValid("apt"));
+ apt.invalidateTimestamp();
+ apt.checkCacheUpdates();
+ assert(apt.isValid("apt"));
+ }
+
+};
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/packagerecord.cc b/ept/apt/packagerecord.cc
new file mode 100644
index 0000000..f842bac
--- /dev/null
+++ b/ept/apt/packagerecord.cc
@@ -0,0 +1,118 @@
+/** \file
+ * Parser for APT records, with specialised accessors for package records
+ */
+
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/apt/packagerecord.h>
+
+#include <cctype>
+#include <cstdlib>
+
+//#include <iostream>
+
+using namespace std;
+
+namespace ept {
+namespace apt {
+
+size_t PackageRecord::parseSize(size_t def, const std::string& str) const
+{
+ if (str == string())
+ return def;
+ return (size_t)strtoul(str.c_str(), NULL, 10);
+}
+
+std::string PackageRecord::parseShortDescription(const std::string& def, const std::string& str) const
+{
+ if (str == std::string())
+ return def;
+ size_t pos = str.find("\n");
+ if (pos == std::string::npos)
+ return str;
+ else
+ return str.substr(0, pos);
+}
+
+std::string PackageRecord::parseLongDescription(const std::string& def, const std::string& str) const
+{
+ if (str == std::string())
+ return def;
+ size_t pos = str.find("\n");
+ if (pos == std::string::npos)
+ return str;
+ else
+ {
+ // Trim trailing spaces
+ for (++pos; pos < str.size() && isspace(str[pos]); ++pos)
+ ;
+ return str.substr(pos);
+ }
+}
+
+std::set<std::string> PackageRecord::parseTags(const std::set<std::string>& def, const std::string& str) const
+{
+ if (str == string())
+ return def;
+
+ set<string> res;
+
+ size_t pos = 0;
+ while (pos < str.size())
+ {
+ string tag;
+ size_t i = str.find(", ", pos);
+ if (i == string::npos)
+ tag = str.substr(pos);
+ else
+ tag = str.substr(pos, i-pos);
+
+ // Check if we need curly brace expansion
+ if (tag[tag.size() - 1] == '}')
+ {
+ size_t begin = tag.find('{');
+ if (begin != string::npos)
+ {
+ string prefix(tag, 0, begin);
+ ++begin;
+ size_t end;
+ while ((end = tag.find(',', begin)) != string::npos)
+ {
+ res.insert(prefix + tag.substr(begin, end-begin));
+ begin = end + 1;
+ }
+ res.insert(prefix + tag.substr(begin, tag.size() - 1 - begin));
+ }
+ } else {
+ res.insert(tag);
+ }
+
+ if (i == string::npos)
+ break;
+ else
+ pos = i + 2;
+ }
+
+ return res;
+}
+
+}
+}
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/packagerecord.h b/ept/apt/packagerecord.h
new file mode 100644
index 0000000..94e0435
--- /dev/null
+++ b/ept/apt/packagerecord.h
@@ -0,0 +1,176 @@
+#ifndef EPT_APT_PACKAGERECORD_H
+#define EPT_APT_PACKAGERECORD_H
+
+/** \file
+ * Parser for APT records, with specialised accessors for package records
+ */
+
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/apt/recordparser.h>
+#include <set>
+
+namespace ept {
+namespace apt {
+
+/**
+ * RecordParser specialised with access methods for common Debian package
+ * information.
+ */
+class PackageRecord : public RecordParser
+{
+ bool parseBool(bool& def, const std::string& str) const
+ {
+ // Believe it or not, this is what apt does to interpret bool fields
+ if (str == "no" || str == "false" || str == "without" ||
+ str == "off" || str == "disable")
+ return false;
+
+ if (str == "yes" || str == "true" || str == "with" ||
+ str == "on" || str == "enable")
+ return true;
+
+ return def;
+ }
+ std::string parseString(const std::string& def, const std::string& str) const
+ {
+ if (str == std::string())
+ return def;
+ return str;
+ }
+ std::string parseShortDescription(const std::string& def, const std::string& str) const;
+ std::string parseLongDescription(const std::string& def, const std::string& str) const;
+ size_t parseSize(size_t def, const std::string& str) const;
+ std::set<std::string> parseTags(const std::set<std::string>& def, const std::string& str) const;
+
+public:
+ PackageRecord() : RecordParser() {}
+ PackageRecord(const std::string& str) : RecordParser(str) {}
+
+ std::string package(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Package"));
+ }
+ std::string priority(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Priority"));
+ }
+ std::string section(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Section"));
+ }
+ size_t installedSize(size_t def = 0) const
+ {
+ return parseSize(def, lookup("Installed-Size"));
+ }
+ std::string maintainer(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Maintainer"));
+ }
+ std::string architecture(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Architecture"));
+ }
+ std::string source(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Source"));
+ }
+ std::string version(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Version"));
+ }
+ std::string replaces(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Replaces"));
+ }
+ std::string depends(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Depends"));
+ }
+ std::string preDepends(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Pre-Depends"));
+ }
+ std::string recommends(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Recommends"));
+ }
+ std::string suggests(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Suggests"));
+ }
+ std::string enhances(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Enhances"));
+ }
+ std::string provides(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Provides"));
+ }
+ std::string conflicts(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Conflicts"));
+ }
+ std::string filename(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Filename"));
+ }
+ size_t packageSize(size_t def = 0) const
+ {
+ return parseSize(def, lookup("Size"));
+ }
+ std::string md5sum(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("MD5sum"));
+ }
+ std::string sha1(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("SHA1"));
+ }
+ std::string sha256(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("SHA256"));
+ }
+ std::string description(const std::string& def = std::string()) const
+ {
+ return parseString(def, lookup("Description"));
+ }
+ std::string shortDescription(const std::string& def = std::string()) const
+ {
+ return parseShortDescription(def, lookup("Description"));
+ }
+ std::string longDescription(const std::string& def = std::string()) const
+ {
+ return parseLongDescription(def, lookup("Description"));
+ }
+ bool buildEssential(bool def = false) const
+ {
+ return parseBool(def, lookup("Build-Essential"));
+ }
+ std::set<std::string> tag(const std::set<std::string>& def = std::set<std::string>()) const
+ {
+ return parseTags(def, lookup("Tag"));
+ }
+};
+
+}
+}
+
+// vim:set ts=4 sw=4:
+#endif
diff --git a/ept/apt/packagerecord.test.h b/ept/apt/packagerecord.test.h
new file mode 100644
index 0000000..657cbab
--- /dev/null
+++ b/ept/apt/packagerecord.test.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/test.h>
+#include <ept/apt/packagerecord.h>
+
+namespace std {
+ ostream& operator<<(ostream& out, const set<string>& s)
+ {
+ for (set<string>::const_iterator i = s.begin();
+ i != s.end(); ++i)
+ if (i == s.begin())
+ out << *i;
+ else
+ out << ", " << *i;
+ return out;
+ }
+}
+
+using namespace std;
+using namespace ept;
+using namespace ept::apt;
+
+struct TestAptPackagerecord {
+
+ // Check that the supported fields are understood
+ Test supportedFields()
+ {
+ string record =
+ "Package: apt\n"
+ "Priority: important\n"
+ "Section: admin\n"
+ "Installed-Size: 4368\n"
+ "Maintainer: APT Development Team <deity@lists.debian.org>\n"
+ "Architecture: amd64\n"
+ "Source: apt\n"
+ "Version: 0.6.46.4-0.1\n"
+ "Replaces: libapt-pkg-doc (<< 0.3.7), libapt-pkg-dev (<< 0.3.7)\n"
+ "Provides: libapt-pkg-libc6.3-6-3.11\n"
+ "Depends: libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12), debian-archive-keyring\n"
+ "Pre-Depends: debtags (maybe)\n"
+ "Suggests: aptitude | synaptic | gnome-apt | wajig, dpkg-dev, apt-doc, bzip2\n"
+ "Recommends: debtags (maybe)\n"
+ "Enhances: debian\n"
+ "Conflicts: marameo\n"
+ "Filename: pool/main/a/apt/apt_0.6.46.4-0.1_amd64.deb\n"
+ "Size: 1436478\n"
+ "MD5sum: 1776421f80d6300c77a608e77a9f4a15\n"
+ "SHA1: 1bd7337d2df56d267632cf72ac930c0a4895898f\n"
+ "SHA256: b92442ab60046b4d0728245f39cc932f26e17db9f7933a5ec9aaa63172f51fda\n"
+ "Description: Advanced front-end for dpkg\n"
+ " This is Debian's next generation front-end for the dpkg package manager.\n"
+ " It provides the apt-get utility and APT dselect method that provides a\n"
+ " simpler, safer way to install and upgrade packages.\n"
+ " .\n"
+ " APT features complete installation ordering, multiple source capability\n"
+ " and several other unique features, see the Users Guide in apt-doc.\n"
+ "Build-Essential: yes\n"
+ "Tag: admin::package-management, filetransfer::ftp, filetransfer::http, hardware::storage:cd, interface::commandline, network::client, protocol::{ftp,http,ipv6}, role::program, suite::debian, use::downloading, use::searching, works-with::software:package\n";
+
+ PackageRecord p(record);
+
+ assert_eq(p.size(), 24u);
+
+ assert_eq(p.package(), "apt");
+ assert_eq(p.priority(), "important");
+ assert_eq(p.section(), "admin");
+ assert_eq(p.installedSize(), 4368u);
+ assert_eq(p.maintainer(), "APT Development Team <deity@lists.debian.org>");
+ assert_eq(p.architecture(), "amd64");
+ assert_eq(p.source(), "apt");
+ assert_eq(p.version(), "0.6.46.4-0.1");
+ assert_eq(p.replaces(), "libapt-pkg-doc (<< 0.3.7), libapt-pkg-dev (<< 0.3.7)");
+ assert_eq(p.provides(), "libapt-pkg-libc6.3-6-3.11");
+ assert_eq(p.depends(), "libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12), debian-archive-keyring");
+ assert_eq(p.preDepends(), "debtags (maybe)");
+ assert_eq(p.recommends(), "debtags (maybe)");
+ assert_eq(p.suggests(), "aptitude | synaptic | gnome-apt | wajig, dpkg-dev, apt-doc, bzip2");
+ assert_eq(p.enhances(), "debian");
+ assert_eq(p.conflicts(), "marameo");
+ assert_eq(p.filename(), "pool/main/a/apt/apt_0.6.46.4-0.1_amd64.deb");
+ assert_eq(p.packageSize(), 1436478u);
+ assert_eq(p.md5sum(), "1776421f80d6300c77a608e77a9f4a15");
+ assert_eq(p.sha1(), "1bd7337d2df56d267632cf72ac930c0a4895898f");
+ assert_eq(p.sha256(), "b92442ab60046b4d0728245f39cc932f26e17db9f7933a5ec9aaa63172f51fda");
+ assert_eq(p.description(), "Advanced front-end for dpkg\n"
+ " This is Debian's next generation front-end for the dpkg package manager.\n"
+ " It provides the apt-get utility and APT dselect method that provides a\n"
+ " simpler, safer way to install and upgrade packages.\n"
+ " .\n"
+ " APT features complete installation ordering, multiple source capability\n"
+ " and several other unique features, see the Users Guide in apt-doc.");
+ assert_eq(p.shortDescription(), "Advanced front-end for dpkg");
+ assert_eq(p.longDescription(),
+ "This is Debian's next generation front-end for the dpkg package manager.\n"
+ " It provides the apt-get utility and APT dselect method that provides a\n"
+ " simpler, safer way to install and upgrade packages.\n"
+ " .\n"
+ " APT features complete installation ordering, multiple source capability\n"
+ " and several other unique features, see the Users Guide in apt-doc.");
+ assert_eq(p.buildEssential(), true);
+
+ std::set<std::string> tags;
+ tags.insert("admin::package-management");
+ tags.insert("filetransfer::ftp");
+ tags.insert("filetransfer::http");
+ tags.insert("hardware::storage:cd");
+ tags.insert("interface::commandline");
+ tags.insert("network::client");
+ tags.insert("protocol::ftp");
+ tags.insert("protocol::http");
+ tags.insert("protocol::ipv6");
+ tags.insert("role::program");
+ tags.insert("suite::debian");
+ tags.insert("use::downloading");
+ tags.insert("use::searching");
+ tags.insert("works-with::software:package");
+ assert_eq(p.tag(), tags);
+ }
+
+};
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/recordparser.cc b/ept/apt/recordparser.cc
new file mode 100644
index 0000000..3562123
--- /dev/null
+++ b/ept/apt/recordparser.cc
@@ -0,0 +1,170 @@
+/** \file
+ * Parser for APT records
+ */
+
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/apt/recordparser.h>
+
+#include <algorithm>
+#include <cctype>
+
+//#include <iostream>
+
+using namespace std;
+
+namespace ept {
+namespace apt {
+
+struct rpcompare
+{
+ const RecordParser& rp;
+ rpcompare(const RecordParser& rp) : rp(rp) {}
+ bool operator()(size_t a, size_t b)
+ {
+ return rp.name(a) < rp.name(b);
+ }
+};
+
+void RecordParser::scan(const std::string& str)
+{
+ buffer = str;
+ ends.clear();
+ sorted.clear();
+
+ //cerr << "PARSE " << endl << buffer << "*****" << endl;
+
+ // Scan the buffer, taking note of all ending offsets of the various fields
+ size_t pos = 0;
+ size_t idx = 0;
+ while (pos < buffer.size() - 1)
+ {
+ //cerr << "PREPOS " << pos << " left: " << buffer.substr(pos, 10) << endl;
+ pos = buffer.find("\n", pos);
+ //cerr << "POSTPOS " << pos << " left: " << (pos == string::npos ? "NONE" : buffer.substr(pos, 10)) << endl;
+
+ // The buffer does not end with a newline
+ if (pos == string::npos)
+ {
+ //cerr << "ENDNOTEOL" << endl;
+ pos = buffer.size();
+ ends.push_back(pos);
+ sorted.push_back(idx++);
+ break;
+ }
+
+ ++pos;
+ //cerr << "POSTPOSINC " << pos << " left: " << buffer.substr(pos, 10) << endl;
+
+ // The buffer ends with a newline
+ if (pos == buffer.size())
+ {
+ //cerr << "ENDEOL" << endl;
+ ends.push_back(pos);
+ sorted.push_back(idx++);
+ break;
+ }
+
+ // Terminate parsing on double newlines
+ if (buffer[pos] == '\n')
+ {
+ //cerr << "ENDDOUBLENL" << endl;
+ ends.push_back(pos);
+ sorted.push_back(idx++);
+ break;
+ }
+
+ // Mark the end of the field if it's not a continuation line
+ if (!isspace(buffer[pos]))
+ {
+ //cerr << "INNERFIELD" << endl;
+ ends.push_back(pos);
+ sorted.push_back(idx++);
+ } //else
+ //cerr << "CONTLINE" << endl;
+ }
+
+ // Sort the sorted array
+ sort(sorted.begin(), sorted.end(), rpcompare(*this));
+
+ //for (size_t i = 0; i < ends.size(); ++i)
+ // cerr << ends[i] << "\t" << name(i) << "\t" << sorted[i] << "\t" << name(sorted[i]) << endl;
+}
+
+std::string RecordParser::field(size_t idx) const
+{
+ if (idx >= ends.size())
+ return string();
+ if (idx == 0)
+ return buffer.substr(0, ends[0]);
+ else
+ return buffer.substr(ends[idx-1], ends[idx]-ends[idx-1]);
+}
+
+std::string RecordParser::name(size_t idx) const
+{
+ string res = field(idx);
+ size_t pos = res.find(":");
+ if (pos == string::npos)
+ return res;
+ return res.substr(0, pos);
+}
+
+std::string RecordParser::lookup(size_t idx) const
+{
+ string res = field(idx);
+ size_t pos = res.find(":");
+ if (pos == string::npos)
+ return res;
+ // Skip initial whitespace after the :
+ for (++pos; pos < res.size() && isspace(res[pos]); ++pos)
+ ;
+ res = res.substr(pos);
+ // Trim spaces at the end
+ while (!res.empty() && isspace(res[res.size() - 1]))
+ res.resize(res.size() - 1);
+ return res;
+}
+
+size_t RecordParser::index(const std::string& str) const
+{
+ int begin, end;
+
+ /* Binary search */
+ begin = -1, end = size();
+ while (end - begin > 1)
+ {
+ int cur = (end + begin) / 2;
+ //cerr << "Test " << cur << " " << str << " < " << name(cur) << endl;
+ if (name(sorted[cur]) > str)
+ end = cur;
+ else
+ begin = cur;
+ }
+
+ if (begin == -1 || name(sorted[begin]) != str)
+ return size();
+ else
+ return sorted[begin];
+}
+
+}
+}
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/recordparser.h b/ept/apt/recordparser.h
new file mode 100644
index 0000000..cbaa466
--- /dev/null
+++ b/ept/apt/recordparser.h
@@ -0,0 +1,95 @@
+#ifndef EPT_APT_RECORDPARSER_H
+#define EPT_APT_RECORDPARSER_H
+
+/** \file
+ * Parser for APT records
+ */
+
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <vector>
+#include <string>
+
+namespace ept {
+namespace apt {
+
+/**
+ * Access the fields of a package record contained inside a std::string.
+ *
+ * Implementation note: this implementation should take advantage of
+ * std::string sharing buffer space among them.
+ */
+class RecordParser
+{
+ /// Buffer containing the whole record
+ std::string buffer;
+
+ /// End offsets of the various fields in the record
+ std::vector<size_t> ends;
+
+ /// Indexes on the ends vector, sorted by field name
+ std::vector<size_t> sorted;
+
+public:
+ RecordParser() {}
+ RecordParser(const std::string& str) { scan(str); }
+
+ /// Index a new record
+ void scan(const std::string& str);
+
+ /**
+ * Get the index of the field with the given name.
+ *
+ * size() is returned if not found
+ */
+ size_t index(const std::string& str) const;
+
+ /// Return the field by its index
+ std::string field(size_t idx) const;
+
+ /// Return the name of a field by its index
+ std::string name(size_t idx) const;
+
+ /// Return the content of a field by its index
+ std::string lookup(size_t idx) const;
+
+ /// Return the content of a field by its name
+ std::string lookup(const std::string& name) const { return lookup(index(name)); }
+
+ /// Return the content of a field by its index
+ std::string operator[](size_t idx) const { return lookup(idx); }
+
+ /// Return the content of a field by its name
+ std::string operator[](const std::string& name) const { return lookup(name); }
+
+ /// Return the entire record
+ const std::string& record() const { return buffer; }
+
+ /// Return the entire record
+ std::string record() { return buffer; }
+
+ /// Return the number of fields in the record
+ size_t size() const { return ends.size(); }
+};
+
+}
+}
+
+// vim:set ts=4 sw=4:
+#endif
diff --git a/ept/apt/recordparser.test.h b/ept/apt/recordparser.test.h
new file mode 100644
index 0000000..629008f
--- /dev/null
+++ b/ept/apt/recordparser.test.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/test.h>
+#include <ept/apt/recordparser.h>
+
+//#include <iostream>
+
+using namespace std;
+using namespace ept;
+using namespace ept::apt;
+
+struct TestAptRecordparser {
+ std::string record;
+ TestAptRecordparser()
+ {
+ record =
+ "A:\n"
+ "D: da de di do du\n"
+ "B: b\n"
+ "C: c \n"
+ "Desc: this is the beginning\n"
+ " this is the continuation\n"
+ " this is the end\n";
+ }
+
+ // Check that the fields are identified and broken up correctly
+ Test parsing()
+ {
+ RecordParser p(record);
+
+ assert_eq(p.record(), record);
+ assert_eq(p.size(), 5u);
+ }
+
+ Test fieldTuples()
+ {
+ RecordParser p(record);
+ assert_eq(p.field(0), "A:\n");
+ assert_eq(p.field(1), "D: da de di do du\n");
+ assert_eq(p.field(2), "B: b\n");
+ assert_eq(p.field(3), "C: c \n");
+ assert_eq(p.field(4), "Desc: this is the beginning\n this is the continuation\n this is the end\n");
+ }
+
+ Test fieldKeys()
+ {
+ RecordParser p(record);
+ assert_eq(p.name(0), "A");
+ assert_eq(p.name(1), "D");
+ assert_eq(p.name(2), "B");
+ assert_eq(p.name(3), "C");
+ assert_eq(p.name(4), "Desc");
+ }
+
+ Test fieldValues()
+ {
+ RecordParser p(record);
+ assert_eq(p[0], "");
+ assert_eq(p[1], "da de di do du");
+ assert_eq(p[2], "b");
+ assert_eq(p[3], "c");
+ assert_eq(p[4], "this is the beginning\n this is the continuation\n this is the end");
+ }
+
+ // Check that the field search by name finds all the fields
+ Test findByName()
+ {
+ RecordParser p(record);
+
+ assert_eq(p.index("A"), 0u);
+ assert_eq(p.index("D"), 1u);
+ assert_eq(p.index("B"), 2u);
+ assert_eq(p.index("C"), 3u);
+ assert_eq(p.index("Desc"), 4u);
+
+ assert_eq(p.name(p.index("A")), "A");
+ assert_eq(p.name(p.index("B")), "B");
+ assert_eq(p.name(p.index("C")), "C");
+ assert_eq(p.name(p.index("D")), "D");
+ assert_eq(p.name(p.index("Desc")), "Desc");
+ }
+
+ Test indexing()
+ {
+ RecordParser p(record);
+ assert_eq(p["A"], "");
+ assert_eq(p["B"], "b");
+ assert_eq(p["C"], "c");
+ assert_eq(p["D"], "da de di do du");
+ assert_eq(p["Desc"], "this is the beginning\n this is the continuation\n this is the end");
+ }
+
+ Test missingBehaviour()
+ {
+ RecordParser p(record);
+ // Missing fields give empty strings
+ assert_eq(p.field(100), "");
+ assert_eq(p.name(100), "");
+ assert_eq(p[100], "");
+ assert_eq(p["Missing"], "");
+ }
+
+ // Check that scanning twice replaces the old fields
+ Test rescan()
+ {
+ std::string record =
+ "A: a\n"
+ "B: b\n"
+ "C: c\n";
+
+ RecordParser p(record);
+ assert_eq(p.size(), 3u);
+ assert_eq(p["A"], "a");
+ assert_eq(p["B"], "b");
+ assert_eq(p["C"], "c");
+
+ std::string record1 =
+ "Foo: bar\n"
+ "A: different\n";
+
+ p.scan(record1);
+
+ //for (size_t i = 0; i < p.size(); ++i)
+ // cerr << ">> " << i << "==" << p.index(p.name(i)) << " " << p.name(i) << " " << p[i] << endl;
+
+ assert_eq(p.size(), 2u);
+ assert_eq(p["A"], "different");
+ assert_eq(p["B"], "");
+ assert_eq(p["C"], "");
+ assert_eq(p["Foo"], "bar");
+ }
+
+ // Real-life example
+ Test realLife()
+ {
+ string record =
+ "Package: apt\n"
+ "Priority: important\n"
+ "Section: admin\n"
+ "Installed-Size: 4368\n"
+ "Maintainer: APT Development Team <deity@lists.debian.org>\n"
+ "Architecture: amd64\n"
+ "Version: 0.6.46.4-0.1\n"
+ "Replaces: libapt-pkg-doc (<< 0.3.7), libapt-pkg-dev (<< 0.3.7)\n"
+ "Provides: libapt-pkg-libc6.3-6-3.11\n"
+ "Depends: libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12), debian-archive-keyring\n"
+ "Suggests: aptitude | synaptic | gnome-apt | wajig, dpkg-dev, apt-doc, bzip2\n"
+ "Filename: pool/main/a/apt/apt_0.6.46.4-0.1_amd64.deb\n"
+ "Size: 1436478\n"
+ "MD5sum: 1776421f80d6300c77a608e77a9f4a15\n"
+ "SHA1: 1bd7337d2df56d267632cf72ac930c0a4895898f\n"
+ "SHA256: b92442ab60046b4d0728245f39cc932f26e17db9f7933a5ec9aaa63172f51fda\n"
+ "Description: Advanced front-end for dpkg\n"
+ " This is Debian's next generation front-end for the dpkg package manager.\n"
+ " It provides the apt-get utility and APT dselect method that provides a\n"
+ " simpler, safer way to install and upgrade packages.\n"
+ " .\n"
+ " APT features complete installation ordering, multiple source capability\n"
+ " and several other unique features, see the Users Guide in apt-doc.\n"
+ "Build-Essential: yes\n"
+ "Tag: admin::package-management, filetransfer::ftp, filetransfer::http, hardware::storage:cd, interface::commandline, network::client, protocol::{ftp,http,ipv6}, role::program, suite::debian, use::downloading, use::searching, works-with::software:package\n";
+ RecordParser p(record);
+
+ assert_eq(p.size(), 19u);
+
+ string rec1;
+ for (size_t i = 0; i < p.size(); ++i)
+ rec1 += p.field(i);
+ assert_eq(record, rec1);
+ }
+
+ // Various buffer termination patterns
+ Test bufferTermination()
+ {
+ std::string record =
+ "A: a\n"
+ "B: b";
+
+ RecordParser p(record);
+ assert_eq(p.size(), 2u);
+ assert_eq(p["A"], "a");
+ assert_eq(p["B"], "b");
+ }
+
+ Test bufferTermination2()
+ {
+ std::string record =
+ "A: a\n"
+ "B: b\n\n";
+
+ RecordParser p(record);
+ assert_eq(p.size(), 2u);
+ assert_eq(p["A"], "a");
+ assert_eq(p["B"], "b");
+ }
+
+ Test bufferTermination3()
+ {
+ std::string record =
+ "A: a\n"
+ "B: b\n\n"
+ "C: c\n";
+
+ RecordParser p(record);
+ assert_eq(p.size(), 2u);
+ assert_eq(p["A"], "a");
+ assert_eq(p["B"], "b");
+ }
+
+};
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/version.cc b/ept/apt/version.cc
new file mode 100644
index 0000000..2b002c1
--- /dev/null
+++ b/ept/apt/version.cc
@@ -0,0 +1,87 @@
+/** \file
+ * Provide a very lightweight Version class that represent a package with a
+ * version, with very cheap value copy operations.
+ */
+
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/apt/version.h>
+#include <apt-pkg/debversion.h>
+
+using namespace std;
+
+namespace ept {
+namespace apt {
+
+std::string Version::upstreamVersion() const
+{
+ // Skip the epoch, if it is there
+ size_t start = m_version.find(':');
+ if (start == string::npos)
+ start = 0;
+ else
+ ++start;
+
+ // Skip everything after the trailing '-', if it is there
+ size_t end = m_version.rfind('-');
+ if (end == string::npos)
+ end = m_version.size();
+
+ return m_version.substr(start, end-start);
+}
+
+/* Version comparison by Debian policy */
+
+bool Version::operator<=(const Version& pkg) const
+{
+ if (name() < pkg.name())
+ return true;
+ if (name() == pkg.name())
+ return debVS.CmpVersion(version(), pkg.version()) <= 0;
+ return false;
+}
+bool Version::operator<(const Version& pkg) const
+{
+ if (name() < pkg.name())
+ return true;
+ if (name() == pkg.name())
+ return debVS.CmpVersion(version(), pkg.version()) < 0;
+ return false;
+}
+bool Version::operator>=(const Version& pkg) const
+{
+ if (name() > pkg.name())
+ return true;
+ if (name() == pkg.name())
+ return debVS.CmpVersion(version(), pkg.version()) >= 0;
+ return false;
+}
+bool Version::operator>(const Version& pkg) const
+{
+ if (name() > pkg.name())
+ return true;
+ if (name() == pkg.name())
+ return debVS.CmpVersion(version(), pkg.version()) > 0;
+ return false;
+}
+
+}
+}
+
+// vim:set ts=4 sw=4:
diff --git a/ept/apt/version.h b/ept/apt/version.h
new file mode 100644
index 0000000..ff8a0ac
--- /dev/null
+++ b/ept/apt/version.h
@@ -0,0 +1,94 @@
+#ifndef EPT_APT_VERSION_H
+#define EPT_APT_VERSION_H
+
+/** \file
+ * Representation of a package with a version
+ */
+
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <string>
+
+namespace ept {
+namespace apt {
+
+/**
+ * Lightweight Version class that represent a package with a version, with very
+ * cheap value copy operations.
+ *
+ * This class can be used to query package information from various information
+ * sources. The purpose is create a middle ground that makes sure that all
+ * sort of different information sources about packages are referring to the
+ * same package.
+ */
+class Version
+{
+protected:
+ std::string m_name;
+ std::string m_version;
+
+public:
+ /**
+ * Create an invalid Version
+ */
+ Version() {}
+
+ /**
+ * Create a Version from strings
+ */
+ Version(const std::string& name, const std::string& version)
+ : m_name(name), m_version(version) {}
+
+ /**
+ * Return the package name
+ */
+ std::string name() const { return m_name; }
+
+ /**
+ * Return the package version, or the empty string if this is a
+ * versionless package.
+ */
+ std::string version() const { return m_version; }
+
+ /**
+ * Return the upstream part of the version
+ */
+ std::string upstreamVersion() const;
+
+ /**
+ * Return true if this package contains a valid value
+ */
+ bool isValid() const { return !m_name.empty() && !m_version.empty(); }
+
+ /**
+ * Comparison operators
+ */
+ bool operator==(const Version& pkg) const { return m_name == pkg.m_name && m_version == pkg.m_version; }
+ bool operator!=(const Version& pkg) const { return m_name != pkg.m_name || m_version != pkg.m_version; }
+ bool operator<=(const Version& pkg) const;
+ bool operator<(const Version& pkg) const;
+ bool operator>=(const Version& pkg) const;
+ bool operator>(const Version& pkg) const;
+};
+
+}
+}
+
+// vim:set ts=4 sw=4:
+#endif
diff --git a/ept/apt/version.test.h b/ept/apt/version.test.h
new file mode 100644
index 0000000..a06a5c6
--- /dev/null
+++ b/ept/apt/version.test.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <ept/test.h>
+#include <ept/apt/version.h>
+
+using namespace std;
+using namespace ept::apt;
+
+struct TestAptVersion {
+
+ // Basic test for invalid version
+ Test invalid()
+ {
+ Version test;
+
+ assert_eq(test.name(), "");
+ assert_eq(test.version(), "");
+ assert_eq(test.isValid(), false);
+
+ string p = test.name();
+
+ assert_eq(p, string());
+ }
+
+ // Basic test for version
+ Test basic()
+ {
+ Version test("test", "1.0");
+
+ assert_eq(test.name(), "test");
+ assert_eq(test.version(), "1.0");
+ assert_eq(test.isValid(), true);
+
+ string p = test.name();
+
+ assert_eq(p, "test");
+
+ Version v(p, "1.1");
+ assert_eq(v.name(), "test");
+ assert_eq(v.version(), "1.1");
+ assert_eq(v.isValid(), true);
+ }
+
+ // Comparison semanthics
+ Test comparison()
+ {
+ Version test("test", "1.0");
+ Version test1("test", "1.0");
+
+ assert(test == test1);
+ assert(! (test != test1));
+ assert(! (test < test1));
+ assert(! (test > test1));
+ assert(test <= test1);
+ assert(test >= test1);
+
+
+ Version test2("test2", "1.0");
+
+ assert(test != test2);
+ assert(test != test2);
+ assert(test < test2);
+ assert(! (test > test2));
+ assert(test <= test2);
+ assert(! (test >= test2));
+
+
+ Version test3("test", "2.0");
+
+ assert(test != test3);
+ assert(test != test3);
+ assert(test < test3);
+ assert(! (test > test3));
+ assert(test <= test3);
+ assert(! (test >= test3));
+ }
+
+ // Value-copy semanthics
+ Test valueCopy()
+ {
+ Version test("test", "1.0");
+ Version test1 = test;
+
+ assert(test == test1);
+
+ Version test2;
+ test2 = test;
+ assert(test == test2);
+ assert(test1 == test2);
+
+ Version test3("test", "1.0");
+ assert(test == test3);
+ assert(test1 == test3);
+ assert(test2 == test3);
+ }
+
+ // Extraction of upstream version
+ Test upstreamVersion()
+ {
+ assert_eq(Version("a", "10.0").upstreamVersion(), "10.0");
+ assert_eq(Version("a", "10.0-1").upstreamVersion(), "10.0");
+ assert_eq(Version("a", "10.0~foo.1-1.0").upstreamVersion(), "10.0~foo.1");
+ assert_eq(Version("a", "1.0:10.0~foo.1-1.0").upstreamVersion(), "10.0~foo.1");
+ }
+
+ // Debian policy comparison semanthics
+ Test policyComparison()
+ {
+ assert(Version("a", "10.0") > Version("a", "2.1"));
+ assert(Version("a", "1:10.0") < Version("a", "2:2.1"));
+ assert(Version("a", "10.0-1") < Version("a", "10.0-2"));
+ assert(Version("a", "10.0-2") > Version("a", "10.0-1"));
+ assert(Version("a", "1:10.0-1") <= Version("a", "1:10.0-1"));
+ assert(Version("a", "1:10.0-1") >= Version("a", "1:10.0-1"));
+ // TODO: add more
+ }
+
+};
+
+// vim:set ts=4 sw=4: