diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) find_package(KF5KIO ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ItemViews ${KF5_DEP_VERSION} REQUIRED) +find_package(KF5Package ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Service ${KF5_DEP_VERSION} REQUIRED) find_package(KF5TextWidgets ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WidgetsAddons ${KF5_DEP_VERSION} REQUIRED) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -73,6 +73,7 @@ KF5::Archive # For decompressing archives KF5::I18n # For translations KF5::ConfigCore + KF5::Package Qt5::Gui # For QImage ) diff --git a/src/core/cache.cpp b/src/core/cache.cpp --- a/src/core/cache.cpp +++ b/src/core/cache.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -235,12 +236,14 @@ metastream << doc.toByteArray(); setProperty("dirty", false); + } void Cache::registerChangedEntry(const KNSCore::EntryInternal &entry) { setProperty("dirty", true); cache.insert(entry); + QTimer::singleShot(1000, this, [this](){ writeRegistry(); }); } void Cache::insertRequest(const KNSCore::Provider::SearchRequest &request, const KNSCore::EntryInternal::List &entries) diff --git a/src/core/installation.cpp b/src/core/installation.cpp --- a/src/core/installation.cpp +++ b/src/core/installation.cpp @@ -33,6 +33,11 @@ #include "krandom.h" #include "kshell.h" +#include +#include +#include +// also ensure the cache is updated (load cache, mark kpackagetool removed items as uninstalled? (check what makes best sense)) + #include #include "klocalizedstring.h" #include @@ -68,8 +73,8 @@ else if (uncompresssetting == QLatin1String("true")) { uncompresssetting = QStringLiteral("always"); } - if (uncompresssetting != QLatin1String("always") && uncompresssetting != QLatin1String("archive") && uncompresssetting != QLatin1String("never") && uncompresssetting != QLatin1String("subdir")) { - qCCritical(KNEWSTUFFCORE) << "invalid Uncompress setting chosen, must be one of: subdir, always, archive, or never"; + if (uncompresssetting != QLatin1String("always") && uncompresssetting != QLatin1String("archive") && uncompresssetting != QLatin1String("never") && uncompresssetting != QLatin1String("subdir") && uncompresssetting != QLatin1String("kpackage")) { + qCCritical(KNEWSTUFFCORE) << "invalid Uncompress setting chosen, must be one of: subdir, always, archive, never, or kpackage"; return false; } uncompression = uncompresssetting; @@ -297,40 +302,42 @@ QString targetPath = targetInstallationPath(); QStringList installedFiles = installDownloadedFileAndUncompress(entry, downloadedFile, targetPath); - if (installedFiles.isEmpty()) { - if (entry.status() == KNS3::Entry::Installing) { - entry.setStatus(KNS3::Entry::Downloadable); - } else if (entry.status() == KNS3::Entry::Updating) { - entry.setStatus(KNS3::Entry::Updateable); + if (uncompression != QLatin1String("kpackage")) { + if (installedFiles.isEmpty()) { + if (entry.status() == KNS3::Entry::Installing) { + entry.setStatus(KNS3::Entry::Downloadable); + } else if (entry.status() == KNS3::Entry::Updating) { + entry.setStatus(KNS3::Entry::Updateable); + } + emit signalEntryChanged(entry); + emit signalInstallationFailed(i18n("Could not install \"%1\": file not found.", entry.name())); + return; } - emit signalEntryChanged(entry); - emit signalInstallationFailed(i18n("Could not install \"%1\": file not found.", entry.name())); - return; - } - entry.setInstalledFiles(installedFiles); + entry.setInstalledFiles(installedFiles); - auto installationFinished = [this, entry]() { - EntryInternal newentry = entry; - // update version and release date to the new ones - if (newentry.status() == KNS3::Entry::Updating) { - if (!newentry.updateVersion().isEmpty()) { - newentry.setVersion(newentry.updateVersion()); - } - if (newentry.updateReleaseDate().isValid()) { - newentry.setReleaseDate(newentry.updateReleaseDate()); + auto installationFinished = [this, entry]() { + EntryInternal newentry = entry; + // update version and release date to the new ones + if (newentry.status() == KNS3::Entry::Updating) { + if (!newentry.updateVersion().isEmpty()) { + newentry.setVersion(newentry.updateVersion()); + } + if (newentry.updateReleaseDate().isValid()) { + newentry.setReleaseDate(newentry.updateReleaseDate()); + } } - } - newentry.setStatus(KNS3::Entry::Installed); - emit signalEntryChanged(newentry); - emit signalInstallationFinished(); - }; - if (!postInstallationCommand.isEmpty()) { - QProcess* p = runPostInstallationCommand(installedFiles.size() == 1 ? installedFiles.first() : targetPath); - connect(p, static_cast(&QProcess::finished), this, installationFinished); - } else { - installationFinished(); + newentry.setStatus(KNS3::Entry::Installed); + emit signalEntryChanged(newentry); + emit signalInstallationFinished(); + }; + if (!postInstallationCommand.isEmpty()) { + QProcess* p = runPostInstallationCommand(installedFiles.size() == 1 ? installedFiles.first() : targetPath); + connect(p, static_cast(&QProcess::finished), this, installationFinished); + } else { + installationFinished(); + } } } @@ -425,141 +432,214 @@ bool isarchive = true; // respect the uncompress flag in the knsrc - if (uncompression == QLatin1String("always") || uncompression == QLatin1String("archive") || uncompression == QLatin1String("subdir")) { - // this is weird but a decompression is not a single name, so take the path instead - QMimeDatabase db; - QMimeType mimeType = db.mimeTypeForFile(payloadfile); - qCDebug(KNEWSTUFFCORE) << "Postinstallation: uncompress the file"; - - // FIXME: check for overwriting, malicious archive entries (../foo) etc. - // FIXME: KArchive should provide "safe mode" for this! - QScopedPointer archive; - - if (mimeType.inherits(QStringLiteral("application/zip"))) { - archive.reset(new KZip(payloadfile)); - } else if (mimeType.inherits(QStringLiteral("application/tar")) - || mimeType.inherits(QStringLiteral("application/x-gzip")) - || mimeType.inherits(QStringLiteral("application/x-bzip")) - || mimeType.inherits(QStringLiteral("application/x-lzma")) - || mimeType.inherits(QStringLiteral("application/x-xz")) - || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) - || mimeType.inherits(QStringLiteral("application/x-compressed-tar"))) { - archive.reset(new KTar(payloadfile)); - } else { - qCCritical(KNEWSTUFFCORE) << "Could not determine type of archive file '" << payloadfile << "'"; - if (uncompression == QLatin1String("always")) { - emit signalInstallationError(i18n("Could not determine the type of archive of the downloaded file %1", payloadfile)); - return QStringList(); + if (uncompression == QLatin1String("kpackage")) { + qCDebug(KNEWSTUFFCORE) << "Using KPackage for installation"; + KPackage::PackageStructure structure; + KPackage::Package package(&structure); + QString serviceType; + package.setPath(payloadfile); + if (package.isValid() && package.metadata().isValid()) { + qCDebug(KNEWSTUFFCORE) << "Package metadata is valid"; + serviceType = package.metadata().value(QStringLiteral("X-Plasma-ServiceType")); + if (serviceType.isEmpty() && !package.metadata().serviceTypes().isEmpty()) { + serviceType = package.metadata().serviceTypes().first(); } - isarchive = false; + if (!serviceType.isEmpty()) { + qCDebug(KNEWSTUFFCORE) << "Service type discovered as" << serviceType; + KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(serviceType); + if (structure) { + KPackage::Package installer = KPackage::Package(structure); + if (installer.hasValidStructure()) { + QString packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + installer.defaultPackageRoot(); + qCDebug(KNEWSTUFFCORE) << "About to attempt to install" << package.metadata().pluginId() << "into" << packageRoot; + const QString expectedDir{packageRoot + package.metadata().pluginId()}; + KJob *installJob = installer.update(payloadfile, packageRoot); + // TODO KF6 Really, i would prefer to make more functions to handle this, but as this is + // an exported class, i'd rather not pollute the public namespace with internal functions, + // and we don't have a pimpl, so... we'll just have to deal with it for now + connect(installJob, &KJob::result, this, [this,entry,payloadfile,expectedDir](KJob* job){ + if (job->error() == KJob::NoError) { + if (QFile::exists(expectedDir)) { + EntryInternal newentry = entry; + newentry.setInstalledFiles(QStringList{expectedDir}); + // update version and release date to the new ones + if (newentry.status() == KNS3::Entry::Updating) { + if (!newentry.updateVersion().isEmpty()) { + newentry.setVersion(newentry.updateVersion()); + } + if (newentry.updateReleaseDate().isValid()) { + newentry.setReleaseDate(newentry.updateReleaseDate()); + } + } + newentry.setStatus(KNS3::Entry::Installed); + emit signalEntryChanged(newentry); + emit signalInstallationFinished(); + qCDebug(KNEWSTUFFCORE) << "Install job finished with no error and we now have files" << expectedDir; + } else { + emit signalInstallationFailed(i18n("The installation of %1 failed to create the expected new directory %2").arg(payloadfile).arg(expectedDir)); + qCDebug(KNEWSTUFFCORE) << "Install job finished with no error, but we do not have the expected new directory" << expectedDir; + } + } else { + emit signalInstallationFailed(i18n("Installation of %1 failed: %2", payloadfile, job->errorText())); + qCDebug(KNEWSTUFFCORE) << "Install job finished with error state" << job->error() << "and description" << job->error(); + } + }); + } else { + emit signalInstallationError(i18n("The installation of %1 failed, as the service type %2 was not accepted by the system (did you forget to install the KPackage support plugin for this type of package?)", payloadfile, serviceType)); + qCWarning(KNEWSTUFFCORE) << "Package serviceType" << serviceType << "not found"; + } + } else { + // no package structure + emit signalInstallationError(i18n("The installation of %1 failed, as the downloaded package does not contain a correct KPackage structure.", payloadfile)); + qCWarning(KNEWSTUFFCORE) << "Could not load the package structure for KPackage service type" << serviceType; + } + } else { + // no service type + emit signalInstallationError(i18n("The installation of %1 failed, as the downloaded package does not list a service type.", payloadfile)); + qCWarning(KNEWSTUFFCORE) << "No service type listed in" << payloadfile; + } + } else { + // package or package metadata is invalid + emit signalInstallationError(i18n("The installation of %1 failed, as the downloaded package does not contain any useful meta information, which means it is not a valid KPackage.", payloadfile)); + qCWarning(KNEWSTUFFCORE) << "No valid meta information (which suggests no valid KPackage) found in" << payloadfile; } - - if (isarchive) { - bool success = archive->open(QIODevice::ReadOnly); - if (!success) { - qCCritical(KNEWSTUFFCORE) << "Cannot open archive file '" << payloadfile << "'"; + } else { + if (uncompression == QLatin1String("always") || uncompression == QLatin1String("archive") || uncompression == QLatin1String("subdir")) { + // this is weird but a decompression is not a single name, so take the path instead + QMimeDatabase db; + QMimeType mimeType = db.mimeTypeForFile(payloadfile); + qCDebug(KNEWSTUFFCORE) << "Postinstallation: uncompress the file"; + + // FIXME: check for overwriting, malicious archive entries (../foo) etc. + // FIXME: KArchive should provide "safe mode" for this! + QScopedPointer archive; + + if (mimeType.inherits(QStringLiteral("application/zip"))) { + archive.reset(new KZip(payloadfile)); + } else if (mimeType.inherits(QStringLiteral("application/tar")) + || mimeType.inherits(QStringLiteral("application/x-gzip")) + || mimeType.inherits(QStringLiteral("application/x-bzip")) + || mimeType.inherits(QStringLiteral("application/x-lzma")) + || mimeType.inherits(QStringLiteral("application/x-xz")) + || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) + || mimeType.inherits(QStringLiteral("application/x-compressed-tar"))) { + archive.reset(new KTar(payloadfile)); + } else { + qCCritical(KNEWSTUFFCORE) << "Could not determine type of archive file '" << payloadfile << "'"; if (uncompression == QLatin1String("always")) { - emit signalInstallationError(i18n("Failed to open the archive file %1. The reported error was: %2", payloadfile, archive->errorString())); + emit signalInstallationError(i18n("Could not determine the type of archive of the downloaded file %1", payloadfile)); return QStringList(); } - // otherwise, just copy the file isarchive = false; } if (isarchive) { - const KArchiveDirectory *dir = archive->directory(); - //if there is more than an item in the file, and we are requested to do so - //put contents in a subdirectory with the same name as the file - QString installpath; - if (uncompression == QLatin1String("subdir") && dir->entries().count() > 1) { - installpath = installdir + QLatin1Char('/') + QFileInfo(archive->fileName()).baseName(); - } else { - installpath = installdir; + bool success = archive->open(QIODevice::ReadOnly); + if (!success) { + qCCritical(KNEWSTUFFCORE) << "Cannot open archive file '" << payloadfile << "'"; + if (uncompression == QLatin1String("always")) { + emit signalInstallationError(i18n("Failed to open the archive file %1. The reported error was: %2", payloadfile, archive->errorString())); + return QStringList(); + } + // otherwise, just copy the file + isarchive = false; } - if (dir->copyTo(installpath)) { - installedFiles << archiveEntries(installpath, dir); - installedFiles << installpath + QLatin1Char('/'); - } else - qCWarning(KNEWSTUFFCORE) << "could not install" << entry.name() << "to" << installpath; - - archive->close(); - QFile::remove(payloadfile); + if (isarchive) { + const KArchiveDirectory *dir = archive->directory(); + //if there is more than an item in the file, and we are requested to do so + //put contents in a subdirectory with the same name as the file + QString installpath; + if (uncompression == QLatin1String("subdir") && dir->entries().count() > 1) { + installpath = installdir + QLatin1Char('/') + QFileInfo(archive->fileName()).baseName(); + } else { + installpath = installdir; + } + + if (dir->copyTo(installpath)) { + installedFiles << archiveEntries(installpath, dir); + installedFiles << installpath + QLatin1Char('/'); + } else + qCWarning(KNEWSTUFFCORE) << "could not install" << entry.name() << "to" << installpath; + + archive->close(); + QFile::remove(payloadfile); + } } } - } - qCDebug(KNEWSTUFFCORE) << "isarchive: " << isarchive; - - //some wallpapers are compressed, some aren't - if ((!isarchive && standardResourceDirectory == QLatin1String("wallpaper")) || - (uncompression == QLatin1String("never") || (uncompression == QLatin1String("archive") && !isarchive))) { - // no decompress but move to target - - /// @todo when using KIO::get the http header can be accessed and it contains a real file name. - // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names - QUrl source = QUrl(entry.payload()); - qCDebug(KNEWSTUFFCORE) << "installing non-archive from " << source.url(); - QString installfile; - QString ext = source.fileName().section(QLatin1Char('.'), -1); - if (customName) { - installfile = entry.name(); - installfile += QLatin1Char('-') + entry.version(); - if (!ext.isEmpty()) { - installfile += QLatin1Char('.') + ext; - } - } else { - // TODO HACK This is a hack, the correct way of fixing it would be doing the KIO::get - // and using the http headers if they exist to get the file name, but as discussed in - // Randa this is not going to happen anytime soon (if ever) so go with the hack - if (source.url().startsWith(QLatin1String("http://newstuff.kde.org/cgi-bin/hotstuff-access?file="))) { - installfile = QUrlQuery(source).queryItemValue(QStringLiteral("file")); - int lastSlash = installfile.lastIndexOf(QLatin1Char('/')); - if (lastSlash >= 0) { - installfile = installfile.mid(lastSlash); + qCDebug(KNEWSTUFFCORE) << "isarchive: " << isarchive; + + //some wallpapers are compressed, some aren't + if ((!isarchive && standardResourceDirectory == QLatin1String("wallpaper")) || + (uncompression == QLatin1String("never") || (uncompression == QLatin1String("archive") && !isarchive))) { + // no decompress but move to target + + /// @todo when using KIO::get the http header can be accessed and it contains a real file name. + // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names + QUrl source = QUrl(entry.payload()); + qCDebug(KNEWSTUFFCORE) << "installing non-archive from " << source.url(); + QString installfile; + QString ext = source.fileName().section(QLatin1Char('.'), -1); + if (customName) { + installfile = entry.name(); + installfile += QLatin1Char('-') + entry.version(); + if (!ext.isEmpty()) { + installfile += QLatin1Char('.') + ext; + } + } else { + // TODO HACK This is a hack, the correct way of fixing it would be doing the KIO::get + // and using the http headers if they exist to get the file name, but as discussed in + // Randa this is not going to happen anytime soon (if ever) so go with the hack + if (source.url().startsWith(QLatin1String("http://newstuff.kde.org/cgi-bin/hotstuff-access?file="))) { + installfile = QUrlQuery(source).queryItemValue(QStringLiteral("file")); + int lastSlash = installfile.lastIndexOf(QLatin1Char('/')); + if (lastSlash >= 0) { + installfile = installfile.mid(lastSlash); + } + } + if (installfile.isEmpty()) { + installfile = source.fileName(); } } - if (installfile.isEmpty()) { - installfile = source.fileName(); + QString installpath = installdir + QLatin1Char('/') + installfile; + + qCDebug(KNEWSTUFFCORE) << "Install to file " << installpath; + // FIXME: copy goes here (including overwrite checking) + // FIXME: what must be done now is to update the cache *again* + // in order to set the new payload filename (on root tag only) + // - this might or might not need to take uncompression into account + // FIXME: for updates, we might need to force an overwrite (that is, deleting before) + QFile file(payloadfile); + bool success = true; + const bool update = ((entry.status() == KNS3::Entry::Updateable) || (entry.status() == KNS3::Entry::Updating)); + + if (QFile::exists(installpath) && QDir::tempPath() != installdir) { + if (!update) { + Question question(Question::YesNoQuestion); + question.setQuestion(i18n("This file already exists on disk (possibly due to an earlier failed download attempt). Continuing means overwriting it. Do you wish to overwrite the existing file?") + QStringLiteral("\n'") + installpath + QLatin1Char('\'')); + question.setTitle(i18n("Overwrite File")); + if(question.ask() != Question::YesResponse) { + return QStringList(); + } + } + success = QFile::remove(installpath); } - } - QString installpath = installdir + QLatin1Char('/') + installfile; - - qCDebug(KNEWSTUFFCORE) << "Install to file " << installpath; - // FIXME: copy goes here (including overwrite checking) - // FIXME: what must be done now is to update the cache *again* - // in order to set the new payload filename (on root tag only) - // - this might or might not need to take uncompression into account - // FIXME: for updates, we might need to force an overwrite (that is, deleting before) - QFile file(payloadfile); - bool success = true; - const bool update = ((entry.status() == KNS3::Entry::Updateable) || (entry.status() == KNS3::Entry::Updating)); - - if (QFile::exists(installpath) && QDir::tempPath() != installdir) { - if (!update) { - Question question(Question::YesNoQuestion); - question.setQuestion(i18n("This file already exists on disk (possibly due to an earlier failed download attempt). Continuing means overwriting it. Do you wish to overwrite the existing file?") + QStringLiteral("\n'") + installpath + QLatin1Char('\'')); - question.setTitle(i18n("Overwrite File")); - if(question.ask() != Question::YesResponse) { - return QStringList(); + if (success) { + //remove in case it's already present and in a temporary directory, so we get to actually use the path again + if (installpath.startsWith(QDir::tempPath())) { + file.remove(installpath); } + success = file.rename(installpath); + qCDebug(KNEWSTUFFCORE) << "move: " << file.fileName() << " to " << installpath; } - success = QFile::remove(installpath); - } - if (success) { - //remove in case it's already present and in a temporary directory, so we get to actually use the path again - if (installpath.startsWith(QDir::tempPath())) { - file.remove(installpath); + if (!success) { + emit signalInstallationError(i18n("Unable to move the file %1 to the intended destination %2", payloadfile, installpath)); + qCCritical(KNEWSTUFFCORE) << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'"; + return QStringList(); } - success = file.rename(installpath); - qCDebug(KNEWSTUFFCORE) << "move: " << file.fileName() << " to " << installpath; - } - if (!success) { - emit signalInstallationError(i18n("Unable to move the file %1 to the intended destination %2", payloadfile, installpath)); - qCCritical(KNEWSTUFFCORE) << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'"; - return QStringList(); + installedFiles << installpath; } - installedFiles << installpath; } } return installedFiles; @@ -618,37 +698,98 @@ } } - const auto lst = entry.installedFiles(); - for (const QString &file : lst) { - if (file.endsWith(QLatin1Char('/'))) { - QDir dir; - bool worked = dir.rmdir(file); - if (!worked) { - // Maybe directory contains user created files, ignore it - continue; - } - } else if (file.endsWith(QLatin1String("/*"))) { - QDir dir(file.left(file.size()-2)); - bool worked = dir.removeRecursively(); - if (!worked) { - qCWarning(KNEWSTUFFCORE) << "Couldn't remove" << dir.path(); - continue; + if (uncompression == QLatin1String("kpackage")) { + const auto lst = entry.installedFiles(); + if (lst.length() == 1) { + const QString installedFile{lst.first()}; + KPackage::PackageStructure structure; + KPackage::Package package(&structure); + QString serviceType; + package.setPath(installedFile); + if (package.isValid() && package.metadata().isValid()) { + serviceType = package.metadata().value(QStringLiteral("X-Plasma-ServiceType")); + if (serviceType.isEmpty() && !package.metadata().serviceTypes().isEmpty()) { + serviceType = package.metadata().serviceTypes().first(); + } + if (!serviceType.isEmpty()) { + KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(serviceType); + if (structure) { + KPackage::Package installer = KPackage::Package(structure); + if (!installer.hasValidStructure()) { + qWarning() << "Package serviceType" << serviceType << "not found"; + } + + QString packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + installer.defaultPackageRoot(); + QStringList pluginTypes{serviceType}; + + bool completed{false}; + QString removedFile; + KJob *installJob = installer.uninstall(installedFile, packageRoot); + connect(installJob, &KJob::finished, [&completed](){ completed = true; }); + connect(installJob, &KJob::result, this, [this,installedFile,installer,&removedFile,&completed](KJob* job){ + if (job->error() == KJob::NoError) { + removedFile = installer.path(); + } else { + emit signalInstallationFailed(i18n("Installation of %1 failed: %2", installedFile, job->errorText())); + } + completed = true; + }); + installJob->start(); + while(!completed) { + qApp->processEvents(); + } + if (!removedFile.isEmpty()) { + entry.setUnInstalledFiles(entry.installedFiles()); + entry.setInstalledFiles(QStringList()); + } + } else { + // no package structure + emit signalInstallationError(i18n("The installation of %1 failed, as the downloaded package does not contain a correct KPackage structure.", installedFile)); + } + } else { + // no service type + emit signalInstallationError(i18n("The installation of %1 failed, as the downloaded package is not a supported type (did you forget to install the KPackage support plugin for this type of package?)", installedFile)); + } + } else { + // package or package metadata is invalid + emit signalInstallationError(i18n("The installation of %1 failed, as the downloaded package does not contain any useful meta information, which means it is not a valid KPackage.", entry.name())); } } else { - QFileInfo info(file); - if (info.exists() || info.isSymLink()) { - bool worked = QFile::remove(file); + emit signalInstallationError(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 based entries.", entry.name())); + } + } else { + const auto lst = entry.installedFiles(); + for (const QString &file : lst) { + if (file.endsWith(QLatin1Char('/'))) { + QDir dir; + bool worked = dir.rmdir(file); + if (!worked) { + // Maybe directory contains user created files, ignore it + continue; + } + } else if (file.endsWith(QLatin1String("/*"))) { + QDir dir(file.left(file.size()-2)); + bool worked = dir.removeRecursively(); if (!worked) { - qWarning() << "unable to delete file " << file; - return; + qCWarning(KNEWSTUFFCORE) << "Couldn't remove" << dir.path(); + continue; } } else { - qWarning() << "unable to delete file " << file << ". file does not exist."; + QFileInfo info(file); + if (info.exists() || info.isSymLink()) { + bool worked = QFile::remove(file); + if (!worked) { + qWarning() << "unable to delete file " << file; + return; + } + } else { + qWarning() << "unable to delete file " << file << ". file does not exist."; + } } } + entry.setUnInstalledFiles(entry.installedFiles()); + entry.setInstalledFiles(QStringList()); } - entry.setUnInstalledFiles(entry.installedFiles()); - entry.setInstalledFiles(QStringList()); emit signalEntryChanged(entry); }