File indexing completed on 2024-04-21 16:30:36

0001 // SPDX-License-Identifier: GPL-3.0-or-later
0002 /*
0003   Copyright 2017 - 2023 Martin Koller, kollix@aon.at
0004 
0005   This file is part of liquidshell.
0006 
0007   liquidshell 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 3 of the License, or
0010   (at your option) any later version.
0011 
0012   liquidshell 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 liquidshell.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include <PkUpdateList.hxx>
0022 #include <DesktopWidget.hxx>
0023 
0024 #include <QScrollBar>
0025 #include <QGridLayout>
0026 #include <QVBoxLayout>
0027 #include <QCheckBox>
0028 #include <QLineEdit>
0029 #include <QPushButton>
0030 #include <QProgressBar>
0031 #include <QToolButton>
0032 #include <QIcon>
0033 #include <QDBusConnection>
0034 #include <QStyle>
0035 #include <QApplication>
0036 #include <QScreen>
0037 #include <QMessageBox>
0038 #include <QTextEdit>
0039 #include <QMenu>
0040 #include <QDialog>
0041 #include <QDialogButtonBox>
0042 #include <QCryptographicHash>
0043 #include <QDebug>
0044 
0045 #include <KLocalizedString>
0046 #include <KNotification>
0047 
0048 //#define TEST_LOGOUT
0049 //#define TEST_REBOOT
0050 
0051 //--------------------------------------------------------------------------------
0052 
0053 PkUpdateListItem::PkUpdateListItem(QWidget *parent, PackageKit::Transaction::Info info, const PkUpdates::PackageData &data)
0054   : QWidget(parent), package(data)
0055 {
0056   setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0057 
0058   QGridLayout *grid = new QGridLayout(this);
0059   grid->setContentsMargins(QMargins());
0060   grid->setSpacing(0);
0061 
0062   checkBox = new QCheckBox;
0063   checkBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0064   grid->addWidget(checkBox, 0, 0);
0065 
0066   QString icon;
0067   switch ( info )
0068   {
0069     case PackageKit::Transaction::InfoSecurity: icon = "update-high"; break;
0070     case PackageKit::Transaction::InfoImportant: icon = "update-medium"; break;
0071     case PackageKit::Transaction::InfoBugfix: icon = "update-low"; break;
0072     default: ;
0073   }
0074   QLabel *iconLabel = new QLabel;
0075   iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0076   iconLabel->setPixmap(QIcon::fromTheme(icon).pixmap(16));
0077   grid->addWidget(iconLabel, 0, 1);
0078 
0079   checkBox->setChecked(!icon.isEmpty());  // auto-check the important ones
0080   connect(checkBox, &QCheckBox::toggled, this, &PkUpdateListItem::toggled);
0081 
0082   label = new QToolButton;
0083   label->setAutoRaise(true);
0084   label->setText(package.summary + " [" +
0085                  PackageKit::Daemon::packageName(package.id) + ", " +
0086                  PackageKit::Daemon::packageVersion(package.id) + ']');
0087   grid->addWidget(label, 0, 2, Qt::AlignLeft);
0088 
0089   detailsLabel = new QLabel;
0090   detailsLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0091   detailsLabel->setIndent(30);
0092   detailsLabel->setAlignment(Qt::AlignLeft);
0093   grid->addWidget(detailsLabel, 1, 2);
0094   detailsLabel->hide();
0095 
0096   connect(label, &QToolButton::clicked, this, &PkUpdateListItem::getUpdateDetails);
0097 
0098   {
0099     QHBoxLayout *progressHbox = new QHBoxLayout;
0100     progressHbox->setSpacing(10);
0101     progress = new QProgressBar;
0102     progress->setFixedWidth(300);
0103     progressHbox->addWidget(progress);
0104 
0105     errorLabel = new QLabel;
0106     errorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0107     errorLabel->hide();
0108     progressHbox->addWidget(errorLabel);
0109 
0110     packageLabel = new QLabel;
0111     packageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0112     packageLabel->hide();
0113     progressHbox->addWidget(packageLabel);
0114 
0115     grid->addLayout(progressHbox, 2, 2, Qt::AlignLeft);
0116     showProgress(false);
0117   }
0118 }
0119 
0120 //--------------------------------------------------------------------------------
0121 
0122 void PkUpdateListItem::showProgress(bool yes)
0123 {
0124   progress->setValue(0);
0125   progress->setVisible(yes);
0126 }
0127 
0128 //--------------------------------------------------------------------------------
0129 
0130 void PkUpdateListItem::getUpdateDetails()
0131 {
0132   if ( !detailsLabel->isHidden() )
0133   {
0134     detailsLabel->hide();
0135     return;
0136   }
0137 
0138   //qDebug() << "getUpdateDetails" << package.id << PackageKit::Daemon::packageName(package.id);
0139   PackageKit::Transaction *transaction = PackageKit::Daemon::getUpdateDetail(package.id);
0140   detailsLabel->setText(i18n("Getting details ..."));
0141   detailsLabel->show();
0142 
0143   connect(transaction, &PackageKit::Transaction::updateDetail, this, &PkUpdateListItem::updateDetail);
0144 
0145   connect(transaction, &PackageKit::Transaction::errorCode, this,
0146           [this](PackageKit::Transaction::Error error, const QString &details)
0147           {
0148             Q_UNUSED(error)
0149             detailsLabel->setText(details);
0150           });
0151 }
0152 
0153 //--------------------------------------------------------------------------------
0154 
0155 void PkUpdateListItem::updateDetail(const QString &packageID,
0156                                     const QStringList &updates,
0157                                     const QStringList &obsoletes,
0158                                     const QStringList &vendorUrls,
0159                                     const QStringList &bugzillaUrls,
0160                                     const QStringList &cveUrls,
0161                                     PackageKit::Transaction::Restart restart,
0162                                     const QString &updateText,
0163                                     const QString &changelog,
0164                                     PackageKit::Transaction::UpdateState state,
0165                                     const QDateTime &issued,
0166                                     const QDateTime &updated)
0167 {
0168   Q_UNUSED(packageID)
0169   Q_UNUSED(updates)
0170   Q_UNUSED(obsoletes)
0171   Q_UNUSED(vendorUrls)
0172   Q_UNUSED(bugzillaUrls)
0173   Q_UNUSED(cveUrls)
0174   Q_UNUSED(changelog)
0175   Q_UNUSED(state)
0176   Q_UNUSED(issued)
0177   Q_UNUSED(updated)
0178 
0179   QString text;
0180 
0181   if ( restart == PackageKit::Transaction::RestartSession )
0182     text = i18n("<b>Session restart required</b><br>");
0183   else if ( restart == PackageKit::Transaction::RestartSystem )
0184     text = i18n("<b>Reboot required</b><br>");
0185 
0186   text += updateText.trimmed();
0187   text.replace("\n", "<br>");
0188 
0189   detailsLabel->setText(text);
0190 }
0191 
0192 //--------------------------------------------------------------------------------
0193 //--------------------------------------------------------------------------------
0194 //--------------------------------------------------------------------------------
0195 
0196 PkUpdateList::PkUpdateList(QWidget *parent)
0197   : QWidget(parent), restart(PackageKit::Transaction::RestartNone)
0198 {
0199   setWindowFlags(windowFlags() | Qt::Tool);
0200   setWindowTitle(i18n("Software Updates"));
0201 
0202   vbox = new QVBoxLayout(this);
0203   QMargins margins = vbox->contentsMargins();
0204   vbox->setContentsMargins(QMargins(0, -1, 0, 0));
0205 
0206   // action buttons
0207   QHBoxLayout *hbox = new QHBoxLayout;
0208   margins.setTop(0);
0209   margins.setBottom(0);
0210   hbox->setContentsMargins(margins);
0211 
0212   checkAllBox = new QCheckBox(i18n("All"));
0213   connect(checkAllBox, &QCheckBox::toggled, this, &PkUpdateList::checkAll);
0214 
0215   filterEdit = new QLineEdit;
0216   filterEdit->setPlaceholderText(i18n("Filter"));
0217   filterEdit->setClearButtonEnabled(true);
0218   connect(filterEdit, &QLineEdit::textEdited, this, &PkUpdateList::filterChanged);
0219 
0220   // create "busy" indicator
0221   progressBar = new QProgressBar;
0222 
0223   installButton = new QToolButton;
0224   installButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0225   installButton->setText(i18n("Install"));
0226   installButton->setEnabled(false);
0227   connect(installButton, &QToolButton::clicked, [this]() { afterInstall = AfterInstall::Nothing; install(); });
0228 
0229   // more install options
0230   installMenu = new QMenu;
0231   installMenu->addAction(i18n("Install, then Sleep"), this, [this]() { afterInstall = AfterInstall::Sleep; install(); });
0232   installMenu->addAction(i18n("Install, then Shutdown"), this, [this]() { afterInstall = AfterInstall::Shutdown; install(); });
0233   installButton->setMenu(installMenu);
0234 
0235   refreshButton = new QToolButton;
0236   refreshButton->setText(i18n("Refresh"));
0237   connect(refreshButton, &QToolButton::clicked, this, [this]() { setPackages(PkUpdates::PackageList()); emit refreshRequested(); });
0238 
0239   hbox->addWidget(checkAllBox);
0240   hbox->addWidget(filterEdit);
0241   hbox->addWidget(progressBar);
0242   hbox->addWidget(installButton);
0243   hbox->addWidget(refreshButton);
0244   vbox->addLayout(hbox);
0245 
0246   // list of items in the order: security, important, bugfix, others
0247   scrollArea = new QScrollArea;
0248   scrollArea->setWidgetResizable(true);
0249 
0250   vbox->addWidget(scrollArea);
0251 
0252   QWidget *w = new QWidget;
0253   vbox = new QVBoxLayout(w);
0254   itemsLayout = new QVBoxLayout;
0255   itemsLayout->setSpacing(0);
0256   vbox->addLayout(itemsLayout);
0257   vbox->addStretch();
0258 
0259   scrollArea->setWidget(w);
0260 }
0261 
0262 //--------------------------------------------------------------------------------
0263 
0264 QSize PkUpdateList::sizeHint() const
0265 {
0266   QSize s;
0267 
0268   if ( savedSize.isValid() )
0269     s = savedSize;
0270   else
0271   {
0272     s = scrollArea->widget()->sizeHint() + QSize(2 * scrollArea->frameWidth(), 2 * scrollArea->frameWidth());
0273     s.setWidth(s.width() + scrollArea->verticalScrollBar()->sizeHint().width());
0274     s.setHeight(layout()->contentsMargins().top() + installButton->sizeHint().height() +
0275                 ((layout()->spacing() == -1) ? style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) : layout()->spacing()) +
0276                 s.height() + layout()->contentsMargins().bottom());
0277 
0278     s = s.expandedTo(QSize(500, 300));
0279   }
0280 
0281   // Qt doc: The size of top-level widgets are constrained to 2/3 of the desktop's height and width.
0282   QSize screen = DesktopWidget::availableSize();
0283   s = s.boundedTo(screen * 2 / 3);
0284 
0285   return s;
0286 }
0287 
0288 //--------------------------------------------------------------------------------
0289 
0290 void PkUpdateList::hideEvent(QHideEvent *event)
0291 {
0292   Q_UNUSED(event)
0293 
0294   if ( itemsLayout->count() )
0295     savedSize = size();
0296   else
0297     savedSize = QSize();
0298 }
0299 
0300 //--------------------------------------------------------------------------------
0301 
0302 void PkUpdateList::setRefreshProgress(int progress)
0303 {
0304   if ( progress >= 100 )
0305   {
0306     filterEdit->show();
0307     progressBar->hide();
0308     refreshButton->setEnabled(true);
0309   }
0310   else if ( progress == 0 )
0311   {
0312     // we don't know if there will be a value between 0..100
0313     // Until the first value arrives, use a busy indicator
0314     progressBar->setMinimum(0);
0315     progressBar->setMaximum(0);
0316     progressBar->setValue(0);
0317     progressBar->show();
0318     filterEdit->hide();
0319     refreshButton->setEnabled(false);
0320   }
0321   else
0322   {
0323     progressBar->setMinimum(0);
0324     progressBar->setMaximum(100);
0325     progressBar->setValue(progress);
0326   }
0327 }
0328 
0329 //--------------------------------------------------------------------------------
0330 
0331 bool comparePackageData(const PkUpdates::PackageData &a, const PkUpdates::PackageData &b)
0332 {
0333   return a.summary.localeAwareCompare(b.summary) < 0;
0334 }
0335 
0336 //--------------------------------------------------------------------------------
0337 
0338 void PkUpdateList::setPackages(const PkUpdates::PackageList &packages)
0339 {
0340   itemsLayout->parentWidget()->layout()->setEnabled(false);
0341   itemsLayout->setEnabled(false);
0342 
0343   QLayoutItem *child;
0344   while ( (child = itemsLayout->takeAt(0)) )
0345   {
0346     delete child->widget();
0347     delete child;
0348   }
0349 
0350   checkAllBox->setChecked(false);
0351 
0352   QList<PkUpdates::PackageData> list = packages.values(PackageKit::Transaction::InfoSecurity);
0353   std::sort(list.begin(), list.end(), comparePackageData);
0354   for (const PkUpdates::PackageData &data : list)
0355   {
0356     auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoSecurity, data);
0357     connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked);
0358     itemsLayout->addWidget(item);
0359     item->show();
0360   }
0361 
0362   list = packages.values(PackageKit::Transaction::InfoImportant);
0363   std::sort(list.begin(), list.end(), comparePackageData);
0364   for (const PkUpdates::PackageData &data : list)
0365   {
0366     auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoImportant, data);
0367     connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked);
0368     itemsLayout->addWidget(item);
0369     item->show();
0370   }
0371 
0372   list = packages.values(PackageKit::Transaction::InfoBugfix);
0373   std::sort(list.begin(), list.end(), comparePackageData);
0374   for (const PkUpdates::PackageData &data : list)
0375   {
0376     auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoBugfix, data);
0377     connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked);
0378     itemsLayout->addWidget(item);
0379     item->show();
0380   }
0381 
0382   // all the others
0383   list.clear();
0384   for (PkUpdates::PackageList::const_iterator it = packages.constBegin(); it != packages.constEnd(); ++it)
0385   {
0386     if ( (it.key() != PackageKit::Transaction::InfoSecurity) &&
0387          (it.key() != PackageKit::Transaction::InfoImportant) &&
0388          (it.key() != PackageKit::Transaction::InfoBugfix) )
0389     {
0390       list.append(it.value());
0391     }
0392   }
0393   std::sort(list.begin(), list.end(), comparePackageData);
0394   for (const PkUpdates::PackageData &data : list)
0395   {
0396     auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoUnknown, data);
0397     connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked);
0398     itemsLayout->addWidget(item);
0399     item->show();
0400   }
0401 
0402   itemsLayout->setEnabled(true);
0403 
0404   filterChanged(filterEdit->text());
0405 
0406   countChecked();
0407 }
0408 
0409 //--------------------------------------------------------------------------------
0410 
0411 void PkUpdateList::checkAll(bool on)
0412 {
0413   for (int i = 0; i < itemsLayout->count(); i++)
0414   {
0415     PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget());
0416 
0417     if ( item && !item->isHidden() )
0418     {
0419       item->checkBox->blockSignals(true);
0420       item->checkBox->setChecked(on);
0421       item->checkBox->blockSignals(false);
0422     }
0423   }
0424   countChecked();
0425 }
0426 
0427 //--------------------------------------------------------------------------------
0428 
0429 void PkUpdateList::countChecked()
0430 {
0431   int count = 0;
0432 
0433   for (int i = 0; i < itemsLayout->count(); i++)
0434   {
0435     PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget());
0436 
0437     if ( item && !item->isHidden() && item->checkBox->isChecked() )
0438       count++;
0439   }
0440 
0441   if ( count )
0442   {
0443     installButton->setText(i18np("Install %1 package", "Install %1 packages", count));
0444     installButton->setEnabled(true);
0445     installButton->setMenu(installMenu);
0446   }
0447   else
0448   {
0449     installButton->setText(i18n("Install"));
0450     installButton->setEnabled(false);
0451   }
0452   installButton->setIcon(QIcon());
0453   refreshButton->setEnabled(true);
0454 }
0455 
0456 //--------------------------------------------------------------------------------
0457 
0458 void PkUpdateList::filterChanged(const QString &text)
0459 {
0460   itemsLayout->parentWidget()->layout()->setEnabled(false);
0461 
0462   for (int i = 0; i < itemsLayout->count(); i++)
0463   {
0464     PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget());
0465 
0466     item->setVisible(text.isEmpty() || (item->label->text().indexOf(text, 0, Qt::CaseInsensitive) != -1));
0467   }
0468 
0469   itemsLayout->parentWidget()->layout()->setEnabled(true);
0470   countChecked();
0471 }
0472 
0473 //--------------------------------------------------------------------------------
0474 
0475 void PkUpdateList::install()
0476 {
0477   if ( !installQ.isEmpty() )  // installation in progress; cancel it
0478   {
0479     QPointer<PkUpdateListItem> currentItem = installQ.head();
0480 
0481     if ( transaction )
0482     {
0483       QDBusPendingReply<> reply = transaction->cancel();
0484       reply.waitForFinished();
0485       if ( reply.isError() && currentItem )
0486       {
0487         currentItem->errorLabel->setText(reply.error().message());
0488         currentItem->errorLabel->show();
0489       }
0490     }
0491 
0492     for (int i = 0; i < itemsLayout->count(); i++)
0493     {
0494       PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget());
0495 
0496       if ( !transaction || (item != currentItem) )
0497       {
0498         item->showProgress(false);
0499         item->errorLabel->hide();
0500       }
0501     }
0502 
0503     installQ.clear();
0504     emit packageCountToInstall(0);
0505 
0506     countChecked();
0507     return;
0508   }
0509 
0510   restart = PackageKit::Transaction::RestartNone;
0511 
0512   for (int i = 0; i < itemsLayout->count(); i++)
0513   {
0514     QPointer<PkUpdateListItem> item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget());
0515 
0516     if ( item && !item->isHidden() && item->checkBox->isChecked() )
0517     {
0518       item->showProgress(true);
0519       item->errorLabel->hide();
0520       item->detailsLabel->hide();
0521 
0522       installQ.enqueue(item);
0523     }
0524   }
0525 
0526   installOne();
0527 }
0528 
0529 //--------------------------------------------------------------------------------
0530 
0531 void PkUpdateList::enqueue(const QString &packageID)
0532 {
0533   // find and add item with packageID
0534   for (int i = 0; i < itemsLayout->count(); i++)
0535   {
0536     QPointer<PkUpdateListItem> item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget());
0537 
0538     if ( item && (item->package.id == packageID) )
0539     {
0540       item->showProgress(true);
0541       item->errorLabel->hide();
0542       item->detailsLabel->hide();
0543 
0544       installQ.enqueue(item);
0545       break;
0546     }
0547   }
0548 
0549   if ( !installQ.isEmpty() )
0550     installOne();
0551 }
0552 
0553 //--------------------------------------------------------------------------------
0554 
0555 void PkUpdateList::installOne()
0556 {
0557   emit packageCountToInstall(installQ.count());
0558 
0559   if ( installQ.isEmpty() ) // installation finished
0560   {
0561     if ( restart != PackageKit::Transaction::RestartNone )
0562     {
0563       QString text;
0564 
0565       if ( ((restart == PackageKit::Transaction::RestartSystem) ||
0566             (restart == PackageKit::Transaction::RestartSecuritySystem)) &&
0567            (afterInstall != AfterInstall::Shutdown) )  // when we shutdown, no need to tell the user to restart
0568       {
0569         KNotification *notif = new KNotification("restart needed", KNotification::Persistent);
0570         notif->setWidget(parentWidget());
0571 
0572         notif->setTitle(i18n("System Reboot Required"));
0573         notif->setText(i18n("One of the installed packages requires a system reboot"));
0574         notif->setActions(QStringList() << i18n("Reboot System"));
0575 
0576         connect(notif, &KNotification::action1Activated, this,
0577                 []()
0578                 {
0579                   QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer",
0580                                                                     "org.kde.KSMServerInterface", "logout");
0581                   msg << 0/*no confirm*/ << 1/*reboot*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h
0582 
0583                   QDBusConnection::sessionBus().send(msg);
0584                 });
0585 
0586         notif->sendEvent();
0587       }
0588       else if ( (restart == PackageKit::Transaction::RestartSession) ||
0589                 (restart == PackageKit::Transaction::RestartSecuritySession) )
0590       {
0591         KNotification *notif = new KNotification("restart needed", KNotification::Persistent);
0592         notif->setWidget(parentWidget());
0593 
0594         notif->setTitle(i18n("Session Restart Required"));
0595         notif->setText(i18n("One of the installed packages requires you to logout"));
0596         notif->setActions(QStringList() << i18n("Logout"));
0597 
0598         connect(notif, &KNotification::action1Activated, this,
0599                 []()
0600                 {
0601                   QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer",
0602                                                                     "org.kde.KSMServerInterface", "logout");
0603                   msg << 0/*no confirm*/ << 0/*logout*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h
0604 
0605                   QDBusConnection::sessionBus().send(msg);
0606                 });
0607 
0608         notif->sendEvent();
0609       }
0610     }
0611 
0612     countChecked();
0613 
0614     if ( eulaDialogs.isEmpty() )
0615     {
0616       if ( afterInstall == AfterInstall::Sleep )
0617       {
0618         QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.PowerManagement",
0619                                                           "/org/freedesktop/PowerManagement",
0620                                                           "org.freedesktop.PowerManagement", "Suspend");
0621 
0622         QDBusConnection::sessionBus().send(msg);
0623       }
0624       else if ( afterInstall == AfterInstall::Shutdown )
0625       {
0626         QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer",
0627                                                           "org.kde.KSMServerInterface", "logout");
0628         msg << 0/*no confirm*/ << 2/*halt*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h
0629 
0630         QDBusConnection::sessionBus().send(msg);
0631       }
0632     }
0633 
0634     return;
0635   }
0636 
0637   QPointer<PkUpdateListItem> item = installQ.head();
0638 
0639   if ( !item )
0640     return;
0641 
0642   installButton->setText(i18n("Cancel Installation"));
0643   installButton->setMenu(nullptr);
0644   installButton->setIcon(QIcon::fromTheme("process-stop"));
0645   refreshButton->setEnabled(false);
0646 
0647   PackageKit::Transaction::TransactionFlag flag = PackageKit::Transaction::TransactionFlagOnlyTrusted;
0648 #ifdef TEST_LOGOUT
0649   flag |= PackageKit::Transaction::TransactionFlagSimulate;
0650   restart = PackageKit::Transaction::RestartSession;
0651 #elif defined TEST_REBOOT
0652   flag |= PackageKit::Transaction::TransactionFlagSimulate;
0653   restart = PackageKit::Transaction::RestartSystem;
0654 #endif
0655   transaction = PackageKit::Daemon::updatePackage(item->package.id, flag);
0656   packageNoLongerAvailable = false;
0657   //qDebug() << "installing" << item->package.id;
0658 
0659   connect(transaction.data(), &PackageKit::Transaction::allowCancelChanged, this,
0660           [this]()
0661           {
0662             installButton->setEnabled(transaction->allowCancel());
0663           });
0664 
0665   connect(transaction.data(), &PackageKit::Transaction::statusChanged, this,
0666           [item, this]()
0667           {
0668             if ( !item )  // already deleted
0669               return;
0670 
0671             //qDebug() << "status" << QMetaEnum::fromType<PackageKit::Transaction::Status>().valueToKey(transaction->status());
0672             QString text;
0673             switch ( transaction->status() )
0674             {
0675               case PackageKit::Transaction::StatusWait: text = i18n("Waiting"); break;
0676               case PackageKit::Transaction::StatusWaitingForAuth: text = i18n("Waiting for authentication"); break;
0677               case PackageKit::Transaction::StatusDepResolve: text = i18n("Resolving dependencies"); break;
0678               case PackageKit::Transaction::StatusUpdate: text = i18n("Updating"); break;
0679               case PackageKit::Transaction::StatusInstall: text = i18n("Installing"); break;
0680               case PackageKit::Transaction::StatusDownload: text = i18n("Downloading"); break;
0681               case PackageKit::Transaction::StatusCancel: text = i18n("Canceling"); break;
0682               case PackageKit::Transaction::StatusFinished: return;  // don't hide error label
0683               default: return;
0684             }
0685 
0686             if ( text.isEmpty() )
0687               item->errorLabel->hide();
0688             else
0689             {
0690               item->errorLabel->setText(text);
0691               item->errorLabel->show();
0692             }
0693           });
0694 
0695   connect(transaction.data(), &PackageKit::Transaction::itemProgress, this,
0696           [item](const QString &itemID, PackageKit::Transaction::Status status, uint percentage)
0697           {
0698             Q_UNUSED(status)
0699 
0700             if ( !item )  // already deleted
0701               return;
0702 
0703             item->packageLabel->setText(PackageKit::Daemon::packageName(itemID));
0704             item->packageLabel->show();
0705 
0706             if ( percentage <= 100 )  // 101 .. unknown
0707               item->progress->setValue(percentage);
0708           });
0709 
0710   connect(transaction.data(), &PackageKit::Transaction::requireRestart, this,
0711           [this](PackageKit::Transaction::Restart type, const QString &/*packageID*/)
0712           {
0713             // keep most important restart type: System, Session
0714             if ( (type == PackageKit::Transaction::RestartSystem) ||
0715                  (type == PackageKit::Transaction::RestartSecuritySystem) ||
0716 
0717                  (((type == PackageKit::Transaction::RestartSession) ||
0718                    (type == PackageKit::Transaction::RestartSecuritySession)) &&
0719                    (restart != PackageKit::Transaction::RestartSystem) &&
0720                    (restart != PackageKit::Transaction::RestartSecuritySystem)) )
0721               restart = type;
0722           });
0723 
0724   connect(transaction.data(), &PackageKit::Transaction::eulaRequired, this, &PkUpdateList::askEULA);
0725 
0726   connect(transaction.data(), &PackageKit::Transaction::repoSignatureRequired, this,
0727           [this](const QString &packageID,
0728                  const QString &repoName,
0729                  const QString &keyUrl,
0730                  const QString &keyUserid,
0731                  const QString &keyId,
0732                  const QString &keyFingerprint,
0733                  const QString &keyTimestamp,
0734                  PackageKit::Transaction::SigType type)
0735           {
0736             if ( QMessageBox::question(this, i18n("Validate Signature"),
0737                                        QString("%1\n%2\n%3\n%4\n%5\n%6\n%7")
0738                                                .arg(packageID)
0739                                                .arg(repoName)
0740                                                .arg(keyUrl)
0741                                                .arg(keyUserid)
0742                                                .arg(keyId)
0743                                                .arg(keyFingerprint)
0744                                                .arg(keyTimestamp)) == QMessageBox::Yes )
0745             {
0746               PackageKit::Daemon::installSignature(type, keyId, packageID);
0747             }
0748           });
0749 
0750   connect(transaction.data(), &PackageKit::Transaction::errorCode, this,
0751           [item, this](PackageKit::Transaction::Error error, const QString &details)
0752           {
0753             if ( !item )
0754               return;
0755 
0756             item->showProgress(false);
0757             item->errorLabel->setText(details);
0758             item->errorLabel->show();
0759             //qDebug() << "errorCode" << details << QMetaEnum::fromType<PackageKit::Transaction::Error>().valueToKey(error);
0760 
0761             if ( (error == PackageKit::Transaction::ErrorDepResolutionFailed) &&
0762                  (transaction->status() == PackageKit::Transaction::StatusSetup) )
0763             {
0764               // when a package was already installed due to another dependency, then it's no more an update candidate.
0765               // Still the finished() signal will arrive which will do cleanup (delete item, dequeue, etc.).
0766               packageNoLongerAvailable = true;
0767             }
0768           });
0769 
0770   connect(transaction.data(), &PackageKit::Transaction::finished, this,
0771           [item, this](PackageKit::Transaction::Exit status, uint runtime)
0772           {
0773             Q_UNUSED(runtime)
0774 
0775             if ( !item )
0776               return;
0777 
0778             if ( (status == PackageKit::Transaction::ExitSuccess) || packageNoLongerAvailable )
0779             {
0780               item->hide();
0781               item->deleteLater();
0782               emit packageInstalled(item->package.id);
0783             }
0784             else
0785             {
0786               item->showProgress(false);
0787               item->detailsLabel->setText(i18n("Update failed"));
0788               item->detailsLabel->show();
0789             }
0790 
0791             if ( !installQ.isEmpty() )  // might have been cancelled, q cleared
0792               installQ.dequeue();
0793 
0794             installOne();
0795           });
0796 }
0797 
0798 //--------------------------------------------------------------------------------
0799 
0800 void PkUpdateList::askEULA(const QString &eulaID, const QString &packageID,
0801                            const QString &vendor, const QString &licenseAgreement)
0802 {
0803   // I get several different eulaIDs but each having the same licenseAgreement text. Don't bother the user
0804   // showing multiple times the same licenseAgreement
0805   QByteArray hash = QCryptographicHash::hash(licenseAgreement.trimmed().toUtf8(), QCryptographicHash::Sha1);
0806 
0807   //qDebug() << "askEULA" << eulaID << packageID << hash.toBase64();
0808 
0809   // we can get multiple signals for the same eulaID
0810   // when installing multiple packages using the same EULA
0811   // or we get different eulaIDs with the same text
0812   if ( eulaDialogs.contains(hash) )
0813   {
0814     bool found = false;
0815     for (const EulaPackage &ep : eulaDialogs.values(hash))
0816     {
0817       if ( ep.eulaID == eulaID )
0818       {
0819         found = true;
0820         break;
0821       }
0822     }
0823 
0824     if ( !found )
0825       eulaDialogs.insert(hash, EulaPackage(eulaID, packageID));
0826 
0827     return;
0828   }
0829 
0830   if ( acceptedEula.contains(hash) )
0831   {
0832     //qDebug() << "already accepted - enqueue again";
0833     PackageKit::Daemon::acceptEula(eulaID);
0834     enqueue(packageID);
0835     return;
0836   }
0837 
0838   eulaDialogs.insert(hash, EulaPackage(eulaID, packageID));
0839 
0840   QDialog *dialog = new QDialog;
0841   dialog->setModal(false);
0842   dialog->setWindowTitle(i18n("Accept EULA"));
0843 
0844   QVBoxLayout *vbox = new QVBoxLayout(dialog);
0845   QHBoxLayout *hbox = new QHBoxLayout;
0846 
0847   QLabel *iconLabel = new QLabel;
0848   iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0849   iconLabel->setAlignment(Qt::AlignTop);
0850   QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, this);
0851   int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, nullptr, this);
0852   iconLabel->setPixmap(icon.pixmap(windowHandle(), QSize(iconSize, iconSize)));
0853 
0854   QLabel *label = new QLabel(i18n("<html>Do you accept the following license agreement "
0855                                   "for package<br>%1<br>from vendor<br>%2 ?</html>").arg(packageID, vendor));
0856   label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
0857   label->setTextInteractionFlags(Qt::TextBrowserInteraction);
0858 
0859   hbox->addWidget(iconLabel);
0860   hbox->addWidget(label);
0861 
0862   vbox->addLayout(hbox);
0863 
0864   QTextEdit *textEdit = new QTextEdit;
0865   textEdit->setPlainText(licenseAgreement);
0866   textEdit->setReadOnly(true);
0867   vbox->addWidget(textEdit);
0868 
0869   QDialogButtonBox *buttonBox = new QDialogButtonBox();
0870   buttonBox->setCenterButtons(style()->styleHint(QStyle::SH_MessageBox_CenterButtons, nullptr, this));
0871   buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
0872   buttonBox->button(QDialogButtonBox::Yes)->setDefault(true);
0873 
0874   connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
0875   connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
0876   vbox->addWidget(buttonBox);
0877 
0878   connect(dialog, &QDialog::finished, this,
0879           [this, dialog, hash](int result)
0880           {
0881             if ( result == QDialog::Accepted )
0882             {
0883               for (const EulaPackage &ep : eulaDialogs.values(hash))
0884               {
0885                 PackageKit::Daemon::acceptEula(ep.eulaID);
0886                 enqueue(ep.packageID);
0887               }
0888 
0889               acceptedEula.insert(hash);
0890             }
0891 
0892             eulaDialogs.remove(hash);
0893             dialog->deleteLater();
0894           });
0895 
0896   // The dialog will be shown non modal and used asynchronously to the installation.
0897   // PackageKit Daemon will already finish this package with an error of
0898   // something like "you need to agree/decline the license"
0899   // So when the user decides what to do, he must afterwards start the installation
0900   // for this package again
0901   dialog->show();
0902 }
0903 
0904 //--------------------------------------------------------------------------------