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

0001 /*
0002  *   SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
0003  *   SPDX-FileCopyrightText: 2021 Mariam Fahmy Sobhy <mariamfahmy66@gmail.com>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "RpmOstreeBackend.h"
0009 #include "RpmOstreeSourcesBackend.h"
0010 
0011 #include "Category/Category.h"
0012 #include "Transaction/TransactionModel.h"
0013 
0014 #include <AppStreamQt/release.h>
0015 #include <AppStreamQt/systeminfo.h>
0016 #include <AppStreamQt/utils.h>
0017 #include <KLocalizedString>
0018 #include <appstream/AppStreamIntegration.h>
0019 
0020 DISCOVER_BACKEND_PLUGIN(RpmOstreeBackend)
0021 
0022 Q_DECLARE_METATYPE(QList<QVariantMap>)
0023 
0024 static const QString DBusServiceName = QStringLiteral("org.projectatomic.rpmostree1");
0025 static const QString SysrootObjectPath = QStringLiteral("/org/projectatomic/rpmostree1/Sysroot");
0026 static const QString TransactionConnection = QStringLiteral("discover_transaction");
0027 static const QString DevelopmentVersionName = QStringLiteral("Rawhide");
0028 
0029 RpmOstreeBackend::RpmOstreeBackend(QObject *parent)
0030     : AbstractResourcesBackend(parent)
0031     , m_currentlyBootedDeployment(nullptr)
0032     , m_transaction(nullptr)
0033     , m_watcher(new QDBusServiceWatcher(this))
0034     , m_interface(nullptr)
0035     , m_updater(new StandardBackendUpdater(this))
0036     , m_fetching(false)
0037     , m_appdata(new AppStream::Pool)
0038     , m_developmentEnabled(false)
0039 {
0040     // Refuse to start on systems not managed by rpm-ostree
0041     if (!this->isValid()) {
0042         qWarning() << "rpm-ostree-backend: Not starting on a system not managed by rpm-ostree";
0043         return;
0044     }
0045 
0046     // Signal that we're fetching ostree deployments
0047     setFetching(true);
0048 
0049     // Switch to development branches for testing.
0050     // TODO: Create a settings option to set this value.
0051     // m_developmentEnabled = true;
0052 
0053     // List configured remotes and display them in the settings page.
0054     // We can do this early as this does not depend on the rpm-ostree daemon.
0055     SourcesModel::global()->addSourcesBackend(new RpmOstreeSourcesBackend(this));
0056 
0057     // Register DBus types
0058     qDBusRegisterMetaType<QList<QVariantMap>>();
0059 
0060     // Setup watcher for rpm-ostree DBus service
0061     m_watcher->setConnection(QDBusConnection::systemBus());
0062     m_watcher->addWatchedService(DBusServiceName);
0063     connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, [this](const QString &serviceName, const QString &oldOwner, const QString &newOwner) {
0064         qDebug() << "rpm-ostree-backend: Acting on DBus service owner change";
0065         if (serviceName != DBusServiceName) {
0066             // This should never happen
0067             qWarning() << "rpm-ostree-backend: Got an unexpected event for service:" << serviceName;
0068         } else if (newOwner.isEmpty()) {
0069             // Re-activate the service if it goes away unexpectedly
0070             m_dbusActivationTimer->start();
0071         } else if (oldOwner.isEmpty()) {
0072             // This is likely the first activation so let's setup the backend
0073             initializeBackend();
0074         } else {
0075             // This should never happen
0076             qWarning() << "rpm-ostree-backend: Got an unexpected event for service:" << serviceName << oldOwner << newOwner;
0077         }
0078     });
0079 
0080     // Setup timer for activation retries
0081     m_dbusActivationTimer = new QTimer(this);
0082     m_dbusActivationTimer->setSingleShot(true);
0083     m_dbusActivationTimer->setInterval(1000);
0084     connect(m_dbusActivationTimer, &QTimer::timeout, [this]() {
0085         QDBusConnection::systemBus().interface()->startService(DBusServiceName);
0086         qDebug() << "rpm-ostree-backend: DBus activating rpm-ostree service";
0087     });
0088 
0089     // Look for rpm-ostree's DBus interface among registered services
0090     const auto reply = QDBusConnection::systemBus().interface()->registeredServiceNames();
0091     if (!reply.isValid()) {
0092         qWarning() << "rpm-ostree-backend: Failed to get the list of registered DBus services";
0093         return;
0094     }
0095     const auto registeredServices = reply.value();
0096     if (registeredServices.contains(DBusServiceName)) {
0097         // rpm-ostree daemon is running, let's intialize the backend
0098         initializeBackend();
0099     } else {
0100         // Activate the rpm-ostreed daemon via DBus service activation
0101         QDBusConnection::systemBus().interface()->startService(DBusServiceName);
0102         qDebug() << "rpm-ostree-backend: DBus activating rpm-ostree service";
0103     }
0104 }
0105 
0106 void RpmOstreeBackend::initializeBackend()
0107 {
0108     // If any, remove a previous connection that is now likely invalid
0109     if (m_interface != nullptr) {
0110         delete m_interface;
0111     }
0112     // Connect to the main interface
0113     m_interface = new OrgProjectatomicRpmostree1SysrootInterface(DBusServiceName, SysrootObjectPath, QDBusConnection::systemBus(), this);
0114     if (!m_interface->isValid()) {
0115         qWarning() << "rpm-ostree-backend: Could not connect to rpm-ostree daemon:" << qPrintable(QDBusConnection::systemBus().lastError().message());
0116         m_dbusActivationTimer->start();
0117         return;
0118     }
0119 
0120     // Register ourselves as update driver
0121     if (!m_registrered) {
0122         QVariantMap options;
0123         options[QLatin1String("id")] = QVariant{QStringLiteral("discover")};
0124         auto reply = m_interface->RegisterClient(options);
0125         QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(reply, this);
0126         connect(callWatcher, &QDBusPendingCallWatcher::finished, [this, callWatcher]() {
0127             QDBusPendingReply<> reply = *callWatcher;
0128             callWatcher->deleteLater();
0129             // Wait and retry if we encounter an error
0130             if (reply.isError()) {
0131                 qWarning() << "rpm-ostree-backend: Error registering as client:" << qPrintable(QDBusConnection::systemBus().lastError().message());
0132                 m_dbusActivationTimer->start();
0133             } else {
0134                 // Mark that we are now registered with rpm-ostree and retry
0135                 // initializing the backend
0136                 m_registrered = true;
0137                 initializeBackend();
0138             }
0139         });
0140         return;
0141     }
0142 
0143     // Fetch existing deployments
0144     refreshDeployments();
0145 
0146     // Look for a potentially already in-progress rpm-ostree transaction that
0147     // was started outside of Discover
0148     if (hasExternalTransaction()) {
0149         setFetching(false);
0150         return;
0151     }
0152 
0153     // Start the check for a new version of the current deployment if there is
0154     // no transaction in progress
0155     checkForUpdates();
0156 }
0157 
0158 void RpmOstreeBackend::refreshDeployments()
0159 {
0160     // Set fetching in all cases but record the state to decide if we should undo it at the end
0161     bool wasFetching = isFetching();
0162     setFetching(true);
0163 
0164     // Get the path for the curently booted OS DBus interface.
0165     m_bootedObjectPath = m_interface->booted().path();
0166 
0167     // Reset the list of deployments
0168     m_currentlyBootedDeployment = nullptr;
0169     m_resources.clear();
0170 
0171     // Get the list of currently available deployments. This is a DBus property
0172     // and not a method call so we should not need to do it async.
0173     const QList<QVariantMap> deployments = m_interface->deployments();
0174     for (QVariantMap d : deployments) {
0175         RpmOstreeResource *deployment = new RpmOstreeResource(d, this);
0176         m_resources << deployment;
0177         if (deployment->isBooted()) {
0178             connect(deployment, &RpmOstreeResource::stateChanged, [this]() {
0179                 Q_EMIT updatesCountChanged();
0180             });
0181             if (m_currentlyBootedDeployment) {
0182                 qWarning() << "rpm-ostree-backend: We already have a booted deployment. This is a bug.";
0183                 passiveMessage(i18n("rpm-ostree: Multiple booted deployments found. Please file a bug."));
0184                 return;
0185             }
0186             m_currentlyBootedDeployment = deployment;
0187         } else if (deployment->isPending()) {
0188             // Signal that we have a pending update
0189             m_updater->setNeedsReboot(true);
0190         }
0191     }
0192 
0193     if (!m_currentlyBootedDeployment) {
0194         qWarning() << "rpm-ostree-backend: We have not found the booted deployment. This is a bug.";
0195         passiveMessage(i18n("rpm-ostree: No booted deployment found. Please file a bug."));
0196         return;
0197     }
0198 
0199     // The number of updates might have changed if we're called after an update
0200     Q_EMIT updatesCountChanged();
0201 
0202     // Only undo the fetching state if we set it up in this function
0203     if (!wasFetching) {
0204         setFetching(false);
0205     }
0206 }
0207 
0208 void RpmOstreeBackend::transactionStatusChanged(Transaction::Status status)
0209 {
0210     switch (status) {
0211     case Transaction::Status::DoneStatus:
0212     case Transaction::Status::DoneWithErrorStatus:
0213     case Transaction::Status::CancelledStatus:
0214         m_transaction = nullptr;
0215         setFetching(false);
0216         break;
0217     default:
0218         // Ignore all other status changes
0219         ;
0220     }
0221 }
0222 
0223 void RpmOstreeBackend::setupTransaction(RpmOstreeTransaction::Operation op, QString arg)
0224 {
0225     m_transaction = new RpmOstreeTransaction(this, m_currentlyBootedDeployment, m_interface, op, arg);
0226     connect(m_transaction, &RpmOstreeTransaction::statusChanged, this, &RpmOstreeBackend::transactionStatusChanged);
0227     connect(m_transaction, &RpmOstreeTransaction::deploymentsUpdated, this, &RpmOstreeBackend::refreshDeployments);
0228     connect(m_transaction, &RpmOstreeTransaction::lookForNextMajorVersion, this, &RpmOstreeBackend::lookForNextMajorVersion);
0229 }
0230 
0231 bool RpmOstreeBackend::hasExternalTransaction()
0232 {
0233     // Do we already know that we have a transaction in progress?
0234     if (m_transaction) {
0235         qInfo() << "rpm-ostree-backend: A transaction is already in progress";
0236         return true;
0237     }
0238 
0239     // Is there actualy a transaction in progress we don't know about yet?
0240     const QString transaction = m_interface->activeTransactionPath();
0241     if (!transaction.isEmpty()) {
0242         qInfo() << "rpm-ostree-backend: Found a transaction in progress";
0243         // We don't check that m_currentlyBootedDeployment is != nullptr here as we expect
0244         // that the backend is initialized when we're called.
0245         setupTransaction(RpmOstreeTransaction::Unknown);
0246         TransactionModel::global()->addTransaction(m_transaction);
0247         return true;
0248     }
0249 
0250     return false;
0251 }
0252 
0253 void RpmOstreeBackend::checkForUpdates()
0254 {
0255     if (!m_currentlyBootedDeployment) {
0256         qInfo() << "rpm-ostree-backend: Called checkForUpdates before the backend is done getting deployments";
0257         return;
0258     }
0259 
0260     // Do not start a transaction if there is already one in-progress (likely externaly started)
0261     if (hasExternalTransaction()) {
0262         qInfo() << "rpm-ostree-backend: Not checking for updates while a transaction is in progress";
0263         return;
0264     }
0265 
0266     // We're fetching updates
0267     setFetching(true);
0268 
0269     setupTransaction(RpmOstreeTransaction::CheckForUpdate);
0270     connect(m_transaction, &RpmOstreeTransaction::newVersionFound, [this](QString newVersion) {
0271         // Mark that there is a newer version for the current deployment
0272         m_currentlyBootedDeployment->setNewVersion(newVersion);
0273 
0274         // Look for an existing deployment for the new version
0275         QVectorIterator<RpmOstreeResource *> iterator(m_resources);
0276         while (iterator.hasNext()) {
0277             RpmOstreeResource *deployment = iterator.next();
0278             if (deployment->version() == newVersion) {
0279                 qInfo() << "rpm-ostree-backend: Found existing deployment for new version. Skipping.";
0280                 // Let the user know that the update is pending a reboot
0281                 m_updater->setNeedsReboot(true);
0282                 if (m_currentlyBootedDeployment->getNextMajorVersion().isEmpty()) {
0283                     Q_EMIT inlineMessageChanged(nullptr);
0284                 } else {
0285                     Q_EMIT inlineMessageChanged(m_rebootBeforeRebaseMessage);
0286                 }
0287                 return;
0288             }
0289         }
0290 
0291         // No existing deployment found. Let's offer the update
0292         m_currentlyBootedDeployment->setState(AbstractResource::Upgradeable);
0293         if (m_currentlyBootedDeployment->getNextMajorVersion().isEmpty()) {
0294             Q_EMIT inlineMessageChanged(nullptr);
0295         } else {
0296             Q_EMIT inlineMessageChanged(m_rebootBeforeRebaseMessage);
0297         }
0298     });
0299     m_transaction->start();
0300     TransactionModel::global()->addTransaction(m_transaction);
0301 }
0302 
0303 void RpmOstreeBackend::lookForNextMajorVersion()
0304 {
0305     // Load AppStream metadata
0306     bool res = m_appdata->load();
0307     if (!res) {
0308         qWarning() << "rpm-ostree-backend: Could not open the AppStream metadata pool" << m_appdata->lastError();
0309         return;
0310     }
0311 
0312     // Get the DistroComponentId. For Fedora Kinoite, we follow Fedora's
0313     // release schedule so we don't have our own ID.
0314     QString distroId = AppStream::SystemInfo::currentDistroComponentId();
0315     if (distroId == QLatin1String("org.fedoraproject.kinoite.fedora")) {
0316         distroId = QStringLiteral("org.fedoraproject.fedora");
0317     }
0318 
0319     // Look at releases to see if we have a new major version available.
0320     const auto distroComponents = m_appdata->componentsById(distroId);
0321     if (distroComponents.isEmpty()) {
0322         qWarning() << "rpm-ostree-backend: No component found for" << distroId;
0323         return;
0324     }
0325 
0326     QString currentVersion = AppStreamIntegration::global()->osRelease()->versionId();
0327     QString nextVersion;
0328     for (const AppStream::Component &dc : distroComponents) {
0329         const auto releases = dc.releasesPlain().entries();
0330         for (const auto &r : releases) {
0331             // Only look at stable releases unless development mode is enabled
0332             if ((r.kind() != AppStream::Release::KindStable) && !m_developmentEnabled) {
0333                 continue;
0334             }
0335 
0336             // Let's look at this potentially new verson
0337             QString newVersion = r.version();
0338 
0339             // Ignore development versions by default for version comparisions.
0340             // With the development mode enabled, we will offer it later if no
0341             // other previous newer major version is found.
0342             if (newVersion == DevelopmentVersionName) {
0343                 continue;
0344             }
0345 
0346             if (AppStream::Utils::vercmpSimple(newVersion, currentVersion) > 0) {
0347                 if (nextVersion.isEmpty()) {
0348                     // No other newer version found yet so let's pick this one
0349                     nextVersion = newVersion;
0350                     qInfo() << "rpm-ostree-backend: Found new major release:" << nextVersion;
0351                 } else if (AppStream::Utils::vercmpSimple(nextVersion, newVersion) > 0) {
0352                     // We only offer updating to the very next major release so
0353                     // we pick the smallest of all the newest versions
0354                     nextVersion = newVersion;
0355                     qInfo() << "rpm-ostree-backend: Found a closer new major release:" << nextVersion;
0356                 }
0357             }
0358         }
0359     }
0360 
0361     if (nextVersion.isEmpty()) {
0362         if (m_developmentEnabled) {
0363             // If development mode is enabled, always offer Rawhide as an option if
0364             // we are already on the latest major version.
0365             nextVersion = DevelopmentVersionName;
0366         } else {
0367             // No new version found, and not in development mode: we're done here
0368             return;
0369         }
0370     }
0371 
0372     if (!m_currentlyBootedDeployment) {
0373         qInfo() << "rpm-ostree-backend: Called lookForNextMajorVersion before the backend is done getting deployments";
0374         return;
0375     }
0376 
0377     // Validate that the branch exists for the version to move to and set it for the resource
0378     if (!m_currentlyBootedDeployment->setNewMajorVersion(nextVersion)) {
0379         qWarning() << "rpm-ostree-backend: Found new major release but could not validate it via ostree. File a bug to your distribution.";
0380         return;
0381     }
0382     // Offer the newly found major version to rebase to
0383     foundNewMajorVersion(nextVersion);
0384 }
0385 
0386 void RpmOstreeBackend::foundNewMajorVersion(const QString &newMajorVersion)
0387 {
0388     qDebug() << "rpm-ostree-backend: Found new release:" << newMajorVersion;
0389 
0390     if (!m_currentlyBootedDeployment) {
0391         qInfo() << "rpm-ostree-backend: Called foundNewMajorVersion before the backend is done getting deployments";
0392         return;
0393     }
0394 
0395     const QString newDistroVersionText = m_currentlyBootedDeployment->packageName() + QStringLiteral(" ") + newMajorVersion;
0396 
0397     QString info;
0398     // Message to display when:
0399     // - A new major version is available
0400     // - An update to the current version is available or pending a reboot
0401     info = i18nc("@info:status %1 is a new major version of the user's distro",
0402                  "<b>%1 is now available.</b>\n"
0403                  "To be able to upgrade to this new version, first apply all available updates, and then restart the system.",
0404                  newDistroVersionText);
0405     m_rebootBeforeRebaseMessage = QSharedPointer<InlineMessage>::create(InlineMessage::Positive, QStringLiteral("system-software-update"), info);
0406 
0407     // Message to display when:
0408     // - A new major version is available
0409     // - No update to the current version are available or pending a reboot
0410     DiscoverAction *majorUpgrade = new DiscoverAction(QStringLiteral("system-upgrade-symbolic"), i18nc("@action: button", "Begin Upgradeā€¦"), this);
0411     connect(majorUpgrade, &DiscoverAction::triggered, this, &RpmOstreeBackend::rebaseToNewVersion);
0412     info = i18nc("@info:status %1 is a new major version of the user's distro", "%1 is now available.", newDistroVersionText);
0413     m_rebaseAvailableMessage = QSharedPointer<InlineMessage>::create(InlineMessage::Positive, QStringLiteral("system-software-update"), info, majorUpgrade);
0414 
0415     // Look for an existing deployment for the new major version
0416     QVectorIterator<RpmOstreeResource *> iterator(m_resources);
0417     while (iterator.hasNext()) {
0418         RpmOstreeResource *deployment = iterator.next();
0419         QString deploymentVersion = deployment->version();
0420         QStringList deploymentVersionSplit = deploymentVersion.split(QLatin1Char('.'));
0421         if (!deploymentVersionSplit.empty()) {
0422             deploymentVersion = deploymentVersionSplit.at(0);
0423         }
0424         if (deploymentVersion == newMajorVersion) {
0425             qInfo() << "rpm-ostree-backend: Found existing deployment for new major version";
0426             m_updater->setNeedsReboot(true);
0427             Q_EMIT inlineMessageChanged(nullptr);
0428             return;
0429         }
0430     }
0431 
0432     // Look for an existing updated deployment or a pending deployment for the
0433     // current version
0434     QString newVersion = m_currentlyBootedDeployment->getNewVersion();
0435     iterator = QVectorIterator<RpmOstreeResource *>(m_resources);
0436     while (iterator.hasNext()) {
0437         RpmOstreeResource *deployment = iterator.next();
0438         if ((deployment->version() == newVersion) || deployment->isPending()) {
0439             qInfo() << "rpm-ostree-backend: Found pending or updated deployment for current version";
0440             m_updater->setNeedsReboot(true);
0441             Q_EMIT inlineMessageChanged(m_rebootBeforeRebaseMessage);
0442             return;
0443         }
0444     }
0445 
0446     // No pending deployment found for the current version. We effectively let
0447     // them upgrade only if they are running the latest version of the current
0448     // release so let's check if there is an update available for the current
0449     // version.
0450     if (m_currentlyBootedDeployment->state() == AbstractResource::Upgradeable) {
0451         qInfo() << "rpm-ostree-backend: Found pending update for current version";
0452         m_updater->setNeedsReboot(true);
0453         Q_EMIT inlineMessageChanged(m_rebootBeforeRebaseMessage);
0454         return;
0455     }
0456 
0457     // No updates pending or avaiable. We are good to offer the rebase to the
0458     // next major version!
0459     Q_EMIT inlineMessageChanged(m_rebaseAvailableMessage);
0460 }
0461 
0462 int RpmOstreeBackend::updatesCount() const
0463 {
0464     if (!m_currentlyBootedDeployment) {
0465         // Not yet initialized
0466         return 0;
0467     }
0468     if (m_currentlyBootedDeployment->state() == AbstractResource::Upgradeable) {
0469         return 1;
0470     }
0471     return 0;
0472 }
0473 
0474 bool RpmOstreeBackend::isValid() const
0475 {
0476     return QFile::exists(QStringLiteral("/run/ostree-booted"));
0477 }
0478 
0479 ResultsStream *RpmOstreeBackend::search(const AbstractResourcesBackend::Filters &filter)
0480 {
0481     // Skip the search if we're looking into a Category, but not the "Operating System" category
0482     if (filter.category && filter.category->untranslatedName() != QLatin1String("Operating System")) {
0483         return new ResultsStream(QStringLiteral("rpm-ostree-empty"), {});
0484     }
0485 
0486     // Trim whitespace from beginning and end of the string entered in the search field.
0487     QString keyword = filter.search.trimmed();
0488 
0489     QVector<StreamResult> res;
0490     for (RpmOstreeResource *r : m_resources) {
0491         // Skip if the state does not match the filter
0492         if (r->state() < filter.state) {
0493             continue;
0494         }
0495         // Skip if the search field is not empty and neither the name, description or version matches
0496         if (!keyword.isEmpty()) {
0497             if (!r->name().contains(keyword) && !r->longDescription().contains(keyword) && !r->installedVersion().contains(keyword)) {
0498                 continue;
0499             }
0500         }
0501         // Add the ressources to the search filter
0502         res << r;
0503     }
0504     return new ResultsStream(QStringLiteral("rpm-ostree"), res);
0505 }
0506 
0507 Transaction *RpmOstreeBackend::installApplication(AbstractResource *app, const AddonList &addons)
0508 {
0509     Q_UNUSED(addons);
0510     return installApplication(app);
0511 }
0512 
0513 Transaction *RpmOstreeBackend::installApplication(AbstractResource *app)
0514 {
0515     Q_UNUSED(app);
0516 
0517     if (!m_currentlyBootedDeployment) {
0518         qInfo() << "rpm-ostree-backend: Called installApplication before the backend is done getting deployments";
0519         return nullptr;
0520     }
0521 
0522     if (m_currentlyBootedDeployment->state() != AbstractResource::Upgradeable) {
0523         return nullptr;
0524     }
0525 
0526     setupTransaction(RpmOstreeTransaction::Update);
0527     m_transaction->start();
0528     return m_transaction;
0529 }
0530 
0531 Transaction *RpmOstreeBackend::removeApplication(AbstractResource *)
0532 {
0533     // TODO: Support removing unbooted & unpinned deployments
0534     qWarning() << "rpm-ostree-backend: Unsupported operation:" << __PRETTY_FUNCTION__;
0535     return nullptr;
0536 }
0537 
0538 void RpmOstreeBackend::rebaseToNewVersion()
0539 {
0540     if (!m_currentlyBootedDeployment) {
0541         qInfo() << "rpm-ostree-backend: Called rebaseToNewVersion before the backend is done getting deployments";
0542         return;
0543     }
0544 
0545     if (m_currentlyBootedDeployment->state() == AbstractResource::Upgradeable) {
0546         if (m_developmentEnabled) {
0547             qInfo() << "rpm-ostree-backend: You have pending updates for current version. Proceeding anyway.";
0548             passiveMessage(i18n("You have pending updates for the current version. Proceeding anyway."));
0549         } else {
0550             qInfo() << "rpm-ostree-backend: Refusing to rebase with pending updates for current version";
0551             passiveMessage(i18n("Please update to the latest version before rebasing to a major version"));
0552             return;
0553         }
0554     }
0555 
0556     const QString ref = m_currentlyBootedDeployment->getNextMajorVersionRef();
0557     if (ref.isEmpty()) {
0558         qWarning() << "rpm-ostree-backend: Error: Empty ref to rebase to";
0559         passiveMessage(i18n("Missing remote ref for rebase operation. Please file a bug."));
0560         return;
0561     }
0562 
0563     // Only start one rebase operation at a time
0564     Q_EMIT inlineMessageChanged(nullptr);
0565     setupTransaction(RpmOstreeTransaction::Rebase, ref);
0566     m_transaction->start();
0567     TransactionModel::global()->addTransaction(m_transaction);
0568 }
0569 
0570 AbstractBackendUpdater *RpmOstreeBackend::backendUpdater() const
0571 {
0572     return m_updater;
0573 }
0574 
0575 QString RpmOstreeBackend::displayName() const
0576 {
0577     return QStringLiteral("rpm-ostree");
0578 }
0579 
0580 bool RpmOstreeBackend::hasApplications() const
0581 {
0582     return true;
0583 }
0584 
0585 AbstractReviewsBackend *RpmOstreeBackend::reviewsBackend() const
0586 {
0587     return nullptr;
0588 }
0589 
0590 bool RpmOstreeBackend::isFetching() const
0591 {
0592     return m_fetching;
0593 }
0594 
0595 void RpmOstreeBackend::setFetching(bool fetching)
0596 {
0597     if (m_fetching != fetching) {
0598         m_fetching = fetching;
0599         Q_EMIT fetchingChanged();
0600     }
0601 }
0602 
0603 #include "RpmOstreeBackend.moc"