File indexing completed on 2024-04-21 03:52:24

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@kde.org>
0004     SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include <QCoreApplication>
0010 #include <QDebug>
0011 #include <QFile>
0012 #include <QFileInfo>
0013 #include <QStandardPaths>
0014 #include <QTimer>
0015 #include <QUrl>
0016 #include <QUrlQuery>
0017 
0018 #include <KLocalizedString>
0019 
0020 #include <KNotification>
0021 
0022 #include <KNSCore/EngineBase>
0023 #include <KNSCore/Question>
0024 #include <KNSCore/QuestionManager>
0025 #include <KNSCore/ResultsStream>
0026 #include <KNSCore/Transaction>
0027 
0028 #include "knshandlerversion.h"
0029 
0030 /**
0031  * Unfortunately there are two knsrc files for the window decorations, but only one is used in the KCM.
0032  * But both are used by third parties, consequently we can not remove one. To solve this we create a symlink
0033  * which links the old cache file to the new cache file, which is exposed on the GUI.
0034  * This way users can again remove window decorations that are installed as a dependency of a global theme.
0035  * BUG: 414570
0036  */
0037 void createSymlinkForWindowDecorations()
0038 {
0039     QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/knewstuff3/aurorae.knsregistry"));
0040     // If we have created the symbolic link already we can exit the function here
0041     if (info.isSymbolicLink()) {
0042         return;
0043     }
0044     // Delete this file, it the KNS entries are not exposed in any GUI
0045     if (info.exists()) {
0046         QFile::remove(info.absoluteFilePath());
0047     }
0048     QFileInfo newFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/knewstuff3/window-decorations.knsregistry"));
0049     QFile file(newFileInfo.absoluteFilePath());
0050     // Make sure that the file exists
0051     if (!newFileInfo.exists()) {
0052         file.open(QFile::WriteOnly);
0053         file.close();
0054     }
0055     file.link(info.absoluteFilePath());
0056 }
0057 
0058 int main(int argc, char **argv)
0059 {
0060     createSymlinkForWindowDecorations();
0061     QCoreApplication app(argc, argv);
0062     app.setApplicationName(QStringLiteral("kpackage-knshandler"));
0063     app.setApplicationVersion(knshandlerversion);
0064     app.setQuitLockEnabled(false);
0065     Q_ASSERT(app.arguments().count() == 2);
0066 
0067 #ifdef TEST
0068     QStandardPaths::setTestModeEnabled(true);
0069 #endif
0070 
0071     const QUrl url(app.arguments().last());
0072     Q_ASSERT(url.isValid());
0073     Q_ASSERT(url.scheme() == QLatin1String("kns"));
0074 
0075     QString knsname;
0076     const QStringList availableConfigFiles = KNSCore::EngineBase::availableConfigFiles();
0077     auto knsNameIt = std::find_if(availableConfigFiles.begin(), availableConfigFiles.end(), [&url](const QString &availableFile) {
0078         return availableFile.endsWith(QLatin1String("/") + url.host());
0079     });
0080 
0081     if (knsNameIt == availableConfigFiles.end()) {
0082         qWarning() << "couldn't find knsrc file for" << url.host();
0083         return 1;
0084     } else {
0085         knsname = *knsNameIt;
0086     }
0087 
0088     const auto pathParts = url.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
0089     if (pathParts.size() != 2) {
0090         qWarning() << "wrong format in the url path" << url << pathParts;
0091         return 1;
0092     }
0093     const auto providerid = pathParts.at(0);
0094     const auto entryid = pathParts.at(1);
0095     int linkid = 1;
0096     if (url.hasQuery()) {
0097         QUrlQuery query(url);
0098         if (query.hasQueryItem(QStringLiteral("linkid"))) {
0099             bool ok;
0100             linkid = query.queryItemValue(QStringLiteral("linkid")).toInt(&ok);
0101             if (!ok) {
0102                 qWarning() << "linkid is not an integer" << url << pathParts;
0103                 return 1;
0104             }
0105         }
0106     }
0107 
0108     KNSCore::EngineBase engine;
0109     int installedCount = 0;
0110     QObject::connect(KNSCore::QuestionManager::instance(), &KNSCore::QuestionManager::askQuestion, &engine, [](KNSCore::Question *question) {
0111         auto discardQuestion = [question]() {
0112             question->setResponse(KNSCore::Question::InvalidResponse);
0113         };
0114         switch (question->questionType()) {
0115         case KNSCore::Question::YesNoQuestion: {
0116             auto f = KNotification::event(KNotification::StandardEvent::Notification, question->title(), question->question());
0117 
0118             auto *yes = f->addAction(i18n("Yes"));
0119             QObject::connect(yes, &KNotificationAction::activated, question, [question] {
0120                 question->setResponse(KNSCore::Question::YesResponse);
0121             });
0122 
0123             auto *no = f->addAction(i18n("Yes"));
0124             QObject::connect(no, &KNotificationAction::activated, question, [question] {
0125                 question->setResponse(KNSCore::Question::NoResponse);
0126             });
0127 
0128             QObject::connect(f, &KNotification::closed, question, discardQuestion);
0129         } break;
0130         case KNSCore::Question::ContinueCancelQuestion: {
0131             auto f = KNotification::event(KNotification::StandardEvent::Notification, question->title(), question->question());
0132 
0133             auto *continueAction = f->addAction(i18n("Continue"));
0134             QObject::connect(continueAction, &KNotificationAction::activated, question, [question]() {
0135                 question->setResponse(KNSCore::Question::ContinueResponse);
0136             });
0137 
0138             auto *cancelAction = f->addAction(i18n("Cancel"));
0139             QObject::connect(cancelAction, &KNotificationAction::activated, question, [question]() {
0140                 question->setResponse(KNSCore::Question::CancelResponse);
0141             });
0142 
0143             QObject::connect(f, &KNotification::closed, question, discardQuestion);
0144         } break;
0145         case KNSCore::Question::InputTextQuestion:
0146         case KNSCore::Question::SelectFromListQuestion:
0147         case KNSCore::Question::PasswordQuestion:
0148             discardQuestion();
0149             break;
0150         }
0151     });
0152 
0153     const auto onError = [](KNSCore::ErrorCode::ErrorCode errorCode, const QString &message, const QVariant &metadata) {
0154         qWarning() << "kns error:" << errorCode << message << metadata;
0155         QCoreApplication::exit(1);
0156     };
0157 
0158     bool entryWasFound = false;
0159     const auto onEntriesLoded = [providerid, linkid, &installedCount, &engine, &entryWasFound, onError](const KNSCore::Entry::List list) {
0160         Q_ASSERT(list.size() == 1);
0161         entryWasFound = true;
0162         const auto entry = list.first();
0163         if (providerid != entry.providerId()) {
0164             qWarning() << "Wrong provider" << providerid << "instead of" << entry.providerId();
0165             QCoreApplication::exit(1);
0166         } else if (entry.status() == KNSCore::Entry::Downloadable) {
0167             qDebug() << "installing...";
0168             installedCount++;
0169             auto transaction = KNSCore::Transaction::install(&engine, entry, linkid);
0170             QObject::connect(transaction, &KNSCore::Transaction::signalErrorCode, onError);
0171             QObject::connect(transaction, &KNSCore::Transaction::signalEntryEvent, [&installedCount](auto entry, auto event) {
0172                 if (event == KNSCore::Entry::StatusChangedEvent) {
0173                     if (entry.status() == KNSCore::Entry::Installed) {
0174                         installedCount--;
0175                     }
0176                     if (installedCount == 0) {
0177                         QCoreApplication::exit(0);
0178                     }
0179                 }
0180             });
0181         } else if (installedCount == 0) {
0182             qDebug() << "already installed.";
0183             QCoreApplication::exit(0);
0184         }
0185     };
0186     QObject::connect(&engine, &KNSCore::EngineBase::signalProvidersLoaded, &engine, [&engine, &entryid, onEntriesLoded, &entryWasFound]() {
0187         qWarning() << "providers are loaded";
0188         KNSCore::Provider::SearchRequest request(KNSCore::Provider::Newest, KNSCore::Provider::ExactEntryId, entryid, QStringList{}, 0);
0189         KNSCore::ResultsStream *results = engine.search(request);
0190         QObject::connect(results, &KNSCore::ResultsStream::entriesFound, &engine, onEntriesLoded);
0191         QObject::connect(results, &KNSCore::ResultsStream::finished, &engine, [&entryWasFound, &entryid]() {
0192             if (!entryWasFound) {
0193                 qWarning() << "Entry with id" << entryid << "could not be found";
0194                 QCoreApplication::exit(1);
0195             }
0196         });
0197         results->fetch();
0198     });
0199 
0200     QObject::connect(&engine, &KNSCore::EngineBase::signalErrorCode, &engine, onError);
0201     if (!engine.init(knsname)) {
0202         qWarning() << "couldn't initialize" << knsname;
0203         return 1;
0204     }
0205     return app.exec();
0206 }