diff --git a/autotests/sourceslisttest.cpp b/autotests/sourceslisttest.cpp index d69fb89..7ef2f50 100644 --- a/autotests/sourceslisttest.cpp +++ b/autotests/sourceslisttest.cpp @@ -1,789 +1,790 @@ /* * sourceslisttest - This is a test program for libqapt * Copyright 2013 Michael D. Stemle * * 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 "sourceslisttest.h" #include #include +#include QTEST_MAIN(SourcesListTest); // Verification routines void SourcesListTest::verifySourceEntry( const QString &label, const QApt::SourceEntry &entry, const QString &type, const QString &uri, const QString &dist, const QString &components, const QString &archs, const bool isEnabled, const bool isValid ) { // Verify type... QVERIFY2( entry.type().compare(type) == 0, qPrintable( label + ": The type isn't \""+ type +"\", it's \"" + entry.type() + "\"!" ) ); // Verify URI... QVERIFY2( entry.uri().compare(uri) == 0, qPrintable( label + ": The URI isn't \""+ uri +"\", it's \"" + entry.uri() + "\"!" ) ); // Verify dist... QVERIFY2( entry.dist().compare(dist) == 0, qPrintable( label + ": The distro isn't \""+ dist +"\", it's \"" + entry.dist() + "\"!" ) ); // Verify components... QVERIFY2( entry.components().join("!").compare(components) == 0, qPrintable( label + ": The components (joined with !) aren't \""+ components +"\", they're \"" + entry.components().join("!") + "\"!" ) ); // Verify architectures... QVERIFY2( entry.architectures().join("!").compare(archs) == 0, qPrintable( label + ": The archs (joined with !) aren't \""+ archs +"\", they're \"" + entry.architectures().join("!") + "\"!" ) ); // Verify isEnabled... const char *isEnabledMsg = (isEnabled) ? "I was expecting this entry to be enabled, but it wasn't." : "I was expecting this entry to be disabled, but it wasn't."; QVERIFY2( entry.isEnabled() == isEnabled, qPrintable(label+": "+isEnabledMsg) ); // Verify isValid... const char *isValidMsg = (isValid) ? "I was expecting this entry to be valid, but it wasn't." : "I was expecting this entry to be invalid, but it wasn't."; QVERIFY2( entry.isValid() == isValid, qPrintable(label+": "+isValidMsg) ); } void SourcesListTest::initTestCase() { pkgInitConfig(*_config); // Called before the first testfunction is executed QString cwd = QCoreApplication::applicationDirPath(); sampleSourcesHasOneFile = QStringList(QString(cwd+"/data/test1.list")); sampleSourcesHasTwoFiles = QStringList( { QString(cwd+"/data/test1.list"), QString(cwd+"/data/test2.list") } ); sampleSourcesHasDuplicateFiles = QStringList( { QString(cwd+"/data/test1.list"), QString(cwd+"/data/test1.list"), QString(cwd+"/data/test2.list") } ); outputFile = QString(cwd+"/data/write_test.list"); dummyFile = QString(cwd+"/data/dummy_file.list"); } void SourcesListTest::cleanupTestCase() { // Called after the last testfunction was executed // Let's trash the written file. QVERIFY2( QFile::remove(outputFile), qPrintable( "Unable to remove file \"" + outputFile + "\"." ) ); } void SourcesListTest::init() { // Called before each testfunction is executed } void SourcesListTest::cleanup() { // Called after every testfunction } void SourcesListTest::testConstructor() { QObject parentObject; parentObject.setProperty("propertyName", QString("propertyValue")); QApt::SourcesList subjectDefaultConstructor; QStringList shouldntBeEmpty = subjectDefaultConstructor.sourceFiles(); QVERIFY2( subjectDefaultConstructor.parent() == 0, "The default constructor doesn't have a zero parent?!" ); QVERIFY2( shouldntBeEmpty.count() > 0, qPrintable( "The default constructor should have given us an empty file list, but it didn't. I got these: " + shouldntBeEmpty.join("!") + " which is " + QString::number(shouldntBeEmpty.count()) ) ); int previousCount = subjectDefaultConstructor.entries().count(); subjectDefaultConstructor.reload(); QVERIFY2( previousCount == subjectDefaultConstructor.entries().count(), qPrintable( "The default constructor should have given us the same number of entries as the subsequent reload() call. I had " + QString::number(previousCount) + " and now I have " + QString::number(subjectDefaultConstructor.entries().count()) ) ); QApt::SourcesList subjectParentOnly(&parentObject); shouldntBeEmpty = subjectParentOnly.sourceFiles(); QVERIFY2( subjectParentOnly.parent() == &parentObject, "The parent-only constructor parent isn't the one we sent in?!" ); QVERIFY2( shouldntBeEmpty.count() > 0, "The parent-only constructor should have given us sources anyway, using the default list." ); QApt::SourcesList subjectListOnly(0, sampleSourcesHasTwoFiles); shouldntBeEmpty = subjectListOnly.sourceFiles(); QVERIFY2( subjectListOnly.parent() == 0, "The list-only constructor doesn't have a zero parent?!" ); QVERIFY2( shouldntBeEmpty.count() == 2, "The list-only constructor should have given us an empty file list, but it didn't." ); QApt::SourcesList subjectDuplicateListOnly(0, sampleSourcesHasDuplicateFiles); shouldntBeEmpty = subjectDuplicateListOnly.sourceFiles(); QVERIFY2( subjectDuplicateListOnly.parent() == 0, "The list-only constructor doesn't have a zero parent?!" ); QVERIFY2( shouldntBeEmpty.count() == 2, "The list-only constructor should have given us an empty file list, but it didn't." ); QApt::SourcesList subjectParentAndList(&parentObject, sampleSourcesHasTwoFiles); shouldntBeEmpty = subjectListOnly.sourceFiles(); QVERIFY2( subjectParentOnly.parent() == &parentObject, "The parent-and-list constructor parent isn't the one we sent in?!" ); QVERIFY2( shouldntBeEmpty.count() == 2, "The parent-and-list constructor should have given us an empty file list, but it didn't." ); } /* * Test plan: * 1. Load the sample data file * 2. Verify that we loaded the file using entries() * 3. Verify that we loaded the file using entries(filename) */ #define SOURCES_COUNT 14 #define TEST1_MD5 "56717306e28a529f89b56c6ee6082375" void SourcesListTest::testLoadSourcesOneFile() { QVERIFY2(sampleSourcesHasOneFile.count() == 1, "Verify we have only one source..."); QApt::SourcesList subject (0, sampleSourcesHasOneFile); QApt::SourceEntryList entries = subject.entries(); qDebug() << "I have " << entries.count() << " entries."; for ( const QApt::SourceEntry &one : entries ) { qDebug() << "Entry " << one.toString(); } // Since this test depends so much on the test1.list file, // let's do everything we can to make sure people know what it // should look like! QVERIFY2( entries.count() == SOURCES_COUNT, qPrintable( "We don't have " + QString::number(SOURCES_COUNT) + " entries, we have " + QString::number(entries.count()) + "! Please check the file, the MD5 should be \"" + TEST1_MD5 + "\"" ) ); // Now we're going to go through each of the lines in the file and verify that // it loaded properly... QVERIFY2( entries[0].toString().compare("## First test") == 0, qPrintable( "I was expecting \"## First test\" as the first line, but I got " + entries[0].toString() ) ); // A commented line is inactive and invalid verifySourceEntry( "Line #0", entries[0], QString::null, QString::null, QString::null, QString::null, QString::null, false, false ); verifySourceEntry( "Line #1", entries[1], "deb", "http://apttest/ubuntu", "saucy", "partner", "", true, true ); verifySourceEntry( "Line #2", entries[2], "deb-src", "http://apttest/ubuntu", "saucy", "partner", "", true, true ); verifySourceEntry( "Line #3", entries[3], "deb", "http://apttest/ubuntu", "saucy", "contrib!main!partner", "", true, true ); verifySourceEntry( "Line #4", entries[4], "deb", "http://apttest/ubuntu", "saucy", "partner", "i386", true, true ); verifySourceEntry( "Line #5", entries[5], "deb", "http://apttest/ubuntu", "saucy", "partner", "i386!ppc", true, true ); verifySourceEntry( "Line #6", entries[6], "deb", "http://apttest/ubuntu", "saucy", "contrib!main!partner", "i386!ppc", true, true ); verifySourceEntry( "Line #7", entries[7], "deb", "https://apttest/ubuntu", // <-- Note, https this time "saucy", "contrib!main!partner", "i386!ppc", true, true ); QVERIFY2( entries[8].toString().compare("") == 0, qPrintable( "I was expecting an empty string as the ninth line (index 8), but I got " + entries[8].toString() ) ); // An empty line isn't invalid verifySourceEntry( "Line #8", entries[8], QString::null, QString::null, QString::null, QString::null, QString::null, true, false ); verifySourceEntry( "Line #9", entries[9], "deb", "http://apttest/ubuntu", "saucy", "contrib!main!partner", "i386!ppc", false, // <-- Note, disabled! true ); verifySourceEntry( "Line #10", entries[10], "deb", "http://apttest/ubuntu", "saucy", "contrib!main!partner", "i386!ppc", false, // <-- Note, disabled! true ); verifySourceEntry( "Line #11", entries[11], "deb", "http://apttest/ubuntu", "saucy", "contrib!main!partner", "i386!ppc", false, // <-- Note, disabled! true ); // This line ensures that lines only containing comment characters (e.g. ##) // are discarded as invalid. verifySourceEntry( "Line #12", entries[12], "", "", "", "", "", false, // <-- Note, disabled! false ); verifySourceEntry( "Line #13", entries[13], "deb", "cdrom:[Kubuntu 11.10 _Oneiric Ocelot_ - Release amd64 (20111012)]/", "oneiric", "main!restricted", "", true, true ); } #define SOURCES_COUNT_2 3 #define ALL_SOURCES_COUNT (SOURCES_COUNT + SOURCES_COUNT_2) #define TEST2_MD5 "af99c3d972146a82c7af125c2022c581" void SourcesListTest::testLoadSourcesManyFiles() { QVERIFY2(sampleSourcesHasTwoFiles.count() == 2, qPrintable( "We don't have the 2 files I was expecting, we have " + QString::number(sampleSourcesHasTwoFiles.count()) ) ); QApt::SourcesList subject (0, sampleSourcesHasTwoFiles); QApt::SourceEntryList entries = subject.entries(); qDebug() << "I have " << entries.count() << " entries."; for ( const QApt::SourceEntry &one : entries ) { qDebug() << one.file() << ": Entry " << one.toString(); } // Since this test depends so much on the test1.list file, // let's do everything we can to make sure people know what it // should look like! QVERIFY2( entries.count() == ALL_SOURCES_COUNT, qPrintable( "We don't have " + QString::number(ALL_SOURCES_COUNT) + " entries, we have " + QString::number(entries.count()) + "! Please check the files test1.list and test2.list, the MD5's should be \"" + TEST1_MD5 + "\" and \"" + TEST2_MD5 + "\" respectively." ) ); // Verify that grabbing each source by file returns the correct number of records. QApt::SourceEntryList test1 = subject.entries(sampleSourcesHasTwoFiles[0]); QApt::SourceEntryList test2 = subject.entries(sampleSourcesHasTwoFiles[1]); QVERIFY2( test1.count() == SOURCES_COUNT, qPrintable( sampleSourcesHasTwoFiles[0] + "\nLooks like test1.list doesn't have " + QString::number(SOURCES_COUNT) + " entries afterall; it has " + QString::number(test1.count()) + " entries." ) ); QVERIFY2( test2.count() == SOURCES_COUNT_2, qPrintable( sampleSourcesHasTwoFiles[0] + "\nLooks like test2.list doesn't have " + QString::number(SOURCES_COUNT_2) + " entries afterall; it has " + QString::number(test1.count()) + " entries." ) ); } void SourcesListTest::testAddSource() { QStringList outfilesListJustOne (dummyFile); QApt::SourcesList subjectSingleEntry(0, outfilesListJustOne); QVERIFY2( subjectSingleEntry.entries().count() == 0, qPrintable( "I expected to have a new, empty file with zero entries. I found " + QString::number(subjectSingleEntry.entries().count()) + " entries." ) ); QApt::SourceEntry entryOne; entryOne.setType("deb"); entryOne.setUri("https://foo.com/bar"); entryOne.setComponents({"main"}); entryOne.setDist("saucy"); entryOne.setEnabled(true); // Verify the item we just created... verifySourceEntry( "New Entry #0", entryOne, "deb", "https://foo.com/bar", "saucy", "main", QString::null, true, true ); QApt::SourceEntry entryTwo; entryTwo.setType("deb"); entryTwo.setUri("https://foo.com/bar2"); entryTwo.setComponents({"main"}); entryTwo.setDist("saucy"); entryTwo.setEnabled(true); // Verify the item we just created... verifySourceEntry( "New Entry #1", entryTwo, "deb", "https://foo.com/bar2", "saucy", "main", QString::null, true, true ); subjectSingleEntry.addEntry(entryOne); QVERIFY2( subjectSingleEntry.entries().count() == 1, qPrintable( "I expected to have a single entry now. I found " + QString::number(subjectSingleEntry.entries().count()) + " entries." ) ); QVERIFY2( subjectSingleEntry.containsEntry(entryOne), qPrintable( "I totally thought I had an entry \"" + entryOne.toString() + "\", but I don't." ) ); QApt::SourcesList subjectMultipleFiles(0, sampleSourcesHasTwoFiles); QVERIFY2( subjectMultipleFiles.entries().count() == ALL_SOURCES_COUNT, qPrintable( "I expected to have both files 1 and 2 loaded with " + QString::number(ALL_SOURCES_COUNT) + " entries. I found " + QString::number(subjectMultipleFiles.entries().count()) + " entries." ) ); subjectMultipleFiles.addEntry(entryOne); QVERIFY2( subjectMultipleFiles.entries().count() == ALL_SOURCES_COUNT+1, qPrintable( "I expected to have a new, empty file with " + QString::number(ALL_SOURCES_COUNT+1) + " entries. I found " + QString::number(subjectMultipleFiles.entries().count()) + " entries." ) ); } void SourcesListTest::testRemoveSource() { QStringList outfilesListJustOne (dummyFile); QApt::SourcesList subject(0, outfilesListJustOne); QApt::SourceEntry entryOne; QApt::SourceEntry entryTwo; subject.reload(); // Construct our two sources entryOne.setType("deb"); entryOne.setUri("https://foo.com/bar"); entryOne.setComponents({"main"}); entryOne.setDist("saucy"); entryOne.setEnabled(true); entryTwo.setType("deb"); entryTwo.setUri("https://foo.com/bar2"); entryTwo.setComponents({"main"}); entryTwo.setDist("saucy"); entryTwo.setEnabled(true); subject.addEntry(entryOne); subject.addEntry(entryTwo); QVERIFY2( subject.entries().count() == 2, qPrintable( "I expected to have 2 sources. I found " + QString::number(subject.entries().count()) + " entries." ) ); subject.removeEntry(entryOne); QVERIFY2( subject.entries().count() == 1, qPrintable( "Now, I expect to have 1 sources. I found " + QString::number(subject.entries().count()) + " entries." ) ); } void SourcesListTest::testSaveSources() { QStringList outfilesListJustOne (outputFile); QApt::SourcesList subject(0, outfilesListJustOne); QApt::SourceEntry entryOne; QApt::SourceEntry entryTwo; // Construct our two sources entryOne.setType("deb"); entryOne.setUri("https://foo.com/bar"); entryOne.setComponents({"main"}); entryOne.setDist("saucy"); entryOne.setEnabled(true); entryTwo.setType("deb"); entryTwo.setUri("https://foo.com/bar2"); entryTwo.setComponents({"main"}); entryTwo.setDist("saucy"); entryTwo.setEnabled(true); subject.addEntry(entryOne); qDebug() << "Here's your sources list:\n" << subject.toString(); QVERIFY2( subject.containsEntry(entryOne), qPrintable( "I expected to have the entry " + entryOne.toString() ) ); QVERIFY2( subject.entries().count() == 1, qPrintable( "I expected to have 1 sources. I found " + QString::number(subject.entries().count()) + " entries." ) ); subject.save(); QApt::SourcesList loadingAfterSave(0, outfilesListJustOne); QVERIFY2( loadingAfterSave.entries().count() == 1, qPrintable( "I expected to have 1 sources after loading. I found " + QString::number(loadingAfterSave.entries().count()) + " entries." ) ); loadingAfterSave.addEntry(entryTwo); loadingAfterSave.save(); QVERIFY2( loadingAfterSave.entries().count() == 2, qPrintable( "I expected to have 2 sources after adding another. I found " + QString::number(loadingAfterSave.entries().count()) + " entries." ) ); QApt::SourcesList loadingAfterSecondSave(0, outfilesListJustOne); QVERIFY2( loadingAfterSave.entries().count() == 2, qPrintable( "I expected to have 2 sources after loading again. I found " + QString::number(loadingAfterSecondSave.entries().count()) + " entries." ) ); loadingAfterSecondSave.save(); QVERIFY2( loadingAfterSecondSave.entries().count() == 2, qPrintable( "I expected to have 2 sources after adding another. I found " + QString::number(loadingAfterSecondSave.entries().count()) + " entries." ) ); QApt::SourcesList loadingAfterSecondSaveB(0, outfilesListJustOne); QVERIFY2( loadingAfterSave.entries().count() == 2, qPrintable( "I expected to have 2 sources after loading again. I found " + QString::number(loadingAfterSecondSaveB.entries().count()) + " entries." ) ); loadingAfterSecondSave.removeEntry(entryOne); loadingAfterSecondSave.removeEntry(entryTwo); QVERIFY2( loadingAfterSecondSave.entries().count() == 0, qPrintable( "I expected to have 0 sources after removing the two I had previously added. I found " + QString::number(loadingAfterSecondSave.entries().count()) + " entries." ) ); loadingAfterSecondSave.save(); QApt::SourcesList loadingAfterThirdSave(0, outfilesListJustOne); QVERIFY2( loadingAfterThirdSave.entries().count() == 3, qPrintable( "I expected to have 3 sources (all the default comments) after saving following the remove. I found " + QString::number(loadingAfterThirdSave.entries().count()) + " entries." ) ); loadingAfterSecondSave.reload(); QVERIFY2( loadingAfterSecondSave.entries().count() == 3, qPrintable( "I expected to have 3 sources (all the default comments) after saving following the remove (using reload()). I found " + QString::number(loadingAfterSecondSave.entries().count()) + " entries." ) ); } // #include "../t/sourceslisttest.moc" diff --git a/cmake/modules/FindAptPkg.cmake b/cmake/modules/FindAptPkg.cmake index 4ecb219..e36d2bd 100644 --- a/cmake/modules/FindAptPkg.cmake +++ b/cmake/modules/FindAptPkg.cmake @@ -1,48 +1,53 @@ # - find Library for managing Debian package information # APTPKG_INCLUDE_DIR - Where to find Library for managing Debian package information header files (directory) # APTPKG_LIBRARIES - Library for managing Debian package information libraries # APTPKG_LIBRARY_RELEASE - Where the release library is # APTPKG_LIBRARY_DEBUG - Where the debug library is # APTPKG_FOUND - Set to TRUE if we found everything (library, includes and executable) # Copyright (c) 2010 David Palacio, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # # Generated by CModuler, a CMake Module Generator - http://gitorious.org/cmoduler include(FindPackageHandleStandardArgs) IF( APTPKG_INCLUDE_DIR AND APTPKG_LIBRARY_RELEASE AND APTPKG_LIBRARY_DEBUG ) SET(APTPKG_FIND_QUIETLY TRUE) ENDIF( APTPKG_INCLUDE_DIR AND APTPKG_LIBRARY_RELEASE AND APTPKG_LIBRARY_DEBUG ) FIND_PATH( APTPKG_INCLUDE_DIR apt-pkg/init.h ) FIND_LIBRARY(APTPKG_LIBRARY_RELEASE NAMES apt-pkg ) FIND_LIBRARY(APTINST_LIBRARY NAMES apt-inst ) +# apt-inst is optional these days! +IF ( NOT APTINST_LIBRARY ) + SET( APTINST_LIBRARY "" ) +ENDIF( ) + FIND_LIBRARY(APTPKG_LIBRARY_DEBUG NAMES apt-pkg apt-pkgd HINTS /usr/lib/debug/usr/lib/ ) IF( APTPKG_LIBRARY_RELEASE OR APTPKG_LIBRARY_DEBUG AND APTPKG_INCLUDE_DIR ) SET( APTPKG_FOUND TRUE ) ENDIF( APTPKG_LIBRARY_RELEASE OR APTPKG_LIBRARY_DEBUG AND APTPKG_INCLUDE_DIR ) IF( APTPKG_LIBRARY_DEBUG AND APTPKG_LIBRARY_RELEASE ) # if the generator supports configuration types then set # optimized and debug libraries, or if the CMAKE_BUILD_TYPE has a value IF( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) SET( APTPKG_LIBRARIES optimized ${APTPKG_LIBRARY_RELEASE} ${APTINST_LIBRARY} debug ${APTPKG_LIBRARY_DEBUG} ) ELSE( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) # if there are no configuration types and CMAKE_BUILD_TYPE has no value # then just use the release libraries SET( APTPKG_LIBRARIES ${APTPKG_LIBRARY_RELEASE} ${APTINST_LIBRARY} ) ENDIF( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) ELSEIF( APTPKG_LIBRARY_RELEASE ) SET( APTPKG_LIBRARIES ${APTPKG_LIBRARY_RELEASE} ${APTINST_LIBRARY}) ELSE( APTPKG_LIBRARY_DEBUG AND APTPKG_LIBRARY_RELEASE ) SET( APTPKG_LIBRARIES ${APTPKG_LIBRARY_DEBUG} ${APTINST_LIBRARY} ) ENDIF( APTPKG_LIBRARY_DEBUG AND APTPKG_LIBRARY_RELEASE ) find_package_handle_standard_args(AptPkg DEFAULT_MSG APTPKG_LIBRARIES APTPKG_INCLUDE_DIR) diff --git a/src/backend.cpp b/src/backend.cpp index 54b911e..5f6a558 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -1,1649 +1,1648 @@ /*************************************************************************** * 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 #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; + std::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"); + std::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(); + std::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(" ,;"); + std::string xpString = "name:"; + std::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 ) { + while ( (pos = unsplitSearchString.find("-", pos)) != std::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()); + APT::Upgrade::Upgrade(*d->cache->depCache(), APT::Upgrade::FORBID_REMOVE_PACKAGES | APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES); emit packageChanged(); } void Backend::markPackagesForDistUpgrade() { Q_D(Backend); - pkgDistUpgrade(*d->cache->depCache()); + APT::Upgrade::Upgrade(*d->cache->depCache(), APT::Upgrade::ALLOW_EVERYTHING); 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) { + FileFd out(tempFileName.toUtf8().toStdString(), FileFd::WriteOnly|FileFd::Create|FileFd::Empty); + if (!out.IsOpen()) { 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"); + tags.Write(out, TFRewritePackageOrder, {}); + out.Write("\n", 1); } } - fclose(out); + out.Close(); 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/dependencyinfo.cpp b/src/dependencyinfo.cpp index 197a1b0..f0a6d7c 100644 --- a/src/dependencyinfo.cpp +++ b/src/dependencyinfo.cpp @@ -1,197 +1,197 @@ /*************************************************************************** * Copyright © 2014 Harald Sitter * * Copyright © 2011 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 "dependencyinfo.h" #include // Own includes #include "package.h" namespace QApt { class DependencyInfoPrivate : public QSharedData { public: DependencyInfoPrivate() : QSharedData() , relationType(NoOperand) , dependencyType(InvalidType) , multiArchAnnotation() {} DependencyInfoPrivate(const QString &package, const QString &version, RelationType rType, DependencyType dType) : QSharedData() , packageName(package) , packageVersion(version) , relationType(rType) , dependencyType(dType) , multiArchAnnotation() { // Check for Multiarch annotation. QStringList parts = package.split(':'); Q_ASSERT(parts.size() <= 2); if (parts.size() >= 2) { packageName = parts.takeFirst(); multiArchAnnotation = parts.takeFirst(); } } QString packageName; QString packageVersion; RelationType relationType; DependencyType dependencyType; QString multiArchAnnotation; }; DependencyInfo::DependencyInfo() : d(new DependencyInfoPrivate()) { } DependencyInfo::DependencyInfo(const QString &package, const QString &version, RelationType rType, DependencyType dType) : d(new DependencyInfoPrivate(package, version, rType, dType)) { } DependencyInfo::DependencyInfo(const DependencyInfo &other) { d = other.d; } DependencyInfo::~DependencyInfo() { } DependencyInfo &DependencyInfo::operator=(const DependencyInfo &rhs) { // Protect against self-assignment if (this == &rhs) { return *this; } d = rhs.d; return *this; } QList DependencyInfo::parseDepends(const QString &field, DependencyType type) { - string package; - string version; + std::string package; + std::string version; unsigned int op; - string fieldStr = field.toStdString(); + std::string fieldStr = field.toStdString(); const char *start = fieldStr.c_str(); const char *stop = start + strlen(start); QList depends; bool hadOr = false; while (start != stop) { DependencyItem depItem; // Random documentatin because apt-pkg isn't big on documentation: // - ParseArchFlags is on whether or not the parser should pay attention // to an architecture flag such that "foo [ !amd64 ]" will return empty // package string iff the system is amd64. // - StripMultiArch is whether or not the multiarch tag "foo:any" should // be stripped from the resulting 'package' string or not. // - ParseRestrcitionList is whether a restriction "foo " should // return a nullptr if the apt config "APT::Build-Profiles" is // set to that restriction. start = debListParser::ParseDepends(start, stop, package, version, op, true /* ParseArchFlags */, false /* StripMultiArch */, true /* ParseRestrictionsList */); if (!start) { // Parsing error return depends; } if (hadOr) { depItem = depends.takeLast(); } if (op & pkgCache::Dep::Or) { hadOr = true; // Remove the Or bit from the op so we can assign to a RelationType op = (op & ~pkgCache::Dep::Or); } else { hadOr = false; } DependencyInfo info(QString::fromStdString(package), QString::fromStdString(version), (RelationType)op, type); depItem.append(info); depends.append(depItem); } return depends; } QString DependencyInfo::packageName() const { return d->packageName; } QString DependencyInfo::packageVersion() const { return d->packageVersion; } RelationType DependencyInfo::relationType() const { return d->relationType; } DependencyType DependencyInfo::dependencyType() const { return d->dependencyType; } QString DependencyInfo::multiArchAnnotation() const { return d->multiArchAnnotation; } QString DependencyInfo::typeName(DependencyType type) { return QString::fromUtf8(pkgCache::DepType(type)); } } diff --git a/src/package.cpp b/src/package.cpp index c87236c..ab7ad1b 100644 --- a/src/package.cpp +++ b/src/package.cpp @@ -1,1347 +1,1353 @@ /*************************************************************************** * 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 #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; // 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; } 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()); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(d->packageIter); + if (!ver.end()) + return QLatin1String(ver.Section()); + return QLatin1String(""); } 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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(d->packageIter); if(ver.end()) return QByteArray(); pkgRecords::Parser &rec = d->backend->records()->Lookup(ver.FileList()); - return rec.MD5Hash().c_str(); + auto MD5HashString = rec.Hashes().find("MD5Sum"); + + return MD5HashString ? QByteArray::fromStdString(MD5HashString->HashValue()) : ""; } QUrl Package::changelogUrl() const { - const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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(); } 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 = releaseDate.addMonths(supportTime); } else if (unit == QLatin1Char('y')) { supportEnd = releaseDate.addYears(supportTime); } return supportEnd; } QString Package::controlField(QLatin1String name) const { - const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVer(d->packageIter); + const pkgCache::VerIterator &ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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(); + std::string s1 = v1.toStdString(); + std::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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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); + const pkgCache::VerIterator &Ver = (*d->backend->cache()->depCache()).GetCandidateVersion(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/worker/aptworker.cpp b/src/worker/aptworker.cpp index 608eb63..e08c993 100644 --- a/src/worker/aptworker.cpp +++ b/src/worker/aptworker.cpp @@ -1,690 +1,692 @@ /*************************************************************************** * 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 "aptworker.h" // Qt includes #include #include #include #include #include #include #include // Apt-pkg includes #include #include #include #include #include #include #include +#include +#include +#include +#include +#include #include // System includes #include #include #define RAMFS_MAGIC 0x858458f6 // Own includes #include "aptlock.h" #include "cache.h" #include "debfile.h" #include "package.h" #include "transaction.h" #include "workeracquire.h" #include "workerinstallprogress.h" class CacheOpenProgress : public OpProgress { public: CacheOpenProgress(Transaction *trans = nullptr, int begin = 0, int end = 100) : m_trans(trans) , m_begin(begin) , m_lastProgress(0) { // APT opens the cache in 4 "steps", and the progress bar will go to 100 // 4 times. As such, we use modifiers for the current step to show progress // relative to the whole job of opening the cache QList modifiers; modifiers << 0.25 << 0.50 << 0.75 << 1.0; for (qreal modifier : modifiers) { m_steps.append((qreal)begin + ((qreal)end - (qreal)begin) * modifier); } m_end = m_steps.takeFirst(); } void Update() { int progress; if (Percent < 101) { progress = qRound(m_begin + qreal(Percent / 100.0) * (m_end - m_begin)); if (m_lastProgress == progress) return; } else { progress = 101; } m_lastProgress = progress; if (m_trans) m_trans->setProgress(progress); } void Done() { // Make sure the progress is set correctly m_lastProgress = m_end; // Update beginning progress for the next "step" m_begin = m_end; if (m_steps.size()) m_end = m_steps.takeFirst(); } private: Transaction *m_trans; QList m_steps; qreal m_begin; qreal m_end; int m_lastProgress; }; AptWorker::AptWorker(QObject *parent) : QObject(parent) , m_cache(nullptr) , m_records(nullptr) , m_trans(nullptr) , m_ready(false) , m_lastActiveTimestamp(QDateTime::currentMSecsSinceEpoch()) { } AptWorker::~AptWorker() { delete m_cache; delete m_records; qDeleteAll(m_locks); } void AptWorker::quit() { thread()->quit(); } Transaction *AptWorker::currentTransaction() { QMutexLocker locker(&m_transMutex); return m_trans; } quint64 AptWorker::lastActiveTimestamp() { QMutexLocker locker(&m_timestampMutex); return m_lastActiveTimestamp; } void AptWorker::init() { if (m_ready) return; pkgInitConfig(*_config); pkgInitSystem(*_config, _system); m_cache = new pkgCacheFile; // Prepare locks to be used later QStringList dirs; dirs << QString::fromStdString(_config->FindDir("Dir::Cache::Archives")) << QString::fromStdString(_config->FindDir("Dir::State::lists")); QString statusFile = QString::fromStdString(_config->FindDir("Dir::State::status")); QFileInfo info(statusFile); dirs << info.dir().absolutePath(); for (const QString &dir : dirs) { m_locks << new AptLock(dir); } m_ready = true; } void AptWorker::runTransaction(Transaction *trans) { // Check for running transactions or uninitialized worker if (m_trans || !m_ready) return; m_timestampMutex.lock(); m_lastActiveTimestamp = QDateTime::currentMSecsSinceEpoch(); m_timestampMutex.unlock(); m_trans = trans; trans->setStatus(QApt::RunningStatus); waitForLocks(); openCache(); // Check for init error if (m_trans->error() != QApt::Success) { cleanupCurrentTransaction(); return; } // Process transactions requiring a cache switch (trans->role()) { // Transactions that can use a broken cache case QApt::UpdateCacheRole: updateCache(); break; // Transactions that need a consistent cache case QApt::UpgradeSystemRole: upgradeSystem(); break; case QApt::CommitChangesRole: if (markChanges()) commitChanges(); break; case QApt::InstallFileRole: installFile(); m_dpkgProcess->waitForFinished(-1); break; case QApt::DownloadArchivesRole: downloadArchives(); break; // Other case QApt::EmptyRole: default: break; } // Cleanup cleanupCurrentTransaction(); } void AptWorker::cleanupCurrentTransaction() { // Well, we're finished now. m_trans->setProgress(100); // Release locks for (AptLock *lock : m_locks) { lock->release(); } // Set transaction exit status // This will notify the transaction queue of the transaction's completion // as well as mark the transaction for deletion in 5 seconds if (m_trans->isCancelled()) m_trans->setExitStatus(QApt::ExitCancelled); else if (m_trans->error() != QApt::Success) m_trans->setExitStatus(QApt::ExitFailed); else m_trans->setExitStatus(QApt::ExitSuccess); m_trans = nullptr; QMutexLocker locker(&m_timestampMutex); m_lastActiveTimestamp = QDateTime::currentMSecsSinceEpoch(); } void AptWorker::waitForLocks() { for (AptLock *lock : m_locks) { if (lock->acquire()) { qDebug() << "locked?" << lock->isLocked(); continue; } // Couldn't get lock m_trans->setIsPaused(true); m_trans->setStatus(QApt::WaitingLockStatus); while (!lock->isLocked() && m_trans->isPaused() && !m_trans->isCancelled()) { // Wait 3 seconds and try again sleep(3); lock->acquire(); } m_trans->setIsPaused(false); } } void AptWorker::openCache(int begin, int end) { m_trans->setStatus(QApt::LoadingCacheStatus); CacheOpenProgress *progress = new CacheOpenProgress(m_trans, begin, end); // Close in case it's already open m_cache->Close(); _error->Discard(); if (!m_cache->ReadOnlyOpen(progress)) { std::string message; bool isError = _error->PopMessage(message); if (isError) qWarning() << QString::fromStdString(message); m_trans->setError(QApt::InitError); m_trans->setErrorDetails(QString::fromStdString(message)); delete progress; return; } delete progress; delete m_records; m_records = new pkgRecords(*(m_cache)); } void AptWorker::updateCache() { WorkerAcquire *acquire = new WorkerAcquire(this, 10, 90); acquire->setTransaction(m_trans); // Initialize fetcher with our progress watcher - pkgAcquire fetcher; - fetcher.Setup(acquire); + pkgAcquire fetcher(acquire); // Fetch the lists. if (!ListUpdate(*acquire, *m_cache->GetSourceList())) { if (!m_trans->isCancelled()) { m_trans->setError(QApt::FetchError); - string message; + std::string message; while(_error->PopMessage(message)) m_trans->setErrorDetails(m_trans->errorDetails() + QString::fromStdString(message)); } } // Clean up delete acquire; openCache(91, 95); } bool AptWorker::markChanges() { pkgDepCache::ActionGroup *actionGroup = new pkgDepCache::ActionGroup(*m_cache); auto mapIter = m_trans->packages().constBegin(); QApt::Package::State operation = QApt::Package::ToKeep; while (mapIter != m_trans->packages().constEnd()) { operation = (QApt::Package::State)mapIter.value().toInt(); // Find package in cache pkgCache::PkgIterator iter; QString packageString = mapIter.key(); QString version; // Check if a version is specified if (packageString.contains(QLatin1Char(','))) { QStringList split = packageString.split(QLatin1Char(',')); iter = (*m_cache)->FindPkg(split.at(0).toStdString()); version = split.at(1); } else { iter = (*m_cache)->FindPkg(packageString.toStdString()); } // Check if the package was found if (iter == 0) { m_trans->setError(QApt::NotFoundError); m_trans->setErrorDetails(packageString); delete actionGroup; return false; } pkgDepCache::StateCache &State = (*m_cache)[iter]; pkgProblemResolver resolver(*m_cache); bool toPurge = false; // Then mark according to the instruction switch (operation) { case QApt::Package::Held: (*m_cache)->MarkKeep(iter, false); (*m_cache)->SetReInstall(iter, false); resolver.Protect(iter); break; case QApt::Package::ToUpgrade: { bool fromUser = !(State.Flags & pkgCache::Flag::Auto); (*m_cache)->MarkInstall(iter, true, 0, fromUser); resolver.Clear(iter); resolver.Protect(iter); break; } case QApt::Package::ToInstall: (*m_cache)->MarkInstall(iter, true); resolver.Clear(iter); resolver.Protect(iter); break; case QApt::Package::ToReInstall: (*m_cache)->SetReInstall(iter, true); break; case QApt::Package::ToDowngrade: { pkgVersionMatch Match(version.toStdString(), pkgVersionMatch::Version); pkgCache::VerIterator Ver = Match.Find(iter); (*m_cache)->SetCandidateVersion(Ver); (*m_cache)->MarkInstall(iter, true); resolver.Clear(iter); resolver.Protect(iter); break; } case QApt::Package::ToPurge: toPurge = true; case QApt::Package::ToRemove: (*m_cache)->SetReInstall(iter, false); (*m_cache)->MarkDelete(iter, toPurge); resolver.Clear(iter); resolver.Protect(iter); resolver.Remove(iter); default: break; } mapIter++; } delete actionGroup; if (_error->PendingError() && ((*m_cache)->BrokenCount() == 0)) _error->Discard(); // We had dep errors, but fixed them if (_error->PendingError()) { // We've failed to mark the packages m_trans->setError(QApt::MarkingError); - string message; + std::string message; if (_error->PopMessage(message)) m_trans->setErrorDetails(QString::fromStdString(message)); return false; } return true; } void AptWorker::upgradeSystem() { if (m_trans->safeUpgrade()) - pkgAllUpgrade(*m_cache); + APT::Upgrade::Upgrade(*m_cache, APT::Upgrade::FORBID_REMOVE_PACKAGES | APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES); else - pkgDistUpgrade(*m_cache); + APT::Upgrade::Upgrade(*m_cache, APT::Upgrade::ALLOW_EVERYTHING); commitChanges(); } void AptWorker::commitChanges() { // Initialize fetcher with our progress watcher WorkerAcquire *acquire = new WorkerAcquire(this, 15, 50); acquire->setTransaction(m_trans); - pkgAcquire fetcher; - fetcher.Setup(acquire); + pkgAcquire fetcher(acquire); pkgPackageManager *packageManager; packageManager = _system->CreatePM(*m_cache); // Populate the fetcher with the needed archives if (!packageManager->GetArchives(&fetcher, m_cache->GetSourceList(), m_records) || _error->PendingError()) { m_trans->setError(QApt::FetchError); delete acquire; return; } // Check for enough free space double FetchBytes = fetcher.FetchNeeded(); double FetchPBytes = fetcher.PartialPresent(); struct statvfs Buf; - string OutputDir = _config->FindDir("Dir::Cache::Archives"); + std::string OutputDir = _config->FindDir("Dir::Cache::Archives"); if (statvfs(OutputDir.c_str(),&Buf) != 0) { m_trans->setError(QApt::DiskSpaceError); delete acquire; return; } if (unsigned(Buf.f_bfree) < (FetchBytes - FetchPBytes)/Buf.f_bsize) { struct statfs Stat; if (statfs(OutputDir.c_str(), &Stat) != 0 || unsigned(Stat.f_type) != RAMFS_MAGIC) { m_trans->setError(QApt::DiskSpaceError); delete acquire; return; } } // Check for untrusted packages QStringList untrustedPackages; for (auto it = fetcher.ItemsBegin(); it < fetcher.ItemsEnd(); ++it) { if (!(*it)->IsTrusted()) untrustedPackages << QString::fromStdString((*it)->ShortDesc()); } if (!untrustedPackages.isEmpty()) { bool allowUntrusted = _config->FindB("APT::Get::AllowUnauthenticated", true); if (!allowUntrusted) { m_trans->setError(QApt::UntrustedError); delete acquire; return; } if (m_trans->frontendCaps() & QApt::UntrustedPromptCap) { m_trans->setUntrustedPackages(untrustedPackages, allowUntrusted); // Wait until the user approves, disapproves, or cancels the transaction while (m_trans->isPaused()) usleep(200000); } if (!m_trans->allowUntrusted()) { m_trans->setError(QApt::UntrustedError); delete acquire; return; } } // Fetch archives from the network if (fetcher.Run() != pkgAcquire::Continue) { // Our fetcher will report warnings for itself, but if it fails entirely // we have to send the error and finished signals if (!m_trans->isCancelled()) { m_trans->setError(QApt::FetchError); } delete acquire; return; } delete acquire; // Check for cancellation during fetch, or fetch errors if (m_trans->isCancelled()) return; bool failed = false; for (auto i = fetcher.ItemsBegin(); i != fetcher.ItemsEnd(); ++i) { if((*i)->Status == pkgAcquire::Item::StatDone && (*i)->Complete) continue; if((*i)->Status == pkgAcquire::Item::StatIdle) continue; failed = true; break; } if (failed && !packageManager->FixMissing()) { m_trans->setError(QApt::FetchError); return; } // Set up the install WorkerInstallProgress installProgress(50, 90); installProgress.setTransaction(m_trans); setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1); pkgPackageManager::OrderResult res = installProgress.start(packageManager); bool success = (res == pkgPackageManager::Completed); // See how the installation went if (!success) { m_trans->setError(QApt::CommitError); // Error details set by WorkerInstallProgress } openCache(91, 95); } void AptWorker::downloadArchives() { // Initialize fetcher with our progress watcher WorkerAcquire *acquire = new WorkerAcquire(this, 15, 100); acquire->setTransaction(m_trans); - pkgAcquire fetcher; - fetcher.Setup(acquire); + pkgAcquire fetcher(acquire); pkgIndexFile *index; for (const QString &packageString : m_trans->packages().keys()) { pkgCache::PkgIterator iter = (*m_cache)->FindPkg(packageString.toStdString()); if (!iter) continue; // Package not found - pkgCache::VerIterator ver = (*m_cache)->GetCandidateVer(iter); + pkgCache::VerIterator ver = (*m_cache)->GetCandidateVersion(iter); if (!ver || !ver.Downloadable() || !ver.Arch()) continue; // Virtual package or not downloadable or broken // Obtain package info pkgCache::VerFileIterator vf = ver.FileList(); pkgRecords::Parser &rec = m_records->Lookup(ver.FileList()); // Try to cross match against the source list if (!m_cache->GetSourceList()->FindIndex(vf.File(), index)) continue; - string fileName = rec.FileName(); - string MD5sum = rec.MD5Hash(); + std::string fileName = rec.FileName(); + auto hashes = rec.Hashes(); if (fileName.empty()) { m_trans->setError(QApt::NotFoundError); m_trans->setErrorDetails(packageString); delete acquire; return; } new pkgAcqFile(&fetcher, index->ArchiveURI(fileName), - MD5sum, + hashes, ver->Size, index->ArchiveInfo(ver), ver.ParentPkg().Name(), m_trans->filePath().toStdString(), ""); } if (fetcher.Run() != pkgAcquire::Continue) { // Our fetcher will report warnings for itself, but if it fails entirely // we have to send the error and finished signals if (!m_trans->isCancelled()) { m_trans->setError(QApt::FetchError); } } delete acquire; return; } void AptWorker::installFile() { m_trans->setStatus(QApt::RunningStatus); // FIXME: bloody workaround. // setTransaction contains logic WRT locale and debconf setup, which // is also useful to dpkg. For now simply reuse WorkerInstallProgress // as it has no adverse effects. WorkerInstallProgress installProgress(50, 90); installProgress.setTransaction(m_trans); QApt::DebFile deb(m_trans->filePath()); QString debArch = deb.architecture(); QStringList archList; archList.append(QLatin1String("all")); std::vector archs = APT::Configuration::getArchitectures(false); for (std::string &arch : archs) archList.append(QString::fromStdString(arch)); if (!archList.contains(debArch)) { m_trans->setError(QApt::WrongArchError); m_trans->setErrorDetails(debArch); return; } m_dpkgProcess = new QProcess(this); QString program = QLatin1String("dpkg") % QLatin1String(" -i ") % '"' % m_trans->filePath() % '"'; setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1); setenv("DEBIAN_FRONTEND", "passthrough", 1); setenv("DEBCONF_PIPE", "/tmp/qapt-sock", 1); m_dpkgProcess->start(program); connect(m_dpkgProcess, SIGNAL(started()), this, SLOT(dpkgStarted())); connect(m_dpkgProcess, SIGNAL(readyRead()), this, SLOT(updateDpkgProgress())); connect(m_dpkgProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(dpkgFinished(int,QProcess::ExitStatus))); } void AptWorker::dpkgStarted() { m_trans->setStatus(QApt::CommittingStatus); } void AptWorker::updateDpkgProgress() { QString str = m_dpkgProcess->readLine(); m_trans->setStatusDetails(str); } void AptWorker::dpkgFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus != QProcess::NormalExit) { m_trans->setError(QApt::CommitError); m_trans->setErrorDetails(m_dpkgProcess->readAllStandardError()); } m_dpkgProcess->deleteLater(); m_dpkgProcess = nullptr; } diff --git a/src/worker/workeracquire.cpp b/src/worker/workeracquire.cpp index 7f6be8c..f8699a5 100644 --- a/src/worker/workeracquire.cpp +++ b/src/worker/workeracquire.cpp @@ -1,237 +1,243 @@ /*************************************************************************** * Copyright © 2010 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 "workeracquire.h" // Qt includes #include #include #include #include // Apt-pkg includes #include #include #include // Own includes #include "aptworker.h" #include "transaction.h" +#include + using namespace std; WorkerAcquire::WorkerAcquire(QObject *parent, int begin, int end) : QObject(parent) , m_calculatingSpeed(true) , m_progressBegin(begin) , m_progressEnd(end) , m_lastProgress(0) { MorePulses = true; } void WorkerAcquire::setTransaction(Transaction *trans) { m_trans = trans; if (!trans->proxy().isEmpty()) setenv("http_proxy", m_trans->proxy().toLatin1(), 1); } void WorkerAcquire::Start() { // Cleanup from old fetches m_calculatingSpeed = true; m_trans->setCancellable(true); m_trans->setStatus(QApt::DownloadingStatus); pkgAcquireStatus::Start(); } void WorkerAcquire::IMSHit(pkgAcquire::ItemDesc &item) { updateStatus(item); Update = true; } void WorkerAcquire::Fetch(pkgAcquire::ItemDesc &item) { Update = true; if (item.Owner->Complete == true) { return; } updateStatus(item); } void WorkerAcquire::Done(pkgAcquire::ItemDesc &item) { Update = true; updateStatus(item); } void WorkerAcquire::Fail(pkgAcquire::ItemDesc &item) { // Ignore certain kinds of transient failures (bad code) if (item.Owner->Status == pkgAcquire::Item::StatIdle) { return; } if (item.Owner->Status == pkgAcquire::Item::StatDone) { updateStatus(item); } else { // an error was found (maybe 404, 403...) // the item that got the error and the error text QString failedItem = QString::fromStdString(item.URI); QString errorText = QString::fromStdString(item.Owner->ErrorText); m_trans->setErrorDetails(m_trans->errorDetails() % failedItem % '\n' % errorText % "\n\n"); } Update = true; } void WorkerAcquire::Stop() { m_trans->setProgress(m_progressEnd); m_trans->setCancellable(false); pkgAcquireStatus::Stop(); } bool WorkerAcquire::MediaChange(string Media, string Drive) { // Check if frontend can prompt for media if (!(m_trans->frontendCaps() & QApt::MediumPromptCap)) return false; // Notify listeners to the transaction m_trans->setMediumRequired(QString::fromUtf8(Media.c_str()), QString::fromUtf8(Drive.c_str())); m_trans->setStatus(QApt::WaitingMediumStatus); // Wait until the media is provided or the user cancels while (m_trans->isPaused()) usleep(200000); m_trans->setStatus(QApt::DownloadingStatus); // As long as the user didn't cancel, we're ok to proceed return (!m_trans->isCancelled()); } bool WorkerAcquire::Pulse(pkgAcquire *Owner) { if (m_trans->isCancelled()) return false; pkgAcquireStatus::Pulse(Owner); for (pkgAcquire::Worker *iter = Owner->WorkersBegin(); iter != 0; iter = Owner->WorkerStep(iter)) { if (!iter->CurrentItem) { continue; } +#if APT_PKG_ABI >= 590 + (*iter->CurrentItem).Owner->PartialSize = iter->CurrentItem->CurrentSize; +#else (*iter->CurrentItem).Owner->PartialSize = iter->CurrentSize; +#endif updateStatus(*iter->CurrentItem); } int percentage = qRound(double((CurrentBytes + CurrentItems) * 100.0)/double (TotalBytes + TotalItems)); int progress = 0; // work-around a stupid problem with libapt-pkg if(CurrentItems == TotalItems) { percentage = 100; } // Calculate global progress, adjusted for given beginning and ending points progress = qRound(m_progressBegin + qreal(percentage / 100.0) * (m_progressEnd - m_progressBegin)); if (m_lastProgress > progress) m_trans->setProgress(101); else { m_trans->setProgress(progress); m_lastProgress = progress; } quint64 ETA = 0; if(CurrentCPS > 0) ETA = (TotalBytes - CurrentBytes) / CurrentCPS; // if the ETA is greater than two days, show unknown time if (ETA > 2*24*60*60) { ETA = 0; } m_trans->setDownloadSpeed(CurrentCPS); m_trans->setETA(ETA); Update = false; return true; } void WorkerAcquire::updateStatus(const pkgAcquire::ItemDesc &Itm) { QString URI = QString::fromStdString(Itm.Description); int status = (int)Itm.Owner->Status; QApt::DownloadStatus downloadStatus = QApt::IdleState; QString shortDesc = QString::fromStdString(Itm.ShortDesc); quint64 fileSize = Itm.Owner->FileSize; quint64 fetchedSize = Itm.Owner->PartialSize; QString errorMsg = QString::fromStdString(Itm.Owner->ErrorText); QString message; // Status mapping switch (status) { case pkgAcquire::Item::StatIdle: downloadStatus = QApt::IdleState; break; case pkgAcquire::Item::StatFetching: downloadStatus = QApt::FetchingState; break; case pkgAcquire::Item::StatDone: downloadStatus = QApt::DoneState; fetchedSize = fileSize; break; case pkgAcquire::Item::StatError: downloadStatus = QApt::ErrorState; break; case pkgAcquire::Item::StatAuthError: downloadStatus = QApt::AuthErrorState; break; case pkgAcquire::Item::StatTransientNetworkError: downloadStatus = QApt::NetworkErrorState; break; default: break; } if (downloadStatus == QApt::DoneState && errorMsg.size()) message = errorMsg; - else if (Itm.Owner->Mode) - message = QString::fromUtf8(Itm.Owner->Mode); + else if (!Itm.Owner->ActiveSubprocess.empty()) + message = QString::fromStdString(Itm.Owner->ActiveSubprocess); QApt::DownloadProgress dp(URI, downloadStatus, shortDesc, fileSize, fetchedSize, message); m_trans->setDownloadProgress(dp); } diff --git a/src/worker/workeracquire.h b/src/worker/workeracquire.h index fe0d823..31031a3 100644 --- a/src/worker/workeracquire.h +++ b/src/worker/workeracquire.h @@ -1,61 +1,61 @@ /*************************************************************************** * Copyright © 2010 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 WORKERACQUIRE_H #define WORKERACQUIRE_H // Qt includes #include // Apt-pkg includes #include class Transaction; class WorkerAcquire : public QObject, public pkgAcquireStatus { Q_OBJECT public: explicit WorkerAcquire(QObject *parent, int begin = 0, int end = 100); void Start(); void IMSHit(pkgAcquire::ItemDesc &Itm); void Fetch(pkgAcquire::ItemDesc &Itm); void Done(pkgAcquire::ItemDesc &Itm); void Fail(pkgAcquire::ItemDesc &Itm); void Stop(); - bool MediaChange(string Media, string Drive); + bool MediaChange(std::string Media, std::string Drive); bool Pulse(pkgAcquire *Owner); void setTransaction(Transaction *trans); private: Transaction *m_trans; bool m_calculatingSpeed; int m_progressBegin; int m_progressEnd; int m_lastProgress; private Q_SLOTS: void updateStatus(const pkgAcquire::ItemDesc &Itm); }; #endif diff --git a/src/worker/workerinstallprogress.cpp b/src/worker/workerinstallprogress.cpp index 361a4d0..807adb3 100644 --- a/src/worker/workerinstallprogress.cpp +++ b/src/worker/workerinstallprogress.cpp @@ -1,228 +1,231 @@ /*************************************************************************** * Copyright © 2010-2012 Jonathan Thomas * * Copyright © 2004 Canonical * * * * 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 "workerinstallprogress.h" #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include "transaction.h" using namespace std; WorkerInstallProgress::WorkerInstallProgress(int begin, int end) : m_trans(nullptr) , m_startCounting(false) , m_progressBegin(begin) , m_progressEnd(end) { setenv("APT_LISTBUGS_FRONTEND", "none", 1); setenv("APT_LISTCHANGES_FRONTEND", "debconf", 1); } void WorkerInstallProgress::setTransaction(Transaction *trans) { m_trans = trans; std::setlocale(LC_ALL, m_trans->locale().toLatin1()); // FIXME: bloody workaround. // Since QLocale::system and consequently QTextCodec::forLocale is // set way before we get here there's a need to manually override // the already set default codec with that is requested by the client. // Since the client talks to us about posix locales however we cannot // expect a reliable mapping from split(.).last() to a proper codec. // Also there is no explicit Qt api to translate a given posix locale // to QLocale & QTextCodec. // Ultimately transactions should get new properties for QLocale::name // and QTextCodec::name, assuming generally meaningful values we can // through those properties accurately recreate the client locale env. if (m_trans->locale().contains(QChar('.'))) { QTextCodec *codec = QTextCodec::codecForName(m_trans->locale().split(QChar('.')).last().toUtf8()); QTextCodec::setCodecForLocale(codec); } if ((trans->frontendCaps() & QApt::DebconfCap) && !trans->debconfPipe().isEmpty()) { setenv("DEBIAN_FRONTEND", "passthrough", 1); setenv("DEBCONF_PIPE", trans->debconfPipe().toLatin1(), 1); } else { setenv("DEBIAN_FRONTEND", "noninteractive", 1); } } pkgPackageManager::OrderResult WorkerInstallProgress::start(pkgPackageManager *pm) { m_trans->setStatus(QApt::CommittingStatus); pkgPackageManager::OrderResult res; res = pm->DoInstallPreFork(); if (res == pkgPackageManager::Failed) { return res; } int readFromChildFD[2]; //Initialize both pipes if (pipe(readFromChildFD) < 0) { return res; } int pty_master; m_child_id = forkpty(&pty_master, 0, 0, 0); if (m_child_id == -1) { return res; } else if (m_child_id == 0) { // close pipe we don't need close(readFromChildFD[0]); - res = pm->DoInstallPostFork(readFromChildFD[1]); + APT::Progress::PackageManagerProgressFd progress(readFromChildFD[1]); + res = pm->DoInstallPostFork(&progress); // dump errors into cerr (pass it to the parent process) _error->DumpErrors(); close(readFromChildFD[1]); _exit(res); } // make it nonblocking fcntl(readFromChildFD[0], F_SETFL, O_NONBLOCK); fcntl(pty_master, F_SETFL, O_NONBLOCK); // Update the interface until the child dies int ret; char masterbuf[1024]; while (waitpid(m_child_id, &ret, WNOHANG) == 0) { // Read dpkg's raw output while(read(pty_master, masterbuf, sizeof(masterbuf)) > 0); // Update high-level status info updateInterface(readFromChildFD[0], pty_master); } res = (pkgPackageManager::OrderResult)WEXITSTATUS(ret); close(readFromChildFD[0]); close(readFromChildFD[1]); close(pty_master); return res; } void WorkerInstallProgress::updateInterface(int fd, int writeFd) { char buf[2]; static char line[1024] = ""; while (1) { int len = read(fd, buf, 1); // Status message didn't change if (len < 1) { break; } if (buf[0] == '\n') { const QStringList list = QString::fromUtf8(line).split(QLatin1Char(':')); const QString status = list.at(0); const QString package = list.at(1); QString percent = list.at(2); QString str = list.at(3); // If str legitimately had a ':' in it (such as a package version) // we need to retrieve the next string in the list. if (list.count() == 5) { str += QString(':' % list.at(4)); } if (package.isEmpty() || status.isEmpty()) { continue; } if (status.contains(QLatin1String("pmerror"))) { // Append error string to existing error details m_trans->setErrorDetails(m_trans->errorDetails() % package % '\n' % str % "\n\n"); } else if (status.contains(QLatin1String("pmconffile"))) { // From what I understand, the original file starts after the ' character ('\'') and // goes to a second ' character. The new conf file starts at the next ' and goes to // the next '. QStringList strList = str.split('\''); QString oldFile = strList.at(1); QString newFile = strList.at(2); // Prompt for which file to use if the frontend supports that if (m_trans->frontendCaps() & QApt::ConfigPromptCap) { m_trans->setConfFileConflict(oldFile, newFile); m_trans->setStatus(QApt::WaitingConfigFilePromptStatus); while (m_trans->isPaused()) usleep(200000); } m_trans->setStatus(QApt::CommittingStatus); if (m_trans->replaceConfFile()) { ssize_t reply = write(writeFd, "Y\n", 2); Q_UNUSED(reply); } else { ssize_t reply = write(writeFd, "N\n", 2); Q_UNUSED(reply); } } else { m_startCounting = true; } int percentage; int progress; if (percent.contains(QLatin1Char('.'))) { QStringList percentList = percent.split(QLatin1Char('.')); percentage = percentList.at(0).toInt(); } else { percentage = percent.toInt(); } progress = qRound(qreal(m_progressBegin + qreal(percentage / 100.0) * (m_progressEnd - m_progressBegin))); m_trans->setProgress(progress); m_trans->setStatusDetails(str); // clean-up line[0] = 0; } else { buf[1] = 0; strcat(line, buf); } } // 30 frames per second usleep(1000000/30); } diff --git a/utils/qapt-deb-installer/DebInstaller.cpp b/utils/qapt-deb-installer/DebInstaller.cpp index f6b46f2..008892d 100644 --- a/utils/qapt-deb-installer/DebInstaller.cpp +++ b/utils/qapt-deb-installer/DebInstaller.cpp @@ -1,459 +1,459 @@ /*************************************************************************** * Copyright © 2011 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 "DebInstaller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DebCommitWidget.h" #include "DebViewer.h" DebInstaller::DebInstaller(QWidget *parent, const QString &debFile) : QDialog(parent) , m_backend(new QApt::Backend(this)) , m_trans(nullptr) , m_commitWidget(nullptr) , m_applyButton(new QPushButton(this)) , m_cancelButton(new QPushButton(this)) , m_buttonBox(new QDialogButtonBox(this)) { if (!m_backend->init()) initError(); QApt::FrontendCaps caps = (QApt::FrontendCaps)(QApt::DebconfCap); m_backend->setFrontendCaps(caps); QFileInfo fi(debFile); m_debFile = new QApt::DebFile(fi.absoluteFilePath()); initGUI(); } void DebInstaller::initError() { QString details = m_backend->initErrorMessage(); QString text = i18nc("@label", "The package system could not be initialized, your " "configuration may be broken."); QString title = i18nc("@title:window", "Initialization error"); KMessageBox::detailedError(this, text, details, title); exit(-1); } void DebInstaller::initGUI() { KGuiItem::assign(m_applyButton, KStandardGuiItem::apply()); m_applyButton->setText(i18nc("@label", "Install Package")); KGuiItem::assign(m_cancelButton, KStandardGuiItem::cancel()); connect(m_applyButton, SIGNAL(clicked()), this, SLOT(installDebFile())); connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(reject())); setLayout(new QVBoxLayout); m_stack = new QStackedWidget(this); layout()->addWidget(m_stack); m_buttonBox->addButton(m_applyButton, QDialogButtonBox::AcceptRole); m_buttonBox->addButton(m_cancelButton, QDialogButtonBox::RejectRole); layout()->addWidget(m_buttonBox); m_debViewer = new DebViewer(m_stack); m_debViewer->setBackend(m_backend); m_stack->addWidget(m_debViewer); m_commitWidget = new DebCommitWidget(this); m_stack->addWidget(m_commitWidget); if (!m_debFile->isValid()) { QString text = i18nc("@label", "Could not open %1. It does not appear to be a " "valid Debian package file.", m_debFile->filePath()); KMessageBox::error(this, text, QString()); QApplication::instance()->quit(); return; } setWindowTitle(i18nc("@title:window", "Package Installer - %1", m_debFile->packageName())); m_debViewer->setDebFile(m_debFile); bool canInstall = checkDeb(); m_applyButton->setEnabled(canInstall); m_debViewer->setStatusText(m_statusString); if (!m_versionInfo.isEmpty()){ m_debViewer->setVersionTitle(m_versionTitle); m_debViewer->setVersionInfo(m_versionInfo); } else { m_debViewer->hideVersionInfo(); } } void DebInstaller::transactionStatusChanged(QApt::TransactionStatus status) { switch (status) { case QApt::RunningStatus: case QApt::DownloadingStatus: case QApt::CommitChangesRole: m_stack->setCurrentWidget(m_commitWidget); break; case QApt::FinishedStatus: if (m_trans->role() == QApt::CommitChangesRole) { delete m_trans; // Dependencies installed, now go for the deb file m_trans = m_backend->installFile(*m_debFile); setupTransaction(m_trans); m_trans->run(); } else { m_buttonBox->removeButton(m_applyButton); KGuiItem::assign(m_cancelButton, KStandardGuiItem::close()); m_cancelButton->setEnabled(true); m_cancelButton->setFocus(); m_cancelButton->grabKeyboard(); } default: break; } } void DebInstaller::errorOccurred(QApt::ErrorCode error) { switch (error) { case QApt::AuthError: case QApt::LockError: m_applyButton->setEnabled(true); m_cancelButton->setEnabled(true); break; } } void DebInstaller::installDebFile() { m_applyButton->setEnabled(false); m_cancelButton->setEnabled(false); if (m_backend->markedPackages().size()) { m_trans = m_backend->commitChanges(); } else { m_trans = m_backend->installFile(*m_debFile); } setupTransaction(m_trans); m_trans->run(); } void DebInstaller::setupTransaction(QApt::Transaction *trans) { // Provide proxy/locale to the transaction if (KProtocolManager::proxyType() == KProtocolManager::ManualProxy) { trans->setProxy(KProtocolManager::proxyFor("http")); } trans->setLocale(QLatin1String(setlocale(LC_MESSAGES, 0))); trans->setDebconfPipe(m_commitWidget->pipe()); m_commitWidget->setTransaction(m_trans); connect(m_trans, SIGNAL(statusChanged(QApt::TransactionStatus)), this, SLOT(transactionStatusChanged(QApt::TransactionStatus))); connect(m_trans, SIGNAL(errorOccurred(QApt::ErrorCode)), this, SLOT(errorOccurred(QApt::ErrorCode))); } bool DebInstaller::checkDeb() { QStringList arches = m_backend->architectures(); arches.append(QLatin1String("all")); QString debArch = m_debFile->architecture(); // Check if we support the arch at all if (debArch != m_backend->nativeArchitecture()) { if (!arches.contains(debArch)) { // Wrong arch m_statusString = i18nc("@info", "Error: Wrong architecture '%1'", debArch); m_statusString.prepend(QLatin1String("")); m_statusString.append(QLatin1String("")); return false; } // We support this foreign arch m_foreignArch = debArch; } compareDebWithCache(); QApt::PackageList conflicts = checkConflicts(); if (!conflicts.isEmpty()) { return false; } QApt::Package *willBreak = checkBreaksSystem(); if (willBreak) { m_statusString = i18nc("@info Error string when installing would break an existing package", "Error: Breaks the existing package \"%1\"", willBreak->name()); m_statusString.prepend(QLatin1String("")); m_statusString.append(QLatin1String("")); return false; } if (!satisfyDepends()) { // create status message m_statusString = i18nc("@info", "Error: Cannot satisfy dependencies"); m_statusString.prepend(QLatin1String("")); m_statusString.append(QLatin1String("")); return false; } int toInstall = m_backend->markedPackages().size(); m_debViewer->showDetailsButton(toInstall); if (toInstall) { m_statusString = i18ncp("@label A note saying that additional packages are needed", "Requires the installation of %1 additional package.", "Requires the installation of %1 additional packages", toInstall); } else { m_statusString = i18nc("@info", "All dependencies are satisfied."); } return true; } void DebInstaller::compareDebWithCache() { QApt::Package *pkg = m_backend->package(m_debFile->packageName()); if (!pkg) { return; } QString version = m_debFile->version(); int res = QApt::Package::compareVersion(m_debFile->version(), pkg->availableVersion()); if (res == 0 && !pkg->isInstalled()) { m_versionTitle = i18nc("@info", "The same version is available in a software channel."); m_versionInfo = i18nc("@info", "It is recommended to install the software from the channel instead"); } else if (res > 0) { m_versionTitle = i18nc("@info", "An older version is available in a software channel."); m_versionInfo = i18nc("@info", "It is recommended to install the version from the " "software channel, since it usually has more support."); } else if (res < 0) { m_versionTitle = i18nc("@info", "A newer version is available in a software channel."); m_versionInfo = i18nc("@info", "It is strongly advised to install the version from the " "software channel, since it usually has more support."); } } QString DebInstaller::maybeAppendArchSuffix(const QString &pkgName, bool checkingConflicts) { // Trivial cases where we don't append if (m_foreignArch.isEmpty()) return pkgName; QApt::Package *pkg = m_backend->package(pkgName); if (!pkg || pkg->architecture() == QLatin1String("all")) return pkgName; // Real multiarch checks QString multiArchName = pkgName % ':' % m_foreignArch; QApt::Package *multiArchPkg = m_backend->package(multiArchName); // Check for a new dependency, we'll handle that later if (!multiArchPkg) return multiArchName; // Check the multi arch state QApt::MultiArchType type = multiArchPkg->multiArchType(); // Add the suffix, unless it's a pkg that can satify foreign deps if (type == QApt::MultiArchForeign) return pkgName; // If this is called as part of a conflicts check, any not-multiarch // enabled package is a conflict implicitly if (checkingConflicts && type == QApt::MultiArchSame) return pkgName; return multiArchName; } QApt::PackageList DebInstaller::checkConflicts() { QApt::PackageList conflictingPackages; QList conflicts = m_debFile->conflicts(); QApt::Package *pkg = 0; QString packageName; bool ok = true; foreach(const QApt::DependencyItem &item, conflicts) { foreach (const QApt::DependencyInfo &info, item) { packageName = maybeAppendArchSuffix(info.packageName(), true); pkg = m_backend->package(packageName); if (!pkg) { // FIXME: Virtual package, must check provides continue; } - string pkgVer = pkg->version().toStdString(); - string depVer = info.packageVersion().toStdString(); + std::string pkgVer = pkg->version().toStdString(); + std::string depVer = info.packageVersion().toStdString(); ok = _system->VS->CheckDep(pkgVer.c_str(), info.relationType(), depVer.c_str()); if (ok) { // Group satisfied break; } } if (!ok && pkg) { conflictingPackages.append(pkg); } } return conflictingPackages; } QApt::Package *DebInstaller::checkBreaksSystem() { QApt::PackageList systemPackages = m_backend->availablePackages(); - string debVer = m_debFile->version().toStdString(); + std::string debVer = m_debFile->version().toStdString(); foreach (QApt::Package *pkg, systemPackages) { if (!pkg->isInstalled()) { continue; } // Check for broken depends foreach(const QApt::DependencyItem &item, pkg->depends()) { foreach (const QApt::DependencyInfo &dep, item) { if (dep.packageName() != m_debFile->packageName()) { continue; } - string depVer = dep.packageVersion().toStdString(); + std::string depVer = dep.packageVersion().toStdString(); if (!_system->VS->CheckDep(debVer.c_str(), dep.relationType(), depVer.c_str())) { return pkg; } } } // Check for existing conflicts against the .deb // FIXME: Check provided virtual packages too foreach(const QApt::DependencyItem &item, pkg->conflicts()) { foreach (const QApt::DependencyInfo &conflict, item) { if (conflict.packageName() != m_debFile->packageName()) { continue; } - string conflictVer = conflict.packageVersion().toStdString(); + std::string conflictVer = conflict.packageVersion().toStdString(); if (_system->VS->CheckDep(debVer.c_str(), conflict.relationType(), conflictVer.c_str())) { return pkg; } } } } return 0; } bool DebInstaller::satisfyDepends() { QApt::Package *pkg = 0; QString packageName; foreach(const QApt::DependencyItem &item, m_debFile->depends()) { bool oneSatisfied = false; foreach (const QApt::DependencyInfo &dep, item) { packageName = maybeAppendArchSuffix(dep.packageName()); pkg = m_backend->package(packageName); if (!pkg) { // FIXME: virtual package handling continue; } - string debVersion = dep.packageVersion().toStdString(); + std::string debVersion = dep.packageVersion().toStdString(); // If we're installed, see if we already satisfy the dependency if (pkg->isInstalled()) { - string pkgVersion = pkg->installedVersion().toStdString(); + std::string pkgVersion = pkg->installedVersion().toStdString(); if (_system->VS->CheckDep(pkgVersion.c_str(), dep.relationType(), debVersion.c_str())) { oneSatisfied = true; break; } } // else check if cand ver will satisfy, then mark - string candVersion = pkg->availableVersion().toStdString(); + std::string candVersion = pkg->availableVersion().toStdString(); if (!_system->VS->CheckDep(candVersion.c_str(), dep.relationType(), debVersion.c_str())) { continue; } pkg->setInstall(); if (!pkg->wouldBreak()) { oneSatisfied = true; break; } } if (!oneSatisfied) { return false; } } return true; }