File indexing completed on 2024-04-14 15:48:52

0001 /***************************************************************************
0002  *   Copyright (C) 2008 by Trever Fischer                                  *
0003  *   wm161@wm161.net                                                       *
0004  *   Copyright (C) 2008-2011 by Daniel Nicoletti                           *
0005  *   dantti12@gmail.com                                                    *
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  *   This program is distributed in the hope that it will be useful,       *
0013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0015  *   GNU General Public License for more details.                          *
0016  *                                                                         *
0017  *   You should have received a copy of the GNU General Public License     *
0018  *   along with this program; see the file COPYING. If not, write to       *
0019  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0020  *   Boston, MA 02110-1301, USA.                                           *
0021  ***************************************************************************/
0022 
0023 #include "Updater.h"
0024 
0025 #include "ApperdThread.h"
0026 
0027 #include <Daemon>
0028 
0029 #include <PkStrings.h>
0030 #include <PkIcons.h>
0031 #include <Enum.h>
0032 
0033 #include <QDBusServiceWatcher>
0034 #include <QDBusMessage>
0035 
0036 #include <KLocalizedString>
0037 #include <KNotification>
0038 #include <KActionCollection>
0039 #include <KToolInvocation>
0040 
0041 #include <QLoggingCategory>
0042 
0043 Q_DECLARE_LOGGING_CATEGORY(APPER_DAEMON)
0044 
0045 #define UPDATES_ICON "system-software-update"
0046 
0047 using namespace PackageKit;
0048 
0049 Updater::Updater(QObject* parent) :
0050     QObject(parent),
0051     m_getUpdatesT(nullptr)
0052 {
0053     // in case registration fails due to another user or application running
0054     // keep an eye on it so we can register when available
0055     auto watcher = new QDBusServiceWatcher(QLatin1String("org.kde.ApperUpdaterIcon"),
0056                                            QDBusConnection::sessionBus(),
0057                                            QDBusServiceWatcher::WatchForOwnerChange,
0058                                            this);
0059     connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &Updater::serviceOwnerChanged);
0060 
0061     m_hasAppletIconified = ApperdThread::nameHasOwner(QLatin1String("org.kde.ApperUpdaterIcon"),
0062                                                       QDBusConnection::sessionBus());
0063 }
0064 
0065 Updater::~Updater()
0066 {
0067 }
0068 
0069 void Updater::setConfig(const QVariantHash &configs)
0070 {
0071     m_configs = configs;
0072 }
0073 
0074 void Updater::setSystemReady()
0075 {
0076     // System ready changed, maybe we can auto
0077     // install some updates
0078     m_systemReady = true;
0079     getUpdateFinished();
0080 }
0081 
0082 void Updater::checkForUpdates(bool systemReady)
0083 {
0084     m_systemReady = systemReady;
0085 
0086     // Skip the check if one is already running or
0087     // the plasmoid is in Icon form and the auto update type is None
0088     if (m_getUpdatesT) {
0089         return;
0090     }
0091 
0092     m_updateList.clear();
0093     m_importantList.clear();
0094     m_securityList.clear();
0095     m_getUpdatesT = Daemon::getUpdates();
0096     connect(m_getUpdatesT, &Transaction::package, this, &Updater::packageToUpdate);
0097     connect(m_getUpdatesT, &Transaction::finished, this, &Updater::getUpdateFinished);
0098 }
0099 
0100 void Updater::packageToUpdate(Transaction::Info info, const QString &packageID, const QString &summary)
0101 {
0102     Q_UNUSED(summary)
0103 
0104     switch (info) {
0105     case Transaction::InfoBlocked:
0106         // Blocked updates are not instalable updates so there is no
0107         // reason to show/count them
0108         return;
0109     case Transaction::InfoImportant:
0110         m_importantList << packageID;
0111         break;
0112     case Transaction::InfoSecurity:
0113         m_securityList << packageID;
0114         break;
0115     default:
0116         break;
0117     }
0118     m_updateList << packageID;
0119 }
0120 
0121 void Updater::getUpdateFinished()
0122 {
0123     m_getUpdatesT = nullptr;
0124     if (!m_updateList.isEmpty()) {
0125         auto transaction = qobject_cast<Transaction*>(sender());
0126 
0127         bool different = false;
0128         if (m_oldUpdateList.size() != m_updateList.size()) {
0129             different = true;
0130         } else {
0131             // The lists have the same size let's make sure
0132             // all the packages are the same
0133             const QStringList updates = m_updateList;
0134             for (const QString &packageId : updates) {
0135                 if (!m_oldUpdateList.contains(packageId)) {
0136                     different = true;
0137                     break;
0138                 }
0139             }
0140         }
0141 
0142         // sender is not a transaction when we systemReady has changed
0143         // if the lists are the same don't show
0144         // a notification or try to upgrade again
0145         if (transaction && !different) {
0146             return;
0147         }
0148 
0149         uint updateType = m_configs[QLatin1String(CFG_AUTO_UP)].value<uint>();
0150         if (m_systemReady && updateType == Enum::All) {
0151             // update all
0152             bool ret;
0153             ret = updatePackages(m_updateList,
0154                                  false,
0155                                  QLatin1String("plasmagik"),
0156                                  i18n("Updates are being automatically installed."));
0157             if (ret) {
0158                 return;
0159             }
0160         } else if (m_systemReady && updateType == Enum::Security && !m_securityList.isEmpty()) {
0161             // Defaults to security
0162             bool ret;
0163             ret = updatePackages(m_securityList,
0164                                  false,
0165                                  QLatin1String(UPDATES_ICON),
0166                                  i18n("Security updates are being automatically installed."));
0167             if (ret) {
0168                 return;
0169             }
0170         } else if (m_systemReady && updateType == Enum::DownloadOnly) {
0171             // Download all updates
0172             bool ret;
0173             ret = updatePackages(m_updateList,
0174                                  true,
0175                                  QLatin1String("download"),
0176                                  i18n("Updates are being automatically downloaded."));
0177             if (ret) {
0178                 return;
0179             }
0180         } else if (!m_systemReady &&
0181                    (updateType == Enum::All ||
0182                     updateType == Enum::DownloadOnly ||
0183                     (updateType == Enum::Security && !m_securityList.isEmpty()))) {
0184             qCDebug(APPER_DAEMON) << "Not auto updating or downloading, as we might be on battery or mobile connection";
0185         }
0186 
0187         // If an erro happened to create the auto update
0188         // transaction show the update list
0189         if (transaction) {
0190             // The transaction is not valid if the systemReady changed
0191             showUpdatesPopup();
0192         }
0193     } else {
0194         m_oldUpdateList.clear();
0195     }
0196 }
0197 
0198 void Updater::autoUpdatesFinished(PkTransaction::ExitStatus status)
0199 {
0200     auto notify = new KNotification(QLatin1String("UpdatesComplete"));
0201     notify->setComponentName(QLatin1String("apperd"));
0202     if (status == PkTransaction::Success) {
0203         if (sender()->property("DownloadOnly").toBool()) {
0204             // We finished downloading show the updates to the user
0205             showUpdatesPopup();
0206         } else {
0207             QIcon icon = QIcon::fromTheme(QLatin1String("task-complete"));
0208             // use of QSize does the right thing
0209             notify->setPixmap(icon.pixmap(KPK_ICON_SIZE, KPK_ICON_SIZE));
0210             notify->setText(i18n("System update was successful."));
0211             notify->sendEvent();
0212         }
0213     } else {
0214         QIcon icon = QIcon::fromTheme(QLatin1String("dialog-cancel"));
0215         // use of QSize does the right thing
0216         notify->setPixmap(icon.pixmap(KPK_ICON_SIZE, KPK_ICON_SIZE));
0217         notify->setText(i18n("The software update failed."));
0218         notify->sendEvent();
0219 
0220         // show updates popup
0221         showUpdatesPopup();
0222     }
0223 }
0224 
0225 void Updater::reviewUpdates()
0226 {
0227     if (m_hasAppletIconified) {
0228         QDBusMessage message;
0229         message = QDBusMessage::createMethodCall(QLatin1String("org.kde.ApperUpdaterIcon"),
0230                                                  QLatin1String("/"),
0231                                                  QLatin1String("org.kde.ApperUpdaterIcon"),
0232                                                  QLatin1String("ReviewUpdates"));
0233         QDBusMessage reply = QDBusConnection::sessionBus().call(message);
0234         if (reply.type() == QDBusMessage::ReplyMessage) {
0235             return;
0236         }
0237         qCWarning(APPER_DAEMON) << "Message did not receive a reply";
0238     }
0239 
0240     // This must be called from the main thread...
0241     KToolInvocation::startServiceByDesktopName(QLatin1String("apper_updates"));
0242 }
0243 
0244 void Updater::installUpdates()
0245 {
0246     bool ret;
0247     ret = updatePackages(m_updateList, false);
0248     if (ret) {
0249         return;
0250     }
0251 
0252     reviewUpdates();
0253 }
0254 
0255 void Updater::serviceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner)
0256 {
0257     Q_UNUSED(service)
0258     Q_UNUSED(oldOwner)
0259 
0260     m_hasAppletIconified = !newOwner.isEmpty();
0261 }
0262 
0263 void Updater::showUpdatesPopup()
0264 {
0265     m_oldUpdateList = m_updateList;
0266 
0267     auto notify = new KNotification(QLatin1String("ShowUpdates"), nullptr, KNotification::Persistent);
0268     notify->setComponentName(QLatin1String("apperd"));
0269     connect(notify, &KNotification::action1Activated, this, &Updater::reviewUpdates);
0270     connect(notify, &KNotification::action2Activated, this, &Updater::installUpdates);
0271     notify->setTitle(i18np("There is one new update", "There are %1 new updates", m_updateList.size()));
0272     QString text;
0273     const QStringList updates = m_updateList;
0274     for (const QString &packageId : updates) {
0275         const QString packageName = Transaction::packageName(packageId);
0276         if (text.length() + packageName.length() > 150) {
0277             text.append(QLatin1String(" ..."));
0278             break;
0279         } else if (!text.isNull()) {
0280             text.append(QLatin1String(", "));
0281         }
0282         text.append(packageName);
0283     }
0284     notify->setText(text);
0285 
0286     QStringList actions;
0287     actions << i18n("Review");
0288     if (m_hasAppletIconified) {
0289         actions << i18n("Install");
0290     }
0291     notify->setActions(actions);
0292 
0293     // use of QSize does the right thing
0294     notify->setPixmap(QIcon::fromTheme(QLatin1String("system-software-update")).pixmap(KPK_ICON_SIZE, KPK_ICON_SIZE));
0295     notify->sendEvent();
0296 }
0297 
0298 bool Updater::updatePackages(const QStringList &packages, bool downloadOnly, const QString &icon, const QString &msg)
0299 {
0300     m_oldUpdateList = m_updateList;
0301 
0302     // Defaults to security
0303     auto transaction = new PkTransaction;
0304     transaction->setProperty("DownloadOnly", downloadOnly);
0305     transaction->enableJobWatcher(true);
0306     transaction->updatePackages(packages, downloadOnly);
0307     connect(transaction, &PkTransaction::finished, this, &Updater::autoUpdatesFinished);
0308     if (!icon.isNull()) {
0309         KNotification *notify;
0310         if (downloadOnly) {
0311             notify = new KNotification(QLatin1String("DownloadingUpdates"));
0312         } else {
0313             notify = new KNotification(QLatin1String("AutoInstallingUpdates"));
0314         }
0315         notify->setComponentName(QLatin1String("apperd"));
0316         notify->setText(msg);
0317         // use of QSize does the right thing
0318         notify->setPixmap(QIcon::fromTheme(icon).pixmap(QSize(KPK_ICON_SIZE, KPK_ICON_SIZE)));
0319         notify->sendEvent();
0320     }
0321 
0322     return true;
0323 }
0324 
0325 #include "moc_Updater.cpp"