diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bb0cf3..a13da68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,155 +1,155 @@ project(qapt) cmake_minimum_required(VERSION 2.8.12) set(PROJECT_VERSION_MAJOR 3) set(PROJECT_VERSION_MINOR 0) set(PROJECT_VERSION_PATCH 4) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" ) set(PROJECT_SOVERSION ${PROJECT_VERSION_MAJOR}) find_package(ECM 0.0.14 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(ECMGenerateHeaders) include(ECMPackageConfigHelpers) include(ECMPoQmTools) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings NO_POLICY_SCOPE) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMGeneratePriFile) # This needs InstallDirs # Turn exceptions on kde_enable_exceptions() -set(REQUIRED_QT_VERSION 5.2.0) # Used in QAptConfig +set(REQUIRED_QT_VERSION 5.8.0) # Used in QAptConfig find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus Widgets) find_package(Xapian REQUIRED) find_package(AptPkg REQUIRED) find_package(PolkitQt5-1 0.112 REQUIRED) # Find the required Libaries include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${POLKITQT-1_INCLUDE_DIR} ${XAPIAN_INCLUDE_DIR} ${APTPKG_INCLUDE_DIR}) ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX QAPT VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/qaptversion.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/QAptConfigVersion.cmake" SOVERSION ${PROJECT_SOVERSION}) set(QAPT_WORKER_VERSION ${PROJECT_SOVERSION}) set(QAPT_WORKER_RDN org.kubuntu.qaptworker) set(QAPT_WORKER_RDN_VERSIONED ${QAPT_WORKER_RDN}${QAPT_WORKER_VERSION}) # For forwarding into C++ convert them into properly excaped strings. set(QAPT_WORKER_VERSION_STRING \"${QAPT_WORKER_VERSION}\") set(QAPT_WORKER_RDN_VERSIONED_STRING \"${QAPT_WORKER_RDN_VERSIONED}\") add_definitions(-DQAPT_WORKER_VERSION_STRING=${QAPT_WORKER_VERSION_STRING}) add_definitions(-DQAPT_WORKER_RDN_VERSIONED_STRING=${QAPT_WORKER_RDN_VERSIONED_STRING}) # Also forward version for utils. add_definitions(-DCMAKE_PROJECT_VERSION=\"${PROJECT_VERSION}\") set(KF5_DEP_VERSION "5.0.0") # Only used to install worker actions find_package(KF5Auth ${KF5_DEP_VERSION}) # Used for utils find_package(KF5CoreAddons ${KF5_DEP_VERSION}) find_package(KF5I18n ${KF5_DEP_VERSION}) find_package(KF5KIO ${KF5_DEP_VERSION}) find_package(KF5Runner ${KF5_DEP_VERSION}) find_package(KF5TextWidgets ${KF5_DEP_VERSION}) find_package(KF5WidgetsAddons ${KF5_DEP_VERSION}) find_package(KF5WindowSystem ${KF5_DEP_VERSION}) find_package(KF5IconThemes ${KF5_DEP_VERSION}) find_package(DebconfKDE 1.0) find_package(GStreamer 1.0) find_package(GLIB2 2.0) if (KF5CoreAddons_FOUND AND KF5I18n_FOUND AND KF5KIO_FOUND AND KF5Runner_FOUND AND KF5TextWidgets_FOUND AND KF5WidgetsAddons_FOUND AND KF5WindowSystem_FOUND AND KF5IconThemes_FOUND AND DebconfKDE_FOUND) set(WITH_UTILS true) endif() if (WITH_UTILS AND GSTREAMER_FOUND AND GLIB2_FOUND) set(WITH_GSTREAMER true) add_definitions(${GSTREAMER_DEFINITIONS}) endif() add_feature_info(qapt-utils WITH_UTILS "Runtime utilities using KDE frameworks") add_feature_info(qapt-gst-helper WITH_GSTREAMER "GStreamer codec helper util") message(WARNING "gettext and tr in the same source is insanely tricky, maybe we should give some ki18n to qapt...") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/QApt") ecm_configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/QAptConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/QAptConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/QAptConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/QAptConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel) install(EXPORT QAptTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE QAptTargets.cmake NAMESPACE QApt:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qaptversion.h DESTINATION include/qapt COMPONENT Devel) add_subdirectory(autotests) add_subdirectory(src) if(WITH_UTILS) add_subdirectory(utils) #Do not remove or modify these. The release script substitutes in for these #comments with appropriate doc and translation directories. #PO_SUBDIR #DOC_SUBDIR endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libqapt.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libqapt.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libqapt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT Devel) ecm_generate_pri_file(BASE_NAME QApt LIB_NAME QApt DEPS "core widgets dbus" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR include/QApt) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/backend.cpp b/src/backend.cpp index 16bc769..54b911e 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -1,1533 +1,1649 @@ /*************************************************************************** * Copyright © 2010-2012 Jonathan Thomas * * * * 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 . * ***************************************************************************/ #include "backend.h" // Qt includes #include #include #include // Apt includes #include #include #include #include #include +#include #include #include #include #include #include // Xapian includes #undef slots #include // QApt includes #include "cache.h" #include "config.h" // krazy:exclude=includes #include "dbusinterfaces_p.h" #include "debfile.h" #include "transaction.h" namespace QApt { class BackendPrivate { public: BackendPrivate() : cache(nullptr) , records(nullptr) , maxStackSize(20) , xapianDatabase(nullptr) , xapianIndexExists(false) , config(nullptr) , actionGroup(nullptr) , frontendCaps(QApt::NoCaps) { } ~BackendPrivate() { qDeleteAll(packages); delete cache; delete records; delete config; delete xapianDatabase; delete actionGroup; } // Caches // The canonical list of all unique, non-virutal package objects PackageList packages; // A list of each package object's ID number QVector packagesIndex; // Set of group names extracted from our packages QSet groups; // Cache of origin/human-readable name pairings QHash originMap; // Relation of an origin and its hostname QHash siteMap; + // Date when the distribution's release was issued. See Backend::releaseDate() + QDateTime releaseDate; + QDateTime getReleaseDateFromDistroInfo(const QString &releaseId, const QString &releaseCodename) const; + QDateTime getReleaseDateFromArchive(const QString &releaseId, const QString &releaseCodename) const; + // Counts int installedCount; // Pointer to the apt cache object Cache *cache; pkgRecords *records; // Undo/redo stuff int maxStackSize; QList undoStack; QList redoStack; // Xapian time_t xapianTimeStamp; Xapian::Database *xapianDatabase; bool xapianIndexExists; // DBus WorkerInterface *worker; // Config Config *config; bool isMultiArch; QString nativeArch; // Event compression bool compressEvents; pkgDepCache::ActionGroup *actionGroup; // Other bool writeSelectionFile(const QString &file, const QString &path) const; QString customProxy; QString initErrorMessage; QApt::FrontendCaps frontendCaps; }; +QDateTime BackendPrivate::getReleaseDateFromDistroInfo(const QString &releaseId, const QString &releaseCodename) const +{ + QDateTime releaseDate; + QString line; + QStringList split; + + QFile distro_info(QStringLiteral("/usr/share/distro-info/%1.csv").arg(releaseId.toLower())); + if (distro_info.open(QFile::ReadOnly)) { + QTextStream info_stream(&distro_info); + line = info_stream.readLine(); + split = line.split(QLatin1Char(',')); + const int codenameColumn = split.indexOf(QStringLiteral("series")); + const int releaseColumn = split.indexOf(QStringLiteral("release")); + if (codenameColumn == -1 || releaseColumn == -1) { + return QDateTime(); + } + do { + line = info_stream.readLine(); + split = line.split(QLatin1Char(',')); + if (split.value(codenameColumn) == releaseCodename) { + releaseDate = QDateTime::fromString(split.value(releaseColumn), Qt::ISODate); + releaseDate.setTimeSpec(Qt::UTC);; + break; + } + } while (!line.isNull()); + } + return releaseDate; +} + +QDateTime BackendPrivate::getReleaseDateFromArchive(const QString &releaseId, const QString &releaseCodename) const +{ + pkgDepCache *depCache = cache->depCache(); + + // We are only interested in `*ubuntu_dists__[In]Release` + // in order to get the release date. In `-updates` and + // `-security` the Date gets updated throughout the life of the release. + pkgCache::RlsFileIterator rls; + for (rls = depCache->GetCache().RlsFileBegin(); !rls.end(); ++rls) { + if (rls.Origin() == releaseId + && rls.Label() == releaseId + && rls.Archive() == releaseCodename) { + + FileFd fd; + if (!OpenMaybeClearSignedFile(rls.FileName(), fd)) { + continue; + } + + time_t releaseDate = -1; + pkgTagSection sec; + pkgTagFile tag(&fd); + tag.Step(sec); + + if(!RFC1123StrToTime(sec.FindS("Date").data(), releaseDate)) { + continue; + } + + return QDateTime::fromSecsSinceEpoch(releaseDate); + } + } + return QDateTime(); +} + bool BackendPrivate::writeSelectionFile(const QString &selectionDocument, const QString &path) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Text)) { return false; } else { QTextStream out(&file); out << selectionDocument; } return true; } Backend::Backend(QObject *parent) : QObject(parent) , d_ptr(new BackendPrivate) { Q_D(Backend); d->worker = new WorkerInterface(QLatin1String(s_workerReverseDomainName), QLatin1String("/"), QDBusConnection::systemBus(), this); connect(d->worker, SIGNAL(transactionQueueChanged(QString,QStringList)), this, SIGNAL(transactionQueueChanged(QString,QStringList))); DownloadProgress::registerMetaTypes(); } Backend::~Backend() { delete d_ptr; } bool Backend::init() { Q_D(Backend); if (!pkgInitConfig(*_config) || !pkgInitSystem(*_config, _system)) { setInitError(); return false; } d->cache = new Cache(this); d->config = new Config(this); d->nativeArch = config()->readEntry(QLatin1String("APT::Architecture"), QLatin1String("")); openXapianIndex(); return reloadCache(); } bool Backend::reloadCache() { Q_D(Backend); emit cacheReloadStarted(); if (!d->cache->open()) { setInitError(); return false; } 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->siteMap.clear(); d->packagesIndex.clear(); d->installedCount = 0; int packageCount = depCache->Head().PackageCount; d->packagesIndex.resize(packageCount); d->packagesIndex.fill(-1); d->packages.reserve(packageCount); // Populate internal package cache int count = 0; d->isMultiArch = architectures().size() > 1; pkgCache::PkgIterator iter; for (iter = depCache->PkgBegin(); !iter.end(); ++iter) { if (!iter->VersionList) { continue; // Exclude virtual packages. } Package *pkg = new Package(this, iter); d->packagesIndex[iter->ID] = count; d->packages.append(pkg); ++count; if (iter->CurrentVer) { d->installedCount++; } QString group = pkg->section(); // Populate groups if (!group.isEmpty()) { d->groups << group; } pkgCache::VerIterator Ver = (*depCache)[iter].CandidateVerIter(*depCache); if(!Ver.end()) { const pkgCache::VerFileIterator VF = Ver.FileList(); const QString origin(QLatin1String(VF.File().Origin())); d->originMap[origin] = QLatin1String(VF.File().Label()); d->siteMap[origin] = QLatin1String(VF.File().Site()); } } d->originMap.remove(QString()); d->undoStack.clear(); d->redoStack.clear(); // Determine which packages are pinned for display purposes loadPackagePins(); + loadReleaseDate(); + emit cacheReloadFinished(); return true; } void Backend::setInitError() { Q_D(Backend); string message; if (_error->PopMessage(message)) d->initErrorMessage = QString::fromStdString(message); } void Backend::loadPackagePins() { Q_D(Backend); QString dirBase = d->config->findDirectory(QLatin1String("Dir::Etc")); QString dir = dirBase % QLatin1String("preferences.d/"); QDir logDirectory(dir); QStringList pinFiles = logDirectory.entryList(QDir::Files, QDir::Name); pinFiles << dirBase % QLatin1String("preferences"); for (const QString &pinName : pinFiles) { // Make all paths absolute QString pinPath = pinName.startsWith('/') ? pinName : dir % pinName; if (!QFile::exists(pinPath)) continue; FileFd Fd(pinPath.toUtf8().data(), FileFd::ReadOnly); pkgTagFile tagFile(&Fd); if (_error->PendingError()) { _error->Discard(); continue; } pkgTagSection tags; while (tagFile.Step(tags)) { string name = tags.FindS("Package"); Package *pkg = package(QLatin1String(name.c_str())); if (pkg) pkg->setPinned(true); } } } +void Backend::loadReleaseDate() +{ + Q_D(Backend); + + // Reset value in case we are re-loading cache + d->releaseDate = QDateTime(); + + QString releaseId; + QString releaseCodename; + + QFile lsb_release(QLatin1String("/etc/os-release")); + if (!lsb_release.open(QFile::ReadOnly)) { + // Though really, your system is screwed if this happens... + return; + } + + QTextStream stream(&lsb_release); + QString line; + do { + line = stream.readLine(); + QStringList split = line.split(QLatin1Char('=')); + if (split.size() != 2) { + continue; + } + + if (split.at(0) == QLatin1String("VERSION_CODENAME")) { + releaseCodename = split.at(1); + } else if (split.at(0) == QLatin1String("ID")) { + releaseId = split.at(1); + } + } while (!line.isNull()); + + d->releaseDate = d->getReleaseDateFromDistroInfo(releaseId, releaseCodename); + if (!d->releaseDate.isValid()) { + // If we could not find the date in the csv file, we fallback to Apt archive. + d->releaseDate = d->getReleaseDateFromArchive(releaseId, releaseCodename); + } +} + QString Backend::initErrorMessage() const { Q_D(const Backend); return d->initErrorMessage; } pkgSourceList *Backend::packageSourceList() const { Q_D(const Backend); return d->cache->list(); } Cache *Backend::cache() const { Q_D(const Backend); return d->cache; } pkgRecords *Backend::records() const { Q_D(const Backend); return d->records; } Package *Backend::package(pkgCache::PkgIterator &iter) const { Q_D(const Backend); int index = d->packagesIndex.at(iter->ID); if (index != -1 && index < d->packages.size()) { return d->packages.at(index); } return nullptr; } Package *Backend::package(const QString &name) const { return package(QLatin1String(name.toLatin1())); } Package *Backend::package(QLatin1String name) const { Q_D(const Backend); pkgCache::PkgIterator pkg = d->cache->depCache()->FindPkg(name.latin1()); if (!pkg.end()) { return package(pkg); } return nullptr; } Package *Backend::packageForFile(const QString &file) const { Q_D(const Backend); if (file.isEmpty()) { return nullptr; } for (Package *package : d->packages) { if (package->installedFilesList().contains(file)) { return package; } } return nullptr; } QStringList Backend::origins() const { Q_D(const Backend); return d->originMap.keys(); } QStringList Backend::originLabels() const { Q_D(const Backend); return d->originMap.values(); } QString Backend::originLabel(const QString &origin) const { Q_D(const Backend); return d->originMap.value(origin); } QString Backend::origin(const QString &originLabel) const { Q_D(const Backend); return d->originMap.key(originLabel); } QStringList Backend::originsForHost(const QString& host) const { Q_D(const Backend); return d->siteMap.keys(host); } int Backend::packageCount() const { Q_D(const Backend); return d->packages.size(); } int Backend::packageCount(const Package::States &states) const { Q_D(const Backend); int packageCount = 0; for (const Package *package : d->packages) { if ((package->state() & states)) { packageCount++; } } return packageCount; } int Backend::installedCount() const { Q_D(const Backend); return d->installedCount; } int Backend::toInstallCount() const { Q_D(const Backend); return d->cache->depCache()->InstCount(); } int Backend::toRemoveCount() const { Q_D(const Backend); return d->cache->depCache()->DelCount(); } qint64 Backend::downloadSize() const { Q_D(const Backend); // Raw size, ignoring already-downloaded or partially downloaded archives qint64 downloadSize = d->cache->depCache()->DebSize(); // If downloadSize() is called during a cache refresh, there is a chance it // will do so at a bad time and produce an error. Discard any errors that // happen during this function, since they will always be innocuous and at // worst will result in the less accurate DebSize() number being returned _error->PushToStack(); // If possible, get what really needs to be downloaded pkgAcquire fetcher; pkgPackageManager *PM = _system->CreatePM(d->cache->depCache()); if (PM->GetArchives(&fetcher, d->cache->list(), d->records)) { downloadSize = fetcher.FetchNeeded(); } delete PM; _error->Discard(); _error->RevertToStack(); return downloadSize; } qint64 Backend::installSize() const { Q_D(const Backend); qint64 installSize = d->cache->depCache()->UsrSize(); return installSize; } PackageList Backend::availablePackages() const { Q_D(const Backend); return d->packages; } PackageList Backend::upgradeablePackages() const { Q_D(const Backend); PackageList upgradeablePackages; for (Package *package : d->packages) { if (package->staticState() & Package::Upgradeable) { upgradeablePackages << package; } } return upgradeablePackages; } PackageList Backend::markedPackages() const { Q_D(const Backend); PackageList markedPackages; for (Package *package : d->packages) { if (package->state() & (Package::ToInstall | Package::ToReInstall | Package::ToUpgrade | Package::ToDowngrade | Package::ToRemove | Package::ToPurge)) { markedPackages << package; } } return markedPackages; } PackageList Backend::search(const QString &searchString) const { Q_D(const Backend); if (d->xapianTimeStamp == 0 || !d->xapianDatabase) { return QApt::PackageList(); } string unsplitSearchString = searchString.toStdString(); static int qualityCutoff = 15; 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) { std::string pkgName = i.get_document().get_data(); Package* pkg = package(QLatin1String(pkgName.c_str())); // 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) { qDebug() << "Search error" << QString::fromStdString(error.get_msg()); return QApt::PackageList(); } return searchResult; } GroupList Backend::availableGroups() const { Q_D(const Backend); GroupList groupList = d->groups.toList(); return groupList; } bool Backend::isMultiArchEnabled() const { Q_D(const Backend); return d->isMultiArch; } QStringList Backend::architectures() const { Q_D(const Backend); return d->config->architectures(); } QString Backend::nativeArchitecture() const { Q_D(const Backend); return d->nativeArch; } +QDateTime Backend::releaseDate() const +{ + Q_D(const Backend); + + return d->releaseDate; +} + bool Backend::areChangesMarked() const { return (toInstallCount() + toRemoveCount()); } bool Backend::isBroken() const { Q_D(const Backend); if (!d->cache->depCache()) { return true; } // Check for broken things if (d->cache->depCache()->BrokenCount()) { return true; } return false; } QApt::FrontendCaps Backend::frontendCaps() const { Q_D(const Backend); return d->frontendCaps; } QDateTime Backend::timeCacheLastUpdated() const { QDateTime sinceUpdate; QFileInfo updateStamp("/var/lib/apt/periodic/update-success-stamp"); if (!updateStamp.exists()) return sinceUpdate; return updateStamp.lastModified(); } bool Backend::xapianIndexNeedsUpdate() const { Q_D(const Backend); // If the cache has been modified after the xapian timestamp, we need to rebuild QDateTime aptCacheTime = QFileInfo(d->config->findFile("Dir::Cache::pkgcache")).lastModified(); return ((d->xapianTimeStamp < aptCacheTime.toTime_t()) || (!d->xapianIndexExists)); } bool Backend::openXapianIndex() { Q_D(Backend); QFileInfo timeStamp(QLatin1String("/var/lib/apt-xapian-index/update-timestamp")); d->xapianTimeStamp = timeStamp.lastModified().toTime_t(); if(d->xapianDatabase) { delete d->xapianDatabase; d->xapianDatabase = 0; } try { d->xapianDatabase = new Xapian::Database("/var/lib/apt-xapian-index/index"); d->xapianIndexExists = true; } catch (Xapian::DatabaseOpeningError) { d->xapianIndexExists = false; return false; }; return true; } Config *Backend::config() const { Q_D(const Backend); return d->config; } CacheState Backend::currentCacheState() const { Q_D(const Backend); CacheState state; int pkgSize = d->packages.size(); state.reserve(pkgSize); for (int i = 0; i < pkgSize; ++i) { state.append(d->packages.at(i)->state()); } return state; } QHash Backend::stateChanges(const CacheState &oldState, const PackageList &excluded) const { Q_D(const Backend); QHash changes; // Return an empty change set for invalid state caches if (oldState.isEmpty()) return changes; Q_ASSERT(d->packages.size() == oldState.size()); for (int i = 0; i < d->packages.size(); ++i) { Package *pkg = d->packages.at(i); if (excluded.contains(pkg)) continue; int status = pkg->state(); if (oldState.at(i) == status) continue; // These flags will never be set together. // We can use this to filter status down to a single flag. status &= (Package::Held | Package::NewInstall | Package::ToReInstall | Package::ToUpgrade | Package::ToDowngrade | Package::ToRemove); if (status == 0) { qWarning() << "Package" << pkg->name() << "had a state change," << "it can however not be presented as a unique state." << "This is often an indication that the package is" << "supposed to be upgraded but can't because its" << "dependencies are not satisfied. This is not" << "considered a held package unless its upgrade is" << "necessary or causing breakage. A simple unsatisfied" << "dependency without the need to upgrade is not" << "considered an issue and thus not reported.\n" << "States were:" << (Package::States)oldState.at(i) << "->" << (Package::States)pkg->state(); // Apt pretends packages like this are not held (which is reflected) // in the state loss. Whether or not this is intentional is not // obvious at the time of writing in case it isn't the states // here would add up again and the package rightfully would be // reported as held. So we would never get here. // Until then ignore these packages as we cannot serialize their // state anyway. continue; } // Add this package/status pair to the changes hash PackageList list = changes.value((Package::State)status); list.append(pkg); changes[(Package::State)status]= list; } return changes; } void Backend::saveCacheState() { Q_D(Backend); CacheState state = currentCacheState(); d->undoStack.prepend(state); d->redoStack.clear(); while (d->undoStack.size() > d->maxStackSize) { d->undoStack.removeLast(); } } void Backend::restoreCacheState(const CacheState &state) { Q_D(Backend); pkgDepCache *deps = d->cache->depCache(); pkgDepCache::ActionGroup group(*deps); int packageCount = d->packages.size(); for (int i = 0; i < packageCount; ++i) { Package *pkg = d->packages.at(i); int flags = pkg->state(); int oldflags = state.at(i); if (oldflags == flags) continue; if ((flags & Package::ToReInstall) && !(oldflags & Package::ToReInstall)) { deps->SetReInstall(pkg->packageIterator(), false); } if (oldflags & Package::ToReInstall) { deps->MarkInstall(pkg->packageIterator(), true); deps->SetReInstall(pkg->packageIterator(), true); } 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(); } void Backend::setUndoRedoCacheSize(int newSize) { Q_D(Backend); d->maxStackSize = newSize; } bool Backend::isUndoStackEmpty() const { Q_D(const Backend); return d->undoStack.isEmpty(); } bool Backend::isRedoStackEmpty() const { Q_D(const Backend); return d->redoStack.isEmpty(); } bool Backend::areEventsCompressed() const { Q_D(const Backend); return d->actionGroup != nullptr; } 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); } 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); } void Backend::markPackagesForUpgrade() { Q_D(Backend); pkgAllUpgrade(*d->cache->depCache()); emit packageChanged(); } void Backend::markPackagesForDistUpgrade() { Q_D(Backend); pkgDistUpgrade(*d->cache->depCache()); emit packageChanged(); } void Backend::markPackagesForAutoRemove() { Q_D(Backend); pkgDepCache &cache = *d->cache->depCache(); bool isResidual; for (pkgCache::PkgIterator pkgIter = cache.PkgBegin(); !pkgIter.end(); ++pkgIter) { // Auto-removable packages are marked as garbage in the cache if (!cache[pkgIter].Garbage) continue; isResidual = pkgIter->CurrentState == pkgCache::State::ConfigFiles; // Delete auto-removable packages, but we can't delete residual packages if (pkgIter.CurrentVer() && !isResidual) cache.MarkDelete(pkgIter, false); } emit packageChanged(); } void Backend::markPackageForInstall(const QString &name) { Package *pkg = package(name); pkg->setInstall(); } void Backend::markPackageForRemoval(const QString &name) { Package *pkg = package(name); pkg->setRemove(); } void Backend::markPackages(const QApt::PackageList &packages, QApt::Package::State action) { Q_D(Backend); if (packages.isEmpty()) { return; } pkgDepCache *deps = d->cache->depCache(); setCompressEvents(true); foreach (Package *package, packages) { const pkgCache::PkgIterator &iter = package->packageIterator(); switch (action) { case Package::ToInstall: { int state = package->staticState(); // Mark for install if not already installed, or if upgradeable if (!(state & Package::Installed) || (state & Package::Upgradeable)) { package->setInstall(); } break; } case Package::ToRemove: if (package->isInstalled()) { package->setRemove(); } break; case Package::ToUpgrade: { bool fromUser = !(package->state() & Package::IsAuto); deps->MarkInstall(iter, true, 0, fromUser); break; } case Package::ToReInstall: { int state = package->staticState(); if(state & Package::Installed && !(state & Package::NotDownloadable) && !(state & Package::Upgradeable)) { package->setReInstall(); } break; } case Package::ToKeep: package->setKeep(); break; case Package::ToPurge: { int state = package->staticState(); if ((state & Package::Installed) || (state & Package::ResidualConfig)) { package->setPurge(); } break; } default: break; } } setCompressEvents(false); emit packageChanged(); } void Backend::setCompressEvents(bool enabled) { Q_D(Backend); if (enabled) { // Ignore if already compressed if (d->actionGroup != nullptr) return; // Presence of an ActionGroup compresses marking events over its lifetime d->actionGroup = new pkgDepCache::ActionGroup(*d->cache->depCache()); } else { delete d->actionGroup; d->actionGroup = nullptr; emit packageChanged(); } } QApt::Transaction * Backend::commitChanges() { Q_D(Backend); QVariantMap packageList; for (const Package *package : d->packages) { int flags = package->state(); std::string fullName = package->packageIterator().FullName(); // Cannot have any of these flags simultaneously int status = flags & (Package::IsManuallyHeld | Package::NewInstall | Package::ToReInstall | Package::ToUpgrade | Package::ToDowngrade | Package::ToRemove); switch (status) { case Package::IsManuallyHeld: packageList.insert(QString::fromStdString(fullName), Package::Held); break; case Package::NewInstall: if (!(flags & Package::IsAuto)) { packageList.insert(QString::fromStdString(fullName), Package::ToInstall); } break; case Package::ToReInstall: packageList.insert(QString::fromStdString(fullName), Package::ToReInstall); break; case Package::ToUpgrade: packageList.insert(QString::fromStdString(fullName), Package::ToUpgrade); break; case Package::ToDowngrade: packageList.insert(QString(QString::fromStdString(fullName)) % ',' % package->availableVersion(), Package::ToDowngrade); break; case Package::ToRemove: if(flags & Package::ToPurge) { packageList.insert(QString::fromStdString(fullName), Package::ToPurge); } else { packageList.insert(QString::fromStdString(fullName), Package::ToRemove); } break; } } QDBusPendingReply rep = d->worker->commitChanges(packageList); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } QApt::Transaction * Backend::installPackages(PackageList packages) { Q_D(Backend); QVariantMap packageList; for (const Package *package : packages) { std::string fullName = package->packageIterator().FullName(); packageList.insert(QString::fromStdString(fullName), Package::ToInstall); } QDBusPendingReply rep = d->worker->commitChanges(packageList); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } QApt::Transaction * Backend::removePackages(PackageList packages) { Q_D(Backend); QVariantMap packageList; for (const Package *package : packages) { std::string fullName = package->packageIterator().FullName(); packageList.insert(QString::fromStdString(fullName), Package::ToRemove); } QDBusPendingReply rep = d->worker->commitChanges(packageList); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } Transaction *Backend::downloadArchives(const QString &listFile, const QString &destination) { Q_D(Backend); QFile file(listFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return nullptr; } QByteArray buffer = file.readAll(); QList lines = buffer.split('\n'); if (lines.isEmpty() || lines.first() != QByteArray("[Download List]")) { return nullptr; } lines.removeAt(0); QStringList packages; foreach (const QByteArray &line, lines) { packages << line; } QString dirName = listFile.left(listFile.lastIndexOf('/')); QDir dir(dirName); dir.mkdir(QLatin1String("packages")); QDBusPendingReply rep = d->worker->downloadArchives(packages, destination); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } Transaction *Backend::installFile(const DebFile &debFile) { Q_D(Backend); QDBusPendingReply rep = d->worker->installFile(debFile.filePath()); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } void Backend::emitPackageChanged() { emit packageChanged(); } Transaction *Backend::updateCache() { Q_D(Backend); QDBusPendingReply rep = d->worker->updateCache(); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } Transaction *Backend::upgradeSystem(UpgradeType upgradeType) { Q_D(Backend); bool safeUpgrade = (upgradeType == QApt::SafeUpgrade); QDBusPendingReply rep = d->worker->upgradeSystem(safeUpgrade); Transaction *trans = new Transaction(rep.value()); trans->setFrontendCaps(d->frontendCaps); return trans; } bool Backend::saveInstalledPackagesList(const QString &path) const { Q_D(const Backend); QString selectionDocument; for (int i = 0; i < d->packages.size(); ++i) { if (d->packages.at(i)->isInstalled()) { selectionDocument.append(d->packages[i]->name() % QLatin1Literal("\t\tinstall") % QLatin1Char('\n')); } } if (selectionDocument.isEmpty()) { return false; } return d->writeSelectionFile(selectionDocument, path); } bool Backend::saveSelections(const QString &path) const { Q_D(const Backend); QString selectionDocument; for (int i = 0; i < d->packages.size(); ++i) { int flags = d->packages.at(i)->state(); if (flags & Package::ToInstall) { selectionDocument.append(d->packages[i]->name() % QLatin1Literal("\t\tinstall") % QLatin1Char('\n')); } else if (flags & Package::ToRemove) { selectionDocument.append(d->packages[i]->name() % QLatin1Literal("\t\tdeinstall") % QLatin1Char('\n')); } } if (selectionDocument.isEmpty()) { return false; } return d->writeSelectionFile(selectionDocument, path); } bool Backend::loadSelections(const QString &path) { Q_D(Backend); QFile file(path); if (!file.open(QFile::ReadOnly)) { return false; } int lineIndex = 0; QByteArray buffer = file.readAll(); QList lines = buffer.split('\n'); QHash actionMap; while (lineIndex < lines.size()) { QByteArray line = lines.at(lineIndex); if (line.isEmpty() || line.at(0) == '#') { lineIndex++; continue; } line = line.trimmed(); QByteArray aKey; std::string keyString; const char *data = line.constData(); if (ParseQuoteWord(data, keyString) == false) return false; aKey = QByteArray(keyString.c_str()); QByteArray aValue; std::string valueString; if (ParseQuoteWord(data, valueString) == false) return false; aValue = QByteArray(valueString.c_str()); if (aValue.at(0) == 'i') { actionMap[aKey] = Package::ToInstall; } else if ((aValue.at(0) == 'd') || (aValue.at(0) == 'u') || (aValue.at(0) == 'r')) { actionMap[aKey] = Package::ToRemove; } else if (aValue.at(0) == 'p') { actionMap[aKey] = Package::ToPurge; } ++lineIndex; } if (actionMap.isEmpty()) { return false; } pkgDepCache &cache = *d->cache->depCache(); // Should protect whatever is already selected in the cache. pkgProblemResolver Fix(&cache); pkgCache::PkgIterator pkgIter; auto mapIter = actionMap.constBegin(); while (mapIter != actionMap.constEnd()) { pkgIter = d->cache->depCache()->FindPkg(mapIter.key().constData()); if (pkgIter.end()) { return false; } Fix.Clear(pkgIter); Fix.Protect(pkgIter); switch (mapIter.value()) { case Package::ToInstall: if (pkgIter.CurrentVer().end()) { // Only mark if not already installed cache.MarkInstall(pkgIter, true); } break; case Package::ToRemove: Fix.Remove(pkgIter); cache.MarkDelete(pkgIter, false); break; case Package::ToPurge: Fix.Remove(pkgIter); cache.MarkDelete(pkgIter, true); break; } ++mapIter; } Fix.Resolve(true); emit packageChanged(); return true; } bool Backend::saveDownloadList(const QString &path) const { Q_D(const Backend); QString downloadDocument; downloadDocument.append(QLatin1String("[Download List]") % QLatin1Char('\n')); for (int i = 0; i < d->packages.size(); ++i) { int flags = d->packages.at(i)->state(); if (flags & Package::ToInstall) { downloadDocument.append(d->packages[i]->name() % QLatin1Char('\n')); } } return d->writeSelectionFile(downloadDocument, path); } bool Backend::setPackagePinned(Package *package, bool pin) { Q_D(Backend); QString dir = d->config->findDirectory("Dir::Etc") % QLatin1String("preferences.d/"); QString path = dir % package->name(); QString pinDocument; if (pin) { if (package->state() & Package::IsPinned) { return true; } pinDocument = QLatin1Literal("Package: ") % package->name() % QLatin1Char('\n'); if (package->installedVersion().isEmpty()) { pinDocument += QLatin1String("Pin: version 0.0\n"); } else { pinDocument += QLatin1Literal("Pin: version ") % package->installedVersion() % QLatin1Char('\n'); } // Make configurable? pinDocument += QLatin1String("Pin-Priority: 1001\n\n"); } else { QDir logDirectory(dir); QStringList pinFiles = logDirectory.entryList(QDir::Files, QDir::Name); pinFiles << QString::fromStdString(_config->FindDir("Dir::Etc")) % QLatin1String("preferences"); // Search all pin files, delete package stanza from file for (const QString &pinName : pinFiles) { QString pinPath; if (!pinName.startsWith(QLatin1Char('/'))) { pinPath = dir % pinName; } else { pinPath = pinName; } if (!QFile::exists(pinPath)) continue; // Open to get a file name QTemporaryFile tempFile; if (!tempFile.open()) { return false; } tempFile.close(); QString tempFileName = tempFile.fileName(); FILE *out = fopen(tempFileName.toUtf8(), "w"); if (!out) { return false; } FileFd Fd(pinPath.toUtf8().data(), FileFd::ReadOnly); pkgTagFile tagFile(&Fd); if (_error->PendingError()) { fclose(out); return false; } pkgTagSection tags; while (tagFile.Step(tags)) { QString name = QLatin1String(tags.FindS("Package").c_str()); if (name.isEmpty()) { return false; } // Include all but the matching name in the new pinfile if (name != package->name()) { TFRewriteData tfrd; tfrd.Tag = 0; tfrd.Rewrite = 0; tfrd.NewTag = 0; TFRewrite(out, tags, TFRewritePackageOrder, &tfrd); fprintf(out, "\n"); } } fclose(out); if (!tempFile.open()) { return false; } pinDocument = tempFile.readAll(); } } if (!d->worker->writeFileToDisk(pinDocument, path)) { return false; } return true; } void Backend::updateXapianIndex() { QDBusMessage m = QDBusMessage::createMethodCall(QLatin1String("org.debian.AptXapianIndex"), QLatin1String("/"), QLatin1String("org.debian.AptXapianIndex"), QLatin1String("update_async")); QVariantList dbusArgs; dbusArgs << /*force*/ true << /*update_only*/ true; m.setArguments(dbusArgs); QDBusConnection::systemBus().send(m); QDBusConnection::systemBus().connect(QLatin1String("org.debian.AptXapianIndex"), QLatin1String("/"), QLatin1String("org.debian.AptXapianIndex"), QLatin1String("UpdateProgress"), this, SIGNAL(xapianUpdateProgress(int))); QDBusConnection::systemBus().connect(QLatin1String("org.debian.AptXapianIndex"), QLatin1String("/"), QLatin1String("org.debian.AptXapianIndex"), QLatin1String("UpdateFinished"), this, SLOT(emitXapianUpdateFinished())); emit xapianUpdateStarted(); } void Backend::emitXapianUpdateFinished() { QDBusConnection::systemBus().disconnect(QLatin1String("org.debian.AptXapianIndex"), QLatin1String("/"), QLatin1String("org.debian.AptXapianIndex"), QLatin1String("UpdateProgress"), this, SIGNAL(xapianUpdateProgress(int))); QDBusConnection::systemBus().disconnect(QLatin1String("org.debian.AptXapianIndex"), QLatin1String("/"), QLatin1String("org.debian.AptXapianIndex"), QLatin1String("UpdateFinished"), this, SLOT(xapianUpdateFinished(bool))); openXapianIndex(); emit xapianUpdateFinished(); } bool Backend::addArchiveToCache(const DebFile &archive) { Q_D(Backend); // Sanity checks Package *pkg = package(archive.packageName()); if (!pkg) { // The package is not in the cache, so we can't do anything // with this .deb return false; } QString arch = archive.architecture(); if (arch != QLatin1String("all") && arch != d->config->readEntry(QLatin1String("APT::Architecture"), QString())) { // Incompatible architecture return false; } QString debVersion = archive.version(); QString candVersion = pkg->availableVersion(); if (debVersion != candVersion) { // Incompatible version return false; } if (archive.md5Sum() != pkg->md5Sum()) { // Not the same as the candidate return false; } // Add the package, but we'll need auth so the worker'll do it return d->worker->copyArchiveToCache(archive.filePath()); } void Backend::setFrontendCaps(FrontendCaps caps) { Q_D(Backend); d->frontendCaps = caps; } } diff --git a/src/backend.h b/src/backend.h index f9879b7..d11e8ea 100644 --- a/src/backend.h +++ b/src/backend.h @@ -1,831 +1,844 @@ /*************************************************************************** * Copyright © 2010-2012 Jonathan Thomas * * * * 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 . * ***************************************************************************/ #ifndef QAPT_BACKEND_H #define QAPT_BACKEND_H #include #include #include #include "globals.h" #include "package.h" class pkgSourceList; class pkgRecords; namespace QApt { class Cache; class Config; class DebFile; class Transaction; } /** * The QApt namespace is the main namespace for LibQApt. All classes in this * library fall under this namespace. */ namespace QApt { class BackendPrivate; /** * @brief The main entry point for performing operations with the dpkg database * * Backend encapsulates all the needed logic to perform most apt operations. * It implements the initializing of the database and all requests to/for the * database. Please note that you @e MUST call init() before doing any * further operations to the backend, or else risk encountering undefined * behavior. * * @author Jonathan Thomas */ class Q_DECL_EXPORT Backend : public QObject { Q_OBJECT public: /** * Default constructor */ explicit Backend(QObject *parent = 0); /** * Default destructor */ ~Backend(); /** * Initializes the Apt database for usage. It sets up everything the backend * will need to perform all operations. Please note that you @b _MUST_ call * this function before doing any further operations in the backend, or else * risk encountering undefined behavior. * * @return @c true if initialization was successful * @return @c false if there was a problem initializing. If this is the case, * calling Backend methods other than initErrorMessage() will result in * undefined behavior, and will likely cause a crash. */ bool init(); /** * In the event that the init() or reloadCache() methods have returned false, * this method provides access to the error message from APT explaining why * opening the cache failed. This is the only safe method to call after * encountering a return false of @c false from init() or reloadCache() * * @since 2.0 */ QString initErrorMessage() const; /** * Returns whether or not APT is configured for installing packages for * additional foreign CPU architectures. */ bool isMultiArchEnabled() const; /** * Returns a list of the CPU architectures APT supports */ QStringList architectures() const; /** * Returns the native CPU architecture of the computer */ QString nativeArchitecture() const; + /** + * Returns the date when the distribution release was issued. + * + * E.g. for Ubuntu 18.04.1 it returns Thu, 26 Apr 2018 23:37:48 UTC. + * + * @return Distribution release date or invalid QDateTime if could not + * be determined. + * + * @since 3.1 + */ + QDateTime releaseDate() const; + /** * Returns whether the undo stack is empty */ bool isUndoStackEmpty() const; /** * Returns whether the redo stack is empty * */ bool isRedoStackEmpty() const; /** * Returns whether or not events are being compressed for multiple * markings. Applications doing custom multiple marking loops can * use this function to check whether or not to perform post-marking * code. * @since 1.3 */ bool areEventsCompressed() const; /** * Repopulates the internal package cache, package list, and group list. * Mostly used internally, like after an update or a package installation * or removal. * * @return @c true when the cache reloads successfully. If it returns false, * assume that you cannot call any methods other than initErrorMessage() * safely. */ bool reloadCache(); /** * Takes a snapshot of the current state of the package cache. (E.g. * which packages are marked for removal, install, etc) * * \return The current state of the cache as a @c CacheState */ CacheState currentCacheState() const; /** * Gets changes made to the cache since the given cache state. * * @param oldState The CacheState to compare against * @param excluded List of packages to exlude from the check * * @return A QHash containing lists of changed packages for each * Package::State change flag. * @since 1.3 */ QHash stateChanges(const CacheState &oldState, const PackageList &excluded) const; /** * Pointer to the QApt Backend's config object. * * \return A pointer to the QApt Backend's config object */ Config *config() const; /** * Queries the backend for a Package object for the specified name. * * @b _WARNING_ : * Note that if a package with a given name cannot be found, a null pointer * will be returned. Also, please note that certain actions like reloading * the cache may invalidate the pointer. * * @param name name used to specify the package returned * * @return A pointer to a @c Package defined by the specified name */ Package *package(const QString &name) const; /** Overload for package(const QString &name) **/ Package *package(QLatin1String name) const; /** * Queries the backend for a Package object that installs the specified * file. * * @b _WARNING_ : * Note that if a package with a given name cannot be found, a null pointer * will be returned. Also, please note that certain actions like reloading * the cache may invalidate the pointer. * * @param file The file used to search for the package * * @return A pointer to a @c Package defined by the specified name */ Package *packageForFile(const QString &file) const; /** * Returns a list of all package origins, as machine-readable strings * * @return The list of machine-readable origin labels * * @since 1.4 */ QStringList origins() const; /** * Returns a list of all package origins, as user readable strings. * * @return The list of human-readable origin labels */ QStringList originLabels() const; /** * Returns the human-readable name for the origin repository of the given * the machine-readable name. * * @return The human-readable origin label */ QString originLabel(const QString &origin) const; /** * Returns the machine-readable name for the origin repository of the given * the human-readable name. * * @return The machine-readable origin label */ QString origin(const QString &originLabel) const; /** * @returns the origins for a given @p host */ QStringList originsForHost(const QString& host) const; /** * Queries the backend for the total number of packages in the APT * database, discarding no-longer-existing packages that linger on in the * status cache (That have a version of 0) * * @return The total number of packages in the Apt database */ int packageCount() const; /** * Queries the backend for the total number of packages in the Apt * database, discarding no-longer-existing packages that linger on in the * status cache (That have a version of 0) * * @param states The package state(s) for which you wish to count packages for * * @return The total number of packages of the given PackageState in the * APT database */ int packageCount(const Package::States &states) const; /** * Queries the backend for the total number of packages in the APT * database that are installed. * * This is quicker than using the * packageCount(const Package::States &states) overload, and is * the recommended way for getting an installed packages count. * * @return The number of installed packages in the APT database * * @since 1.1 */ int installedCount() const; /** * Queries the backend for the total number of packages in the APT * database marked for installation. * * This is quicker than using the * packageCount(const Package::States &states) overload, and is * the recommended way for checking how many packages are to be * installed/upgraded. * * @return The number of packages marked for installation * * @since 1.1 */ int toInstallCount() const; /** * Queries the backend for the total number of packages in the APT * database marked for removal or purging. * * This is quicker than using the * packageCount(const Package::States &states) overload, and is * the recommended way for checking how many packages are to be * removed/purged. * * @return The number of packages marked for removal/purging * * @since 1.1 */ int toRemoveCount() const; /** * Returns the total amount of data that will be downloaded if the user * commits changes. Cached packages will not show up in this count. * * @return The total amount that will be downloaded in bytes. */ qint64 downloadSize() const; /** * Returns the total amount of disk space that will be consumed or * freed once the user commits changes. Freed space will show up as a * negative number. * * @return The total disk space to be used in bytes. */ qint64 installSize() const; /** * Returns a list of all available packages. This includes essentially all * packages, excluding now-nonexistent packages that have a version of 0. * * \return A @c PackageList of all available packages in the Apt database */ PackageList availablePackages() const; /** * Returns a list of all upgradeable packages * * \return A @c PackageList of all upgradeable packages */ PackageList upgradeablePackages() const; /** * Returns a list of all packages that have been marked for change. (To be * installed, removed, etc) * * \return A @c PackageList of all packages marked to be changed */ PackageList markedPackages() const; /** * A quick search that uses the APT Xapian index to search for packages * that match the given search string. While it is quite fast in 95% of * all cases, the relevancy of its results may in some cases not be 100% * accurate. Irrelevant results may slip in, and some relevant results * may be cut. * * You @e must call the openXapianIndex() function before search will work * * In the future, a "slow" search that searches by exact matches for * certain parameters will be implemented. * * @param searchString The string to narrow the search by. * * \return A @c PackageList of all packages matching the search string. * * @see openXapianIndex() */ PackageList search(const QString &searchString) const; /** * Returns a list of all available groups * * \return A @c GroupList of all available groups in the Apt database */ GroupList availableGroups() const; /** * Returns whether the search index needs updating * * @see updateXapianIndex() */ bool xapianIndexNeedsUpdate() const; /** * Attempts to open the APT Xapian index, needed for searching * * \returns true if opening was successful * \returns false otherwise */ bool openXapianIndex(); /** * Returns whether there are packages with marked changes waiting to be * committed */ bool areChangesMarked() const; /** * Returns whether the cache has broken packages or has a null dependency * cache */ bool isBroken() const; /** * Returns the last time the APT repository sources have been refreshed/checked * for updates. (Either with updateCache() or externally via other tools * like apt-get) * * @returns @c QDateTime The time that the cache was last checked for updates. * If this cannot be determined, an invalid QDateTime will be returned, * which can be checked with QDateTime::isValid() */ QDateTime timeCacheLastUpdated() const; /** * Returns the capabilities advertised by the frontend associated with the QApt * Backend. */ QApt::FrontendCaps frontendCaps() const; protected: BackendPrivate *const d_ptr; /** * Returns a pointer to the internal package source list. Mainly used for * internal purposes in QApt::Package. * * @return @c pkgSourceList The package source list used by the backend */ pkgSourceList *packageSourceList() const; /** * Returns a pointer to the internal package cache. Mainly used for * internal purposes in QApt::Package. * * @return @c pkgSourceList The package cache list used by the backend */ Cache *cache() const; /** * Returns a pointer to the internal package records object. Mainly used * for internal purposes in QApt::Package * * @return the package records object used by the backend * @since 1.4 */ pkgRecords *records() const; private: Q_DECLARE_PRIVATE(Backend) friend class Package; friend class PackagePrivate; Package *package(pkgCache::PkgIterator &iter) const; void setInitError(); void loadPackagePins(); + void loadReleaseDate(); Q_SIGNALS: /** * Emitted whenever a package changes state. Useful for knowning when to * react to state changes. */ void packageChanged(); /** * Emitted when the apt cache reload is started. * * After this signal is emitted all @c Package in the backend will be * deleted. Therefore, all pointers obtained in precedence from the backend * shall not be used anymore. This includes any @c PackageList returned by * availablePackages(), upgradeablePackages(), markedPackages() and search(). * * Also @c pkgCache::PkgIterator are invalidated. * * Wait for signal cacheReloadFinished() before requesting a new list of packages. * * @see reloadCache() */ void cacheReloadStarted(); /** * Emitted after the apt cache has been reloaded. * * @see cacheReloadStarted(); */ void cacheReloadFinished(); /** * This signal is emitted when a Xapian search cache update is started. * * Slots connected to this signal can then listen to the xapianUpdateProgress * signal for progress updates. */ void xapianUpdateStarted(); /** * This signal is emitted when a Xapian search cache update is finished. */ void xapianUpdateFinished(); /** * Emits the progress of the Apt Xapian Indexer * * @param percentage The progress percentage of the indexer */ void xapianUpdateProgress(int percentage); /** * Emitted whenever the QApt Worker's transaction queue has * changed. * * @param active The transaction ID of the active transaction * @param queue A list of transaction IDs of all transactions * currently in the queue, including the active one. * * @since 2.0 */ void transactionQueueChanged(QString active, QStringList queue); public Q_SLOTS: /** * Sets the maximum size of the undo and redo stacks. * The default size is 20. * * @param newSize The new size of the undo/redo stack * * @since 1.1 */ void setUndoRedoCacheSize(int newSize); /** * Takes the current state of the cache and puts it on the undo stack */ void saveCacheState(); /** * Restores the package cache to the given state. * * @param state The state to restore the cache to */ void restoreCacheState(const CacheState &state); /** * Un-performs the last action performed to the package cache */ void undo(); /** * Re-performs the last un-done action to the package cache. */ void redo(); /** * Marks all upgradeable packages for upgrading, without marking new * packages for installation. */ void markPackagesForUpgrade(); /** * Marks all upgradeable packages for upgrading, including updates that * would require marking new packages for installation. */ void markPackagesForDistUpgrade(); /** * Marks all packages that are autoremoveable, as determined by APT. In * general these are packages that were automatically installed that now * no longer have any packages dependening on them. (Like after a * library transition libqapt0 -> libqapt1) * * @since 1.1 */ void markPackagesForAutoRemove(); /** * Marks a package for install. * * @param name The name of the package to be installed */ void markPackageForInstall(const QString &name); /** * Marks a package for removal. * * @param name The name of the package to be removed */ void markPackageForRemoval(const QString &name); /** * Marks multiple packages at once. This is more efficient than marking * packages individually, as event compression is utilized to prevent * post-marking calculations from being performed until after all packages * have been marked. * * @param packages The list of packages to be marked * @param action The action to perform on the list of packages * * @since 1.3 */ void markPackages(const QApt::PackageList &packages, QApt::Package::State action); /** * Manual control for enabling/disabling event compression. Useful for when * an application needs to have its own multiple marking loop, but still wants * to utilize event compression */ void setCompressEvents(bool enabled); /** * Starts a transaction which will commit all pending package state * changes that have been made to the backend * * @return A pointer to a @c Transaction object tracking the commit * * @since 2.0 */ QApt::Transaction *commitChanges(); /** * Starts a transaction which will install the list of provided packages. * This function is useful when you only need a few packages installed and * don't need to commit a complex set of changes with commitChanges(). * * @param packages The packages to be installed * * @return A pointer to a @c Transaction object tracking the install * * @since 2.0 * @see removePackages * @see commitChanges */ QApt::Transaction *installPackages(QApt::PackageList packages); /** * Starts a transaction which will remove the list of provided packages. * This function is useful when you only need a few packages removed and * don't need to commit a complex set of changes with commitChanges(). * * @param packages The packages to be removed * * @return A pointer to a @c Transaction object tracking the removal * * @since 2.0 * @see installPackages * @see commitChanges */ QApt::Transaction *removePackages(QApt::PackageList packages); /** * Downloads the packages listed in the provided list file to the provided * destination directory. * * If the list file provided cannot be opened, a null pointer will be returned. * * @return A pointer to a @c Transaction object tracking the download * * @param listFile The path to the package list file * @param destination The path of the directory to download the packages to * * @since 2.0 */ Transaction *downloadArchives(const QString &listFile, const QString &destination); /** * Installs a .deb package archive file. * * If the file has any additional dependencies that are not currently * installed, the worker will install these. The backend sends out normal * download event signals. * * When the commit process for the DebFile starts, the backend will emit the * QApt::DebInstallStarted worker signal. Similarly, when the commit is * finished the backend will emit the QApt::DebInstallFinished signal. * * @param file the DebFile to install * * @see workerEvent() * @see packageDownloadProgress() * * @since 2.0 */ Transaction *installFile(const DebFile &file); /** * Starts a transaction that will check for and downloads new package * source lists. (Essentially, checking for updates.) * * @return A pointer to a @c Transaction object tracking the cache update. * * @since 2.0 */ Transaction *updateCache(); /** * Starts a transaction which will upgrade as many of the packages as it can. * If the upgrade type is a "safe" upgrade, only packages that can be upgraded * without installing or removing new packages will be upgraded. If a "full" * upgrade is chosen, the transaction will upgrade all packages, and can * install or remove packages to do so. * * @param upgradeType The type of upgrade to be performed. (safe vs. full) * * @return A pointer to a @c Transaction object tracking the upgrade * * @since 2.0 */ Transaction *upgradeSystem(QApt::UpgradeType upgradeType); /** * Exports a list of all packages currently installed on the system. This * list can be read by the readSelections() function or by Synaptic. * * @param path The path to save the selection list to * * @return @c true if saving succeeded * @return @c false if the saving failed * * @since 1.2 * * @see loadSelections() * @see saveSelections() */ bool saveInstalledPackagesList(const QString &path) const; /** * Writes a list of packages that have been marked for install, removal or * upgrade. * * @param path The path to save the selection list to * * @return @c true if saving succeeded * @return @c false if the saving failed * * @since 1.2 * * @see saveInstalledPackagesList() * @see loadSelections() */ bool saveSelections(const QString &path) const; /** * Reads and applies selections from a text file generated from either * saveSelections() or from Synaptic * * @param path The path from which to read the selection list * * @return @c true if reading/marking succeeded * @return @c false if the reading/marking failed * * @since 1.2 * * @see saveSelections() * @see saveInstalledPackagesList() */ bool loadSelections(const QString &path); /** * Writes a list of packages that have been marked for installation. This * list can then be loaded with the loadDownloadList() function to start * downloading the packages. * * @param path The path to save the download list to * * @return @c true if savign succeeded, @c false if the saving failed * * @since 1.2 */ bool saveDownloadList(const QString &path) const; /** * Locks the package at either the current version if installed, or * prevents automatic installation if not installed. * * The backend must be reloaded before the pinning will take effect * * @param package The package to control pinning for * @param pin Whether to pin or unpin the package * * @return @c true on success, @c false on failure * * @since 1.2 */ bool setPackagePinned(QApt::Package *package, bool pin); /** * Tells the QApt Worker to initiate a rebuild of the Xapian package search * index. * * This function is asynchronous. The worker will report start and finish * events using the workerEvent() signal. Progress is reported by the * xapianUpdateProgress() signal. * * @see xapianUpdateProgress() * @see xapianIndexNeedsUpdate() */ void updateXapianIndex(); /** * Add the given .deb package archive to the APT package cache. * * To succeed, the .deb file's corresponding package must already be known * to APT. The version of the package that the .deb file provides must match * the candidate version from APT, and additionally the md5 sums of the .deb * file and the candidate version of the package in APT must match. * * The main use for this function is to add .deb archives from e.g. a USB * stick so that computers without internet connections can install/upgrade * packages. * * @param archive The .deb archive to be added to the package cache * * @return @c true on success, @c false on failure */ bool addArchiveToCache(const DebFile &archive); /** * Sets the capabilities of the frontend. All transactions created after this * is set will inherit these capability flags. * * @param caps The capabilities to advertise the frontend as having * * @since 2.1 */ void setFrontendCaps(QApt::FrontendCaps caps); private Q_SLOTS: void emitPackageChanged(); void emitXapianUpdateFinished(); }; } #endif diff --git a/src/package.cpp b/src/package.cpp index c6a5152..c87236c 100644 --- a/src/package.cpp +++ b/src/package.cpp @@ -1,1419 +1,1347 @@ /*************************************************************************** * Copyright © 2010-2011 Jonathan Thomas * * Heavily inspired by Synaptic library code ;-) * * * * 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 . * ***************************************************************************/ //krazy:excludeall=qclasses // Qt-only library, so things like QUrl *should* be used #include "package.h" // Qt includes #include #include #include #include #include #include #include // Apt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // Own includes #include "backend.h" #include "cache.h" #include "config.h" // krazy:exclude=includes #include "markingerrorinfo.h" namespace QApt { class PackagePrivate { public: PackagePrivate(pkgCache::PkgIterator iter, Backend *back) : packageIter(iter) , backend(back) , state(0) , staticStateCalculated(false) , foreignArchCalculated(false) , isInUpdatePhase(false) , inUpdatePhaseCalculated(false) { } ~PackagePrivate() { } pkgCache::PkgIterator packageIter; QApt::Backend *backend; int state; bool staticStateCalculated; bool isForeignArch; bool foreignArchCalculated; bool isInUpdatePhase; bool inUpdatePhaseCalculated; pkgCache::PkgFileIterator searchPkgFileIter(QLatin1String label, const QString &release) const; - QString getReleaseFileForOrigin(QLatin1String label, const QString &release) const; // Calculate state flags that cannot change void initStaticState(const pkgCache::VerIterator &ver, pkgDepCache::StateCache &stateCache); bool setInUpdatePhase(bool inUpdatePhase); }; pkgCache::PkgFileIterator PackagePrivate::searchPkgFileIter(QLatin1String label, const QString &release) const { pkgCache::VerIterator verIter = packageIter.VersionList(); pkgCache::VerFileIterator verFileIter; pkgCache::PkgFileIterator found; while (!verIter.end()) { for (verFileIter = verIter.FileList(); !verFileIter.end(); ++verFileIter) { for(found = verFileIter.File(); !found.end(); ++found) { const char *verLabel = found.Label(); const char *verOrigin = found.Origin(); const char *verArchive = found.Archive(); if (verLabel && verOrigin && verArchive) { if(verLabel == label && verOrigin == label && QLatin1String(verArchive) == release) { return found; } } } } ++verIter; } found = pkgCache::PkgFileIterator(*packageIter.Cache()); return found; } -QString PackagePrivate::getReleaseFileForOrigin(QLatin1String label, const QString &release) const -{ - pkgCache::PkgFileIterator pkg = searchPkgFileIter(label, release); - - // Return empty if no package matches the given label and release - if (pkg.end()) - return QString(); - - // Search for the matching meta-index - pkgSourceList *list = backend->packageSourceList(); - pkgIndexFile *index; - - // Return empty if the source list doesn't contain an index for the package - if (!list->FindIndex(pkg, index)) - return QString(); - - for (auto I = list->begin(); I != list->end(); ++I) { - vector *ifv = (*I)->GetIndexFiles(); - if (find(ifv->begin(), ifv->end(), index) == ifv->end()) - continue; - - // Construct release file path - QString uri = backend->config()->findDirectory("Dir::State::lists") - % QString::fromStdString(URItoFileName((*I)->GetURI())) - % QLatin1String("dists_") - % QString::fromStdString((*I)->GetDist()) - % QLatin1String("_Release"); - - return uri; - } - - return QString(); -} - void PackagePrivate::initStaticState(const pkgCache::VerIterator &ver, pkgDepCache::StateCache &stateCache) { int packageState = 0; if (!ver.end()) { packageState |= QApt::Package::Installed; if (stateCache.CandidateVer && stateCache.Upgradable()) { packageState |= QApt::Package::Upgradeable; } } else { packageState |= QApt::Package::NotInstalled; } // Broken/garbage statuses are constant until a cache reload if (stateCache.NowBroken()) { packageState |= QApt::Package::NowBroken; } if (stateCache.InstBroken()) { packageState |= QApt::Package::InstallBroken; } if (stateCache.Garbage) { packageState |= QApt::Package::IsGarbage; } if (stateCache.NowPolicyBroken()) { packageState |= QApt::Package::NowPolicyBroken; } if (stateCache.InstPolicyBroken()) { packageState |= QApt::Package::InstallPolicyBroken; } // Essential/important status can only be changed by cache reload if (packageIter->Flags & (pkgCache::Flag::Important | pkgCache::Flag::Essential)) { packageState |= QApt::Package::IsImportant; } if (packageIter->CurrentState == pkgCache::State::ConfigFiles) { packageState |= QApt::Package::ResidualConfig; } // Packages will stay undownloadable until a sources file is refreshed // and the cache is reloaded. bool downloadable = true; if (!stateCache.CandidateVer || !stateCache.CandidateVerIter(*backend->cache()->depCache()).Downloadable()) downloadable = false; if (!downloadable) packageState |= QApt::Package::NotDownloadable; state |= packageState; staticStateCalculated = true; } bool PackagePrivate::setInUpdatePhase(bool inUpdatePhase) { inUpdatePhaseCalculated = true; isInUpdatePhase = inUpdatePhase; return inUpdatePhase; } Package::Package(QApt::Backend* backend, pkgCache::PkgIterator &packageIter) : d(new PackagePrivate(packageIter, backend)) { } Package::~Package() { delete d; } const pkgCache::PkgIterator &Package::packageIterator() const { return d->packageIter; } QLatin1String Package::name() const { return QLatin1String(d->packageIter.Name()); } int Package::id() const { return d->packageIter->ID; } QLatin1String Package::section() const { return QLatin1String(d->packageIter.Section()); } QString Package::sourcePackage() const { QString sourcePackage; // In the APT package record format, the only time when a "Source:" field // is present is when the binary package name doesn't match the source // name const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (!ver.end()) { pkgRecords::Parser &rec = d->backend->records()->Lookup(ver.FileList()); sourcePackage = QString::fromStdString(rec.SourcePkg()); } // If the package record didn't have a "Source:" field, then this package's // name must be the source package's name. (Or there isn't a record for this package) if (sourcePackage.isEmpty()) { sourcePackage = name(); } return sourcePackage; } QString Package::shortDescription() const { QString shortDescription; const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (!ver.end()) { pkgCache::DescIterator Desc = ver.TranslatedDescription(); pkgRecords::Parser & parser = d->backend->records()->Lookup(Desc.FileList()); shortDescription = QString::fromUtf8(parser.ShortDesc().data()); return shortDescription; } return shortDescription; } QString Package::longDescription() const { const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (!ver.end()) { QString rawDescription; pkgCache::DescIterator Desc = ver.TranslatedDescription(); pkgRecords::Parser & parser = d->backend->records()->Lookup(Desc.FileList()); rawDescription = QString::fromUtf8(parser.LongDesc().data()); // Apt acutally returns the whole description, we just want the // extended part. rawDescription.remove(shortDescription() % '\n'); // *Now* we're really raw. Sort of. ;) QString parsedDescription; // Split at double newline, by "section" QStringList sections = rawDescription.split(QLatin1String("\n .")); for (int i = 0; i < sections.count(); ++i) { sections[i].replace(QRegExp(QLatin1String("\n( |\t)+(-|\\*)")), QLatin1Literal("\n\r ") % QString::fromUtf8("\xE2\x80\xA2")); // There should be no new lines within a section. sections[i].remove(QLatin1Char('\n')); // Hack to get the lists working again. sections[i].replace(QLatin1Char('\r'), QLatin1Char('\n')); // Merge multiple whitespace chars into one sections[i].replace(QRegExp(QLatin1String("\\ \\ +")), QChar::fromLatin1(' ')); // Remove the initial whitespace if (sections[i].startsWith(QChar::Space)) { sections[i].remove(0, 1); } // Append to parsedDescription if (sections[i].startsWith(QLatin1String("\n ") % QString::fromUtf8("\xE2\x80\xA2 ")) || !i) { parsedDescription += sections[i]; } else { parsedDescription += QLatin1Literal("\n\n") % sections[i]; } } return parsedDescription; } return QString(); } QString Package::maintainer() const { QString maintainer; const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (!ver.end()) { pkgRecords::Parser &parser = d->backend->records()->Lookup(ver.FileList()); maintainer = QString::fromUtf8(parser.Maintainer().data()); // This replacement prevents frontends from interpreting '<' as // an HTML tag opening maintainer.replace(QLatin1Char('<'), QLatin1String("<")); } return maintainer; } QString Package::homepage() const { QString homepage; const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (!ver.end()) { pkgRecords::Parser &parser = d->backend->records()->Lookup(ver.FileList()); homepage = QString::fromUtf8(parser.Homepage().data()); } return homepage; } QString Package::version() const { if (!d->packageIter->CurrentVer) { pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if (!State.CandidateVer) { return QString(); } else { return QLatin1String(State.CandidateVerIter(*d->backend->cache()->depCache()).VerStr()); } } else { return QLatin1String(d->packageIter.CurrentVer().VerStr()); } } QString Package::upstreamVersion() const { const char *ver; if (!d->packageIter->CurrentVer) { pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if (!State.CandidateVer) { return QString(); } else { ver = State.CandidateVerIter(*d->backend->cache()->depCache()).VerStr(); } } else { ver = d->packageIter.CurrentVer().VerStr(); } return QString::fromStdString(_system->VS->UpstreamVersion(ver)); } QString Package::upstreamVersion(const QString &version) { QByteArray ver = version.toLatin1(); return QString::fromStdString(_system->VS->UpstreamVersion(ver.constData())); } QString Package::architecture() const { pkgDepCache *depCache = d->backend->cache()->depCache(); pkgCache::VerIterator ver = (*depCache)[d->packageIter].InstVerIter(*depCache); // the arch:all property is part of the version if (ver && ver.Arch()) return QLatin1String(ver.Arch()); return QLatin1String(d->packageIter.Arch()); } QStringList Package::availableVersions() const { QStringList versions; // Get available Versions. for (auto Ver = d->packageIter.VersionList(); !Ver.end(); ++Ver) { // We always take the first available version. pkgCache::VerFileIterator VF = Ver.FileList(); if (VF.end()) continue; pkgCache::PkgFileIterator File = VF.File(); // Files without an archive will have a site QString archive = File.Archive() ? QLatin1String(File.Archive()) : QLatin1String(File.Site()); versions.append(QLatin1String(Ver.VerStr()) % QLatin1String(" (") % archive % ')'); } return versions; } QString Package::installedVersion() const { if (!d->packageIter->CurrentVer) { return QString(); } return QLatin1String(d->packageIter.CurrentVer().VerStr()); } QString Package::availableVersion() const { pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if (!State.CandidateVer) { return QString(); } return QLatin1String(State.CandidateVerIter(*d->backend->cache()->depCache()).VerStr()); } QString Package::priority() const { const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (ver.end()) return QString(); return QLatin1String(ver.PriorityType()); } QStringList Package::installedFilesList() const { QStringList installedFilesList; QString path = QLatin1String("/var/lib/dpkg/info/") % name() % QLatin1String(".list"); // Fallback for multiarch packages if (!QFile::exists(path)) { path = QLatin1String("/var/lib/dpkg/info/") % name() % ':' % architecture() % QLatin1String(".list"); } QFile infoFile(path); if (infoFile.open(QFile::ReadOnly)) { QTextStream stream(&infoFile); QString line; do { line = stream.readLine(); installedFilesList << line; } while (!line.isNull()); // The first item won't be a file installedFilesList.removeFirst(); // Remove non-file directory listings for (int i = 0; i < installedFilesList.size() - 1; ++i) { if (installedFilesList.at(i+1).contains(installedFilesList.at(i) + '/')) { installedFilesList[i] = QString(QLatin1Char(' ')); } } installedFilesList.removeAll(QChar::fromLatin1(' ')); // Last line is empty for some reason... if (!installedFilesList.isEmpty()) { installedFilesList.removeLast(); } } return installedFilesList; } QString Package::origin() const { const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if(Ver.end()) return QString(); pkgCache::VerFileIterator VF = Ver.FileList(); return QString::fromUtf8(VF.File().Origin()); } QString Package::site() const { const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if(Ver.end()) return QString(); pkgCache::VerFileIterator VF = Ver.FileList(); return QString::fromUtf8(VF.File().Site()); } QStringList Package::archives() const { const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if(Ver.end()) return QStringList(); QStringList archiveList; for (auto VF = Ver.FileList(); !VF.end(); ++VF) archiveList << QLatin1String(VF.File().Archive()); return archiveList; } QString Package::component() const { QString sect = section(); if(sect.isEmpty()) return QString(); QStringList split = sect.split('/'); if (split.count() > 1) return split.first(); return QString("main"); } QByteArray Package::md5Sum() const { const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if(ver.end()) return QByteArray(); pkgRecords::Parser &rec = d->backend->records()->Lookup(ver.FileList()); return rec.MD5Hash().c_str(); } QUrl Package::changelogUrl() const { const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (ver.end()) return QUrl(); const QString url = QString::fromStdString(pkgAcqChangelog::URI(ver)); // pkgAcqChangelog::URI(ver) may return URIs with schemes other than http(s) // e.g. copy:// gzip:// for local files. We exclude them for backward // compatibility with libQApt <= 3.0.3. if (!url.startsWith("http")) return QUrl(); return QUrl(url); } QUrl Package::screenshotUrl(QApt::ScreenshotType type) const { QUrl url; switch (type) { case QApt::Thumbnail: url = QUrl(controlField(QLatin1String("Thumbnail-Url"))); if(url.isEmpty()) url = QUrl("http://screenshots.debian.net/thumbnail/" % name()); break; case QApt::Screenshot: url = QUrl(controlField(QLatin1String("Screenshot-Url"))); if(url.isEmpty()) url = QUrl("http://screenshots.debian.net/screenshot/" % name()); break; default: qDebug() << "I do not know how to handle the screenshot type given to me: " << QString::number(type); } return url; } QDateTime Package::supportedUntil() const { if (!isSupported()) { return QDateTime(); } - QFile lsb_release(QLatin1String("/etc/lsb-release")); - if (!lsb_release.open(QFile::ReadOnly)) { - // Though really, your system is screwed if this happens... - return QDateTime(); - } - - pkgTagSection sec; - time_t releaseDate = -1; - QString release; - - QTextStream stream(&lsb_release); - QString line; - do { - line = stream.readLine(); - QStringList split = line.split(QLatin1Char('=')); - if (split.size() != 2) { - continue; - } - - if (split.at(0) == QLatin1String("DISTRIB_CODENAME")) { - release = split.at(1); - } - } while (!line.isNull()); - - // Canonical only provides support for Ubuntu, but we don't have to worry - // about Debian systems as long as we assume that this function can fail. - QString releaseFile = d->getReleaseFileForOrigin(QLatin1String("Ubuntu"), release); - - if(!FileExists(releaseFile.toStdString())) { - // happens e.g. when there is no release file and is harmless - return QDateTime(); - } - - // read the relase file - FileFd fd(releaseFile.toStdString(), FileFd::ReadOnly); - pkgTagFile tag(&fd); - tag.Step(sec); - - if(!RFC1123StrToTime(sec.FindS("Date").data(), releaseDate)) { + QDateTime releaseDate = d->backend->releaseDate(); + if (!releaseDate.isValid()) { return QDateTime(); } // Default to 18m in case the package has no "supported" field QString supportTimeString = QLatin1String("18m"); QString supportTimeField = controlField(QLatin1String("Supported")); if (!supportTimeField.isEmpty()) { supportTimeString = supportTimeField; } QChar unit = supportTimeString.at(supportTimeString.length() - 1); supportTimeString.chop(1); // Remove the letter signifying months/years const int supportTime = supportTimeString.toInt(); QDateTime supportEnd; if (unit == QLatin1Char('m')) { - supportEnd = QDateTime::fromTime_t(releaseDate).addMonths(supportTime); + supportEnd = releaseDate.addMonths(supportTime); } else if (unit == QLatin1Char('y')) { - supportEnd = QDateTime::fromTime_t(releaseDate).addYears(supportTime); + supportEnd = releaseDate.addYears(supportTime); } return supportEnd; } QString Package::controlField(QLatin1String name) const { const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (ver.end()) { return QString(); } pkgRecords::Parser &rec = d->backend->records()->Lookup(ver.FileList()); return QString::fromStdString(rec.RecordField(name.latin1())); } QString Package::controlField(const QString &name) const { return controlField(QLatin1String(name.toLatin1())); } qint64 Package::currentInstalledSize() const { const pkgCache::VerIterator &ver = d->packageIter.CurrentVer(); if (!ver.end()) { return qint64(ver->InstalledSize); } else { return qint64(-1); } } qint64 Package::availableInstalledSize() const { pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if (!State.CandidateVer) { return qint64(-1); } return qint64(State.CandidateVerIter(*d->backend->cache()->depCache())->InstalledSize); } qint64 Package::installedSize() const { const pkgCache::VerIterator &ver = d->packageIter.CurrentVer(); if (!ver.end()) { return qint64(ver->InstalledSize); } return availableInstalledSize(); } qint64 Package::downloadSize() const { pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if (!State.CandidateVer) { return qint64(-1); } return qint64(State.CandidateVerIter(*d->backend->cache()->depCache())->Size); } int Package::state() const { int packageState = 0; const pkgCache::VerIterator &ver = d->packageIter.CurrentVer(); pkgDepCache::StateCache &stateCache = (*d->backend->cache()->depCache())[d->packageIter]; if (!d->staticStateCalculated) { d->initStaticState(ver, stateCache); } if (stateCache.Install()) { packageState |= ToInstall; } if (stateCache.Flags & pkgCache::Flag::Auto) { packageState |= QApt::Package::IsAuto; } if (stateCache.iFlags & pkgDepCache::ReInstall) { packageState |= ToReInstall; } else if (stateCache.NewInstall()) { // Order matters here. packageState |= NewInstall; } else if (stateCache.Upgrade()) { packageState |= ToUpgrade; } else if (stateCache.Downgrade()) { packageState |= ToDowngrade; } else if (stateCache.Delete()) { packageState |= ToRemove; if (stateCache.iFlags & pkgDepCache::Purge) { packageState |= ToPurge; } } else if (stateCache.Keep()) { packageState |= ToKeep; if (stateCache.Held()) { packageState |= QApt::Package::Held; } } return packageState | d->state; } int Package::staticState() const { if (!d->staticStateCalculated) { const pkgCache::VerIterator &ver = d->packageIter.CurrentVer(); pkgDepCache::StateCache &stateCache = (*d->backend->cache()->depCache())[d->packageIter]; d->initStaticState(ver, stateCache); } return d->state; } int Package::compareVersion(const QString &v1, const QString &v2) { // Make deep copies of toStdString(), since otherwise they would // go out of scope when we call c_str() string s1 = v1.toStdString(); string s2 = v2.toStdString(); const char *a = s1.c_str(); const char *b = s2.c_str(); int lenA = strlen(a); int lenB = strlen(b); return _system->VS->DoCmpVersion(a, a+lenA, b, b+lenB); } bool Package::isInstalled() const { return !d->packageIter.CurrentVer().end(); } bool Package::isSupported() const { if (origin() == QLatin1String("Ubuntu")) { QString componentString = component(); if ((componentString == QLatin1String("main") || componentString == QLatin1String("restricted")) && isTrusted()) { return true; } } return false; } bool Package::isInUpdatePhase() const { if (!(state() & Package::Upgradeable)) { return false; } // Try to use the cached values, otherwise we have to do the calculation. if (d->inUpdatePhaseCalculated) { return d->isInUpdatePhase; } bool intConversionOk = true; int phasedUpdatePercent = controlField(QLatin1String("Phased-Update-Percentage")).toInt(&intConversionOk); if (!intConversionOk) { // Upgradable but either the phase percent field is not there at all // or it has a non-convertable value. // In either case this package is good for upgrade. return d->setInUpdatePhase(true); } // This is a more or less an exact reimplementation of the update phasing // algorithm Ubuntu uses. // Deciding whether a machine is in the phasing pool or not happens in // two steps. // 1. repeatable random number generation between 0..100 // 2. comparison of random number with phasing percentage and marking // as upgradable if rand is greater than the phasing. // Repeatable discrete random number generation is based on // the MD5 hash of "sourcename-sourceversion-dbusmachineid", this // hash is used as seed for the random number generator to provide // stable randomness based on the stable seed. Combined with the discrete // quasi-randomiziation we get about even distribution of machines across // phases. static QString machineId; if (machineId.isNull()) { QFile file(QStringLiteral("/var/lib/dbus/machine-id")); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { machineId = file.readLine().trimmed(); } } if (machineId.isEmpty()) { // Without machineId we cannot differentiate one machine from another, so // we have no way to build a unique hash. return true; // Don't change cache as we might have more luck next time. } QString seedString = QStringLiteral("%1-%2-%3").arg(sourcePackage(), availableVersion(), machineId); QByteArray seed = QCryptographicHash::hash(seedString.toLatin1(), QCryptographicHash::Md5); // MD5 would be 128bits, that's two quint64 stdlib random default_engine // uses a uint32 seed though, so we'd loose precision anyway, so screw // this, we'll get the first 32bit and screw the rest! This is not // rocket science, worst case the update only arrives once the phasing // tag is removed. seed = seed.toHex(); seed.truncate(8 /* each character in a hex string values 4 bit, 8*4=32bit */); bool ok = false; uint a = seed.toUInt(&ok, 16); Q_ASSERT(ok); // Hex conversion always is supposed to work at this point. std::default_random_engine generator(a); std::uniform_int_distribution distribution(0, 100); int rand = distribution(generator); // rand is the percentage at which the machine starts to be in the phase. // Once rand is less than the phasing percentage e.g. 40rand vs. 50phase // the machine is supposed to start phasing. return d->setInUpdatePhase(rand <= phasedUpdatePercent); } bool Package::isMultiArchDuplicate() const { // Excludes installed packages, which are always "interesting" if (isInstalled()) return false; // Otherwise, check if the pkgIterator is the "best" from its group return (d->packageIter.Group().FindPkg() != d->packageIter); } QString Package::multiArchTypeString() const { return controlField(QLatin1String("Multi-Arch")); } MultiArchType Package::multiArchType() const { QString typeString = multiArchTypeString(); MultiArchType archType = InvalidMultiArchType; if (typeString == QLatin1String("same")) archType = MultiArchSame; else if (typeString == QLatin1String("foreign")) archType = MultiArchForeign; else if (typeString == QLatin1String("allowed")) archType = MultiArchAllowed; return archType; } bool Package::isForeignArch() const { if (!d->foreignArchCalculated) { QString arch = architecture(); d->isForeignArch = (d->backend->nativeArchitecture() != arch) & (arch != QLatin1String("all")); d->foreignArchCalculated = true; } return d->isForeignArch; } QList Package::depends() const { return DependencyInfo::parseDepends(controlField("Depends"), Depends); } QList Package::preDepends() const { return DependencyInfo::parseDepends(controlField("Pre-Depends"), PreDepends); } QList Package::suggests() const { return DependencyInfo::parseDepends(controlField("Suggests"), Suggests); } QList Package::recommends() const { return DependencyInfo::parseDepends(controlField("Recommends"), Recommends); } QList Package::conflicts() const { return DependencyInfo::parseDepends(controlField("Conflicts"), Conflicts); } QList Package::replaces() const { return DependencyInfo::parseDepends(controlField("Replaces"), Replaces); } QList Package::obsoletes() const { return DependencyInfo::parseDepends(controlField("Obsoletes"), Obsoletes); } QList Package::breaks() const { return DependencyInfo::parseDepends(controlField("Breaks"), Breaks); } QList Package::enhances() const { return DependencyInfo::parseDepends(controlField("Enhance"), Enhances); } QStringList Package::dependencyList(bool useCandidateVersion) const { QStringList dependsList; pkgCache::VerIterator current; pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if(!useCandidateVersion) { current = State.InstVerIter(*d->backend->cache()->depCache()); } if(useCandidateVersion || current.end()) { current = State.CandidateVerIter(*d->backend->cache()->depCache()); } // no information found if(current.end()) { return dependsList; } for(pkgCache::DepIterator D = current.DependsList(); D.end() != true; ++D) { QString type; bool isOr = false; bool isVirtual = false; QString name; QString version; QString versionCompare; QString finalString; // check target and or-depends status pkgCache::PkgIterator Trg = D.TargetPkg(); if ((D->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or) { isOr=true; } // common information type = QString::fromUtf8(D.DepType()); name = QLatin1String(Trg.Name()); if (!Trg->VersionList) { isVirtual = true; } else { version = QLatin1String(D.TargetVer()); versionCompare = QLatin1String(D.CompType()); } finalString = QLatin1Literal("") % type % QLatin1Literal(": "); if (isVirtual) { finalString += QLatin1Literal("") % name % QLatin1Literal(""); } else { finalString += name; } // Escape the compare operator so it won't be seen as HTML if (!version.isEmpty()) { QString compMarkup(versionCompare); compMarkup.replace(QLatin1Char('<'), QLatin1String("<")); finalString += QLatin1String(" (") % compMarkup % QLatin1Char(' ') % version % QLatin1Char(')'); } if (isOr) { finalString += QLatin1String(" |"); } dependsList.append(finalString); } return dependsList; } QStringList Package::requiredByList() const { QStringList reverseDependsList; for(pkgCache::DepIterator it = d->packageIter.RevDependsList(); !it.end(); ++it) { reverseDependsList << QLatin1String(it.ParentPkg().Name()); } return reverseDependsList; } QStringList Package::providesList() const { pkgDepCache::StateCache &State = (*d->backend->cache()->depCache())[d->packageIter]; if (!State.CandidateVer) { return QStringList(); } QStringList provides; for (pkgCache::PrvIterator Prv = State.CandidateVerIter(*d->backend->cache()->depCache()).ProvidesList(); !Prv.end(); ++Prv) { provides.append(QLatin1String(Prv.Name())); } return provides; } QStringList Package::recommendsList() const { QStringList recommends; const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (Ver.end()) { return recommends; } for(pkgCache::DepIterator it = Ver.DependsList(); !it.end(); ++it) { pkgCache::PkgIterator pkg = it.TargetPkg(); // Skip purely virtual packages if (!pkg->VersionList) { continue; } pkgDepCache::StateCache &rState = (*d->backend->cache()->depCache())[pkg]; if (it->Type == pkgCache::Dep::Recommends && (rState.CandidateVer != 0 )) { recommends << QLatin1String(it.TargetPkg().Name()); } } return recommends; } QStringList Package::suggestsList() const { QStringList suggests; const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (Ver.end()) { return suggests; } for(pkgCache::DepIterator it = Ver.DependsList(); !it.end(); ++it) { pkgCache::PkgIterator pkg = it.TargetPkg(); // Skip purely virtual packages if (!pkg->VersionList) { continue; } pkgDepCache::StateCache &sState = (*d->backend->cache()->depCache())[pkg]; if (it->Type == pkgCache::Dep::Suggests && (sState.CandidateVer != 0 )) { suggests << QLatin1String(it.TargetPkg().Name()); } } return suggests; } QStringList Package::enhancesList() const { QStringList enhances; const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (Ver.end()) { return enhances; } for(pkgCache::DepIterator it = Ver.DependsList(); !it.end(); ++it) { pkgCache::PkgIterator pkg = it.TargetPkg(); // Skip purely virtual packages if (!pkg->VersionList) { continue; } pkgDepCache::StateCache &eState = (*d->backend->cache()->depCache())[pkg]; if (it->Type == pkgCache::Dep::Enhances && (eState.CandidateVer != 0 )) { enhances << QLatin1String(it.TargetPkg().Name()); } } return enhances; } QStringList Package::enhancedByList() const { QStringList enhancedByList; Q_FOREACH (QApt::Package *package, d->backend->availablePackages()) { if (package->enhancesList().contains(name())) { enhancedByList << package->name(); } } return enhancedByList; } QList Package::brokenReason() const { const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); QList reasons; // check if there is actually something to install if (!Ver) { QApt::DependencyInfo info(name(), QString(), NoOperand, InvalidType); QApt::MarkingErrorInfo error(QApt::ParentNotInstallable, info); reasons.append(error); return reasons; } for (pkgCache::DepIterator D = Ver.DependsList(); !D.end();) { // Compute a single dependency element (glob or) pkgCache::DepIterator Start; pkgCache::DepIterator End; D.GlobOr(Start, End); pkgCache::PkgIterator Targ = Start.TargetPkg(); if (!d->backend->cache()->depCache()->IsImportantDep(End)) { continue; } if (((*d->backend->cache()->depCache())[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall) { continue; } if (!Targ->ProvidesList) { // Ok, not a virtual package since no provides pkgCache::VerIterator Ver = (*d->backend->cache()->depCache())[Targ].InstVerIter(*d->backend->cache()->depCache()); QString requiredVersion; if(Start.TargetVer() != 0) { requiredVersion = '(' % QLatin1String(Start.CompType()) % QLatin1String(Start.TargetVer()) % ')'; } if (!Ver.end()) { // Happens when a package needs an upgraded dep, but the dep won't // upgrade. Example: // "apt 0.5.4 but 0.5.3 is to be installed" QString targetName = QLatin1String(Start.TargetPkg().Name()); QApt::DependencyType relation = (QApt::DependencyType)End->Type; QApt::DependencyInfo errorInfo(targetName, requiredVersion, NoOperand, relation); QApt::MarkingErrorInfo error(QApt::WrongCandidateVersion, errorInfo); reasons.append(error); } else { // We have the package, but for some reason it won't be installed // In this case, the required version does not exist at all QString targetName = QLatin1String(Start.TargetPkg().Name()); QApt::DependencyType relation = (QApt::DependencyType)End->Type; QApt::DependencyInfo errorInfo(targetName, requiredVersion, NoOperand, relation); QApt::MarkingErrorInfo error(QApt::DepNotInstallable, errorInfo); reasons.append(error); } } else { // Ok, candidate has provides. We're a virtual package QString targetName = QLatin1String(Start.TargetPkg().Name()); QApt::DependencyType relation = (QApt::DependencyType)End->Type; QApt::DependencyInfo errorInfo(targetName, QString(), NoOperand, relation); QApt::MarkingErrorInfo error(QApt::VirtualPackage, errorInfo); reasons.append(error); } } return reasons; } bool Package::isTrusted() const { const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); if (!Ver) return false; pkgSourceList *Sources = d->backend->packageSourceList(); QHash *trustCache = d->backend->cache()->trustCache(); for (pkgCache::VerFileIterator i = Ver.FileList(); !i.end(); ++i) { pkgIndexFile *Index; //FIXME: Should be done in apt auto trustIter = trustCache->constBegin(); while (trustIter != trustCache->constEnd()) { if (trustIter.key() == i.File()) break; // Found it trustIter++; } // Find the index of the package file from the package sources if (trustIter == trustCache->constEnd()) { // Not found if (!Sources->FindIndex(i.File(), Index)) continue; } else Index = trustIter.value(); if (Index->IsTrusted()) return true; } return false; } bool Package::wouldBreak() const { int pkgState = state(); if ((pkgState & ToRemove) || (!(pkgState & Installed) && (pkgState & ToKeep))) { return false; } return pkgState & InstallBroken; } void Package::setAuto(bool flag) { d->backend->cache()->depCache()->MarkAuto(d->packageIter, flag); } void Package::setKeep() { d->backend->cache()->depCache()->MarkKeep(d->packageIter, false); if (state() & ToReInstall) { d->backend->cache()->depCache()->SetReInstall(d->packageIter, false); } if (d->backend->cache()->depCache()->BrokenCount() > 0) { pkgProblemResolver Fix(d->backend->cache()->depCache()); Fix.ResolveByKeep(); } d->state |= IsManuallyHeld; if (!d->backend->areEventsCompressed()) { d->backend->emitPackageChanged(); } } void Package::setInstall() { d->backend->cache()->depCache()->MarkInstall(d->packageIter, true); d->state &= ~IsManuallyHeld; // FIXME: can't we get rid of it here? // if there is something wrong, try to fix it if (!state() & ToInstall || d->backend->cache()->depCache()->BrokenCount() > 0) { pkgProblemResolver Fix(d->backend->cache()->depCache()); Fix.Clear(d->packageIter); Fix.Protect(d->packageIter); Fix.Resolve(true); } if (!d->backend->areEventsCompressed()) { d->backend->emitPackageChanged(); } } void Package::setReInstall() { d->backend->cache()->depCache()->SetReInstall(d->packageIter, true); d->state &= ~IsManuallyHeld; if (!d->backend->areEventsCompressed()) { d->backend->emitPackageChanged(); } } // TODO: merge into one function with bool_purge param void Package::setRemove() { pkgProblemResolver Fix(d->backend->cache()->depCache()); Fix.Clear(d->packageIter); Fix.Protect(d->packageIter); Fix.Remove(d->packageIter); d->backend->cache()->depCache()->SetReInstall(d->packageIter, false); d->backend->cache()->depCache()->MarkDelete(d->packageIter, false); Fix.Resolve(true); d->state &= ~IsManuallyHeld; if (!d->backend->areEventsCompressed()) { d->backend->emitPackageChanged(); } } void Package::setPurge() { pkgProblemResolver Fix(d->backend->cache()->depCache()); Fix.Clear(d->packageIter); Fix.Protect(d->packageIter); Fix.Remove(d->packageIter); d->backend->cache()->depCache()->SetReInstall(d->packageIter, false); d->backend->cache()->depCache()->MarkDelete(d->packageIter, true); Fix.Resolve(true); d->state &= ~IsManuallyHeld; if (!d->backend->areEventsCompressed()) { d->backend->emitPackageChanged(); } } bool Package::setVersion(const QString &version) { pkgDepCache::StateCache &state = (*d->backend->cache()->depCache())[d->packageIter]; QLatin1String defaultCandVer(state.CandVersion); bool isDefault = (version == defaultCandVer); pkgVersionMatch Match(version.toLatin1().constData(), pkgVersionMatch::Version); const pkgCache::VerIterator &Ver = Match.Find(d->packageIter); if (Ver.end()) return false; d->backend->cache()->depCache()->SetCandidateVersion(Ver); for (auto VF = Ver.FileList(); !VF.end(); ++VF) { if (!VF.File() || !VF.File().Archive()) continue; d->backend->cache()->depCache()->SetCandidateRelease(Ver, VF.File().Archive()); break; } if (isDefault) d->state &= ~OverrideVersion; else d->state |= OverrideVersion; return true; } void Package::setPinned(bool pin) { pin ? d->state |= IsPinned : d->state &= ~IsPinned; } } diff --git a/src/package.h b/src/package.h index 7b6917b..1799320 100644 --- a/src/package.h +++ b/src/package.h @@ -1,690 +1,689 @@ /*************************************************************************** * Copyright © 2010-2011 Jonathan Thomas * * Heavily inspired by Synaptic library code ;-) * * * * 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 . * ***************************************************************************/ #ifndef QAPT_PACKAGE_H #define QAPT_PACKAGE_H #include #include #include #include #include #include "dependencyinfo.h" #include "globals.h" namespace QApt { class Backend; class MarkingErrorInfo; /** * PackagePrivate is a class containing all private members of the Package class */ class PackagePrivate; /** * The Package class is an object for referencing a software package in the Apt * package database. You will be getting most of your information about your * packages from this class. * * @author Jonathan Thomas */ class Q_DECL_EXPORT Package { public: /** * Destructor. */ ~Package(); /** * Returns the name of the package * * \return The name of the package */ QLatin1String name() const; /** * Returns the unique internal identifier for the package * * \return The identifier of the package */ int id() const; /** * Returns the version of the package, regardless of whether it is installed * or not. If not installed, it returns the version of the candidate for * installation, which may not necessarily be the latest. (If the version has * been changed with setVersion()) * * \return The version of the package * * \sa Package::setVersion() */ QString version() const; /** * Returns the upstream version of the package. This is the Debian version * with the epoch and Debian revision information (if any) removed. * * (E.g. 0.2-0ubuntu1 becomes 0.2) * * @return The upstream version of the package * * @since 1.2 */ QString upstreamVersion() const; /** * Returns the upstream portion of @c version * * @param version The version to get the upstream portion from * * @return The upstream version from the given version string */ static QString upstreamVersion(const QString &version); /** * Returns the CPU architecture that the package supports * * @return The package's architecture */ QString architecture() const; /** * Returns a list of all available versions of the package in the form of * "version, release" (E.g. "0.2-0ubuntu1, maverick") * * \return All available versions of the package */ QStringList availableVersions() const; /** * Returns the categorical section where the package resides * * \return The section of the package */ QLatin1String section() const; /** * Returns the source package corresponding to the package * * \return The source package of the package */ QString sourcePackage() const; /** * Returns the short description (or "summary" in libapt-pkg terms) of the * package * * \return The short description of the package */ QString shortDescription() const; /** * Returns the maintainer of the package * * \return The maintainer of the package */ QString maintainer() const; /** * Returns the homepage of the package * * \return The homepage of the package */ QString homepage() const; /** * Returns the installed version of the package. * * \return The installed version of the package. If this package is not * installed, this function will return a null @c QString */ QString installedVersion() const; /** * Returns the newest available version of the package if it is not * installed. * * \return The available version of the package. If this package is * installed, this function will return a null @c QString. */ QString availableVersion() const; /** * Returns the priority of the package * * \return The priority of the package */ QString priority() const; /** * Returns the files that this package has installed. * * \return The file list of the package. If the package is not installed, it * will return an empty list. */ QStringList installedFilesList() const; /** * Returns the long description of the package. * * \return The long description of the package */ QString longDescription() const; /** * Returns the origin of the package. * (Usually Ubuntu or Debian) * * \return The origin of the package */ QString origin() const; /** * Returns the site the package comes from. * (e.g. archive.ubuntu.com) * * \return The site the package originates from */ QString site() const; /** * Returns a list of archives that the candidate version of the package is * available from. * (E.g. oneiric, oneiric-updates, sid, etc) * * @return The origin of the package * * @since 1.3 */ QStringList archives() const; /** * Returns the archive component of the package. (E.g. main, restricted, * universe, contrib, etc) * * \return The archive component of the package */ QString component() const; /** * Returns the md5sum of the candidate version of the package * * @return The md5sum of the package */ QByteArray md5Sum() const; /** * Returns the url to the location of a package's changelog. * * \return The location of the package's changelog */ QUrl changelogUrl() const; /** * Returns the url of the package's screenshot over the Internet. * * @param type The type of screenshot to be fetched as a QApt::ScreenshotType * * \return The url of the package changelog */ QUrl screenshotUrl(QApt::ScreenshotType type) const; /** * Returns the date when Canonical's support of the package ends. * * \return The date that the package is supported until. If it is not - * supported now, then it will return an empty QString. The date - * will be localized in the "month year" format. + * supported now, then it will return an invalid date. */ QDateTime supportedUntil() const; /** * Returns the specified field of the package's debian/control file * * This function can be used to return data from custom control fields * which do not have an official function inside APT to retrieve them. * * For example, the supportedUntil() function uses this function to * retrieve the value of the "Supported" field, which is Ubuntu-specific * and does not have an APT function with which to obtain it. * * Another usecase is the GStreamer metadata fields for GStreamer packages, * which are used to give information on what mimetypes/GStreamer version * the package supports. * * @since 1.1 */ QString controlField(QLatin1String name) const; /** Overload for QString controlField(QLatin1String name) const; **/ QString controlField(const QString &name) const; /** * Returns the amount of hard drive space that the currently-installed * version of this package takes up. * This is human-unreadable, so KDE applications may wish to run this * through the KFormat().formatByteSize() function to get a * localized, human-readable number. * * Returns -1 on error. * * \return The installed size of the package */ qint64 currentInstalledSize() const; /** * Returns the amount of hard drive space that this package will take up * once installed. * This is human-unreadable, so KDE applications may wish to run this * through the KFormat().formatByteSize() function to get a * localized, human-readable number. * * Returns -1 on error. * * \return The installed size of the package */ qint64 availableInstalledSize() const; /** * Returns the amount of hard drive space that this package takes up * when installed. * If the package is installed, then it is the size of the currently * installed version. Otherwise, it is the size of the candidate * version. * This is human-unreadable, so KDE applications may wish to run this * through the KFormat().formatByteSize() function to get a * localized, human-readable number. * * Returns -1 on error. * * @return The installed size of the package * * @see currentInstalledSize() * @see availableInstalledSize() * * @since 3.1 */ qint64 installedSize() const; /** * Returns the download size of the package archive in bytes. * This is human-unreadable, so KDE applications may wish to run this * through the KFormat().formatByteSize() function to get a * localized, human-readable number. * * Returns -1 on error. * * \return The installed size of the package */ qint64 downloadSize() const; /** * Returns the state of a package, using the @b PackageState enum to define * states. * * \return The PackageState flags of the package as an @c int */ int state() const; /** * Compares v1 with v2 and returns an integer less than, equal to, or * greater than zero if s1 is less than, equal to, or greater than s2. * * @since 1.2 */ static int compareVersion(const QString &v1, const QString &v2); /** * Returns whether the Package is installed */ bool isInstalled() const; /** * Returns whether the package is supported by Canonical */ bool isSupported() const; /** * Check whether the candidate version for an update should actually be * installed. This is based on an optional Phased-Update-Percentage control * field specifying a number between 0 and 100 indicating how many systems * should get this update. * * Whether or not a system is in the update phase is determined by a * repeatable discrete random number calculation. * * @returns @c false if a candidate definitely does not fall into the update * phase or there is no candidate. @c true is returned in all other cases. * * @warning this function uses statics and is not in the least way threadsafe * nor reentrant. * * @since 3.1 */ bool isInUpdatePhase() const; /** * A package prepared for MultiArch can have any of three MultiArch "states" * that control how dpkg treats the package as a dependency. A package can * either be MultiArch: same, MultiArch: foreign, or MultiArch: Allowed. * * MultiArch: same: * - This package is co-installable with itself, but it must not be used to * satisfy the dependency of any package of a different architecture from itself. * (Basically, this package is not multiarch) * * MultiArch: foreign: * - The package is @b not co-installable with itself, but should be allowed to * satisfy the dependencies of a package of a different arch from itself. * * MultiArch: allowed: * - This permits the reverse-dependencies of the package to annotate their Depends: * field to indicate that a foreign architecture version of the package satisfies * the dependencies, but does not change the resolution of any existing dependencies. * * @return a @c QString for the package's MultiArch state * * @see multiArchType() * * @since 1.4 */ QString multiArchTypeString() const; /** * A package prepared for MultiArch can have any of three MultiArch "states" * that control how dpkg treats the package as a dependency. A package can * either be MultiArch: same, MultiArch: foreign, or MultiArch: Allowed. * * @return a @c MultiArchType for the package's MultiArch state * * @see multiArchTypeString() * * @since 1.4 */ MultiArchType multiArchType() const; /** * Returns whether or not a package is a foreign-arch version of a package * that also has a native-architecture counterpart (a "duplicate") * * This includes installed packages, which are always considered * "interesting". * * @return @c true when the same package is available for the native arch * @return @c false when the package is a unique foreign-arch package * * @since 1.4 */ bool isMultiArchDuplicate() const; /** * Returns whether or not the package is for the native CPU architecture */ bool isForeignArch() const; /// Returns a list of DependencyItems that this package depends on. QList depends() const; /// Returns a list of DependencyItems that required to install this package. QList preDepends() const; /// Returns a list of DependencyItems that this package suggests to be installed. QList suggests() const; /// Returns a list of DependencyItems that this package recommends to be installed. QList recommends() const; /// Returns a list of DependencyItems that conflict with this package QList conflicts() const; /// Returns a list of DependencyItems that this package replaces. QList replaces() const; /// Returns a list of DependencyItems that this package obsoletes. QList obsoletes() const; /// Returns a list of DependencyItems that this package breaks. QList breaks() const; /// Returns a list of DependencyItems that this package enhances. QList enhances() const; /** * Returns a display-ready list of the names of all the dependencies of this package. * * \return A \c QStringList of packages that this package depends on */ QStringList dependencyList(bool useCandidateVersion) const; /** * Returns a list of the names of all the packages that depend on this * package. (Reverse dependencies) * * \return A \c QStringList of packages that depend on this package */ QStringList requiredByList() const; /** * Returns a list of the names of all the virtual packages that this package * provides. * * \return A \c QStringList of packages that this package provides */ QStringList providesList() const; /** * Returns a list of the names of all the packages that this package recommends. * * \return A \c QStringList of packages that this package recommends */ QStringList recommendsList() const; /** * Returns a list of the names of all the packages that this package suggests. * * \return A \c QStringList of packages that this package suggests */ QStringList suggestsList() const; /** * Returns a list of the names of all the packages that this package enhances. * * \return A \c QStringList of packages that this package enhances */ QStringList enhancesList() const; /** * Returns a list of the names of all the packages that enhance this package. * * \return A \c QStringList of packages that enhance this package */ QStringList enhancedByList() const; /** * If a package is in a broke state, this function returns a why the package * is broken by showing all errors in the dependency cache that marking the * package has caused. */ QList brokenReason() const; /** * Returns whether the package is signed with a trusted GPG signature. */ bool isTrusted() const; /** * Returns whether the package would break if the current potential changes * are committed */ bool wouldBreak() const; /** * Sets and unsets the auto-install flag */ void setAuto(bool flag = true); /** * Marks the package to be kept */ void setKeep(); /** * Marks the package for installation */ void setInstall(); /** * Member function that sets whether or not the package needs * reinstallation, based on a boolean value passed to it. */ void setReInstall(); /** * Marks the package for removal. */ void setRemove(); /** * Marks the package for complete removal, including config files. */ void setPurge(); /** * Overrides the candidate version, setting it to the version string */ bool setVersion(const QString & version); /** * Set the package as pinned internally for display purposes. * * To actually pin a package use @c Backend::setPackagePinned * * @since 1.2 * @see Backend::setPackagePinned() */ void setPinned(bool pin); /** * An enumerator for various states that a @c Package may hold. A package * may hold several states at once. */ enum State { /// The package will not be changed ToKeep = 1 << 0, /// The package has been marked for install ToInstall = 1 << 1, /// The package is a new install, never have been installed before NewInstall = 1 << 2, /// The package has been marked for reinstall ToReInstall = 1 << 3, /// The package has been marked for upgrade ToUpgrade = 1 << 4, /// The package has been marked for downgrade ToDowngrade = 1 << 5, /// The package has been marked for removal ToRemove = 1 << 6, /// The package has been held from being upgraded Held = 1 << 7, /// The package is currently installed Installed = 1 << 8, /// The package is currently upgradeable Upgradeable = 1 << 9, /// The package is currently broken NowBroken = 1 << 10, /// The package's install is broken InstallBroken = 1 << 11, /// This package is a dependency of another package that is not installed Orphaned = 1 << 12,// /// The package has been manually prevented from upgrade Pinned = 1 << 13,// /// The package is new in the archives New = 1 << 14,// /// The package still has residual config. (Was not purged) ResidualConfig = 1 << 15, /// The package is no longer downloadable NotDownloadable = 1 << 16, /// The package has been marked for purging ToPurge = 1 << 17, /// The package is essential for a base installation IsImportant = 1 << 18, /// The package has had its candidate version overridden by setVersion() OverrideVersion = 1 << 19, /// The package was automatically installed as a dependency IsAuto = 1 << 20, /// The package is invalid IsGarbage = 1 << 21, /// The package's policy is broken NowPolicyBroken = 1 << 22, /// The package's install policy is broken InstallPolicyBroken = 1 << 23, /// The package is not installed NotInstalled = 1 << 24, /// The package has been pinned IsPinned = 1 << 25, /// The package was auto-marked as a recommend, but then manually held IsManuallyHeld = 1 << 26 }; Q_DECLARE_FLAGS(States, State) private: PackagePrivate *const d; /** * Internal constructor. * * @param parent The backend that this package is being made a child of * @param packageIter The underlying object representing the package in APT */ Package(QApt::Backend* parent, pkgCache::PkgIterator &packageIter); /** * Returns the internal APT representation of the package * * \return The interal APT package pointer */ const pkgCache::PkgIterator &packageIterator() const; /** * Returns a set of state flags that won't change until the next * cache reload, and excluding any flags that are able to change. * Used internally to avoid having to calculate mutable flags when we know * the flag we want to check is immutable. */ int staticState() const; friend class Backend; }; /** * Defines the StateChanges type, which is a QHash of Package States * and QLists of packages which have those states. */ typedef QHash StateChanges; } #endif