diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f3f470..285b1dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,50 +1,50 @@ cmake_minimum_required(VERSION 3.0) -project(plasma-pk-updates VERSION 0.3.0) +project(plasma-pk-updates VERSION 0.3.1) add_definitions(-DPROJECT_VERSION="\\\"${PROJECT_VERSION}\\\"") find_package(ECM 1.3.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) set(CMAKE_AUTOMOC ON) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(ECMInstallIcons) include(FeatureSummary) find_package(Qt5 REQUIRED Core Gui Widgets Quick DBus ) find_package(KF5 REQUIRED Plasma I18n CoreAddons # KFormat Notifications IconThemes # KIconLoader KDELibs4Support #Solid::Power ) find_package(packagekitqt5 REQUIRED) add_definitions( -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -DQT_STRICT_ITERATORS -DCMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}" ) if(CMAKE_COMPILER_IS_GNUCXX) # more aggressive warnings and C++11 support SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") endif() add_subdirectory(src) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/declarative/pkupdates.cpp b/src/declarative/pkupdates.cpp index b4d877f..910c331 100644 --- a/src/declarative/pkupdates.cpp +++ b/src/declarative/pkupdates.cpp @@ -1,503 +1,506 @@ /*************************************************************************** * Copyright (C) 2015 Lukáš Tinkl * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "pkupdates.h" #include "PkStrings.h" Q_LOGGING_CATEGORY(PLASMA_PK_UPDATES, "plasma-pk-updates") PkUpdates::PkUpdates(QObject *parent) : QObject(parent), m_updatesTrans(Q_NULLPTR), m_cacheTrans(Q_NULLPTR), m_installTrans(Q_NULLPTR), m_detailTrans(Q_NULLPTR) { setStatusMessage(i18n("Idle")); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::changed, this, &PkUpdates::onChanged); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::updatesChanged, this, &PkUpdates::onUpdatesChanged); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::networkStateChanged, this, &PkUpdates::networkStateChanged); connect(Solid::PowerManagement::notifier(), &Solid::PowerManagement::Notifier::resumingFromSuspend, this, - [this] {PackageKit::Daemon::stateHasChanged("resume");}); + [this] {PackageKit::Daemon::stateHasChanged(QStringLiteral("resume"));}); connect(Solid::PowerManagement::notifier(), &Solid::PowerManagement::Notifier::appShouldConserveResourcesChanged, this, &PkUpdates::isOnBatteryChanged); } PkUpdates::~PkUpdates() { if (m_cacheTrans) { if (m_cacheTrans->allowCancel()) m_cacheTrans->cancel(); m_cacheTrans->deleteLater(); } if (m_updatesTrans) { if (m_updatesTrans->allowCancel()) m_updatesTrans->cancel(); m_updatesTrans->deleteLater(); } if (m_installTrans) { m_installTrans->deleteLater(); } if (m_detailTrans) { m_detailTrans->deleteLater(); } } int PkUpdates::count() const { return m_updateList.count(); } int PkUpdates::importantCount() const { return m_importantList.count(); } int PkUpdates::securityCount() const { return m_securityList.count(); } bool PkUpdates::isSystemUpToDate() const { return m_updateList.isEmpty(); } QString PkUpdates::iconName() const { if (securityCount() > 0) { - return "update-high"; + return QStringLiteral("update-high"); } else if (importantCount() > 0) { - return "update-medium"; + return QStringLiteral("update-medium"); } else if (count() > 0) { - return "update-low"; + return QStringLiteral("update-low"); } else { - return "update-none"; + return QStringLiteral("update-none"); } } QString PkUpdates::message() const { if (isActive()) { if (m_activity == CheckingUpdates) return i18n("Checking updates"); else if (m_activity == GettingUpdates) return i18n("Getting updates"); else if (m_activity == InstallingUpdates) return i18n("Installing updates"); return i18n("Working"); } else if (!isSystemUpToDate()) { QStringList extra; const QString msg = i18np("You have 1 new update", "You have %1 new updates", count()); if (securityCount() > 0) extra += i18np("1 security update", "%1 security updates", securityCount()); if (importantCount() > 0) extra += i18np("1 important update", "%1 important updates", importantCount()); if (extra.isEmpty()) return msg; else return msg + "
" + i18n("(including %1)", extra.join(i18n(" and "))); } else if (!isNetworkOnline()) { return i18n("Your system is offline"); } return i18n("Your system is up to date"); } int PkUpdates::percentage() const { return m_percentage; } QString PkUpdates::statusMessage() const { return m_statusMessage; } bool PkUpdates::isActive() const { return m_activity != Idle; } QVariantMap PkUpdates::packages() const { return m_updateList; } bool PkUpdates::isNetworkOnline() const { qCDebug(PLASMA_PK_UPDATES) << "Is net online:" << (PackageKit::Daemon::networkState() > PackageKit::Daemon::Network::NetworkOffline); return (PackageKit::Daemon::networkState() > PackageKit::Daemon::Network::NetworkOffline); } bool PkUpdates::isNetworkMobile() const { qCDebug(PLASMA_PK_UPDATES) << "Is net mobile:" << (PackageKit::Daemon::networkState() == PackageKit::Daemon::Network::NetworkMobile); return (PackageKit::Daemon::networkState() == PackageKit::Daemon::Network::NetworkMobile); } bool PkUpdates::isOnBattery() const { qCDebug(PLASMA_PK_UPDATES) << "Is on battery:" << Solid::PowerManagement::appShouldConserveResources(); return Solid::PowerManagement::appShouldConserveResources(); } void PkUpdates::getUpdateDetails(const QString &pkgID) { qCDebug(PLASMA_PK_UPDATES) << "Requesting update details for" << pkgID; m_detailTrans = PackageKit::Daemon::getUpdateDetail(pkgID); connect(m_detailTrans.data(), &PackageKit::Transaction::updateDetail, this, &PkUpdates::onUpdateDetail); } QString PkUpdates::timestamp() const { const qint64 lastCheck = QDateTime::currentMSecsSinceEpoch() - lastRefreshTimestamp(); if (lastCheck != -1) - return i18n("Last update: %1 ago", KFormat().formatSpelloutDuration(lastCheck)); + return i18n("Last check: %1 ago", KFormat().formatSpelloutDuration(lastCheck)); - return i18n("Last update: never"); + return i18n("Last check: never"); } -void PkUpdates::checkUpdates() +void PkUpdates::checkUpdates(bool force) { qCDebug(PLASMA_PK_UPDATES) << "Checking updates, forced"; // save the timestamp KConfigGroup grp(KSharedConfig::openConfig("plasma-pk-updates"), "General"); grp.writeEntry("Timestamp", QDateTime::currentDateTime().toMSecsSinceEpoch()); grp.sync(); - // ask the Packagekit daemon to refresh the cache - m_cacheTrans = PackageKit::Daemon::refreshCache(true); + // ask the Packagekit daemon to refresh the cache + m_cacheTrans = PackageKit::Daemon::refreshCache(force); setActivity(CheckingUpdates); // evaluate the result connect(m_cacheTrans.data(), &PackageKit::Transaction::statusChanged, this, &PkUpdates::onStatusChanged); connect(m_cacheTrans.data(), &PackageKit::Transaction::finished, this, &PkUpdates::onFinished); connect(m_cacheTrans.data(), &PackageKit::Transaction::errorCode, this, &PkUpdates::onErrorCode); connect(m_cacheTrans.data(), &PackageKit::Transaction::requireRestart, this, &PkUpdates::onRequireRestart); connect(m_cacheTrans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PkUpdates::onRepoSignatureRequired); } qint64 PkUpdates::lastRefreshTimestamp() const { KConfigGroup grp(KSharedConfig::openConfig("plasma-pk-updates"), "General"); return grp.readEntry("Timestamp", -1); } QString PkUpdates::packageName(const QString &pkgId) { return PackageKit::Daemon::packageName(pkgId); } QString PkUpdates::packageVersion(const QString &pkgId) { return PackageKit::Daemon::packageVersion(pkgId); } void PkUpdates::getUpdates() { m_updatesTrans = PackageKit::Daemon::getUpdates(); setActivity(GettingUpdates); m_updateList.clear(); m_importantList.clear(); m_securityList.clear(); connect(m_updatesTrans.data(), &PackageKit::Transaction::statusChanged, this, &PkUpdates::onStatusChanged); connect(m_updatesTrans.data(), &PackageKit::Transaction::finished, this, &PkUpdates::onFinished); connect(m_updatesTrans.data(), &PackageKit::Transaction::errorCode, this, &PkUpdates::onErrorCode); connect(m_updatesTrans.data(), &PackageKit::Transaction::package, this, &PkUpdates::onPackage); connect(m_updatesTrans.data(), &PackageKit::Transaction::requireRestart, this, &PkUpdates::onRequireRestart); connect(m_updatesTrans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PkUpdates::onRepoSignatureRequired); } void PkUpdates::installUpdates(const QStringList &packageIds, bool simulate, bool untrusted) { qCDebug(PLASMA_PK_UPDATES) << "Installing updates" << packageIds << ", simulate:" << simulate << ", untrusted:" << untrusted; PackageKit::Transaction::TransactionFlags flags = PackageKit::Transaction::TransactionFlagOnlyTrusted; if (simulate) { flags |= PackageKit::Transaction::TransactionFlagSimulate; } else if (untrusted) { flags = PackageKit::Transaction::TransactionFlagNone; } m_installTrans = PackageKit::Daemon::updatePackages(packageIds, flags); m_installTrans->setProperty("packages", packageIds); 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); } void PkUpdates::onChanged() { qCDebug(PLASMA_PK_UPDATES) << "Daemon changed"; } void PkUpdates::onUpdatesChanged() { qCDebug(PLASMA_PK_UPDATES) << "Updates changed, getting updates!"; getUpdates(); } void PkUpdates::onStatusChanged() { PackageKit::Transaction * trans; if ((trans = qobject_cast(sender()))) { qCDebug(PLASMA_PK_UPDATES) << "Transaction status changed:" << PackageKit::Daemon::enumToString((int)trans->status(), "Status") << QStringLiteral("(%1%)").arg(trans->percentage()); if (trans->status() == PackageKit::Transaction::StatusFinished) return; setStatusMessage(PkStrings::status(trans->status(), trans->speed(), trans->downloadSizeRemaining())); setPercentage(trans->percentage()); } } void PkUpdates::onPackage(PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) { qCDebug(PLASMA_PK_UPDATES) << "Got update package:" << packageID << ", summary:" << summary << ", type:" << PackageKit::Daemon::enumToString((int)info, "Info"); switch (info) { case PackageKit::Transaction::InfoBlocked: // Blocked updates are not instalable updates so there is no // reason to show/count them return; case PackageKit::Transaction::InfoImportant: m_importantList << packageID; break; case PackageKit::Transaction::InfoSecurity: m_securityList << packageID; break; default: break; } m_updateList.insert(packageID, summary); } void PkUpdates::onPackageUpdating(PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) { Q_UNUSED(summary) qCDebug(PLASMA_PK_UPDATES) << "Package updating:" << packageID << ", info:" << PackageKit::Daemon::enumToString((int)info, "Info"); const uint percent = m_installTrans->percentage(); if (percent >= 0 && percent <= 100) { setStatusMessage(i18nc("1 installation status, 2 pkg name, 3 percentage", "%1 %2 (%3%)", PkStrings::infoPresent(info), PackageKit::Daemon::packageName(packageID), percent)); } else { setStatusMessage(i18nc("1 installation status, 2 pkg name", "%1 %2", PkStrings::infoPresent(info), PackageKit::Daemon::packageName(packageID), percent)); } setPercentage(percent); } void PkUpdates::onFinished(PackageKit::Transaction::Exit status, uint runtime) { PackageKit::Transaction * trans = qobject_cast(sender()); if (!trans) return; trans->deleteLater(); qCDebug(PLASMA_PK_UPDATES) << "Transaction" << trans->tid().path() << "finished with status" << PackageKit::Daemon::enumToString((int)status, "Exit") << "in" << runtime/1000 << "seconds"; if (trans->role() == PackageKit::Transaction::RoleRefreshCache) { if (status == PackageKit::Transaction::ExitSuccess) { qCDebug(PLASMA_PK_UPDATES) << "Cache transaction finished successfully"; return; } else { qCDebug(PLASMA_PK_UPDATES) << "Cache transaction didn't finish successfully"; emit done(); } } else if (trans->role() == PackageKit::Transaction::RoleGetUpdates) { if (status == PackageKit::Transaction::ExitSuccess) { qCDebug(PLASMA_PK_UPDATES) << "Check updates transaction finished successfully"; const int upCount = count(); if (upCount > 0) { KNotification::event(KNotification::Notification, i18n("Software Updates Available"), i18np("You have 1 new update", "You have %1 new updates", upCount), KIconLoader::global()->loadIcon("system-software-update", KIconLoader::Desktop), 0, KNotification::Persistent); } } else { qCDebug(PLASMA_PK_UPDATES) << "Check updates transaction didn't finish successfully"; } 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; if (status == PackageKit::Transaction::ExitNeedUntrusted) { qCDebug(PLASMA_PK_UPDATES) << "Transaction needs untrusted packages"; // restart transaction with "untrusted" flag installUpdates(packages, false /*simulate*/, true /*untrusted*/); 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*/); return; } else if (status == PackageKit::Transaction::ExitSuccess) { qCDebug(PLASMA_PK_UPDATES) << "Update packages transaction finished successfully"; KNotification::event(KNotification::Notification, i18n("Updates Installed"), i18np("Successfully updated %1 package", "Successfully updated %1 packages", packages.count()), KIconLoader::global()->loadIcon("system-software-update", KIconLoader::Desktop), 0, KNotification::Persistent); } else { qCDebug(PLASMA_PK_UPDATES) << "Update packages transaction didn't finish successfully"; + // just try to refresh cache in case of error, the user might have installed the updates manually meanwhile + checkUpdates(false /* force */); + return; } setActivity(Idle); return; } else { qCDebug(PLASMA_PK_UPDATES) << "Unhandled transaction type:" << PackageKit::Daemon::enumToString(trans->role(), "Role"); setActivity(Idle); return; } setActivity(Idle); emit updatesChanged(); } 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) return; - KNotification::event(KNotification::Error, i18n("Update Error"), PkStrings::error(error) + " " + PkStrings::errorMessage(error), + KNotification::event(KNotification::Error, i18n("Update Error"), details, KIconLoader::global()->loadIcon("system-software-update", KIconLoader::Desktop), 0, KNotification::Persistent); } void PkUpdates::onRequireRestart(PackageKit::Transaction::Restart type, const QString &packageID) { if (type == PackageKit::Transaction::RestartSystem || type == PackageKit::Transaction::RestartSession) { KNotification *notification = new KNotification(QLatin1String("notification"), KNotification::Persistent); notification->setPixmap(KIconLoader::global()->loadIcon("system-software-update", KIconLoader::Desktop)); if (type == PackageKit::Transaction::RestartSystem) { notification->setActions(QStringList{QLatin1String("Restart")}); notification->setTitle(i18n("Restart is required")); notification->setText(i18n("The computer will have to be restarted after the update for the changes to take effect.")); } else { notification->setActions(QStringList{QLatin1String("Logout")}); notification->setTitle(i18n("Session restart is required")); notification->setText(i18n("You will need to log out and back in after the update for the changes to take effect.")); } connect(notification, &KNotification::action1Activated, this, [type] () { QDBusInterface interface("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface", QDBusConnection::sessionBus()); if (type == PackageKit::Transaction::RestartSystem) { interface.asyncCall("logout", 0, 1, 2); // Options: do not ask again | reboot | force } else { interface.asyncCall("logout", 0, 0, 2); // Options: do not ask again | logout | force } }); notification->sendEvent(); } qCDebug(PLASMA_PK_UPDATES) << "RESTART" << PackageKit::Daemon::enumToString((int)type, "Restart") << "is required for package" << packageID; } void PkUpdates::onUpdateDetail(const QString &packageID, const QStringList &updates, const QStringList &obsoletes, const QStringList &vendorUrls, const QStringList &bugzillaUrls, const QStringList &cveUrls, PackageKit::Transaction::Restart restart, const QString &updateText, const QString &changelog, PackageKit::Transaction::UpdateState state, const QDateTime &issued, const QDateTime &updated) { Q_UNUSED(updates); Q_UNUSED(obsoletes); Q_UNUSED(vendorUrls); Q_UNUSED(cveUrls); Q_UNUSED(restart); Q_UNUSED(changelog); Q_UNUSED(state); Q_UNUSED(issued); Q_UNUSED(updated); qCDebug(PLASMA_PK_UPDATES) << "Got update details for" << packageID; emit updateDetail(packageID, updateText, bugzillaUrls); } void PkUpdates::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) { Q_UNUSED(repoName); Q_UNUSED(keyUrl); Q_UNUSED(keyUserid); Q_UNUSED(keyId); Q_UNUSED(keyFingerprint); Q_UNUSED(keyTimestamp); Q_UNUSED(type); // TODO provide a way to confirm and import GPG keys qCDebug(PLASMA_PK_UPDATES) << "Repo sig required" << packageID; } void PkUpdates::setStatusMessage(const QString &message) { m_statusMessage = message; emit statusMessageChanged(); } void PkUpdates::setActivity(Activity act) { if (act != m_activity) { m_activity = act; emit isActiveChanged(); } } void PkUpdates::setPercentage(int value) { if (value != m_percentage) { m_percentage = value; emit percentageChanged(); } } diff --git a/src/declarative/pkupdates.h b/src/declarative/pkupdates.h index 7831709..10eeea8 100644 --- a/src/declarative/pkupdates.h +++ b/src/declarative/pkupdates.h @@ -1,233 +1,232 @@ /*************************************************************************** * Copyright (C) 2015 Lukáš Tinkl * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef PLASMA_PK_UPDATES_H #define PLASMA_PK_UPDATES_H #include #include #include #include #include class QTimer; Q_DECLARE_LOGGING_CATEGORY(PLASMA_PK_UPDATES) /** * @brief The PkUpdates class * * Backend class to check for available PackageKit system updates. * Use checkUpdates() to perform the check, retrieve them with packages() */ class PkUpdates : public QObject { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY updatesChanged) Q_PROPERTY(int importantCount READ importantCount NOTIFY updatesChanged) Q_PROPERTY(int securityCount READ securityCount NOTIFY updatesChanged) Q_PROPERTY(bool isSystemUpToDate READ isSystemUpToDate NOTIFY updatesChanged) Q_PROPERTY(QString iconName READ iconName NOTIFY updatesChanged) Q_PROPERTY(QString message READ message NOTIFY isActiveChanged) Q_PROPERTY(int percentage READ percentage NOTIFY percentageChanged) Q_PROPERTY(QString timestamp READ timestamp NOTIFY updatesChanged) Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged) Q_PROPERTY(QVariantMap packages READ packages NOTIFY updatesChanged) Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) Q_PROPERTY(bool isNetworkOnline READ isNetworkOnline NOTIFY networkStateChanged) Q_PROPERTY(bool isNetworkMobile READ isNetworkMobile NOTIFY networkStateChanged) Q_PROPERTY(bool isOnBattery READ isOnBattery NOTIFY isOnBatteryChanged) - Q_ENUMS(Activity) public: enum Activity {Idle, CheckingUpdates, GettingUpdates, InstallingUpdates}; + Q_ENUM(Activity) - explicit PkUpdates(QObject *parent = 0); + explicit PkUpdates(QObject *parent = nullptr); ~PkUpdates(); /** * @return the total number of updates, including important and/or security ones */ int count() const; /** * @return the number of important updates, included in total count() */ int importantCount() const; /** * @return the number of security updates, included in total count() */ int securityCount() const; /** * @return whether the system is up to date (count() == 0) */ bool isSystemUpToDate() const; /** * @return the system update status icon name */ QString iconName() const; /** * @return the overal status with number of available updates */ QString message() const; /** * @return the progress percentage (0..100), 101 as a special value indicating indeterminate value */ int percentage() const; /** * @return time stamp of the last update check */ QString timestamp() const; /** * @return status messsage conveying the action being currently performed */ QString statusMessage() const; /** * @return whether we're currently checking for updates or not */ bool isActive() const; /** * @return the packages to update (key=packageId, value=description) */ QVariantMap packages() const; /** * @return whether the network is online */ bool isNetworkOnline() const; /** * @return whether we are on a mobile network connection (assumes isNetworkOnline()) */ bool isNetworkMobile() const; /** * @return whether we are running on battery */ bool isOnBattery() const; signals: /** * Emitted when the number uf updates has changed */ void updatesChanged(); /** * Emitted when the updates check is finished (with success or error) */ void done(); /** * Emitted with update details * @see getUpdateDetails() */ void updateDetail(const QString &packageID, const QString &updateText, const QStringList &urls); // private ;) void statusMessageChanged(); - void timestampChanged(); void isActiveChanged(); void percentageChanged(); void networkStateChanged(); void isOnBatteryChanged(); public slots: /** * Perform a cache update, possibly resulting in an update check. Signal updatesChanged() gets emitted * as a result. Consult the count() property whether there are new updates available. * * @param force whether to force the cache refresh */ - Q_INVOKABLE void checkUpdates(); + Q_INVOKABLE void checkUpdates(bool force = true); /** * Launch the update process * * @param packageIds list of package IDs to update */ Q_INVOKABLE void installUpdates(const QStringList & packageIds, bool simulate = true, bool untrusted = false); /** * @return the timestamp (in milliseconds) of the last cache check, -1 if never */ Q_INVOKABLE qint64 lastRefreshTimestamp() const; /** * @return the package name extracted from its ID */ Q_INVOKABLE static QString packageName(const QString & pkgId); /** * @return the package version extracted from its ID */ Q_INVOKABLE static QString packageVersion(const QString & pkgId); /** * Request details about the details * @param pkgIds Package IDs * * Emits updateDetail() */ Q_INVOKABLE void getUpdateDetails(const QString & pkgID); private slots: void getUpdates(); void onChanged(); void onUpdatesChanged(); void onStatusChanged(); void onPackage(PackageKit::Transaction::Info info, const QString &packageID, const QString &summary); void onPackageUpdating(PackageKit::Transaction::Info info, const QString &packageID, const QString &summary); void onFinished(PackageKit::Transaction::Exit status, uint runtime); void onErrorCode(PackageKit::Transaction::Error error, const QString &details); void onRequireRestart(PackageKit::Transaction::Restart type, const QString &packageID); void onUpdateDetail(const QString &packageID, const QStringList &updates, const QStringList &obsoletes, const QStringList &vendorUrls, const QStringList &bugzillaUrls, const QStringList &cveUrls, PackageKit::Transaction::Restart restart, const QString &updateText, const QString &changelog, PackageKit::Transaction::UpdateState state, 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); private: void setStatusMessage(const QString &message); void setActivity(Activity act); void setPercentage(int value); QPointer m_updatesTrans; QPointer m_cacheTrans; QPointer m_installTrans; QPointer m_detailTrans; QVariantMap m_updateList; QStringList m_importantList; QStringList m_securityList; QString m_statusMessage; int m_percentage = 0; Activity m_activity = Idle; }; #endif // PLASMA_PK_UPDATES_H diff --git a/src/plasma/contents/ui/Full.qml b/src/plasma/contents/ui/Full.qml index 74a62f3..304f366 100644 --- a/src/plasma/contents/ui/Full.qml +++ b/src/plasma/contents/ui/Full.qml @@ -1,297 +1,298 @@ /*************************************************************************** * Copyright (C) 2013 by Aleix Pol Gonzalez * * Copyright (C) 2015 by Lukáš Tinkl * * Copyright (C) 2015 by Jan Grulich * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.3 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 import org.kde.plasma.PackageKit 1.0 Item { id: fullRepresentation property bool anySelected: checkAnySelected() property bool allSelected: checkAllSelected() property bool populatePreSelected: true Binding { target: timestampLabel property: "text" value: PkUpdates.timestamp when: !plasmoid.expanded } - Component.onCompleted: { - PkUpdates.updatesChanged.connect(populateModel) - PkUpdates.updateDetail.connect(updateDetails) - populateModel() + Connections { + target: PkUpdates + onUpdatesChanged: populateModel() + onUpdateDetail: updateDetails() } + Component.onCompleted: populateModel() + ListModel { id: updatesModel } PlasmaExtras.Heading { id: header level: 4 wrapMode: Text.WordWrap text: !PkUpdates.isNetworkOnline ? i18n("Network is offline") : PkUpdates.message } ColumnLayout { id: statusbar anchors { left: parent.left right: parent.right top: header.bottom rightMargin: Math.round(units.gridUnit / 2) } spacing: units.largeSpacing PlasmaComponents.Label { id: timestampLabel visible: !PkUpdates.isActive wrapMode: Text.WordWrap font.italic: true font.pointSize: theme.smallestFont.pointSize; opacity: 0.6; text: PkUpdates.timestamp } PlasmaComponents.Label { visible: PkUpdates.isActive || !PkUpdates.count font.pointSize: theme.smallestFont.pointSize; opacity: 0.6; text: { if (PkUpdates.isActive) return PkUpdates.statusMessage else if (PkUpdates.isNetworkOnline) return i18n("Updates are automatically checked %1.
" + "Click the 'Check For Updates' button below to search for updates manually.", updateInterval(plasmoid.configuration.daily, plasmoid.configuration.weekly, plasmoid.configuration.monthly)); return "" } Layout.fillWidth: true wrapMode: Text.WordWrap } } ProgressBar { id: progressBar anchors { left: parent.left right: parent.right top: statusbar.bottom } visible: PkUpdates.isActive minimumValue: 0 maximumValue: 101 // BUG workaround a bug in ProgressBar! if the value is > max, it's set to max and never changes below value: PkUpdates.percentage indeterminate: PkUpdates.percentage > 100 } ColumnLayout { spacing: units.smallSpacing anchors { bottom: parent.bottom left: parent.left right: parent.right top: statusbar.bottom } PlasmaExtras.ScrollArea { id: updatesScrollArea Layout.fillWidth: true Layout.fillHeight: true visible: PkUpdates.count && !PkUpdates.isActive ListView { id: updatesView clip: true model: PlasmaCore.SortFilterModel { sourceModel: updatesModel filterRole: "name" } anchors.fill: parent currentIndex: -1 boundsBehavior: Flickable.StopAtBounds delegate: PackageDelegate { onClicked: { if (updatesView.currentIndex === index) { updatesView.currentIndex = -1 } else { updatesView.currentIndex = index PkUpdates.getUpdateDetails(id) } } onCheckedStateChanged: { if (checked) { fullRepresentation.anySelected = true } else { - anySelected = checkAnySelected() + fullRepresentation.anySelected = checkAnySelected() } } } } } RowLayout { visible: PkUpdates.count && !PkUpdates.isActive PlasmaComponents.CheckBox { id: chkSelectAll anchors { left: parent.left leftMargin: Math.round(units.gridUnit / 3) } - checkedState: anySelected ? (allSelected ? Qt.Checked : Qt.PartiallyChecked) : Qt.Unchecked + checkedState: fullRepresentation.anySelected ? (fullRepresentation.allSelected ? Qt.Checked : Qt.PartiallyChecked) : Qt.Unchecked partiallyCheckedEnabled: true } PlasmaComponents.Label { id: lblSelectAll height: paintedHeight anchors { left: chkSelectAll.right leftMargin: Math.round(units.gridUnit / 2) } elide: Text.ElideRight; text: i18n("Select all packages") } MouseArea { anchors.fill: parent enabled: true onClicked: { if (chkSelectAll.checkedState == Qt.Unchecked) { populatePreSelected = true populateModel() } else if (chkSelectAll.checkedState == Qt.PartiallyChecked) { populatePreSelected = true populateModel() } else { populatePreSelected = false populateModel() } fullRepresentation.anySelected = checkAnySelected() } } } PlasmaComponents.Button { id: btnCheck visible: !PkUpdates.count && PkUpdates.isNetworkOnline && !PkUpdates.isActive enabled: !PkUpdates.isActive anchors { bottom: parent.bottom bottomMargin: Math.round(units.gridUnit / 3) horizontalCenter: parent.horizontalCenter } text: i18n("Check For Updates") tooltip: i18n("Checks for any available updates") onClicked: PkUpdates.checkUpdates() // circumvent the checks, the user knows what they're doing ;) } PlasmaComponents.Button { id: btnUpdate visible: PkUpdates.count && PkUpdates.isNetworkOnline && !PkUpdates.isActive - enabled: anySelected + enabled: fullRepresentation.anySelected anchors { bottom: parent.bottom bottomMargin: Math.round(units.gridUnit / 3) horizontalCenter: parent.horizontalCenter } text: i18n("Install Updates") tooltip: i18n("Performs the software update") onClicked: PkUpdates.installUpdates(selectedPackages()) } PlasmaComponents.BusyIndicator { running: PkUpdates.isActive visible: running - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter + anchors.centerIn: parent } } function checkAnySelected() { for (var i = 0; i < updatesModel.count; i++) { var pkg = updatesModel.get(i) if (pkg.selected) return true } return false } function checkAllSelected() { for (var i = 0; i < updatesModel.count; i++) { var pkg = updatesModel.get(i) if (!pkg.selected) return false } return true } function selectedPackages() { var result = [] for (var i = 0; i < updatesModel.count; i++) { var pkg = updatesModel.get(i) if (pkg.selected) { print("Package " + pkg.id + " selected for update") result.push(pkg.id) } } return result } function populateModel() { print("Populating model") updatesModel.clear() var packages = PkUpdates.packages for (var id in packages) { if (packages.hasOwnProperty(id)) { var desc = packages[id] updatesModel.append({"selected": populatePreSelected, "id": id, "name": PkUpdates.packageName(id), "desc": desc, "version": PkUpdates.packageVersion(id)}) } } } function updateDetails(packageID, updateText, urls) { //print("Got update details for: " + packageID) print("Update text: " + updateText) print("URLs: " + urls) updatesView.currentItem.updateText = updateText updatesView.currentItem.updateUrls = urls } function updateInterval(daily, weekly, monthly) { if (weekly) return i18n("weekly"); else if (monthly) return i18n("monthly"); return i18n("daily"); } } diff --git a/src/plasma/contents/ui/PackageDelegate.qml b/src/plasma/contents/ui/PackageDelegate.qml index bf4125b..678d21d 100644 --- a/src/plasma/contents/ui/PackageDelegate.qml +++ b/src/plasma/contents/ui/PackageDelegate.qml @@ -1,188 +1,183 @@ /*************************************************************************** * Copyright (C) 2013 by Aleix Pol Gonzalez * * Copyright (C) 2015 by Lukáš Tinkl * * Copyright (C) 2015 by Jan Grulich * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.3 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 PlasmaComponents.ListItem { id: packageDelegate property string updateText - property variant updateUrls: [ ] - property bool expanded: ListView.isCurrentItem + property variant updateUrls: [] + readonly property bool expanded: ListView.isCurrentItem signal checkedStateChanged(bool checked) height: packageInfoColumn.height + detailsInfoColumn.height + Math.round(units.gridUnit / 2) width: parent.width enabled: true checked: containsMouse || expanded PlasmaComponents.CheckBox { id: checkbox anchors { left: parent.left verticalCenter: packageInfoColumn.verticalCenter } checked: selected onClicked: { updatesModel.setProperty(index, "selected", checked) packageDelegate.checkedStateChanged(checked) } } Column { id: packageInfoColumn height: packageNameLabel.height + packageDescriptionLabel.height anchors { left: checkbox.right right: parent.right top: parent.top leftMargin: Math.round(units.gridUnit / 2) rightMargin: Math.round(units.gridUnit / 2) } PlasmaComponents.Label { id: packageNameLabel height: paintedHeight anchors { left: parent.left right: parent.right } elide: Text.ElideRight; text: i18nc("Package Name (Version)", "%1 (%2)", name, version) } PlasmaComponents.Label { id: packageDescriptionLabel height: paintedHeight anchors { left: parent.left right: parent.right } elide: Text.ElideRight; font.pointSize: theme.smallestFont.pointSize; opacity: 0.6; text: desc } } Column { id: detailsInfoColumn height: packageDelegate.expanded ? childrenRect.height + Math.round(units.gridUnit / 2) : 0 visible: expanded spacing: 2 anchors { left: checkbox.right right: parent.right top: packageInfoColumn.bottom leftMargin: Math.round(units.gridUnit / 2) rightMargin: Math.round(units.gridUnit / 2) topMargin: Math.round(units.gridUnit / 3) } PlasmaCore.SvgItem { id: detailsSeparator; height: lineSvg.elementSize("horizontal-line").height; width: parent.width; anchors { left: parent.left; right: parent.right; } elementId: "horizontal-line"; svg: PlasmaCore.Svg { id: lineSvg; imagePath: "widgets/line"; } } PlasmaComponents.Label { id: descriptionLabel height: paintedHeight anchors { left: parent.left right: parent.right } font.weight: Font.DemiBold - text: i18n("Update Description") + text: i18nc("description of the update", "Update Description") } PlasmaComponents.Label { id: descriptionContentLabel height: paintedHeight anchors { left: parent.left right: parent.right } font.pointSize: theme.smallestFont.pointSize; opacity: 0.6; - text: (!updateText || updateText.length === 0) ? i18n("No description available") : updateText + text: updateText == "" ? i18n("No description available") : updateText wrapMode: Text.WordWrap } PlasmaComponents.Label { id: urlsLabel height: visible ? paintedHeight : 0 - visible: updateUrls.length !== 0 && updateUrls !== null && updateUrls !== undefined + visible: !!updateUrls anchors { left: parent.left right: parent.right } font.weight: Font.DemiBold text: i18n("Related URLs") } Repeater { model: updateUrls PlasmaComponents.Label { height: paintedHeight color: theme.linkColor font.pointSize: theme.smallestFont.pointSize font.underline: true opacity: 0.6; text: modelData wrapMode: Text.WordWrap MouseArea { anchors.fill: parent hoverEnabled: true + cursorShape: Qt.PointingHandCursor onClicked: { Qt.openUrlExternally(modelData) } - onEntered: { - cursorShape = Qt.PointingHandCursor - } - onExited: { - cursorShape = Qt.ArrowCursor - } } } } } } diff --git a/src/plasma/contents/ui/main.qml b/src/plasma/contents/ui/main.qml index 574ab5a..387ae63 100644 --- a/src/plasma/contents/ui/main.qml +++ b/src/plasma/contents/ui/main.qml @@ -1,102 +1,98 @@ /*************************************************************************** * Copyright (C) 2014 by Aleix Pol Gonzalez * * Copyright (C) 2015 by Lukáš Tinkl * * Copyright (C) 2015 by Jan Grulich * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.2 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.PackageKit 1.0 Item { Plasmoid.fullRepresentation: Full {} Plasmoid.toolTipSubText: PkUpdates.message Plasmoid.icon: PkUpdates.iconName Plasmoid.switchWidth: units.gridUnit * 10; Plasmoid.switchHeight: units.gridUnit * 10; property bool checkDaily: plasmoid.configuration.daily property bool checkWeekly: plasmoid.configuration.weekly property bool checkMonthly: plasmoid.configuration.monthly property bool checkOnMobile: plasmoid.configuration.check_on_mobile property bool checkOnBattery: plasmoid.configuration.check_on_battery readonly property int secsInDay: 60 * 60 * 24; readonly property int secsInWeek: secsInDay * 7; readonly property int secsInMonth: secsInDay * 30; + readonly property bool networkAllowed: PkUpdates.isNetworkMobile ? checkOnMobile : PkUpdates.isNetworkOnline + readonly property bool batteryAllowed: PkUpdates.isOnBattery ? checkOnBattery : true + Timer { id: timer repeat: true triggeredOnStart: true interval: 1000 * 60 * 60; // 1 hour onTriggered: { - if (needsForcedUpdate() && networkAllowed() && batteryAllowed()) - PkUpdates.checkUpdates() + if (needsForcedUpdate() && networkAllowed && batteryAllowed) { + PkUpdates.checkUpdates(); + } } } Binding { target: plasmoid property: "status" value: PkUpdates.isActive || !PkUpdates.isSystemUpToDate ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus; } Plasmoid.compactRepresentation: PlasmaCore.IconItem { source: PkUpdates.iconName anchors.fill: parent MouseArea { anchors.fill: parent - onClicked: { - plasmoid.expanded = !plasmoid.expanded - } + onClicked: plasmoid.expanded = !plasmoid.expanded } } function needsForcedUpdate() { var secs = (Date.now() - PkUpdates.lastRefreshTimestamp())/1000; // compare with the saved timestamp if (secs < 0) { // never checked before return true; } else if (checkDaily) { return secs >= secsInDay; } else if (checkWeekly) { return secs >= secsInWeek; } else if (checkMonthly) { return secs >= secsInMonth; } return false; } - function networkAllowed() { - return PkUpdates.isNetworkMobile ? checkOnMobile : PkUpdates.isNetworkOnline + Connections { + target: PkUpdates + onNetworkStateChanged: timer.restart() + onIsOnBatteryChanged: timer.restart() } - function batteryAllowed() { - return PkUpdates.isOnBattery ? checkOnBattery : true - } - - Component.onCompleted: { - PkUpdates.networkStateChanged.connect(timer.restart) - PkUpdates.isOnBatteryChanged.connect(timer.restart) - timer.start() - } + Component.onCompleted: timer.start() } diff --git a/src/plasma/metadata.desktop.cmake b/src/plasma/metadata.desktop.cmake index c457762..2e395ec 100644 --- a/src/plasma/metadata.desktop.cmake +++ b/src/plasma/metadata.desktop.cmake @@ -1,68 +1,68 @@ [Desktop Entry] Name=Software Updates Name[ast]=Anovamientos de software Name[ca]=Actualitzacions de programari Name[ca@valencia]=Actualitzacions de programari Name[cs]=Aktualizace software Name[da]=Softwareopdateringer Name[de]=Softwareaktualisierungen Name[en_GB]=Software Updates Name[es]=Actualizaciones de software Name[fi]=Ohjelmistopäivitykset Name[gl]=Actualizacións de sofware Name[lt]=Programų atnaujinimai Name[nl]=Software bijwerken Name[nn]=Programoppdateringar Name[pl]=Uaktualnienia oprogramowania Name[pt]=Actualizações de Aplicações Name[pt_BR]=Atualização de aplicativos Name[ru]=Обновления программ Name[sk]=Aktualizácie softvéru Name[sv]=Uppdateringar av programvara Name[tr]=Yazılım Güncellemeleri Name[uk]=Оновлення програм Name[x-test]=xxSoftware Updatesxx Name[zh_CN]=软件更新 Comment=Get software updates Comment[ast]=Consigue anovamientos de software Comment[ca]=Obtén les actualitzacions de programari Comment[ca@valencia]=Obtén les actualitzacions de programari Comment[cs]=Získejte aktualizace software Comment[da]=Hent softwareopdateringer Comment[de]=Softwareaktualisierungen beziehen Comment[en_GB]=Get software updates Comment[es]=Obtener actualizaciones de software Comment[fi]=Nouda ohjelmistopäivitykset Comment[gl]=Obteña actualizacións de software. Comment[lt]=Parsisiųsti programų atnaujinimus Comment[nl]=Elementen voor software bijwerken ophalen Comment[nn]=Hent program­oppdateringar Comment[pl]=Pobiera uaktualnienia oprogramowania Comment[pt]=Obter actualizações das aplicações Comment[pt_BR]=Obter a atualização de aplicativos Comment[ru]=Обновления программного обеспечения Comment[sk]=Získať aktualizácie softvéru Comment[sv]=Hämta uppdateringar av programvara Comment[tr]=Yazılım güncellemelerini al Comment[uk]=Отримання оновлень програм Comment[x-test]=xxGet software updatesxx Comment[zh_CN]=获取软件更新 Icon=system-software-update Type=Service X-KDE-ServiceTypes=Plasma/Applet X-Plasma-API=declarativeappletscript X-Plasma-MainScript=ui/main.qml X-Plasma-NotificationArea=true X-KDE-PluginInfo-Name=org.kde.plasma.pkupdates X-KDE-PluginInfo-Category=System Information X-KDE-PluginInfo-Author=Lukáš Tinkl X-KDE-PluginInfo-Email=lukas@kde.org X-KDE-PluginInfo-Version=@PROJECT_VERSION@ -X-KDE-PluginInfo-Website=https://github.com/caybro/plasma-pk-updates +X-KDE-PluginInfo-Website=https://phabricator.kde.org/source/plasma-pk-updates/ X-KDE-PluginInfo-License=GPL2 X-KDE-PluginInfo-EnabledByDefault=true