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"