File indexing completed on 2024-04-21 16:29:33

0001 /***************************************************************************
0002  *   Copyright (C) 2012 by Daniel Nicoletti <dantti12@gmail.com>           *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; see the file COPYING. If not, write to       *
0016  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,  *
0017  *   Boston, MA 02110-1301, USA.                                           *
0018  ***************************************************************************/
0019 
0020 #include "ApperdThread.h"
0021 
0022 #include "RefreshCacheTask.h"
0023 #include "Updater.h"
0024 #include "DistroUpgrade.h"
0025 #include "TransactionWatcher.h"
0026 #include "DBusInterface.h"
0027 #include "RebootListener.h"
0028 
0029 #include <Enum.h>
0030 #include <Daemon>
0031 
0032 #include <QStandardPaths>
0033 #include <KConfig>
0034 #include <KConfigGroup>
0035 #include <KDirWatch>
0036 #include <KProtocolManager>
0037 #include <KLocalizedString>
0038 //#include <Solid/PowerManagement>
0039 #include <KFormat>
0040 
0041 #include <QtDBus/QDBusConnection>
0042 #include <QtDBus/QDBusReply>
0043 #include <QDBusServiceWatcher>
0044 
0045 #include <limits.h>
0046 
0047 #include <QLoggingCategory>
0048 
0049 Q_DECLARE_LOGGING_CATEGORY(APPER_DAEMON)
0050 
0051 #define FIVE_MIN 360000
0052 #define ONE_MIN   72000
0053 
0054 /*
0055  * What we need:
0056  * - Refresh the package cache periodicaly (implies listenning to PK's updates changed)
0057  * - Update or Display Package Updates
0058  * - Display Distro Upgrades
0059  * - Start Sentinel to keep an eye on running transactions
0060  */
0061 
0062 using namespace PackageKit;
0063 //using namespace Solid;
0064 
0065 ApperdThread::ApperdThread(QObject *parent) :
0066     QObject(parent),
0067     m_proxyChanged(true),
0068     m_AptRebootListener(new AptRebootListener(this))
0069 {
0070 }
0071 
0072 ApperdThread::~ApperdThread()
0073 {
0074 }
0075 
0076 void ApperdThread::init()
0077 {
0078 //    connect(PowerManagement::notifier(), SIGNAL(appShouldConserveResourcesChanged(bool)),
0079 //            this, SLOT(appShouldConserveResourcesChanged()));
0080 
0081     // This timer keeps polling to see if it has
0082     // to refresh the cache
0083     m_qtimer = new QTimer(this);
0084     m_qtimer->setInterval(FIVE_MIN);
0085     connect(m_qtimer, &QTimer::timeout, this, &ApperdThread::poll);
0086     m_qtimer->start();
0087 
0088     //check if any changes to the file occour
0089     //this also prevents from reading when a checkUpdate happens
0090     auto confWatch = new KDirWatch(this);
0091     confWatch->addFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/apper"));
0092     connect(confWatch, SIGNAL(dirty(QString)), this, SLOT(configFileChanged()));
0093     connect(confWatch, SIGNAL(created(QString)), this, SLOT(configFileChanged()));
0094     connect(confWatch, SIGNAL(deleted(QString)), this, SLOT(configFileChanged()));
0095     confWatch->startScan();
0096 
0097     // Watch for changes in the KDE proxy settings
0098     auto proxyWatch = new KDirWatch(this);
0099     proxyWatch->addFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/kioslaverc"));
0100     connect(proxyWatch, SIGNAL(dirty(QString)), this, SLOT(proxyChanged()));
0101     connect(proxyWatch, SIGNAL(created(QString)), this, SLOT(proxyChanged()));
0102     connect(proxyWatch, SIGNAL(deleted(QString)), this, SLOT(proxyChanged()));
0103     proxyWatch->startScan();
0104 
0105     Daemon::global()->setHints(QLatin1String("locale=") + QLocale::system().name() + QLatin1String(".UTF-8"));
0106 
0107     connect(Daemon::global(), &Daemon::updatesChanged, this, &ApperdThread::updatesChanged);
0108 
0109     m_interface = new DBusInterface(this);
0110 
0111     m_refreshCache = new RefreshCacheTask(this);
0112     connect(m_interface, &DBusInterface::refreshCache, m_refreshCache, &RefreshCacheTask::refreshCache);
0113 
0114     m_updater = new Updater(this);
0115 
0116     m_distroUpgrade = new DistroUpgrade(this);
0117 
0118     // read the current settings
0119     configFileChanged();
0120 
0121     // In case PackageKit is not running watch for it's registration to configure proxy
0122     auto watcher = new QDBusServiceWatcher(QLatin1String("org.freedesktop.PackageKit"),
0123                                            QDBusConnection::systemBus(),
0124                                            QDBusServiceWatcher::WatchForRegistration,
0125                                            this);
0126     connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &ApperdThread::setProxy);
0127 
0128     // if PackageKit is running check to see if there are running transactons already
0129     bool packagekitIsRunning = nameHasOwner(QLatin1String("org.freedesktop.PackageKit"),
0130                                             QDBusConnection::systemBus());
0131 
0132     m_transactionWatcher = new TransactionWatcher(packagekitIsRunning, this);
0133 
0134     // connect the watch transaction coming from the updater icon to our watcher
0135     connect(m_interface, &DBusInterface::watchTransaction, m_transactionWatcher, &TransactionWatcher::watchTransactionInteractive);
0136 
0137      // listen to Debian/Apt reboot signals from other sources (apt)
0138     connect(m_AptRebootListener, &AptRebootListener::requestReboot, m_transactionWatcher, &TransactionWatcher::showRebootNotificationApt);
0139     QTimer::singleShot(2 /*minutes*/ * 60 /*seconds*/ * 1000 /*msec*/, m_AptRebootListener, SLOT(checkForReboot()));
0140 
0141     if (packagekitIsRunning) {
0142         // PackageKit is running set the session Proxy
0143         setProxy();
0144 
0145         // If packagekit is already running go check
0146         // for updates
0147         updatesChanged();
0148     } else {
0149         // Initial check for updates
0150         QTimer::singleShot(ONE_MIN, this, SLOT(updatesChanged()));
0151     }
0152 }
0153 
0154 // This is called every 5 minutes
0155 void ApperdThread::poll()
0156 {
0157     if (m_lastRefreshCache.isNull()) {
0158         // This value wasn't set
0159         // convert this to QDateTime
0160         m_lastRefreshCache = getTimeSinceRefreshCache();
0161     }
0162 
0163     // If check for updates is active
0164     if (m_configs[QLatin1String(CFG_INTERVAL)].value<uint>() != Enum::Never) {
0165         // Find out how many seconds passed since last refresh cache
0166         qint64 msecsSinceLastRefresh = (QDateTime::currentDateTime().toMSecsSinceEpoch() - m_lastRefreshCache.toMSecsSinceEpoch()) / 1000;
0167 
0168         // If lastRefreshCache is null it means that the cache was never refreshed
0169         if (m_lastRefreshCache.isNull() || msecsSinceLastRefresh > m_configs[QLatin1String(CFG_INTERVAL)].value<uint>()) {
0170             bool ignoreBattery = m_configs[QLatin1String(CFG_CHECK_UP_BATTERY)].value<bool>();
0171             bool ignoreMobile = m_configs[QLatin1String(CFG_CHECK_UP_MOBILE)].value<bool>();
0172             if (isSystemReady(ignoreBattery, ignoreMobile)) {
0173                 m_refreshCache->refreshCache();
0174             }
0175 
0176             // Invalidate the last time the cache was refreshed
0177             m_lastRefreshCache = QDateTime();
0178         }
0179     }
0180 }
0181 
0182 void ApperdThread::configFileChanged()
0183 {
0184     KConfig config(QLatin1String("apper"));
0185     KConfigGroup checkUpdateGroup(&config, "CheckUpdate");
0186     m_configs[QLatin1String(CFG_CHECK_UP_BATTERY)] = checkUpdateGroup.readEntry(CFG_CHECK_UP_BATTERY, DEFAULT_CHECK_UP_BATTERY);
0187     m_configs[QLatin1String(CFG_CHECK_UP_MOBILE)] = checkUpdateGroup.readEntry(CFG_CHECK_UP_MOBILE, DEFAULT_CHECK_UP_MOBILE);
0188     m_configs[QLatin1String(CFG_INSTALL_UP_BATTERY)] = checkUpdateGroup.readEntry(CFG_INSTALL_UP_BATTERY, DEFAULT_INSTALL_UP_BATTERY);
0189     m_configs[QLatin1String(CFG_INSTALL_UP_MOBILE)] = checkUpdateGroup.readEntry(CFG_INSTALL_UP_MOBILE, DEFAULT_INSTALL_UP_MOBILE);
0190     m_configs[QLatin1String(CFG_AUTO_UP)] = checkUpdateGroup.readEntry(CFG_AUTO_UP, Enum::AutoUpdateDefault);
0191     m_configs[QLatin1String(CFG_INTERVAL)] = checkUpdateGroup.readEntry(CFG_INTERVAL, Enum::TimeIntervalDefault);
0192     m_configs[QLatin1String(CFG_DISTRO_UPGRADE)] = checkUpdateGroup.readEntry(CFG_DISTRO_UPGRADE, Enum::DistroUpgradeDefault);
0193     m_updater->setConfig(m_configs);
0194     m_distroUpgrade->setConfig(m_configs);
0195 
0196     KDirWatch *confWatch = qobject_cast<KDirWatch*>(sender());
0197     if (confWatch) {
0198         // Check for updates again since the config changed
0199         updatesChanged();
0200     }
0201 }
0202 
0203 void ApperdThread::proxyChanged()
0204 {
0205     // We must reparse the configuration since the values are all cached
0206     KProtocolManager::reparseConfiguration();
0207 
0208     QHash<QString, QString> proxyConfig;
0209     if (KProtocolManager::proxyType() == KProtocolManager::ManualProxy) {
0210         proxyConfig[QLatin1String("http")] = KProtocolManager::proxyFor(QLatin1String("http"));
0211         proxyConfig[QLatin1String("https")] = KProtocolManager::proxyFor(QLatin1String("https"));
0212         proxyConfig[QLatin1String("ftp")] = KProtocolManager::proxyFor(QLatin1String("ftp"));
0213         proxyConfig[QLatin1String("socks")] = KProtocolManager::proxyFor(QLatin1String("socks"));
0214     }
0215 
0216     // Check if the proxy settings really changed to avoid setting them twice
0217     if (proxyConfig != m_proxyConfig) {
0218         m_proxyConfig = proxyConfig;
0219         m_proxyChanged = true;
0220         setProxy();
0221     }
0222 }
0223 
0224 void ApperdThread::setProxy()
0225 {
0226     if (!m_proxyChanged) {
0227         return;
0228     }
0229 
0230     // If we were called by the watcher it is because PackageKit is running
0231     bool packagekitIsRunning = true;
0232     auto watcher = qobject_cast<QDBusServiceWatcher*>(sender());
0233     if (!watcher) {
0234         packagekitIsRunning = nameHasOwner(QLatin1String("org.freedesktop.PackageKit"),
0235                                            QDBusConnection::systemBus());
0236     }
0237 
0238     if (packagekitIsRunning) {
0239         // Apply the proxy changes only if packagekit is running
0240         // use value() to not insert items on the hash
0241         Daemon::global()->setProxy(m_proxyConfig.value(QLatin1String("http")),
0242                                    m_proxyConfig.value(QLatin1String("https")),
0243                                    m_proxyConfig.value(QLatin1String("ftp")),
0244                                    m_proxyConfig.value(QLatin1String("socks")),
0245                                    QString(),
0246                                    QString());
0247         m_proxyChanged = false;
0248     }
0249 }
0250 
0251 void ApperdThread::updatesChanged()
0252 {
0253     // update the last time the cache was refreshed
0254     QDateTime lastCacheRefresh;
0255     lastCacheRefresh = getTimeSinceRefreshCache();
0256     if (lastCacheRefresh != m_lastRefreshCache) {
0257         m_lastRefreshCache = lastCacheRefresh;
0258     }
0259 
0260     bool ignoreBattery = m_configs[QLatin1String(CFG_INSTALL_UP_BATTERY)].value<bool>();
0261     bool ignoreMobile = m_configs[QLatin1String(CFG_INSTALL_UP_MOBILE)].value<bool>();
0262 
0263     // Make sure the user sees the updates
0264     m_updater->checkForUpdates(isSystemReady(ignoreBattery, ignoreMobile));
0265     m_distroUpgrade->checkDistroUpgrades();
0266 }
0267 
0268 void ApperdThread::appShouldConserveResourcesChanged()
0269 {
0270     bool ignoreBattery = m_configs[QLatin1String(CFG_INSTALL_UP_BATTERY)].value<bool>();
0271     bool ignoreMobile = m_configs[QLatin1String(CFG_INSTALL_UP_MOBILE)].value<bool>();
0272 
0273     if (isSystemReady(ignoreBattery, ignoreMobile)) {
0274         m_updater->setSystemReady();
0275     }
0276 }
0277 
0278 QDateTime ApperdThread::getTimeSinceRefreshCache() const
0279 {
0280     uint value = Daemon::global()->getTimeSinceAction(Transaction::RoleRefreshCache);
0281 
0282     // When the refresh cache value was not yet defined UINT_MAX is returned
0283     if (value == UINT_MAX) {
0284         return QDateTime();
0285     } else {
0286         // Calculate the last time the cache was refreshed by
0287         // subtracting the seconds from the current time
0288         return QDateTime::currentDateTime().addSecs(value * -1);
0289     }
0290 }
0291 
0292 bool ApperdThread::nameHasOwner(const QString &name, const QDBusConnection &connection)
0293 {
0294     QDBusMessage message;
0295     message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.DBus"),
0296                                              QLatin1String("/"),
0297                                              QLatin1String("org.freedesktop.DBus"),
0298                                              QLatin1String("NameHasOwner"));
0299     message << qVariantFromValue(name);
0300     QDBusReply<bool> reply = connection.call(message);
0301     return reply.value();
0302 }
0303 
0304 bool ApperdThread::isSystemReady(bool ignoreBattery, bool ignoreMobile) const
0305 {
0306     // First check if we should conserve resources
0307     // check how applications should behave (e.g. on battery power)
0308 //    if (!ignoreBattery && Solid::PowerManagement::appShouldConserveResources()) {
0309         qCDebug(APPER_DAEMON) << "System is not ready, application should conserve resources";
0310         // This was fixed for KDElibs 4.8.5
0311         return false;
0312 //    }
0313 
0314     // TODO it would be nice is Solid provided this
0315     // so we wouldn't be waking up PackageKit for this Solid task.
0316     Daemon::Network network = Daemon::global()->networkState();
0317     // test whether network is connected
0318     if (network == Daemon::NetworkOffline || network == Daemon::NetworkUnknown) {
0319         qCDebug(APPER_DAEMON) << "System is not ready, network state" << network;
0320         return false;
0321     }
0322 
0323     // check how applications should behave (e.g. on battery power)
0324     if (!ignoreMobile && network == Daemon::NetworkMobile) {
0325         qCDebug(APPER_DAEMON) << "System is not ready, network state" << network;
0326         return false;
0327     }
0328 
0329     return true;
0330 }
0331 
0332 #include "moc_ApperdThread.cpp"