File indexing completed on 2024-04-28 15:28:57

0001 /*
0002     This file is part of KNewStuff2.
0003     SPDX-FileCopyrightText: 2007 Josef Spillner <spillner@kde.org>
0004     SPDX-FileCopyrightText: 2009 Frederik Gladhorn <gladhorn@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "installation.h"
0010 
0011 #include <QDesktopServices>
0012 #include <QDir>
0013 #include <QFile>
0014 #include <QProcess>
0015 #include <QTemporaryFile>
0016 #include <QUrlQuery>
0017 
0018 #include "karchive.h"
0019 #include "qmimedatabase.h"
0020 #include <KRandom>
0021 #include <KShell>
0022 #include <KTar>
0023 #include <KZip>
0024 
0025 #include "jobs/kpackagejob.h"
0026 #include <KPackage/Package>
0027 #include <KPackage/PackageLoader>
0028 #include <KPackage/PackageStructure>
0029 
0030 #include <KLocalizedString>
0031 #include <knewstuffcore_debug.h>
0032 #include <qstandardpaths.h>
0033 
0034 #include "jobs/filecopyjob.h"
0035 #include "question.h"
0036 #ifdef Q_OS_WIN
0037 #include <shlobj.h>
0038 #include <windows.h>
0039 #endif
0040 
0041 using namespace KNSCore;
0042 
0043 Installation::Installation(QObject *parent)
0044     : QObject(parent)
0045 {
0046     // TODO KF6 Make these real properties, when we can refactor this and add a proper dptr
0047     setProperty("kpackageType", QLatin1String(""));
0048     setProperty("uncompressSetting", UncompressionOptions::NeverUncompress);
0049 }
0050 
0051 bool Installation::readConfig(const KConfigGroup &group)
0052 {
0053     // FIXME: add support for several categories later on
0054     // FIXME: read out only when actually installing as a performance improvement?
0055     uncompression = group.readEntry("Uncompress", QStringLiteral("never"));
0056     UncompressionOptions opt;
0057     // support old value of true as equivalent of always
0058     if (uncompression == QLatin1String("true")) {
0059         uncompression = QStringLiteral("always");
0060     }
0061     if (uncompression == QLatin1String("always")) {
0062         opt = AlwaysUncompress;
0063     } else if (uncompression == QLatin1String("archive")) {
0064         opt = UncompressIfArchive;
0065     } else if (uncompression == QLatin1String("subdir")) {
0066         opt = UncompressIntoSubdir;
0067     } else if (uncompression == QLatin1String("kpackage")) {
0068         opt = UseKPackageUncompression;
0069     } else if (uncompression == QLatin1String("subdir-archive")) {
0070         opt = UncompressIntoSubdirIfArchive;
0071     } else if (uncompression == QLatin1String("never")) {
0072         opt = NeverUncompress;
0073     } else {
0074         qCCritical(KNEWSTUFFCORE) << "invalid Uncompress setting chosen, must be one of: subdir, always, archive, never, or kpackage";
0075         return false;
0076     }
0077     setProperty("uncompressSetting", opt);
0078     setProperty("kpackageType", group.readEntry("KPackageType"));
0079     postInstallationCommand = group.readEntry("InstallationCommand");
0080     uninstallCommand = group.readEntry("UninstallCommand");
0081     standardResourceDirectory = group.readEntry("StandardResource");
0082     targetDirectory = group.readEntry("TargetDir");
0083     xdgTargetDirectory = group.readEntry("XdgTargetDir");
0084 
0085 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 77)
0086     // Provide some compatibility
0087     if (standardResourceDirectory == QLatin1String("wallpaper")) {
0088         xdgTargetDirectory = QStringLiteral("wallpapers");
0089     }
0090 
0091     // also, ensure wallpapers are decompressed into subdirectories
0092     // this ensures that wallpapers with multiple resolutions continue to function
0093     // as expected
0094     if (xdgTargetDirectory == QLatin1String("wallpapers")) {
0095         uncompression = QStringLiteral("subdir");
0096     }
0097 
0098     // A touch of special treatment for the various old kpackage based knsrc files, so they work
0099     // with the new, internal stuff. The result unfortunately is that all entries marked as
0100     // installed in knewstuff no longer will be, but since it never worked right anyway... we'll
0101     // simply have to live with that.
0102     // clang-format off
0103     if (postInstallationCommand.startsWith(QLatin1String("kpackagetool5 -t")) &&
0104             postInstallationCommand.endsWith(QLatin1String("-i %f")) &&
0105             uninstallCommand.startsWith(QLatin1String("kpackagetool5 -t")) &&
0106             uninstallCommand.endsWith(QLatin1String("-r %f"))) {
0107         uncompression = QStringLiteral("kpackage");
0108         postInstallationCommand = QLatin1String("");
0109         // Not clearing uninstallCommand, as this is used for the fallback situation
0110         setProperty("kpackageType", uninstallCommand.mid(17, uninstallCommand.length() - 17 - 6));
0111         qCWarning(KNEWSTUFFCORE) << "Your configuration file uses an old version of the kpackage support, and should be converted. Please report this to the author of the software you are currently using. The package type, we assume, is" << property("kpackageType").toString();
0112     } else if (postInstallationCommand.startsWith(QLatin1String("kpackagetool5 --type")) &&
0113             postInstallationCommand.endsWith(QLatin1String("--install %f")) &&
0114             uninstallCommand.startsWith(QLatin1String("kpackagetool5 --type")) &&
0115             uninstallCommand.endsWith(QLatin1String("--remove %f"))) {
0116         uncompression = QStringLiteral("kpackage");
0117         postInstallationCommand = QLatin1String("");
0118         // Not clearing uninstallCommand, as this is used for the fallback situation
0119         setProperty("kpackageType", uninstallCommand.mid(21, uninstallCommand.length() - 21 - 12));
0120         qCWarning(KNEWSTUFFCORE) << "Your configuration file uses an old version of the kpackage support, and should be converted. Please report this to the author of the software you are currently using. The package type, we assume, is" << property("kpackageType").toString();
0121     }
0122     // clang-format on
0123 #endif
0124 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 79)
0125     customName = group.readEntry("CustomName", false);
0126     if (customName) {
0127         qWarning(KNEWSTUFFCORE) << "The CustomName property is deprecated and will be removed in KF6";
0128     }
0129     QString scopeString = group.readEntry("Scope");
0130     if (!scopeString.isEmpty()) {
0131         qWarning(KNEWSTUFFCORE) << "Setting the scope is deprecated, it will default to user";
0132         if (scopeString == QLatin1String("user")) {
0133             scope = ScopeUser;
0134         } else if (scopeString == QLatin1String("system")) {
0135             scope = ScopeSystem;
0136         } else {
0137             qCCritical(KNEWSTUFFCORE) << QStringLiteral("The scope '") + scopeString + QStringLiteral("' is unknown.");
0138             return false;
0139         }
0140 
0141         if (scope == ScopeSystem) {
0142             if (!installPath.isEmpty()) {
0143                 qCCritical(KNEWSTUFFCORE) << "System installation cannot be mixed with InstallPath.";
0144                 return false;
0145             }
0146         }
0147     }
0148 
0149     QString checksumpolicy = group.readEntry("ChecksumPolicy");
0150     if (!checksumpolicy.isEmpty()) {
0151         qWarning(KNEWSTUFFCORE) << "The ChecksumPolicy feature is defunct";
0152         if (checksumpolicy == QLatin1String("never")) {
0153             checksumPolicy = Installation::CheckNever;
0154         } else if (checksumpolicy == QLatin1String("ifpossible")) {
0155             checksumPolicy = Installation::CheckIfPossible;
0156         } else if (checksumpolicy == QLatin1String("always")) {
0157             checksumPolicy = Installation::CheckAlways;
0158         } else {
0159             qCCritical(KNEWSTUFFCORE) << QStringLiteral("The checksum policy '") + checksumpolicy + QStringLiteral("' is unknown.");
0160             return false;
0161         }
0162     }
0163 
0164     QString signaturepolicy = group.readEntry("SignaturePolicy");
0165     if (!signaturepolicy.isEmpty()) {
0166         qWarning(KNEWSTUFFCORE) << "The SignaturePolicy feature is defunct";
0167         if (signaturepolicy == QLatin1String("never")) {
0168             signaturePolicy = Installation::CheckNever;
0169         } else if (signaturepolicy == QLatin1String("ifpossible")) {
0170             signaturePolicy = Installation::CheckIfPossible;
0171         } else if (signaturepolicy == QLatin1String("always")) {
0172             signaturePolicy = Installation::CheckAlways;
0173         } else {
0174             qCCritical(KNEWSTUFFCORE) << QStringLiteral("The signature policy '") + signaturepolicy + QStringLiteral("' is unknown.");
0175             return false;
0176         }
0177     }
0178     acceptHtml = group.readEntry("AcceptHtmlDownloads", false);
0179     if (acceptHtml) {
0180         qWarning(KNEWSTUFFCORE) << "The AcceptHtmlDownload property is deprecated and will default to false. If there"
0181                                    "is a HTML download link the user will be prompted if the installation should proceed";
0182     }
0183 #endif
0184 
0185     installPath = group.readEntry("InstallPath");
0186     absoluteInstallPath = group.readEntry("AbsoluteInstallPath");
0187 
0188     if (standardResourceDirectory.isEmpty() && targetDirectory.isEmpty() && xdgTargetDirectory.isEmpty() && installPath.isEmpty()
0189         && absoluteInstallPath.isEmpty()) {
0190         qCCritical(KNEWSTUFFCORE) << "No installation target set";
0191         return false;
0192     }
0193     return true;
0194 }
0195 
0196 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 71)
0197 bool Installation::isRemote() const
0198 {
0199     return false;
0200 }
0201 #endif
0202 
0203 void Installation::install(const EntryInternal &entry)
0204 {
0205     downloadPayload(entry);
0206 }
0207 
0208 void Installation::downloadPayload(const KNSCore::EntryInternal &entry)
0209 {
0210     if (!entry.isValid()) {
0211         Q_EMIT signalInstallationFailed(i18n("Invalid item."));
0212         return;
0213     }
0214     QUrl source = QUrl(entry.payload());
0215 
0216     if (!source.isValid()) {
0217         qCCritical(KNEWSTUFFCORE) << "The entry doesn't have a payload.";
0218         Q_EMIT signalInstallationFailed(i18n("Download of item failed: no download URL for \"%1\".", entry.name()));
0219         return;
0220     }
0221 
0222     QString fileName(source.fileName());
0223     QTemporaryFile tempFile(QDir::tempPath() + QStringLiteral("/XXXXXX-") + fileName);
0224     tempFile.setAutoRemove(false);
0225     if (!tempFile.open()) {
0226         return; // ERROR
0227     }
0228     QUrl destination = QUrl::fromLocalFile(tempFile.fileName());
0229     qCDebug(KNEWSTUFFCORE) << "Downloading payload" << source << "to" << destination;
0230 #ifdef Q_OS_WIN // can't write to the file if it's open, on Windows
0231     tempFile.close();
0232 #endif
0233 
0234     // FIXME: check for validity
0235     FileCopyJob *job = FileCopyJob::file_copy(source, destination, -1, JobFlag::Overwrite | JobFlag::HideProgressInfo);
0236     connect(job, &KJob::result, this, &Installation::slotPayloadResult);
0237 
0238     entry_jobs[job] = entry;
0239 }
0240 
0241 void Installation::slotPayloadResult(KJob *job)
0242 {
0243     // for some reason this slot is getting called 3 times on one job error
0244     if (entry_jobs.contains(job)) {
0245         EntryInternal entry = entry_jobs[job];
0246         entry_jobs.remove(job);
0247 
0248         if (job->error()) {
0249             const QString errorMessage = i18n("Download of \"%1\" failed, error: %2", entry.name(), job->errorString());
0250             qCWarning(KNEWSTUFFCORE) << errorMessage;
0251             Q_EMIT signalInstallationFailed(errorMessage);
0252         } else {
0253             FileCopyJob *fcjob = static_cast<FileCopyJob *>(job);
0254             qCDebug(KNEWSTUFFCORE) << "Copied to" << fcjob->destUrl();
0255 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 79)
0256             // check if the app likes html files - disabled by default as too many bad links have been submitted to opendesktop.org
0257             if (!acceptHtml) {
0258 #endif
0259                 QMimeDatabase db;
0260                 QMimeType mimeType = db.mimeTypeForFile(fcjob->destUrl().toLocalFile());
0261                 if (mimeType.inherits(QStringLiteral("text/html")) || mimeType.inherits(QStringLiteral("application/x-php"))) {
0262                     const auto error = i18n("Cannot install '%1' because it points to a web page. Click <a href='%2'>here</a> to finish the installation.",
0263                                             entry.name(),
0264                                             fcjob->srcUrl().toString());
0265                     Q_EMIT signalInstallationFailed(error);
0266                     entry.setStatus(KNS3::Entry::Invalid);
0267                     Q_EMIT signalEntryChanged(entry);
0268                     return;
0269                 }
0270 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 79)
0271             }
0272 #endif
0273 
0274             Q_EMIT signalPayloadLoaded(fcjob->destUrl());
0275             install(entry, fcjob->destUrl().toLocalFile());
0276         }
0277     }
0278 }
0279 
0280 void KNSCore::Installation::install(KNSCore::EntryInternal entry, const QString &downloadedFile)
0281 {
0282     qCDebug(KNEWSTUFFCORE) << "Install:" << entry.name() << "from" << downloadedFile;
0283     Q_ASSERT(QFileInfo::exists(downloadedFile));
0284 
0285     if (entry.payload().isEmpty()) {
0286         qCDebug(KNEWSTUFFCORE) << "No payload associated with:" << entry.name();
0287         return;
0288     }
0289 
0290     // TODO Add async checksum verification
0291 
0292     QString targetPath = targetInstallationPath();
0293     QStringList installedFiles = installDownloadedFileAndUncompress(entry, downloadedFile, targetPath);
0294 
0295     if (uncompressionSetting() != UseKPackageUncompression) {
0296         if (installedFiles.isEmpty()) {
0297             if (entry.status() == KNS3::Entry::Installing) {
0298                 entry.setStatus(KNS3::Entry::Downloadable);
0299             } else if (entry.status() == KNS3::Entry::Updating) {
0300                 entry.setStatus(KNS3::Entry::Updateable);
0301             }
0302             Q_EMIT signalEntryChanged(entry);
0303             Q_EMIT signalInstallationFailed(i18n("Could not install \"%1\": file not found.", entry.name()));
0304             return;
0305         }
0306 
0307         entry.setInstalledFiles(installedFiles);
0308 
0309         auto installationFinished = [this, entry]() {
0310             EntryInternal newentry = entry;
0311             if (!newentry.updateVersion().isEmpty()) {
0312                 newentry.setVersion(newentry.updateVersion());
0313             }
0314             if (newentry.updateReleaseDate().isValid()) {
0315                 newentry.setReleaseDate(newentry.updateReleaseDate());
0316             }
0317             newentry.setStatus(KNS3::Entry::Installed);
0318             Q_EMIT signalEntryChanged(newentry);
0319             Q_EMIT signalInstallationFinished();
0320         };
0321         if (!postInstallationCommand.isEmpty()) {
0322             QString scriptArgPath = !installedFiles.isEmpty() ? installedFiles.first() : targetPath;
0323             if (scriptArgPath.endsWith(QLatin1Char('*'))) {
0324                 scriptArgPath = scriptArgPath.left(scriptArgPath.lastIndexOf(QLatin1Char('*')));
0325             }
0326             QProcess *p = runPostInstallationCommand(scriptArgPath);
0327             connect(p, &QProcess::finished, this, [entry, installationFinished, this](int exitCode) {
0328                 if (exitCode) {
0329                     EntryInternal newEntry = entry;
0330                     newEntry.setStatus(KNS3::Entry::Invalid);
0331                     Q_EMIT signalEntryChanged(newEntry);
0332                 } else {
0333                     installationFinished();
0334                 }
0335             });
0336         } else {
0337             installationFinished();
0338         }
0339     }
0340 }
0341 
0342 QString Installation::targetInstallationPath() const
0343 {
0344     // installdir is the target directory
0345     QString installdir;
0346 
0347 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 79)
0348     const bool userScope = scope == ScopeUser;
0349 #else
0350     const bool userScope = true;
0351 #endif
0352     // installpath also contains the file name if it's a single file, otherwise equal to installdir
0353     int pathcounter = 0;
0354     // wallpaper is already managed in the case of !xdgTargetDirectory.isEmpty()
0355     if (!standardResourceDirectory.isEmpty() && standardResourceDirectory != QLatin1String("wallpaper")) {
0356         QStandardPaths::StandardLocation location = QStandardPaths::TempLocation;
0357         // crude translation KStandardDirs names -> QStandardPaths enum
0358         if (standardResourceDirectory == QLatin1String("tmp")) {
0359             location = QStandardPaths::TempLocation;
0360         } else if (standardResourceDirectory == QLatin1String("config")) {
0361             location = QStandardPaths::ConfigLocation;
0362         }
0363 
0364         if (userScope) {
0365             installdir = QStandardPaths::writableLocation(location);
0366         } else { // system scope
0367             installdir = QStandardPaths::standardLocations(location).constLast();
0368         }
0369         pathcounter++;
0370     }
0371     if (!targetDirectory.isEmpty() && targetDirectory != QLatin1String("/")) {
0372         if (userScope) {
0373             installdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + targetDirectory + QLatin1Char('/');
0374         } else { // system scope
0375             installdir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, targetDirectory, QStandardPaths::LocateDirectory) + QLatin1Char('/');
0376         }
0377         pathcounter++;
0378     }
0379     if (!xdgTargetDirectory.isEmpty() && xdgTargetDirectory != QLatin1String("/")) {
0380         installdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + xdgTargetDirectory + QLatin1Char('/');
0381         pathcounter++;
0382     }
0383     if (!installPath.isEmpty()) {
0384 #if defined(Q_OS_WIN)
0385         WCHAR wPath[MAX_PATH + 1];
0386         if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wPath) == S_OK) {
0387             installdir = QString::fromUtf16((const ushort *)wPath) + QLatin1Char('/') + installPath + QLatin1Char('/');
0388         } else {
0389             installdir = QDir::homePath() + QLatin1Char('/') + installPath + QLatin1Char('/');
0390         }
0391 #else
0392         installdir = QDir::homePath() + QLatin1Char('/') + installPath + QLatin1Char('/');
0393 #endif
0394         pathcounter++;
0395     }
0396     if (!absoluteInstallPath.isEmpty()) {
0397         installdir = absoluteInstallPath + QLatin1Char('/');
0398         pathcounter++;
0399     }
0400 
0401     if (pathcounter != 1) {
0402         qCCritical(KNEWSTUFFCORE) << "Wrong number of installation directories given.";
0403         return QString();
0404     }
0405 
0406     qCDebug(KNEWSTUFFCORE) << "installdir: " << installdir;
0407 
0408     // create the dir if it doesn't exist (QStandardPaths doesn't create it, unlike KStandardDirs!)
0409     QDir().mkpath(installdir);
0410 
0411     return installdir;
0412 }
0413 
0414 QStringList Installation::installDownloadedFileAndUncompress(const KNSCore::EntryInternal &entry, const QString &payloadfile, const QString installdir)
0415 {
0416     // Collect all files that were installed
0417     QStringList installedFiles;
0418     bool isarchive = true;
0419     UncompressionOptions uncompressionOpt = uncompressionSetting();
0420 
0421     // respect the uncompress flag in the knsrc
0422     if (uncompressionOpt == UseKPackageUncompression) {
0423         qCDebug(KNEWSTUFFCORE) << "Using KPackage for installation";
0424         KPackage::PackageStructure structure;
0425         KPackage::Package package(&structure);
0426         package.setPath(payloadfile);
0427         auto resetEntryStatus = [this, entry]() {
0428             KNSCore::EntryInternal changedEntry(entry);
0429             if (changedEntry.status() == KNS3::Entry::Installing || changedEntry.status() == KNS3::Entry::Installed) {
0430                 changedEntry.setStatus(KNS3::Entry::Downloadable);
0431             } else if (changedEntry.status() == KNS3::Entry::Updating) {
0432                 changedEntry.setStatus(KNS3::Entry::Updateable);
0433             }
0434             Q_EMIT signalEntryChanged(changedEntry);
0435         };
0436         if (package.isValid() && package.metadata().isValid()) {
0437             qCDebug(KNEWSTUFFCORE) << "Package metadata is valid";
0438 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 90)
0439             QString serviceType;
0440             serviceType = package.metadata().value(QStringLiteral("X-Plasma-ServiceType"));
0441             const auto serviceTypes =
0442                 package.metadata().rawData().value(QLatin1String("KPlugin")).toObject().value(QLatin1String("ServiceTypes")).toVariant().toStringList();
0443             if (serviceType.isEmpty() && !serviceTypes.isEmpty()) {
0444                 serviceType = serviceTypes.first();
0445             }
0446             if (serviceType.isEmpty()) {
0447                 serviceType = property("kpackageType").toString();
0448             } else if (serviceType != property("kpackageType").toString()) {
0449                 qCWarning(KNEWSTUFFCORE) << "The package" << package.metadata().fileName()
0450                                          << "defines a different kpackage type than the one defined by the app."
0451                                          << "Please report this to the author of the addon.";
0452             }
0453 #else
0454             const QString serviceType = property("kpackageType").toString();
0455 #endif
0456 
0457             if (!serviceType.isEmpty()) {
0458                 qCDebug(KNEWSTUFFCORE) << "Service type discovered as" << serviceType;
0459                 KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(serviceType);
0460                 if (structure) {
0461                     KPackage::Package installer = KPackage::Package(structure);
0462                     if (installer.hasValidStructure()) {
0463                         QString packageRoot =
0464                             QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + installer.defaultPackageRoot();
0465                         qCDebug(KNEWSTUFFCORE) << "About to attempt to install" << package.metadata().pluginId() << "into" << packageRoot;
0466                         const QString expectedDir{packageRoot + package.metadata().pluginId()};
0467                         KJob *installJob = KPackageJob::update(payloadfile, packageRoot, serviceType);
0468                         // TODO KF6 Really, i would prefer to make more functions to handle this, but as this is
0469                         // an exported class, i'd rather not pollute the public namespace with internal functions,
0470                         // and we don't have a pimpl, so... we'll just have to deal with it for now
0471                         connect(installJob, &KJob::result, this, [this, entry, payloadfile, expectedDir, resetEntryStatus](KJob *job) {
0472                             if (job->error() == KJob::NoError) {
0473                                 if (QFile::exists(expectedDir)) {
0474                                     EntryInternal newentry = entry;
0475                                     newentry.setInstalledFiles(QStringList{expectedDir});
0476                                     // update version and release date to the new ones
0477                                     if (newentry.status() == KNS3::Entry::Updating) {
0478                                         if (!newentry.updateVersion().isEmpty()) {
0479                                             newentry.setVersion(newentry.updateVersion());
0480                                         }
0481                                         if (newentry.updateReleaseDate().isValid()) {
0482                                             newentry.setReleaseDate(newentry.updateReleaseDate());
0483                                         }
0484                                     }
0485                                     newentry.setStatus(KNS3::Entry::Installed);
0486                                     // We can remove the downloaded file, because we don't save its location and don't need it to uninstall the entry
0487                                     QFile::remove(payloadfile);
0488                                     Q_EMIT signalEntryChanged(newentry);
0489                                     Q_EMIT signalInstallationFinished();
0490                                     qCDebug(KNEWSTUFFCORE) << "Install job finished with no error and we now have files" << expectedDir;
0491                                 } else {
0492                                     Q_EMIT signalInstallationFailed(
0493                                         i18n("The installation of %1 failed to create the expected new directory %2").arg(payloadfile, expectedDir));
0494                                     resetEntryStatus();
0495                                     qCDebug(KNEWSTUFFCORE)
0496                                         << "Install job finished with no error, but we do not have the expected new directory" << expectedDir;
0497                                 }
0498                             } else {
0499                                 if (job->error() == KPackage::Package::JobError::NewerVersionAlreadyInstalledError) {
0500                                     EntryInternal newentry = entry;
0501                                     newentry.setStatus(KNS3::Entry::Installed);
0502                                     newentry.setInstalledFiles(QStringList{expectedDir});
0503                                     // update version and release date to the new ones
0504                                     if (!newentry.updateVersion().isEmpty()) {
0505                                         newentry.setVersion(newentry.updateVersion());
0506                                     }
0507                                     if (newentry.updateReleaseDate().isValid()) {
0508                                         newentry.setReleaseDate(newentry.updateReleaseDate());
0509                                     }
0510                                     Q_EMIT signalEntryChanged(newentry);
0511                                     Q_EMIT signalInstallationFinished();
0512                                     qCDebug(KNEWSTUFFCORE) << "Install job finished telling us this item was already installed with this version, so... let's "
0513                                                               "just make a small fib and say we totally installed that, honest, and we now have files"
0514                                                            << expectedDir;
0515                                 } else {
0516                                     Q_EMIT signalInstallationFailed(i18n("Installation of %1 failed: %2", payloadfile, job->errorText()));
0517                                     resetEntryStatus();
0518                                     qCDebug(KNEWSTUFFCORE) << "Install job finished with error state" << job->error() << "and description" << job->error();
0519                                 }
0520                             }
0521                         });
0522                         installJob->start();
0523                     } else {
0524                         Q_EMIT signalInstallationFailed(
0525                             i18n("The installation of %1 failed, as the service type %2 was not accepted by the system (did you forget to install the KPackage "
0526                                  "support plugin for this type of package?)",
0527                                  payloadfile,
0528                                  serviceType));
0529                         resetEntryStatus();
0530                         qCWarning(KNEWSTUFFCORE) << "Package serviceType" << serviceType << "not found";
0531                     }
0532                 } else {
0533                     // no package structure
0534                     Q_EMIT signalInstallationFailed(
0535                         i18n("The installation of %1 failed, as the downloaded package does not contain a correct KPackage structure.", payloadfile));
0536                     resetEntryStatus();
0537                     qCWarning(KNEWSTUFFCORE) << "Could not load the package structure for KPackage service type" << serviceType;
0538                 }
0539             } else {
0540                 // no service type
0541                 Q_EMIT signalInstallationFailed(i18n("The installation of %1 failed, as the downloaded package does not list a service type.", payloadfile));
0542                 resetEntryStatus();
0543                 qCWarning(KNEWSTUFFCORE) << "No service type listed in" << payloadfile;
0544             }
0545         } else {
0546             // package or package metadata is invalid
0547             Q_EMIT signalInstallationFailed(
0548                 i18n("The installation of %1 failed, as the downloaded package does not contain any useful meta information, which means it is not a valid "
0549                      "KPackage.",
0550                      payloadfile));
0551             resetEntryStatus();
0552             qCWarning(KNEWSTUFFCORE) << "No valid meta information (which suggests no valid KPackage) found in" << payloadfile;
0553         }
0554     } else {
0555         if (uncompressionOpt == AlwaysUncompress || uncompressionOpt == UncompressIntoSubdirIfArchive || uncompressionOpt == UncompressIfArchive
0556             || uncompressionOpt == UncompressIntoSubdir) {
0557             // this is weird but a decompression is not a single name, so take the path instead
0558             QMimeDatabase db;
0559             QMimeType mimeType = db.mimeTypeForFile(payloadfile);
0560             qCDebug(KNEWSTUFFCORE) << "Postinstallation: uncompress the file";
0561 
0562             // FIXME: check for overwriting, malicious archive entries (../foo) etc.
0563             // FIXME: KArchive should provide "safe mode" for this!
0564             QScopedPointer<KArchive> archive;
0565 
0566             if (mimeType.inherits(QStringLiteral("application/zip"))) {
0567                 archive.reset(new KZip(payloadfile));
0568                 // clang-format off
0569             } else if (mimeType.inherits(QStringLiteral("application/tar"))
0570                     || mimeType.inherits(QStringLiteral("application/x-tar")) // BUG 450662
0571                     || mimeType.inherits(QStringLiteral("application/x-gzip"))
0572                     || mimeType.inherits(QStringLiteral("application/x-bzip"))
0573                     || mimeType.inherits(QStringLiteral("application/x-lzma"))
0574                     || mimeType.inherits(QStringLiteral("application/x-xz"))
0575                     || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar"))
0576                     || mimeType.inherits(QStringLiteral("application/x-compressed-tar"))) {
0577                 // clang-format on
0578                 archive.reset(new KTar(payloadfile));
0579             } else {
0580                 qCCritical(KNEWSTUFFCORE) << "Could not determine type of archive file '" << payloadfile << "'";
0581                 if (uncompressionOpt == AlwaysUncompress) {
0582                     Q_EMIT signalInstallationError(i18n("Could not determine the type of archive of the downloaded file %1", payloadfile));
0583                     return QStringList();
0584                 }
0585                 isarchive = false;
0586             }
0587 
0588             if (isarchive) {
0589                 bool success = archive->open(QIODevice::ReadOnly);
0590                 if (!success) {
0591                     qCCritical(KNEWSTUFFCORE) << "Cannot open archive file '" << payloadfile << "'";
0592                     if (uncompressionOpt == AlwaysUncompress) {
0593                         Q_EMIT signalInstallationError(
0594                             i18n("Failed to open the archive file %1. The reported error was: %2", payloadfile, archive->errorString()));
0595                         return QStringList();
0596                     }
0597                     // otherwise, just copy the file
0598                     isarchive = false;
0599                 }
0600 
0601                 if (isarchive) {
0602                     const KArchiveDirectory *dir = archive->directory();
0603                     // if there is more than an item in the file, and we are requested to do so
0604                     // put contents in a subdirectory with the same name as the file
0605                     QString installpath;
0606                     const bool isSubdir =
0607                         (uncompressionOpt == UncompressIntoSubdir || uncompressionOpt == UncompressIntoSubdirIfArchive) && dir->entries().count() > 1;
0608                     if (isSubdir) {
0609                         installpath = installdir + QLatin1Char('/') + QFileInfo(archive->fileName()).baseName();
0610                     } else {
0611                         installpath = installdir;
0612                     }
0613 
0614                     if (dir->copyTo(installpath)) {
0615                         // If we extracted the subdir we want to save it using the /* notation like we would when using the "archive" option
0616                         // Also if we use an (un)install command we only call it once with the folder as argument and not for each file
0617                         if (isSubdir) {
0618                             installedFiles << QDir(installpath).absolutePath() + QLatin1String("/*");
0619                         } else {
0620                             installedFiles << archiveEntries(installpath, dir);
0621                         }
0622                     } else {
0623                         qCWarning(KNEWSTUFFCORE) << "could not install" << entry.name() << "to" << installpath;
0624                     }
0625 
0626                     archive->close();
0627                     QFile::remove(payloadfile);
0628                 }
0629             }
0630         }
0631 
0632         qCDebug(KNEWSTUFFCORE) << "isarchive:" << isarchive;
0633 
0634         // some wallpapers are compressed, some aren't
0635         if ((!isarchive && standardResourceDirectory == QLatin1String("wallpaper"))
0636             || (uncompressionOpt == NeverUncompress || (uncompressionOpt == UncompressIfArchive && !isarchive)
0637                 || (uncompressionOpt == UncompressIntoSubdirIfArchive && !isarchive))) {
0638             // no decompress but move to target
0639 
0640             /// @todo when using KIO::get the http header can be accessed and it contains a real file name.
0641             // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names
0642             QUrl source = QUrl(entry.payload());
0643             qCDebug(KNEWSTUFFCORE) << "installing non-archive from" << source;
0644 #if KNEWSTUFF_BUILD_DEPRECATED_SINCE(5, 79)
0645             QString installfile;
0646             QString ext = source.fileName().section(QLatin1Char('.'), -1);
0647             if (customName) {
0648                 // Otherwise name can be interpreted as path
0649                 installfile = entry.name().remove(QLatin1Char('/'));
0650                 if (!entry.version().isEmpty()) {
0651                     installfile += QLatin1Char('-') + entry.version();
0652                 }
0653                 if (!ext.isEmpty()) {
0654                     installfile += QLatin1Char('.') + ext;
0655                 }
0656             } else {
0657                 installfile = source.fileName();
0658             }
0659             const QString installpath = QDir(installdir).filePath(installfile);
0660 #else
0661             const QString installpath = QDir(installdir).filePath(source.fileName());
0662 #endif
0663 
0664             qCDebug(KNEWSTUFFCORE) << "Install to file" << installpath;
0665             // FIXME: copy goes here (including overwrite checking)
0666             // FIXME: what must be done now is to update the cache *again*
0667             //        in order to set the new payload filename (on root tag only)
0668             //        - this might or might not need to take uncompression into account
0669             // FIXME: for updates, we might need to force an overwrite (that is, deleting before)
0670             QFile file(payloadfile);
0671             bool success = true;
0672             const bool update = ((entry.status() == KNS3::Entry::Updateable) || (entry.status() == KNS3::Entry::Updating));
0673 
0674             if (QFile::exists(installpath) && QDir::tempPath() != installdir) {
0675                 if (!update) {
0676                     Question question(Question::YesNoQuestion);
0677                     question.setEntry(entry);
0678                     question.setQuestion(i18n("This file already exists on disk (possibly due to an earlier failed download attempt). Continuing means "
0679                                               "overwriting it. Do you wish to overwrite the existing file?")
0680                                          + QStringLiteral("\n'") + installpath + QLatin1Char('\''));
0681                     question.setTitle(i18n("Overwrite File"));
0682                     if (question.ask() != Question::YesResponse) {
0683                         return QStringList();
0684                     }
0685                 }
0686                 success = QFile::remove(installpath);
0687             }
0688             if (success) {
0689                 // remove in case it's already present and in a temporary directory, so we get to actually use the path again
0690                 if (installpath.startsWith(QDir::tempPath())) {
0691                     QFile::remove(installpath);
0692                 }
0693                 success = file.rename(installpath);
0694                 qCDebug(KNEWSTUFFCORE) << "move:" << file.fileName() << "to" << installpath;
0695                 if (!success) {
0696                     qCWarning(KNEWSTUFFCORE) << file.errorString();
0697                 }
0698             }
0699             if (!success) {
0700                 Q_EMIT signalInstallationError(i18n("Unable to move the file %1 to the intended destination %2", payloadfile, installpath));
0701                 qCCritical(KNEWSTUFFCORE) << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'";
0702                 return QStringList();
0703             }
0704             installedFiles << installpath;
0705         }
0706     }
0707 
0708     return installedFiles;
0709 }
0710 
0711 QProcess *Installation::runPostInstallationCommand(const QString &installPath)
0712 {
0713     QString command(postInstallationCommand);
0714     QString fileArg(KShell::quoteArg(installPath));
0715     command.replace(QLatin1String("%f"), fileArg);
0716 
0717     qCDebug(KNEWSTUFFCORE) << "Run command:" << command;
0718 
0719     QProcess *ret = new QProcess(this);
0720     auto onProcessFinished = [this, command, ret](int exitcode, QProcess::ExitStatus status) {
0721         const QString output{QString::fromLocal8Bit(ret->readAllStandardError())};
0722         if (status == QProcess::CrashExit) {
0723             QString errorMessage = i18n("The installation failed while attempting to run the command:\n%1\n\nThe returned output was:\n%2", command, output);
0724             Q_EMIT signalInstallationError(errorMessage);
0725             qCCritical(KNEWSTUFFCORE) << "Process crashed with command: " << command;
0726         } else if (exitcode) {
0727             // 130 means Ctrl+C as an exit code this is interpreted by KNewStuff as cancel operation
0728             // and no error will be displayed to the user, BUG: 436355
0729             if (exitcode == 130) {
0730                 qCCritical(KNEWSTUFFCORE) << "Command '" << command << "' failed was aborted by the user";
0731             } else {
0732                 Q_EMIT signalInstallationError(
0733                     i18n("The installation failed with code %1 while attempting to run the command:\n%2\n\nThe returned output was:\n%3",
0734                          exitcode,
0735                          command,
0736                          output));
0737                 qCCritical(KNEWSTUFFCORE) << "Command '" << command << "' failed with code" << exitcode;
0738             }
0739         }
0740         sender()->deleteLater();
0741     };
0742     connect(ret, &QProcess::finished, this, onProcessFinished);
0743 
0744     QStringList args = KShell::splitArgs(command);
0745     ret->setProgram(args.takeFirst());
0746     ret->setArguments(args);
0747     ret->start();
0748     return ret;
0749 }
0750 
0751 void Installation::uninstall(EntryInternal entry)
0752 {
0753     // TODO Put this in pimpl or job
0754     const auto deleteFilesAndMarkAsUninstalled = [entry, this]() {
0755         KNS3::Entry::Status newStatus{KNS3::Entry::Deleted};
0756         const auto lst = entry.installedFiles();
0757         for (const QString &file : lst) {
0758             // This is used to delete the download location if there are no more entries
0759             QFileInfo info(file);
0760             if (info.isDir()) {
0761                 QDir().rmdir(file);
0762             } else if (file.endsWith(QLatin1String("/*"))) {
0763                 QDir dir(file.left(file.size() - 2));
0764                 bool worked = dir.removeRecursively();
0765                 if (!worked) {
0766                     qCWarning(KNEWSTUFFCORE) << "Couldn't remove" << dir.path();
0767                     continue;
0768                 }
0769             } else {
0770                 if (info.exists() || info.isSymLink()) {
0771                     bool worked = QFile::remove(file);
0772                     if (!worked) {
0773                         qWarning() << "unable to delete file " << file;
0774                         Q_EMIT signalInstallationFailed(
0775                             i18n("The removal of %1 failed, as the installed file %2 could not be automatically removed. You can attempt to manually delete "
0776                                  "this file, if you believe this is an error.",
0777                                  entry.name(),
0778                                  file));
0779                         // Assume that the uninstallation has failed, and reset the entry to an installed state
0780                         newStatus = KNS3::Entry::Installed;
0781                         break;
0782                     }
0783                 } else {
0784                     qWarning() << "unable to delete file " << file << ". file does not exist.";
0785                 }
0786             }
0787         }
0788         EntryInternal newEntry = entry;
0789         if (newStatus == KNS3::Entry::Deleted) {
0790             newEntry.setUnInstalledFiles(entry.installedFiles());
0791             newEntry.setInstalledFiles(QStringList());
0792         }
0793         newEntry.setStatus(newStatus);
0794         Q_EMIT signalEntryChanged(newEntry);
0795     };
0796 
0797     if (uncompressionSetting() == UseKPackageUncompression) {
0798         const auto lst = entry.installedFiles();
0799         if (lst.length() == 1) {
0800             const QString installedFile{lst.first()};
0801             if (QFileInfo(installedFile).isDir()) {
0802                 KPackage::PackageStructure structure;
0803                 KPackage::Package package(&structure);
0804                 package.setPath(installedFile);
0805                 if (package.isValid() && package.metadata().isValid()) {
0806 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 90)
0807                     QString serviceType = package.metadata().value(QStringLiteral("X-Plasma-ServiceType"));
0808                     const auto serviceTypes =
0809                         package.metadata().rawData().value(QLatin1String("KPlugin")).toObject().value(QLatin1String("ServiceTypes")).toVariant().toStringList();
0810                     if (serviceType.isEmpty() && !serviceTypes.isEmpty()) {
0811                         serviceType = serviceTypes.first();
0812                     }
0813                     if (serviceType.isEmpty()) {
0814                         serviceType = property("kpackageType").toString();
0815                     } else if (serviceType != property("kpackageType").toString()) {
0816                         qCWarning(KNEWSTUFFCORE) << "The package" << package.metadata().fileName()
0817                                                  << "defines a different kpackage type than the one defined by the app."
0818                                                  << "Please report this to the author of the addon.";
0819                     }
0820 #else
0821                     const QString serviceType = property("kpackageType").toString();
0822 #endif
0823                     if (!serviceType.isEmpty()) {
0824                         KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(serviceType);
0825                         if (structure) {
0826                             KPackage::Package installer = KPackage::Package(structure);
0827                             if (!installer.hasValidStructure()) {
0828                                 qWarning() << "Package serviceType" << serviceType << "not found";
0829                             }
0830                             QString packageRoot =
0831                                 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + installer.defaultPackageRoot();
0832                             KJob *removalJob = KPackageJob::uninstall(package.metadata().pluginId(), packageRoot, serviceType);
0833                             connect(removalJob, &KJob::result, this, [this, installedFile, installer, entry](KJob *job) {
0834                                 EntryInternal newEntry = entry;
0835                                 if (job->error() == KJob::NoError) {
0836                                     newEntry.setStatus(KNS3::Entry::Deleted);
0837                                     newEntry.setUnInstalledFiles(newEntry.installedFiles());
0838                                     newEntry.setInstalledFiles(QStringList());
0839                                     Q_EMIT signalEntryChanged(newEntry);
0840                                 } else {
0841                                     Q_EMIT signalInstallationFailed(i18n("Installation of %1 failed: %2", installedFile, job->errorText()));
0842                                 }
0843                             });
0844                             removalJob->start();
0845                         } else {
0846                             // no package structure
0847                             Q_EMIT signalInstallationFailed(
0848                                 i18n("The removal of %1 failed, as the installed package does not contain a correct KPackage structure.", installedFile));
0849                         }
0850                     } else {
0851                         // no service type
0852                         Q_EMIT signalInstallationFailed(
0853                             i18n("The removal of %1 failed, as the installed package is not a supported type (did you forget to install the KPackage support "
0854                                  "plugin for this type of package?)",
0855                                  installedFile));
0856                     }
0857                 } else {
0858                     // package or package metadata is invalid
0859                     Q_EMIT signalInstallationFailed(
0860                         i18n("The removal of %1 failed, as the installed package does not contain any useful meta information, which means it is not a valid "
0861                              "KPackage.",
0862                              entry.name()));
0863                 }
0864             } else {
0865                 QMimeDatabase db;
0866                 QMimeType mimeType = db.mimeTypeForFile(installedFile);
0867                 if (mimeType.inherits(QStringLiteral("application/zip")) || mimeType.inherits(QStringLiteral("application/x-compressed-tar"))
0868                     || mimeType.inherits(QStringLiteral("application/x-gzip")) || mimeType.inherits(QStringLiteral("application/x-tar"))
0869                     || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mimeType.inherits(QStringLiteral("application/x-xz"))
0870                     || mimeType.inherits(QStringLiteral("application/x-lzma"))) {
0871                     // Then it's one of the downloaded files installed with an old version of knewstuff prior to
0872                     // the native kpackage support being added, and we need to do some inspection-and-removal work...
0873                     KPackage::PackageStructure structure;
0874                     KPackage::Package package(&structure);
0875                     const QString serviceType{property("kpackageType").toString()};
0876                     package.setPath(installedFile);
0877                     if (package.isValid() && package.metadata().isValid()) {
0878                         // try and load the kpackage and sniff the expected location of its installation, and ask KPackage to remove that thing, if it's there
0879                         KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(serviceType);
0880                         if (structure) {
0881                             KPackage::Package installer = KPackage::Package(structure);
0882                             if (installer.hasValidStructure()) {
0883                                 QString packageRoot =
0884                                     QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + installer.defaultPackageRoot();
0885                                 qCDebug(KNEWSTUFFCORE) << "About to attempt to uninstall" << package.metadata().pluginId() << "from" << packageRoot;
0886                                 // Frankly, we don't care whether or not this next step succeeds, and it can just fizzle if it wants
0887                                 // to. This is a cleanup step, and if it fails, it's just not really important.
0888                                 KPackageJob::uninstall(package.metadata().pluginId(), packageRoot, serviceType);
0889                             }
0890                         }
0891                     }
0892                     // Also get rid of the downloaded file, and tell everything they've gone
0893                     if (QFile::remove(installedFile)) {
0894                         entry.setStatus(KNS3::Entry::Deleted);
0895                         entry.setUnInstalledFiles(entry.installedFiles());
0896                         entry.setInstalledFiles(QStringList());
0897                         Q_EMIT signalEntryChanged(entry);
0898                     } else {
0899                         Q_EMIT signalInstallationFailed(
0900                             i18n("The removal of %1 failed, as the downloaded file %2 could not be automatically removed.", entry.name(), installedFile));
0901                     }
0902                 } else {
0903                     // Not sure what's installed, but it's not a KPackage, not a lot we can do with this...
0904                     Q_EMIT signalInstallationFailed(
0905                         i18n("The removal of %1 failed, due to the installed file not being a KPackage. The file in question was %2, and you can attempt to "
0906                              "delete it yourself, if you are certain that it is not needed.",
0907                              entry.name(),
0908                              installedFile));
0909                 }
0910             }
0911         } else {
0912             Q_EMIT signalInstallationFailed(
0913                 i18n("The removal of %1 failed, as there seems to somehow be more than one thing installed, which is not supposed to be possible for KPackage "
0914                      "based entries.",
0915                      entry.name()));
0916         }
0917         deleteFilesAndMarkAsUninstalled();
0918     } else {
0919         const auto lst = entry.installedFiles();
0920         // If there is an uninstall script, make sure it runs without errors
0921         if (!uninstallCommand.isEmpty()) {
0922             bool validFileExisted = false;
0923             for (const QString &file : lst) {
0924                 QString filePath = file;
0925                 bool validFile = QFileInfo::exists(filePath);
0926                 // If we have uncompressed a subdir we write <path>/* in the config, but when calling a script
0927                 // we want to convert this to a normal path
0928                 if (!validFile && file.endsWith(QLatin1Char('*'))) {
0929                     filePath = filePath.left(filePath.lastIndexOf(QLatin1Char('*')));
0930                     validFile = QFileInfo::exists(filePath);
0931                 }
0932                 if (validFile) {
0933                     validFileExisted = true;
0934                     QString fileArg(KShell::quoteArg(filePath));
0935                     QString command(uninstallCommand);
0936                     command.replace(QLatin1String("%f"), fileArg);
0937 
0938                     QStringList args = KShell::splitArgs(command);
0939                     const QString program = args.takeFirst();
0940                     QProcess *process = new QProcess(this);
0941                     process->start(program, args);
0942                     auto onProcessFinished = [this, command, process, entry, deleteFilesAndMarkAsUninstalled](int, QProcess::ExitStatus status) {
0943                         if (status == QProcess::CrashExit) {
0944                             const QString processOutput = QString::fromLocal8Bit(process->readAllStandardError());
0945                             const QString err = i18n(
0946                                 "The uninstallation process failed to successfully run the command %1\n"
0947                                 "The output of was: \n%2\n"
0948                                 "If you think this is incorrect, you can continue or cancel the uninstallation process",
0949                                 KShell::quoteArg(command),
0950                                 processOutput);
0951                             Q_EMIT signalInstallationError(err);
0952                             // Ask the user if he wants to continue, even though the script failed
0953                             Question question(Question::ContinueCancelQuestion);
0954                             question.setEntry(entry);
0955                             question.setQuestion(err);
0956                             Question::Response response = question.ask();
0957                             if (response == Question::CancelResponse) {
0958                                 // Use can delete files manually
0959                                 EntryInternal newEntry = entry;
0960                                 newEntry.setStatus(KNS3::Entry::Installed);
0961                                 Q_EMIT signalEntryChanged(newEntry);
0962                                 return;
0963                             }
0964                         } else {
0965                             qCDebug(KNEWSTUFFCORE) << "Command executed successfully:" << command;
0966                         }
0967                         deleteFilesAndMarkAsUninstalled();
0968                     };
0969                     connect(process, &QProcess::finished, this, onProcessFinished);
0970                 }
0971             }
0972             // If the entry got deleted, but the RemoveDeadEntries option was not selected this case can happen
0973             if (!validFileExisted) {
0974                 deleteFilesAndMarkAsUninstalled();
0975             }
0976         } else {
0977             deleteFilesAndMarkAsUninstalled();
0978         }
0979     }
0980 }
0981 
0982 Installation::UncompressionOptions Installation::uncompressionSetting() const
0983 {
0984     return property("uncompressSetting").value<UncompressionOptions>();
0985 }
0986 
0987 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 31)
0988 void Installation::slotInstallationVerification(int result)
0989 {
0990     Q_UNUSED(result)
0991     // Deprecated, was wired up to defunct Security class.
0992 }
0993 #endif
0994 
0995 QStringList Installation::archiveEntries(const QString &path, const KArchiveDirectory *dir)
0996 {
0997     QStringList files;
0998     const auto lst = dir->entries();
0999     for (const QString &entry : lst) {
1000         const auto currentEntry = dir->entry(entry);
1001 
1002         const QString childPath = QDir(path).filePath(entry);
1003         if (currentEntry->isFile()) {
1004             files << childPath;
1005         } else if (currentEntry->isDirectory()) {
1006             files << childPath + QStringLiteral("/*");
1007         }
1008     }
1009     return files;
1010 }
1011 
1012 #include "moc_installation.cpp"