/* * SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez * SPDX-FileCopyrightText: 2017 Jan Grulich * SPDX-FileCopyrightText: 2023 Harald Sitter * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ #include "FlatpakJobTransaction.h" #include "FlatpakBackend.h" #include "FlatpakResource.h" #include "FlatpakTransactionThread.h" #include "libdiscover_backend_flatpak_debug.h" #include #include #include namespace { struct InstallationContext { FlatpakJobTransaction::Role role; FlatpakInstallation *installation; bool operator==(const InstallationContext &) const = default; }; uint qHash(const InstallationContext &context, uint seed) { return qHash(context.role, seed) ^ qHash(context.installation, seed); } } class FlatpakTransactionsMerger : public QObject { Q_OBJECT public: [[nodiscard]] static FlatpakTransactionsMerger *instance() { static FlatpakTransactionsMerger merger; return &merger; } ~FlatpakTransactionsMerger() override { for (const auto &thread : m_activeThreads) { thread->cancel(); if (FlatpakThreadPool::instance()->tryTake(thread)) { // immediately delete if the runnable hasn't started yet delete thread; } else { // otherwise defer cleanup to the pool thread->setAutoDelete(true); } } } Q_DISABLE_COPY_MOVE(FlatpakTransactionsMerger) void schedule(FlatpakJobTransaction *transaction) { qCDebug(LIBDISCOVER_BACKEND_FLATPAK_LOG) << "Scheduling transaction" << transaction; m_pendingJobTransactions.push_back(transaction); connect(&m_timer, &QTimer::timeout, this, &FlatpakTransactionsMerger::dispatch, Qt::UniqueConnection); // We use a 0 timer because we merge all transactions that are being scheduled in one eventloop run m_timer.setSingleShot(true); m_timer.start(0); } void discard(FlatpakJobTransaction *transaction) { m_pendingJobTransactions.removeAll(transaction); } private Q_SLOTS: void dispatch() { qCDebug(LIBDISCOVER_BACKEND_FLATPAK_LOG) << "Dispatching from amount of jobs" << m_pendingJobTransactions.size(); if (m_pendingJobTransactions.isEmpty()) { return; } QHash threadsByInstallation; for (const auto &pendingJobTransaction : std::as_const(m_pendingJobTransactions)) { Q_ASSERT(pendingJobTransaction->m_app); auto installation = pendingJobTransaction->m_app->installation(); Q_ASSERT(installation); auto role = pendingJobTransaction->role(); InstallationContext installationContext{.role = role, .installation = installation}; if (!threadsByInstallation.contains(installationContext)) { auto thread = new FlatpakTransactionThread(installationContext.role, installationContext.installation); connect(thread, &QObject::destroyed, this, [this, thread] { m_activeThreads.removeAll(thread); }); m_activeThreads.append(thread); threadsByInstallation.insert(installationContext, thread); } auto &thread = threadsByInstallation[installationContext]; thread->setAutoDelete(false); thread->addJobTransaction(pendingJobTransaction); pendingJobTransaction->m_thread = thread; } m_pendingJobTransactions.clear(); for (auto &thread : std::as_const(threadsByInstallation)) { FlatpakThreadPool::instance()->start(thread); } } private: using QObject::QObject; // Purely exists for cleanup. Do not call into these! Do not recycle these! They have been dispatched to the pool. QList m_activeThreads; QList m_pendingJobTransactions; QTimer m_timer; }; FlatpakJobTransaction::FlatpakJobTransaction(FlatpakResource *app, Role role) : Transaction(app->backend(), app, role, {}) , m_app(app) { setCancellable(true); setStatus(CommittingStatus); FlatpakTransactionsMerger::instance()->schedule(this); } FlatpakJobTransaction::~FlatpakJobTransaction() { FlatpakTransactionsMerger::instance()->discard(this); } void FlatpakJobTransaction::cancel() { if (m_thread) { m_thread->cancel(); } } void FlatpakJobTransaction::finishTransaction(bool cancelled, const QString &errorMessage, const FlatpakTransactionThread::Repositories &addedRepositories, bool success) { if (static_cast(m_app->backend())->getInstalledRefForApp(m_app)) { m_app->setState(AbstractResource::Installed); } else { m_app->setState(AbstractResource::None); } if (addedRepositories.isEmpty()) { Q_EMIT repositoriesAdded(addedRepositories); } if (!cancelled && !errorMessage.isEmpty()) { Q_EMIT passiveMessage(errorMessage); } if (success) { setStatus(DoneStatus); } else { setStatus(cancelled ? CancelledStatus : DoneWithErrorStatus); } } void FlatpakJobTransaction::proceed() { if (m_thread) { m_thread->proceed(); } } #include "FlatpakJobTransaction.moc" #include "moc_FlatpakJobTransaction.cpp"