File indexing completed on 2022-09-20 16:18:06

0001 /***************************************************************************
0002  *   Copyright (C) 2008-2011 by Daniel Nicoletti                           *
0003  *   dantti12@gmail.com                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; see the file COPYING. If not, write to       *
0017  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0018  *   Boston, MA 02110-1301, USA.                                           *
0019  ***************************************************************************/
0020 
0021 #include "TransactionWatcher.h"
0022 
0023 #include "TransactionJob.h"
0024 
0025 #include <PkStrings.h>
0026 #include <PkIcons.h>
0027 #include <PackageImportance.h>
0028 
0029 #include <KNotification>
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 #include <KNotification>
0033 //#include <KComponentData>
0034 
0035 //#include <Solid/PowerManagement>
0036 #include <QtDBus/QDBusMessage>
0037 #include <QtDBus/QDBusConnection>
0038 
0039 #include <kworkspace5/kworkspace.h>
0040 #include <Daemon>
0041 
0042 #include <QLoggingCategory>
0043 
0044 Q_DECLARE_LOGGING_CATEGORY(APPER_DAEMON)
0045 
0046 TransactionWatcher::TransactionWatcher(bool packagekitIsRunning, QObject *parent) :
0047     QObject(parent),
0048     m_inhibitCookie(-1)
0049 {
0050     m_tracker = new KUiServerJobTracker(this);
0051 
0052     // keep track of new transactions
0053     connect(Daemon::global(), &Daemon::transactionListChanged, this, &TransactionWatcher::transactionListChanged);
0054 
0055     // if PackageKit is running check to see if there are running transactons already
0056     if (packagekitIsRunning) {
0057         // here we check whether a transaction job should be created or not
0058         QStringList tids;
0059         const QList<QDBusObjectPath> paths = Daemon::global()->getTransactionList();
0060         for (const QDBusObjectPath &path : paths) {
0061             tids << path.path();
0062         }
0063         transactionListChanged(tids);
0064     }
0065 }
0066 
0067 TransactionWatcher::~TransactionWatcher()
0068 {
0069     // release any cookie that we might have
0070     suppressSleep(false, m_inhibitCookie);
0071 }
0072 
0073 void TransactionWatcher::watchTransactionInteractive(const QDBusObjectPath &tid)
0074 {
0075     watchTransaction(tid);
0076 }
0077 
0078 void TransactionWatcher::transactionListChanged(const QStringList &tids)
0079 {
0080     if (tids.isEmpty()) {
0081         // release any cookie that we might have
0082         suppressSleep(false, m_inhibitCookie);
0083     } else {
0084         for (const QString &tid : tids) {
0085             watchTransaction(QDBusObjectPath(tid), false);
0086         }
0087     }
0088 }
0089 
0090 void TransactionWatcher::watchTransaction(const QDBusObjectPath &tid, bool interactive)
0091 {
0092     Transaction *transaction;
0093     if (!m_transactions.contains(tid)) {
0094         // Check if the current transaction is still the same
0095         transaction = new Transaction(tid);
0096         connect(transaction, &Transaction::roleChanged, this, &TransactionWatcher::transactionReady);
0097         connect(transaction, &Transaction::finished, this, &TransactionWatcher::finished);
0098 
0099         // Store the transaction id
0100         m_transactions[tid] = transaction;
0101     } else {
0102         transaction = m_transactions[tid];
0103 
0104         if (transaction->role() != Transaction::RoleUnknown) {
0105             // force the first changed or create a TransactionJob
0106             transactionChanged(transaction, interactive);
0107         }
0108     }
0109 }
0110 
0111 void TransactionWatcher::transactionReady()
0112 {
0113     auto transaction = qobject_cast<Transaction*>(sender());
0114 
0115     Transaction::Role role = transaction->role();
0116     Transaction::TransactionFlags flags = transaction->transactionFlags();
0117     if (!(flags & Transaction::TransactionFlagOnlyDownload || flags & Transaction::TransactionFlagSimulate) &&
0118             (role == Transaction::RoleInstallPackages ||
0119              role == Transaction::RoleInstallFiles    ||
0120              role == Transaction::RoleRemovePackages  ||
0121              role == Transaction::RoleUpdatePackages)) {
0122         // AVOID showing messages and restart requires when
0123         // the user was just simulating an instalation
0124         connect(transaction, &Transaction::requireRestart, this, &TransactionWatcher::requireRestart);
0125 
0126         // Don't let the system sleep while doing some sensible actions
0127         suppressSleep(true, m_inhibitCookie, PkStrings::action(role, flags));
0128     }
0129 
0130     connect(transaction, &Transaction::isCallerActiveChanged, this, [this, transaction] () {
0131         transactionChanged(transaction);
0132     });
0133 
0134 }
0135 
0136 void TransactionWatcher::showRebootNotificationApt() {
0137     // Create the notification about this transaction
0138     auto notify = new KNotification(QLatin1String("RestartRequired"), nullptr, KNotification::Persistent);
0139     connect(notify, QOverload<uint>::of(&KNotification::activated), this, &TransactionWatcher::logout);
0140     notify->setComponentName(QLatin1String("apperd"));
0141 
0142     QString text(QLatin1String("<b>") + i18n("The system update has completed") + QLatin1String("</b>"));
0143     text.append(QLatin1String("<br>") + PkStrings::restartType(Transaction::RestartSystem));
0144     notify->setPixmap(PkIcons::restartIcon(Transaction::RestartSystem).pixmap(KPK_ICON_SIZE, KPK_ICON_SIZE));
0145     notify->setText(text);
0146 
0147     // TODO RestartApplication should be handled differently
0148     QStringList actions;
0149     actions << i18n("Restart");
0150     notify->setActions(actions);
0151 
0152     notify->sendEvent();
0153 }
0154 
0155 void TransactionWatcher::finished(PackageKit::Transaction::Exit exit)
0156 {
0157     // check if the transaction emitted any require restart
0158     auto transaction = qobject_cast<Transaction*>(sender());
0159     QDBusObjectPath tid = transaction->tid();
0160     transaction->disconnect(this);
0161     m_transactions.remove(tid);
0162     m_transactionJob.remove(tid);
0163 
0164     if (exit == Transaction::ExitSuccess && !transaction->property("restartType").isNull()) {
0165         Transaction::Restart type = transaction->property("restartType").value<Transaction::Restart>();
0166         QStringList restartPackages = transaction->property("restartPackages").toStringList();
0167 
0168         // Create the notification about this transaction
0169         auto notify = new KNotification(QLatin1String("RestartRequired"), nullptr, KNotification::Persistent);
0170         connect(notify, QOverload<uint>::of(&KNotification::activated), this, &TransactionWatcher::logout);
0171         notify->setComponentName(QLatin1String("apperd"));
0172         notify->setProperty("restartType", qVariantFromValue(type));
0173         notify->setPixmap(PkIcons::restartIcon(type).pixmap(KPK_ICON_SIZE, KPK_ICON_SIZE));
0174         notify->setTitle(PkStrings::restartType(type));
0175 
0176         // Create a readable text with package names that required the restart
0177         if (!restartPackages.isEmpty()) {
0178             restartPackages.removeDuplicates();
0179             restartPackages.sort();
0180 
0181             QString text;
0182             text = i18np("Package: %2",
0183                          "Packages: %2",
0184                          restartPackages.size(),
0185                          restartPackages.join(QLatin1String(", ")));
0186             notify->setText(text);
0187         }
0188 
0189         // TODO RestartApplication should be handled differently
0190         QStringList actions;
0191         actions << i18n("Restart");
0192         notify->setActions(actions);
0193 
0194         notify->sendEvent();
0195     }
0196 }
0197 
0198 void TransactionWatcher::transactionChanged(Transaction *transaction, bool interactive)
0199 {
0200     if (!transaction) {
0201         transaction = qobject_cast<Transaction*>(sender());
0202     }
0203 
0204     QDBusObjectPath tid = transaction->tid();
0205     if (!interactive) {
0206         interactive = !transaction->isCallerActive();
0207     }
0208 
0209     // If the
0210     if (!m_transactionJob.contains(tid) && interactive) {
0211         auto job = new TransactionJob(transaction, this);
0212         connect(transaction, &Transaction::errorCode, this, &TransactionWatcher::errorCode);
0213         connect(job, &TransactionJob::canceled, this, &TransactionWatcher::watchedCanceled);
0214         m_tracker->registerJob(job);
0215         m_transactionJob[tid] = job;
0216         job->start();
0217     }
0218 }
0219 
0220 void TransactionWatcher::errorCode(PackageKit::Transaction::Error err, const QString &details)
0221 {
0222     auto notify = new KNotification(QLatin1String("TransactionError"), nullptr, KNotification::Persistent);
0223     notify->setComponentName(QLatin1String("apperd"));
0224     notify->setTitle(PkStrings::error(err));
0225     notify->setText(PkStrings::errorMessage(err));
0226     notify->setProperty("ErrorType", QVariant::fromValue(err));
0227     notify->setProperty("Details", details);
0228 
0229     QStringList actions;
0230     actions << i18n("Details");
0231     notify->setActions(actions);
0232     notify->setPixmap(QIcon::fromTheme(QLatin1String("dialog-error")).pixmap(KPK_ICON_SIZE, KPK_ICON_SIZE));
0233     connect(notify, QOverload<uint>::of(&KNotification::activated), this, &TransactionWatcher::errorActivated);
0234     notify->sendEvent();
0235 }
0236 
0237 void TransactionWatcher::errorActivated(uint action)
0238 {
0239     auto notify = qobject_cast<KNotification*>(sender());
0240 
0241     // if the user clicked "Details"
0242     if (action == 1) {
0243         Transaction::Error error = notify->property("ErrorType").value<Transaction::Error>();
0244         QString details = notify->property("Details").toString();
0245         KMessageBox::detailedError(nullptr,
0246                                    PkStrings::errorMessage(error),
0247                                    details.replace(QLatin1Char('\n'), QLatin1String("<br>")),
0248                                    PkStrings::error(error),
0249                                    KMessageBox::Notify);
0250     }
0251 
0252     notify->close();
0253 }
0254 
0255 void TransactionWatcher::requireRestart(PackageKit::Transaction::Restart type, const QString &packageID)
0256 {
0257     auto transaction = qobject_cast<Transaction*>(sender());
0258     if (transaction->property("restartType").isNull()) {
0259         transaction->setProperty("restartType", qVariantFromValue(type));
0260     } else {
0261         Transaction::Restart oldType;
0262         oldType = transaction->property("restartType").value<Transaction::Restart>();
0263         int old = PackageImportance::restartImportance(oldType);
0264         int newer = PackageImportance::restartImportance(type);
0265         // Check to see which one is more important
0266         if (newer > old) {
0267             transaction->setProperty("restartType", qVariantFromValue(type));
0268         }
0269     }
0270 
0271     if (!Transaction::packageName(packageID).isEmpty()) {
0272         QStringList restartPackages = transaction->property("restartPackages").toStringList();
0273         restartPackages << Transaction::packageName(packageID);
0274         transaction->setProperty("restartPackages", restartPackages);
0275     }
0276 }
0277 
0278 void TransactionWatcher::logout()
0279 {
0280     auto notify = qobject_cast<KNotification*>(sender());
0281     Transaction::Restart restartType;
0282     restartType = notify->property("restartType").value<Transaction::Restart>();
0283 
0284     KWorkSpace::ShutdownType shutdownType;
0285     switch (restartType) {
0286     case Transaction::RestartSystem:
0287     case Transaction::RestartSecuritySystem:
0288         // The restart type was system
0289         shutdownType = KWorkSpace::ShutdownTypeReboot;
0290         break;
0291     case Transaction::RestartSession:
0292     case Transaction::RestartSecuritySession:
0293         // The restart type was session
0294         shutdownType = KWorkSpace::ShutdownTypeLogout;
0295         break;
0296     default:
0297         qCWarning(APPER_DAEMON) << "Unknown restart type:" << restartType;
0298         return;
0299     }
0300 
0301     // We call KSM server to restart or logout our system
0302     KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmYes,
0303                                 shutdownType,
0304                                 KWorkSpace::ShutdownModeInteractive);
0305 }
0306 
0307 void TransactionWatcher::watchedCanceled()
0308 {
0309     auto job = qobject_cast<TransactionJob*>(sender());
0310     if (job->isFinished()) {
0311         job->deleteLater();
0312         return;
0313     }
0314 
0315     Transaction::Role role = job->transaction()->role();
0316     if (role != Transaction::RoleCancel &&
0317             role != Transaction::RoleUnknown) {
0318         m_tracker->unregisterJob(job);
0319         m_tracker->registerJob(job);
0320         job->start();
0321     }
0322 }
0323 
0324 void TransactionWatcher::suppressSleep(bool enable, int &inhibitCookie, const QString &reason)
0325 {
0326     if (inhibitCookie == -1) {
0327         return;
0328     }
0329 
0330     if (enable) {
0331         qCDebug(APPER_DAEMON) << "Begin Suppressing Sleep";
0332 //        inhibitCookie = Solid::PowerManagement::beginSuppressingSleep(reason);
0333         if (inhibitCookie == -1) {
0334             qCDebug(APPER_DAEMON) << "Sleep suppression denied!";
0335         }
0336     } else {
0337         qCDebug(APPER_DAEMON) << "Stop Suppressing Sleep";
0338 //        if (!Solid::PowerManagement::stopSuppressingSleep(inhibitCookie)) {
0339             qCDebug(APPER_DAEMON) << "Stop failed: invalid cookie.";
0340 //        }
0341         inhibitCookie = -1;
0342     }
0343 }
0344 
0345 #include "moc_TransactionWatcher.cpp"