File indexing completed on 2024-11-17 04:55:38

0001 /*
0002  *   SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *   SPDX-FileCopyrightText: 2017 Jan Grulich <jgrulich@redhat.com>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "FlatpakNotifier.h"
0009 
0010 #include <glib.h>
0011 
0012 #include <QDebug>
0013 #include <QFutureWatcher>
0014 #include <QTimer>
0015 #include <QtConcurrentRun>
0016 
0017 using namespace std::chrono_literals;
0018 
0019 static void installationChanged(GFileMonitor *monitor, GFile *child, GFile *other_file, GFileMonitorEvent event_type, gpointer self)
0020 {
0021     Q_UNUSED(monitor);
0022     Q_UNUSED(child);
0023     Q_UNUSED(other_file);
0024     Q_UNUSED(event_type);
0025 
0026     FlatpakNotifier::Installation *installation = (FlatpakNotifier::Installation *)self;
0027     if (!installation)
0028         return;
0029 
0030     FlatpakNotifier *notifier = installation->m_notifier;
0031     notifier->loadRemoteUpdates(installation);
0032 }
0033 
0034 FlatpakNotifier::FlatpakNotifier(QObject *parent)
0035     : BackendNotifierModule(parent)
0036     , m_user(this)
0037     , m_system(this)
0038     , m_cancellable(g_cancellable_new())
0039 {
0040     QTimer *dailyCheck = new QTimer(this);
0041     dailyCheck->setInterval(24h); // refresh at least once every day
0042     connect(dailyCheck, &QTimer::timeout, this, &FlatpakNotifier::recheckSystemUpdateNeeded);
0043 }
0044 
0045 FlatpakNotifier::Installation::Installation(FlatpakNotifier *notifier)
0046     : m_notifier(notifier)
0047 {
0048 }
0049 
0050 FlatpakNotifier::Installation::~Installation()
0051 {
0052     if (m_monitor)
0053         g_object_unref(m_monitor);
0054     if (m_installation)
0055         g_object_unref(m_installation);
0056 }
0057 
0058 FlatpakNotifier::~FlatpakNotifier()
0059 {
0060     g_object_unref(m_cancellable);
0061 }
0062 
0063 void FlatpakNotifier::recheckSystemUpdateNeeded()
0064 {
0065     g_autoptr(GError) error = nullptr;
0066 
0067     // Load flatpak installation
0068     if (!setupFlatpakInstallations(&error)) {
0069         qWarning() << "Failed to setup flatpak installations: " << error->message;
0070     } else {
0071         // Load updates from remote repositories
0072         loadRemoteUpdates(&m_system);
0073         loadRemoteUpdates(&m_user);
0074     }
0075 }
0076 
0077 void FlatpakNotifier::onFetchUpdatesFinished(Installation *installation, bool hasUpdates)
0078 {
0079     if (installation->m_hasUpdates == hasUpdates) {
0080         return;
0081     }
0082     bool hadUpdates = this->hasUpdates();
0083     installation->m_hasUpdates = hasUpdates;
0084 
0085     if (hadUpdates != this->hasUpdates()) {
0086         Q_EMIT foundUpdates();
0087     }
0088 }
0089 
0090 void FlatpakNotifier::loadRemoteUpdates(Installation *installation)
0091 {
0092     auto fw = new QFutureWatcher<bool>(this);
0093     connect(fw, &QFutureWatcher<bool>::finished, this, [this, installation, fw]() {
0094         onFetchUpdatesFinished(installation, fw->result());
0095         fw->deleteLater();
0096     });
0097     fw->setFuture(QtConcurrent::run([installation]() -> bool {
0098         g_autoptr(GCancellable) cancellable = g_cancellable_new();
0099         g_autoptr(GError) localError = nullptr;
0100         g_autoptr(GPtrArray) fetchedUpdates = flatpak_installation_list_installed_refs_for_update(installation->m_installation, cancellable, &localError);
0101         bool hasUpdates = false;
0102 
0103         if (!fetchedUpdates) {
0104             qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message;
0105             return false;
0106         }
0107         for (uint i = 0; !hasUpdates && i < fetchedUpdates->len; i++) {
0108             FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(fetchedUpdates, i));
0109             const QString refName = QString::fromUtf8(flatpak_ref_get_name(FLATPAK_REF(ref)));
0110             // FIXME right now I can't think of any other filter than this, in FlatpakBackend updates are matched
0111             // with apps so .Locale/.Debug subrefs are not shown and updated automatically. Also this will show
0112             // updates for refs we don't show in Discover if appstream metadata or desktop file for them is not found
0113             if (refName.endsWith(QLatin1String(".Locale")) || refName.endsWith(QLatin1String(".Debug"))) {
0114                 continue;
0115             }
0116             hasUpdates = true;
0117         }
0118         return hasUpdates;
0119     }));
0120 }
0121 
0122 bool FlatpakNotifier::hasUpdates()
0123 {
0124     return m_system.m_hasUpdates || m_user.m_hasUpdates;
0125 }
0126 
0127 bool FlatpakNotifier::Installation::ensureInitialized(std::function<FlatpakInstallation *()> func, GCancellable *cancellable, GError **error)
0128 {
0129     if (!m_installation) {
0130         m_installation = func();
0131         m_monitor = flatpak_installation_create_monitor(m_installation, cancellable, error);
0132         g_signal_connect(m_monitor, "changed", G_CALLBACK(installationChanged), this);
0133     }
0134     return m_installation && m_monitor;
0135 }
0136 
0137 bool FlatpakNotifier::setupFlatpakInstallations(GError **error)
0138 {
0139     if (!m_system.ensureInitialized(
0140             [this, error] {
0141                 return flatpak_installation_new_system(m_cancellable, error);
0142             },
0143             m_cancellable,
0144             error))
0145         return false;
0146     if (!m_user.ensureInitialized(
0147             [this, error] {
0148                 return flatpak_installation_new_user(m_cancellable, error);
0149             },
0150             m_cancellable,
0151             error))
0152         return false;
0153 
0154     return true;
0155 }