diff --git a/src/declarative/pkupdates.h b/src/declarative/pkupdates.h --- a/src/declarative/pkupdates.h +++ b/src/declarative/pkupdates.h @@ -156,6 +156,17 @@ */ void updateDetail(const QString &packageID, const QString &updateText, const QStringList &urls); + /** + * Emitted when an EULA agreement prevents the transaction from running + * @param eulaId the EULA identifier + * @param packageID ID of the package for which an EULA is required + * @param vendorName the vendor name + * @param licenseAgreement the EULA text + * + * @see eulaAgreementResult() + */ + void eulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement); + // private ;) void statusMessageChanged(); void isActiveChanged(); @@ -205,6 +216,11 @@ Q_INVOKABLE void doDelayedCheckUpdates(); + /** + * If agreed to eulaID, starts an EULA acceptance transaction and continues. + */ + Q_INVOKABLE void eulaAgreementResult(const QString &eulaID, bool agreed); + private slots: void getUpdates(); void onChanged(); @@ -221,15 +237,25 @@ const QDateTime &issued, const QDateTime &updated); void onRepoSignatureRequired(const QString & packageID, const QString & repoName, const QString & keyUrl, const QString & keyUserid, const QString & keyId, const QString & keyFingerprint, const QString & keyTimestamp, PackageKit::Transaction::SigType type); + void onEulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement); private: + struct EulaData { + QString packageID; + QString vendor; + QString licenseAgreement; + }; + void setStatusMessage(const QString &message); void setActivity(Activity act); void setPercentage(int value); + void promptNextEulaAgreement(); QPointer m_updatesTrans; QPointer m_cacheTrans; QPointer m_installTrans; QPointer m_detailTrans; + QPointer m_eulaTrans; + QStringList m_packages; QPointer m_lastNotification; int m_lastUpdateCount = 0; QVariantMap m_updateList; @@ -241,6 +267,9 @@ bool m_lastCheckSuccessful = false; bool m_checkUpdatesWhenNetworkOnline = false; bool m_isOnBattery; + // If a transaction failed because of required EULAs, + // this contains a map of their IDs to their data + QMap m_requiredEulas; }; #endif // PLASMA_PK_UPDATES_H diff --git a/src/declarative/pkupdates.cpp b/src/declarative/pkupdates.cpp --- a/src/declarative/pkupdates.cpp +++ b/src/declarative/pkupdates.cpp @@ -296,16 +296,18 @@ flags = PackageKit::Transaction::TransactionFlagNone; } - m_installTrans = PackageKit::Daemon::updatePackages(packageIds, flags); - m_installTrans->setProperty("packages", packageIds); + m_requiredEulas.clear(); + m_packages = packageIds; + m_installTrans = PackageKit::Daemon::updatePackages(m_packages, flags); setActivity(InstallingUpdates); connect(m_installTrans.data(), &PackageKit::Transaction::statusChanged, this, &PkUpdates::onStatusChanged); connect(m_installTrans.data(), &PackageKit::Transaction::finished, this, &PkUpdates::onFinished); connect(m_installTrans.data(), &PackageKit::Transaction::errorCode, this, &PkUpdates::onErrorCode); connect(m_installTrans.data(), &PackageKit::Transaction::package, this, &PkUpdates::onPackageUpdating); connect(m_installTrans.data(), &PackageKit::Transaction::requireRestart, this, &PkUpdates::onRequireRestart); connect(m_installTrans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PkUpdates::onRepoSignatureRequired); + connect(m_installTrans.data(), &PackageKit::Transaction::eulaRequired, this, &PkUpdates::onEulaRequired); } void PkUpdates::onChanged() @@ -431,25 +433,28 @@ qCDebug(PLASMA_PK_UPDATES) << "Total number of updates: " << count(); emit done(); } else if (trans->role() == PackageKit::Transaction::RoleUpdatePackages) { - const QStringList packages = trans->property("packages").toStringList(); - qCDebug(PLASMA_PK_UPDATES) << "Finished updating packages:" << packages; + qCDebug(PLASMA_PK_UPDATES) << "Finished updating packages:" << m_packages; if (status == PackageKit::Transaction::ExitNeedUntrusted) { qCDebug(PLASMA_PK_UPDATES) << "Transaction needs untrusted packages"; // restart transaction with "untrusted" flag - installUpdates(packages, false /*simulate*/, true /*untrusted*/); + installUpdates(m_packages, false /*simulate*/, true /*untrusted*/); + return; + } else if (status == PackageKit::Transaction::ExitEulaRequired) { + qCDebug(PLASMA_PK_UPDATES) << "Acceptance of EULAs required"; + promptNextEulaAgreement(); return; } else if (status == PackageKit::Transaction::ExitSuccess && trans->transactionFlags().testFlag(PackageKit::Transaction::TransactionFlagSimulate)) { qCDebug(PLASMA_PK_UPDATES) << "Simulation finished with success, restarting the transaction"; - installUpdates(packages, false /*simulate*/, false /*untrusted*/); + installUpdates(m_packages, false /*simulate*/, false /*untrusted*/); return; } else if (status == PackageKit::Transaction::ExitSuccess) { qCDebug(PLASMA_PK_UPDATES) << "Update packages transaction finished successfully"; if (m_lastNotification) { m_lastNotification->close(); } KNotification::event(s_eventIdUpdatesInstalled, i18n("Updates Installed"), - i18np("Successfully updated %1 package", "Successfully updated %1 packages", packages.count()), + i18np("Successfully updated %1 package", "Successfully updated %1 packages", m_packages.count()), s_pkUpdatesIconName, nullptr, KNotification::CloseOnTimeout, s_componentName); @@ -475,7 +480,7 @@ void PkUpdates::onErrorCode(PackageKit::Transaction::Error error, const QString &details) { qWarning() << "PK error:" << details << "type:" << PackageKit::Daemon::enumToString((int)error, "Error"); - if (error == PackageKit::Transaction::ErrorBadGpgSignature) + if (error == PackageKit::Transaction::ErrorBadGpgSignature || error == PackageKit::Transaction::ErrorNoLicenseAgreement) return; KNotification::event(s_eventIdError, i18n("Update Error"), @@ -553,6 +558,46 @@ qCDebug(PLASMA_PK_UPDATES) << "Repo sig required" << packageID; } +void PkUpdates::onEulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement) +{ + m_requiredEulas[eulaID] = {packageID, vendor, licenseAgreement}; +} + +void PkUpdates::promptNextEulaAgreement() +{ + if(m_requiredEulas.empty()) { + // Restart the transaction + installUpdates(m_packages, false, false); + return; + } + + QString eulaID = m_requiredEulas.firstKey(); + const EulaData &eula = m_requiredEulas[eulaID]; + emit eulaRequired(eulaID, eula.packageID, eula.vendor, eula.licenseAgreement); +} + +void PkUpdates::eulaAgreementResult(const QString &eulaID, bool agreed) +{ + if(!agreed) { + qCDebug(PLASMA_PK_UPDATES) << "EULA declined"; + // Do the same as the failure case in onFinished + checkUpdates(false /* force */); + return; + } + + m_eulaTrans = PackageKit::Daemon::acceptEula(eulaID); + connect(m_eulaTrans.data(), &PackageKit::Transaction::finished, this, + [this, eulaID] (PackageKit::Transaction::Exit exit, uint) { + if (exit == PackageKit::Transaction::ExitSuccess) { + m_requiredEulas.remove(eulaID); + promptNextEulaAgreement(); + } else { + qCWarning(PLASMA_PK_UPDATES) << "EULA acceptance failed"; + } + } + ); +} + void PkUpdates::setStatusMessage(const QString &message) { m_statusMessage = message; diff --git a/src/plasma/contents/ui/Full.qml b/src/plasma/contents/ui/Full.qml --- a/src/plasma/contents/ui/Full.qml +++ b/src/plasma/contents/ui/Full.qml @@ -22,6 +22,7 @@ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.3 +import QtQuick.Dialogs 1.2 import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.core 2.0 as PlasmaCore @@ -46,10 +47,68 @@ onUpdatesChanged: populateModel() onUpdateDetail: updateDetails(packageID, updateText, urls) onUpdatesInstalled: plasmoid.expanded = false + onEulaRequired: eulaDialog.showPrompt(eulaID, packageID, vendor, licenseAgreement) } Component.onCompleted: populateModel() + Dialog { + property string eulaID: "" + property string packageName: "" + property string vendor: "" + property string licenseText: "" + + property bool buttonClicked: false + + id: eulaDialog + title: i18n("License Agreement for %1").arg(packageName) + standardButtons: StandardButton.Yes | StandardButton.No + + ColumnLayout { + anchors.fill: parent + + Label { + text: i18n("License agreement required for %1 (from %2):").arg(eulaDialog.packageName).arg(eulaDialog.vendor) + } + + TextArea { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumWidth: 400 + Layout.minimumHeight: 200 + text: eulaDialog.licenseText + readOnly: true + } + + Label { + text: i18n("Do you accept?") + } + } + + onVisibleChanged: { + // onRejected does not fire on dialog closing, so implement that ourselves + if(!visible && !buttonClicked) + onNo(); + } + onNo: { + buttonClicked = true; + PkUpdates.eulaAgreementResult(this.eulaID, false); + } + onYes: { + buttonClicked = true; + PkUpdates.eulaAgreementResult(this.eulaID, true); + } + + function showPrompt(eulaID, packageID, vendor, licenseAgreement) { + this.eulaID = eulaID; + this.packageName = PkUpdates.packageName(packageID); + this.vendor = vendor; + this.licenseText = licenseAgreement; + + this.visible = true; + } + } + ListModel { id: updatesModel }