Logo Search packages:      
Sourcecode: qapt version File versions  Download package

backend.cpp

/***************************************************************************
 *   Copyright © 2010 Jonathan Thomas <echidnaman@kubuntu.org>             *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as        *
 *   published by the Free Software Foundation; either version 2 of        *
 *   the License or (at your option) version 3 or any later version        *
 *   accepted by the membership of KDE e.V. (or its successor approved     *
 *   by the membership of KDE e.V.), which shall act as a proxy            *
 *   defined in Section 14 of version 3 of the license.                    *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "backend.h"

// Qt includes
#include <QtCore/QDebug>
#include <QtCore/QStringList>
#include <QtCore/QTextStream>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusServiceWatcher>

// Apt includes
#include <apt-pkg/algorithms.h>
#include <apt-pkg/init.h>
#include <apt-pkg/depcache.h>
#include <apt-pkg/policy.h>
#include <apt-pkg/sourcelist.h>
#include <apt-pkg/strutl.h>

// Xapian includes
#include <xapian.h>

// QApt includes
#include "cache.h"
#include "workerdbus.h" // OrgKubuntuQaptworkerInterface

00045 namespace QApt {

class BackendPrivate
{
public:
    BackendPrivate() : cache(0), records(0) {}
    ~BackendPrivate()
    {
        delete cache;
        delete records;
    }
    // Watches DBus for our worker appearing/disappearing
    QDBusServiceWatcher *watcher;
    // The canonical list of all unique, non-virutal package objects
    PackageList packages;
    QVector<int> packagesIndex;
    // Set of group names extracted from our packages
    QSet<Group> groups;

    QMap<QString, QString> originMap;

    OrgKubuntuQaptworkerInterface *worker;

    // Pointer to the apt cache object
    Cache *cache;
    pkgPolicy *policy;
    pkgRecords *records;

    // undo/redo stuff
    QList<CacheState> undoStack;
    QList<CacheState> redoStack;

    time_t xapianTimeStamp;
    Xapian::Database xapianDatabase;
};

00081 Backend::Backend()
        : d_ptr(new BackendPrivate)
{
    Q_D(Backend);

    d->worker = new OrgKubuntuQaptworkerInterface("org.kubuntu.qaptworker",
                                                  "/", QDBusConnection::systemBus(),
                                                  this);

    connect(d->worker, SIGNAL(errorOccurred(int, const QVariantMap&)),
            this, SLOT(emitErrorOccurred(int, const QVariantMap&)));
    connect(d->worker, SIGNAL(workerStarted()), this, SLOT(workerStarted()));
    connect(d->worker, SIGNAL(workerEvent(int)), this, SLOT(emitWorkerEvent(int)));
    connect(d->worker, SIGNAL(workerFinished(bool)), this, SLOT(workerFinished(bool)));

    d->watcher = new QDBusServiceWatcher(this);
    d->watcher->setConnection(QDBusConnection::systemBus());
    d->watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
    d->watcher->addWatchedService("org.kubuntu.qaptworker");
}

00102 Backend::~Backend()
{
}

00106 bool Backend::init()
{
    Q_D(Backend);
    if (!pkgInitConfig(*_config)) {
        return false;
    }

    if (!pkgInitSystem(*_config, _system)) {
        return false;
    }

    d->cache = new Cache(this);
    reloadCache();
    openXapianIndex();

    return true;
}

00124 void Backend::reloadCache()
{
    Q_D(Backend);

    d->cache->open();

    pkgDepCache *depCache = d->cache->depCache();

    delete d->records;
    d->records = new pkgRecords(*depCache);

    qDeleteAll(d->packages);
    d->packages.clear();
    d->groups.clear();
    d->originMap.clear();
    d->packagesIndex.clear();

    int packageCount = depCache->Head().PackageCount;
    d->packagesIndex.resize(packageCount);

    // Remove check when sid has >= 4.7
    #if QT_VERSION >= 0x040700
    d->packages.reserve(packageCount);
    #endif

    // Populate internal package cache
    int count = 0;
    QSet<Group> groupSet;
    QStringList originList;
    QStringList originLabelList;

    pkgCache::PkgIterator iter;
    for (iter = depCache->PkgBegin(); !iter.end(); ++iter) {
        if (iter->VersionList == 0) {
            continue; // Exclude virtual packages.
        }

        Package *pkg = new Package(this, depCache, d->records, iter);
        d->packagesIndex[iter->ID] = count;
        d->packages.insert(count++, pkg);

        QString group = QString::fromStdString(iter.Section());

        if (!group.isEmpty()) {
            groupSet << group;
        }

        pkgCache::VerIterator Ver = (*depCache)[iter].CandidateVerIter(*depCache);

        if(!Ver.end()) {
            pkgCache::VerFileIterator VF = Ver.FileList();
            d->originMap[VF.File().Origin()] = VF.File().Label();
        }
    }

    if (d->originMap.contains("")) {
        d->originMap.remove("");
    }

    // Populate groups
    foreach (const QString &group, groupSet) {
        d->groups << group;
    }

    d->undoStack.clear();
    d->redoStack.clear();
}

00192 pkgSourceList *Backend::packageSourceList() const
{
    Q_D(const Backend);

    return d->cache->list();
}

00199 Package *Backend::package(pkgCache::PkgIterator &iter) const
{
    Q_D(const Backend);
    int index = d->packagesIndex.at(iter->ID);
    if (index != -1) {
        return d->packages.at(index);
    }
    return 0;
}

00209 Package *Backend::package(const QString &name) const
{
    Q_D(const Backend);

    pkgCache::PkgIterator pkg = d->cache->depCache()->FindPkg(name.toStdString());
    if (!pkg.end()) {
        return package(pkg);
    }

    return 0;
}

00221 QStringList Backend::originLabels() const
{
    Q_D(const Backend);

    return d->originMap.values();
}

00228 QString Backend::originLabel(const QString &origin) const
{
    Q_D(const Backend);

    QString originLabel = d->originMap[origin];

    return originLabel;
}

00237 QString Backend::origin(QString originLabel) const
{
    Q_D(const Backend);

    QString origin = d->originMap.key(originLabel);

    return origin;
}

00246 int Backend::packageCount() const
{
    Q_D(const Backend);

    int packageCount = d->packages.size();

    return packageCount;
}

00255 int Backend::packageCount(const Package::States &states) const
{
    Q_D(const Backend);

    int packageCount = 0;

    foreach(const Package *package, d->packages) {
        if ((package->state() & states)) {
            packageCount++;
        }
    }

    return packageCount;
}

00270 qint64 Backend::downloadSize() const
{
    Q_D(const Backend);

    qint64 downloadSize = d->cache->depCache()->DebSize();

    pkgAcquire fetcher;
    pkgPackageManager *PM = _system->CreatePM(d->cache->depCache());
    if (PM->GetArchives(&fetcher, d->cache->list(), d->records)) {
        downloadSize = fetcher.FetchNeeded();
    }
    delete PM;

    return downloadSize;
}

00286 qint64 Backend::installSize() const
{
    Q_D(const Backend);

    qint64 installSize = d->cache->depCache()->UsrSize();

    return installSize;
}

00295 PackageList Backend::availablePackages() const
{
    Q_D(const Backend);

    return d->packages;
}

00302 PackageList Backend::upgradeablePackages() const
{
    Q_D(const Backend);

    PackageList upgradeablePackages;

    foreach (Package *package, d->packages) {
        if (package->state() & Package::Upgradeable) {
            upgradeablePackages << package;
        }
    }

    return upgradeablePackages;
}

00317 PackageList Backend::markedPackages() const
{
    Q_D(const Backend);

    PackageList markedPackages;

    foreach(Package *package, d->packages) {
        if (package->state() & (Package::ToInstall | Package::ToReInstall |
                                Package::ToUpgrade | Package::ToDowngrade |
                                Package::ToRemove | Package::ToPurge)) {
            markedPackages << package;
        }
    }
    return markedPackages;
}

00333 PackageList Backend::search(const QString &searchString) const
{
    Q_D(const Backend);

    if (d->xapianTimeStamp == 0) {
        return QApt::PackageList();
    }

    string unsplitSearchString = searchString.toStdString();
    static int qualityCutoff = 25;
    PackageList searchResult;

    // Doesn't follow style guidelines to ease merging with synaptic
    try {
        int maxItems = d->xapianDatabase.get_doccount();
        Xapian::Enquire enquire(d->xapianDatabase);
        Xapian::QueryParser parser;
        parser.set_database(d->xapianDatabase);
        parser.add_prefix("name","XP");
        parser.add_prefix("section","XS");
        // default op is AND to narrow down the resultset
        parser.set_default_op( Xapian::Query::OP_AND );

        /* Workaround to allow searching an hyphenated package name using a prefix (name:)
        * LP: #282995
        * Xapian currently doesn't support wildcard for boolean prefix and
        * doesn't handle implicit wildcards at the end of hypenated phrases.
        *
        * e.g searching for name:ubuntu-res will be equivalent to 'name:ubuntu res*'
        * however 'name:(ubuntu* res*) won't return any result because the
        * index is built with the full package name
        */
        // Always search for the package name
        string xpString = "name:";
        string::size_type pos = unsplitSearchString.find_first_of(" ,;");
        if (pos > 0) {
            xpString += unsplitSearchString.substr(0,pos);
        } else {
            xpString += unsplitSearchString;
        }
        Xapian::Query xpQuery = parser.parse_query(xpString);

        pos = 0;
        while ( (pos = unsplitSearchString.find("-", pos)) != string::npos ) {
            unsplitSearchString.replace(pos, 1, " ");
            pos+=1;
        }

        // Build the query
        // apply a weight factor to XP term to increase relevancy on package name
        Xapian::Query query = parser.parse_query(unsplitSearchString,
           Xapian::QueryParser::FLAG_WILDCARD |
           Xapian::QueryParser::FLAG_BOOLEAN |
           Xapian::QueryParser::FLAG_PARTIAL);
        query = Xapian::Query(Xapian::Query::OP_OR, query,
                Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT, xpQuery, 3));
        enquire.set_query(query);
        Xapian::MSet matches = enquire.get_mset(0, maxItems);

      // Retrieve the results
      int top_percent = 0;
      for (Xapian::MSetIterator i = matches.begin(); i != matches.end(); ++i)
      {
         Package* pkg = package(QString::fromStdString(i.get_document().get_data()));
         // Filter out results that apt doesn't know
         if (!pkg)
            continue;

         // Save the confidence interval of the top value, to use it as
         // a reference to compute an adaptive quality cutoff
         if (top_percent == 0)
            top_percent = i.get_percent();

         // Stop producing if the quality goes below a cutoff point
         if (i.get_percent() < qualityCutoff * top_percent / 100)
         {
            break;
         }

         searchResult.append(pkg);
         }
    } catch (const Xapian::Error & error) {
        return QApt::PackageList();
    }

    return searchResult;
}

00421 GroupList Backend::availableGroups() const
{
    Q_D(const Backend);

    GroupList groupList = d->groups.toList();

    return groupList;
}

00430 bool Backend::isBroken() const
{
    Q_D(const Backend);

    if (d->cache->depCache() == 0) {
        return true;
    }

    // Check for broken things
    if (d->cache->depCache()->BrokenCount() != 0) {
        return true;
    }

    return false;
}

00446 bool Backend::xapianIndexNeedsUpdate() const
{
    Q_D(const Backend);

   // If the cache has been modified after the xapian timestamp, we need to rebuild
   QDateTime statTime;
   statTime = QFileInfo(_config->FindFile("Dir::Cache::pkgcache").c_str()).lastModified();
   if (d->xapianTimeStamp < statTime.toTime_t()) {
      return true;
   }

   return false;
}

bool Backend::openXapianIndex()
{
    Q_D(Backend);

    QFileInfo timeStamp("/var/lib/apt-xapian-index/update-timestamp");
    d->xapianTimeStamp = timeStamp.lastModified().toTime_t();

    try {
        d->xapianDatabase.add_database(Xapian::Database("/var/lib/apt-xapian-index/index"));
    } catch (Xapian::DatabaseOpeningError) {
        return false;
    };

    return true;
}

00476 CacheState Backend::currentCacheState() const
{
    Q_D(const Backend);

    CacheState state;
    int pkgSize = d->packages.size();
    #if QT_VERSION >= 0x040700
    state.reserve(pkgSize);
    #endif
    for (unsigned i = 0; i < pkgSize; ++i) {
        state.append(d->packages[i]->state());
    }

    return state;
}

00492 void Backend::saveCacheState()
{
    Q_D(Backend);
    CacheState state = currentCacheState();
    d->undoStack.prepend(state);
    d->redoStack.clear();

    // TODO: Configurable
    int maxStackSize = 20;

    while (d->undoStack.size() > maxStackSize) {
        d->undoStack.removeLast();
    }
}

00507 void Backend::restoreCacheState(const CacheState &state)
{
    Q_D(Backend);

    pkgDepCache *deps = d->cache->depCache();
    pkgDepCache::ActionGroup group(*deps);

    for (unsigned i = 0; i < d->packages.size(); ++i) {
        Package *pkg = d->packages[i];
        int flags = pkg->state();
        int oldflags = state[i];

        if (oldflags != flags) {
            if (oldflags & Package::ToReInstall) {
                deps->MarkInstall(*(pkg->packageIterator()), true);
                deps->SetReInstall(*(pkg->packageIterator()), false);
            } else if (oldflags & Package::ToInstall) {
                deps->MarkInstall(*(pkg->packageIterator()), true);
            } else if (oldflags & Package::ToRemove) {
                deps->MarkDelete(*(pkg->packageIterator()), (bool)(oldflags & Package::ToPurge));
            } else if (oldflags & Package::ToKeep) {
                deps->MarkKeep(*(pkg->packageIterator()), false);
            }
            // fix the auto flag
            deps->MarkAuto(*pkg->packageIterator(), (oldflags & Package::IsAuto));
        }
    }
    emit packageChanged();
}

00537 bool Backend::isUndoStackEmpty() const
{
    Q_D(const Backend);

    return d->undoStack.isEmpty();
}

00544 bool Backend::isRedoStackEmpty() const
{
    Q_D(const Backend);

    return d->redoStack.isEmpty();
}

00551 void Backend::undo()
{
    Q_D(Backend);

    if (d->undoStack.isEmpty()) {
        return;
    }

    // Place current state on redo stack
    d->redoStack.prepend(currentCacheState());

    CacheState state = d->undoStack.takeFirst();
    restoreCacheState(state);
}

00566 void Backend::redo()
{
    Q_D(Backend);

    if (d->redoStack.isEmpty()) {
        return;
    }

    // Place current state on undo stack
    d->undoStack.append(currentCacheState());

    CacheState state = d->redoStack.takeFirst();
    restoreCacheState(state);
}

00581 void Backend::markPackagesForUpgrade()
{
    Q_D(Backend);

    pkgAllUpgrade(*d->cache->depCache());
    emit packageChanged();
}

00589 void Backend::markPackagesForDistUpgrade()
{
    Q_D(Backend);

    pkgDistUpgrade(*d->cache->depCache());
    emit packageChanged();
}

00597 void Backend::markPackageForInstall(const QString &name)
{
    Package *pkg = package(name);
    pkg->setInstall();
}

00603 void Backend::markPackageForRemoval(const QString &name)
{
    Package *pkg = package(name);
    pkg->setRemove();
}

00609 void Backend::commitChanges()
{
    Q_D(Backend);

    QVariantMap packageList;

    foreach (const Package *package, d->packages) {
        int flags = package->state();
        // Cannot have any of these flags simultaneously
        int status = flags & (Package::ToKeep |
                              Package::NewInstall |
                              Package::ToReInstall |
                              Package::ToUpgrade |
                              Package::ToDowngrade |
                              Package::ToRemove);
        switch (status) {
           case Package::ToKeep:
               if (flags & Package::Held) {
                   packageList.insert(package->name(), Package::Held);
               }
               break;
           case Package::NewInstall:
               packageList.insert(package->name(), Package::ToInstall);
               qDebug() << "Installing:" << package->name();
               break;
           case Package::ToReInstall:
               packageList.insert(package->name(), Package::ToReInstall);
               break;
           case Package::ToUpgrade:
               packageList.insert(package->name(), Package::ToUpgrade);
               qDebug() << "Upgrading:" << package->name();
               break;
           case Package::ToDowngrade:
               packageList.insert(package->name() % ',' % package->availableVersion(), Package::ToDowngrade);
               qDebug() << "Downgrading:" << package->name() << package->availableVersion();
               break;
           case Package::ToRemove:
               if(flags & Package::ToPurge) {
                   packageList.insert(package->name(), Package::ToPurge);
               } else {
                   qDebug() << "Removing:" << package->name();
                   packageList.insert(package->name(), Package::ToRemove);
               }
               break;
        }
    }

    d->worker->setLocale(setlocale(LC_ALL, 0));
    d->worker->commitChanges(packageList);
}

00660 void Backend::packageChanged(Package *package)
{
    emit packageChanged();
}

00665 void Backend::updateCache()
{
    Q_D(Backend);

    d->worker->setLocale(setlocale(LC_ALL, 0));
    d->worker->updateCache();
}

00673 bool Backend::saveSelections(const QString &path) const
{
    Q_D(const Backend);

    QString selectionDocument;
    for (unsigned i = 0; i < d->packages.size(); ++i) {
        int flags = d->packages[i]->state();

        if (flags & Package::ToInstall) {
            selectionDocument.append(d->packages[i]->name() % "\t\tinstall" % '\n');
        } else if (flags & Package::ToRemove) {
            selectionDocument.append(d->packages[i]->name() % "\t\tdeinstall" % '\n');
        }
    }

    if (selectionDocument.isEmpty()) {
        return false;
    }

    QFile file(path);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        return false;
    } else {
        QTextStream out(&file);
        out << selectionDocument;
    }

    return true;
}

00703 bool Backend::loadSelections(const QString &path)
{
    Q_D(Backend);

    QFile file(path);
    if (!file.open(QFile::ReadOnly)) {
        return false;
    }

    QTextStream in(&file);

    QString line;

    string stdName;
    string stdAction;
    QString packageName;
    QString packageAction;

    QMap<QString, int> actionMap;
    do {
        line = in.readLine();
        if (line.isEmpty() || line.at(0) == '#') {
            continue;
        }
        line = line.simplified();

        const char *C = line.toStdString().data();
        if (!ParseQuoteWord(C, stdName)) {
            return false;
        }
        packageName = QString::fromStdString(stdName);

        if (!ParseQuoteWord(C, stdAction)) {
            return false;
        }
        packageAction = QString::fromStdString(stdAction);

        if (packageAction.at(0) == 'i') {
            actionMap[packageName] = Package::ToInstall;
        } else if ((packageAction.at(0) == 'd') || (packageAction.at(0) == 'u')) {
            actionMap[packageName] = Package::ToRemove;
        }
    } while (!line.isNull());

    if (actionMap.isEmpty()) {
       return false;
    }

    pkgDepCache &cache = *d->cache->depCache();
    pkgProblemResolver Fix(&cache);
    // XXX Should protect whatever is already selected in the cache.

    pkgCache::PkgIterator pkgIter;
    QMap<QString, int>::const_iterator mapIter = actionMap.begin();
    while (mapIter != actionMap.end()) {
        pkgIter = d->cache->depCache()->FindPkg(mapIter.key().toStdString());
        if (pkgIter.end()) {
            return false;
        }

        Fix.Clear(pkgIter);
        Fix.Protect(pkgIter);

        switch (mapIter.value()) {
           case Package::ToInstall:
               if(_config->FindB("Volatile::SetSelectionDoReInstall",false)) {
                   cache.SetReInstall(pkgIter, true);
               }
               if(_config->FindB("Volatile::SetSelectionsNoFix",false)) {
                   cache.MarkInstall(pkgIter, false);
               } else {
                   cache.MarkInstall(pkgIter, true);
               }
               break;
           case Package::ToRemove:
               Fix.Remove(pkgIter);
               cache.MarkDelete(pkgIter, false);
               break;
        }
        ++mapIter;
    }

    Fix.InstallProtect();
    Fix.Resolve(true);

    return true;
}

void Backend::updateXapianIndex()
{
    Q_D(Backend);

    d->worker->updateXapianIndex();
}

void Backend::workerStarted()
{
    Q_D(Backend);

    connect(d->watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
            this, SLOT(serviceOwnerChanged(QString, QString, QString)));

    connect(d->worker, SIGNAL(downloadProgress(int, int, int)),
            this, SIGNAL(downloadProgress(int, int, int)));
    connect(d->worker, SIGNAL(downloadMessage(int, const QString&)),
            this, SIGNAL(downloadMessage(int, const QString&)));
    connect(d->worker, SIGNAL(commitProgress(const QString&, int)),
            this, SIGNAL(commitProgress(const QString&, int)));
    connect(d->worker, SIGNAL(questionOccurred(int, const QVariantMap&)),
            this, SLOT(emitWorkerQuestionOccurred(int, const QVariantMap&)));
    connect(d->worker, SIGNAL(warningOccurred(int, const QVariantMap&)),
            this, SLOT(emitWarningOccurred(int, const QVariantMap&)));
}

void Backend::workerFinished(bool result)
{
    Q_D(Backend);

    disconnect(d->watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
               this, SLOT(serviceOwnerChanged(QString, QString, QString)));

    disconnect(d->worker, SIGNAL(downloadProgress(int, int, int)),
               this, SIGNAL(downloadProgress(int, int, int)));
    disconnect(d->worker, SIGNAL(downloadMessage(int, const QString&)),
               this, SIGNAL(downloadMessage(int, const QString&)));
    disconnect(d->worker, SIGNAL(commitProgress(const QString&, int)),
               this, SIGNAL(commitProgress(const QString&, int)));
    disconnect(d->worker, SIGNAL(questionOccurred(int, const QVariantMap&)),
               this, SLOT(emitWorkerQuestionOccurred(int, const QVariantMap&)));
    disconnect(d->worker, SIGNAL(warningOccurred(int, const QVariantMap&)),
               this, SLOT(emitWarningOccurred(int, const QVariantMap&)));
}

00836 void Backend::cancelDownload()
{
    Q_D(Backend);

    d->worker->cancelDownload();
}

00843 void Backend::answerWorkerQuestion(const QVariantMap &response)
{
    Q_D(Backend);

    d->worker->answerWorkerQuestion(response);
}

void Backend::emitErrorOccurred(int errorCode, const QVariantMap &details)
{
    emit errorOccurred((ErrorCode) errorCode, details);
}

void Backend::emitWarningOccurred(int warningCode, const QVariantMap &details)
{
    emit warningOccurred((WarningCode) warningCode, details);
}

void Backend::emitWorkerEvent(int event)
{
    emit workerEvent((WorkerEvent) event);
}

void Backend::emitWorkerQuestionOccurred(int question, const QVariantMap &details)
{
    emit questionOccurred((WorkerQuestion) question, details);
}

void Backend::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
{
    if (oldOwner.isEmpty()) {
        return; // Don't care, just appearing
    }

    if (newOwner.isEmpty()) {
        // Ok, something got screwed. Report and flee
        emit errorOccurred(QApt::WorkerDisappeared, QVariantMap());
        workerFinished(false);
    }
}

}

Generated by  Doxygen 1.6.0   Back to index