From 13f988fe58691f1120d8171c5398f2df5a6b4248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomi=20Lepp=C3=A4nen?= Date: Tue, 5 Nov 2019 16:42:41 +0200 Subject: [PATCH] Allow enabling developer mode from local package. [developermode] Implement installing from local package. Contributes to JB#47559 This allows enabling developer mode from local package and thus doesn't require Internet or repository access. The package must not have non-preinstalled requirements. Before installing version in package file name is checked against -preload subpackage. [developermode] Add repositoryAccessRequired property. Contributes to JB#47056 Repository access may be required if the local package does not exist or it can not be installed for some reason. In that case developer mode settings page must ask for repository access (usually some kind of account). --- rpm/nemo-qml-plugin-systemsettings.spec | 2 +- src/developermodesettings.cpp | 208 +++++++++++++++++------- src/developermodesettings.h | 5 + 3 files changed, 153 insertions(+), 62 deletions(-) diff --git a/rpm/nemo-qml-plugin-systemsettings.spec b/rpm/nemo-qml-plugin-systemsettings.spec index 762762e..2cf24db 100644 --- a/rpm/nemo-qml-plugin-systemsettings.spec +++ b/rpm/nemo-qml-plugin-systemsettings.spec @@ -4,7 +4,7 @@ Version: 0.5.27 Release: 1 Group: System/Libraries License: BSD -URL: https://git.merproject.org/mer-core/nemo-qml-plugin-systemsettings +URL: https://git.sailfishos.org/mer-core/nemo-qml-plugin-systemsettings Source0: %{name}-%{version}.tar.bz2 Requires(post): /sbin/ldconfig Requires(postun): /sbin/ldconfig diff --git a/src/developermodesettings.cpp b/src/developermodesettings.cpp index f7e1340..0975bb5 100644 --- a/src/developermodesettings.cpp +++ b/src/developermodesettings.cpp @@ -55,6 +55,7 @@ /* A file that is provided by the developer mode package */ #define DEVELOPER_MODE_PROVIDED_FILE "/usr/bin/devel-su" #define DEVELOPER_MODE_PACKAGE "jolla-developer-mode" +#define DEVELOPER_MODE_PACKAGE_PRELOAD_DIR "/var/lib/jolla-developer-mode/preloaded/" /* D-Bus service */ #define USB_MODED_SERVICE "com.meego.usb_moded" @@ -84,6 +85,17 @@ static QMap enumerate_network_interfaces() return result; } +static QString get_cached_package(const QString &version) +{ + QDir dir(DEVELOPER_MODE_PACKAGE_PRELOAD_DIR); + QStringList filters; + filters << QStringLiteral("%1-%2.*.rpm").arg(DEVELOPER_MODE_PACKAGE).arg(version); + auto preloaded = dir.entryList(filters, QDir::Files, QDir::Name); + if (preloaded.empty()) + return QString(); + return dir.absoluteFilePath(preloaded.last()); +} + DeveloperModeSettings::DeveloperModeSettings(QObject *parent) : QObject(parent) , m_usbModeDaemon(USB_MODED_SERVICE, USB_MODED_PATH, USB_MODED_INTERFACE, QDBusConnection::systemBus()) @@ -97,6 +109,8 @@ DeveloperModeSettings::DeveloperModeSettings(QObject *parent) , m_transactionRole(PackageKit::Transaction::RoleUnknown) , m_transactionStatus(PackageKit::Transaction::StatusUnknown) , m_refreshedForInstall(false) + , m_localInstallFailed(false) + , m_localDeveloperModePackagePath(get_cached_package(QStringLiteral("*"))) // Initialized to possibly incompatible package { int uid = getdef_num("UID_MIN", -1); struct passwd *pwd; @@ -106,6 +120,23 @@ DeveloperModeSettings::DeveloperModeSettings(QObject *parent) qCWarning(lcDeveloperModeLog) << "Failed to return username using getpwuid()"; } + // Resolve and update local package path + if (!m_localDeveloperModePackagePath.isEmpty()) { + PackageKit::Transaction *resolvePackage = PackageKit::Daemon::resolve(DEVELOPER_MODE_PACKAGE"-preload", PackageKit::Transaction::FilterInstalled); + connect(resolvePackage, &PackageKit::Transaction::errorCode, this, &DeveloperModeSettings::reportTransactionErrorCode); + connect(resolvePackage, &PackageKit::Transaction::package, + this, [this](PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) { + Q_UNUSED(summary) + Q_ASSERT(info == PackageKit::Transaction::InfoInstalled); + const QString version = PackageKit::Transaction::packageVersion(packageID); + m_localDeveloperModePackagePath = get_cached_package(version); + if (m_localDeveloperModePackagePath.isEmpty()) { + emit repositoryAccessRequiredChanged(); + } + qCDebug(lcDeveloperModeLog) << "Preload package version: " << version << ", local package path: " << m_localDeveloperModePackagePath; + }); + } + refresh(); // TODO: Watch WLAN / USB IP addresses for changes @@ -146,6 +177,12 @@ int DeveloperModeSettings::workProgress() const return m_workProgress; } +bool DeveloperModeSettings::repositoryAccessRequired() const +{ + // Aka local-install-of-developer-mode-package-is-not-possible + return m_localInstallFailed || m_localDeveloperModePackagePath.isEmpty(); +} + void DeveloperModeSettings::setDeveloperMode(bool enabled) { if (m_developerModeEnabled != enabled) { @@ -233,72 +270,114 @@ void DeveloperModeSettings::refreshPackageCacheAndInstall() void DeveloperModeSettings::resolveAndExecute(Command command) { setWorkStatus(Preparing); + m_workProgress = 0; m_developerModePackageId.clear(); // might differ between installed/available - PackageKit::Transaction::Filters filters; - if (command == RemoveCommand) { - filters = PackageKit::Transaction::FilterInstalled; - } else { - filters = PackageKit::Transaction::FilterNewest; - } - PackageKit::Transaction *resolvePackage = PackageKit::Daemon::resolve(DEVELOPER_MODE_PACKAGE, filters); + if (command == InstallCommand && !m_localInstallFailed && !m_localDeveloperModePackagePath.isEmpty()) { + // Resolve which version of developer mode package is expected + PackageKit::Transaction *resolvePackage = PackageKit::Daemon::resolve(DEVELOPER_MODE_PACKAGE"-preload", PackageKit::Transaction::FilterInstalled); + connect(resolvePackage, &PackageKit::Transaction::errorCode, this, &DeveloperModeSettings::reportTransactionErrorCode); + connect(resolvePackage, &PackageKit::Transaction::package, + this, [this](PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) { + Q_UNUSED(summary) + Q_ASSERT(info == PackageKit::Transaction::InfoInstalled); + const QString version = PackageKit::Transaction::packageVersion(packageID); + m_localDeveloperModePackagePath = get_cached_package(version); + emit repositoryAccessRequiredChanged(); + qCDebug(lcDeveloperModeLog) << "Preload package version: " << version << ", local package path: " << m_localDeveloperModePackagePath; + }); + + connect(resolvePackage, &PackageKit::Transaction::finished, + this, [this](PackageKit::Transaction::Exit status, uint runtime) { + Q_UNUSED(runtime) + if (status != PackageKit::Transaction::ExitSuccess || m_localDeveloperModePackagePath.isEmpty()) { + qCDebug(lcDeveloperModeLog) << "Preloaded package not found, must use remote package"; + // No cached package => install from repos + resolveAndExecute(InstallCommand); + } else { + PackageKit::Transaction *tx = PackageKit::Daemon::installFiles(QStringList() << m_localDeveloperModePackagePath); + connectCommandSignals(tx); + connect(tx, &PackageKit::Transaction::finished, + this, [this](PackageKit::Transaction::Exit status, uint runtime) { + if (status == PackageKit::Transaction::ExitSuccess) { + qCDebug(lcDeveloperModeLog) << "Developer mode installation from local package transaction done:" << status << runtime; + resetState(); + } else if (status == PackageKit::Transaction::ExitFailed) { + qCWarning(lcDeveloperModeLog) << "Developer mode installation from local package failed, trying from repos"; + m_localInstallFailed = true; + emit repositoryAccessRequiredChanged(); + resolveAndExecute(InstallCommand); // TODO: If repo access is not available this can not bail out + } // else ExitUnknown (ignored) + }); + } + }); - connect(resolvePackage, &PackageKit::Transaction::errorCode, this, &DeveloperModeSettings::reportTransactionErrorCode); - connect(resolvePackage, &PackageKit::Transaction::package, - this, [this](PackageKit::Transaction::Info info, const QString &packageId, const QString &summary) { - qCDebug(lcDeveloperModeLog) << "Package transaction:" << info << packageId << "summary:" << summary; - m_developerModePackageId = packageId; - }); + } else { + PackageKit::Transaction::Filters filters; + if (command == RemoveCommand) { + filters = PackageKit::Transaction::FilterInstalled; + } else { + filters = PackageKit::Transaction::FilterNewest; + } + PackageKit::Transaction *resolvePackage = PackageKit::Daemon::resolve(DEVELOPER_MODE_PACKAGE, filters); + + connect(resolvePackage, &PackageKit::Transaction::errorCode, this, &DeveloperModeSettings::reportTransactionErrorCode); + connect(resolvePackage, &PackageKit::Transaction::package, + this, [this](PackageKit::Transaction::Info info, const QString &packageId, const QString &summary) { + qCDebug(lcDeveloperModeLog) << "Package transaction:" << info << packageId << "summary:" << summary; + m_developerModePackageId = packageId; + }); + + connect(resolvePackage, &PackageKit::Transaction::finished, + this, [this, command](PackageKit::Transaction::Exit status, uint runtime) { + Q_UNUSED(runtime) + + if (status != PackageKit::Transaction::ExitSuccess || m_developerModePackageId.isEmpty()) { + if (command == InstallCommand) { + if (m_refreshedForInstall) { + qCWarning(lcDeveloperModeLog) << "Failed to install developer mode, package didn't resolve."; + resetState(); + } else { + refreshPackageCacheAndInstall(); // try once if it helps + } + } else if (command == RemoveCommand) { + qCWarning(lcDeveloperModeLog) << "Removing developer mode but package didn't resolve into anything. Shouldn't happen."; + resetState(); + } - connect(resolvePackage, &PackageKit::Transaction::finished, - this, [this, command](PackageKit::Transaction::Exit status, uint runtime) { - Q_UNUSED(runtime) + } else if (command == InstallCommand) { + PackageKit::Transaction *tx = PackageKit::Daemon::installPackage(m_developerModePackageId); + connectCommandSignals(tx); - if (status != PackageKit::Transaction::ExitSuccess || m_developerModePackageId.isEmpty()) { - if (command == InstallCommand) { if (m_refreshedForInstall) { - qCWarning(lcDeveloperModeLog) << "Failed to install developer mode, package didn't resolve."; - resetState(); + connect(tx, &PackageKit::Transaction::finished, + this, [this](PackageKit::Transaction::Exit status, uint runtime) { + qCDebug(lcDeveloperModeLog) << "Developer mode installation transaction done (with refresh):" << status << runtime; + resetState(); + }); } else { - refreshPackageCacheAndInstall(); // try once if it helps + connect(tx, &PackageKit::Transaction::finished, + this, [this](PackageKit::Transaction::Exit status, uint runtime) { + if (status == PackageKit::Transaction::ExitSuccess) { + qCDebug(lcDeveloperModeLog) << "Developer mode installation transaction done:" << status << runtime; + resetState(); + } else { + qCDebug(lcDeveloperModeLog) << "Developer mode installation failed, trying again after refresh"; + refreshPackageCacheAndInstall(); + } + }); } - } else if (command == RemoveCommand) { - qCWarning(lcDeveloperModeLog) << "Removing developer mode but package didn't resolve into anything. Shouldn't happen."; - resetState(); - } - - } else if (command == InstallCommand) { - PackageKit::Transaction *tx = PackageKit::Daemon::installPackage(m_developerModePackageId); - connectCommandSignals(tx); - - if (m_refreshedForInstall) { - connect(tx, &PackageKit::Transaction::finished, - this, [this](PackageKit::Transaction::Exit status, uint runtime) { - qCDebug(lcDeveloperModeLog) << "Developer mode installation transaction done (with refresh):" << status << runtime; - resetState(); - }); } else { + PackageKit::Transaction *tx = PackageKit::Daemon::removePackage(m_developerModePackageId, true, true); + connectCommandSignals(tx); connect(tx, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status, uint runtime) { - if (status == PackageKit::Transaction::ExitSuccess) { - qCDebug(lcDeveloperModeLog) << "Developer mode installation transaction done:" << status << runtime; - resetState(); - } else { - qCDebug(lcDeveloperModeLog) << "Developer mode installation failed, trying again after refresh"; - refreshPackageCacheAndInstall(); - } + qCDebug(lcDeveloperModeLog) << "Developer mode removal transaction done:" << status << runtime; + resetState(); }); } - } else { - PackageKit::Transaction *tx = PackageKit::Daemon::removePackage(m_developerModePackageId, true, true); - connectCommandSignals(tx); - connect(tx, &PackageKit::Transaction::finished, - this, [this](PackageKit::Transaction::Exit status, uint runtime) { - qCDebug(lcDeveloperModeLog) << "Developer mode removal transaction done:" << status << runtime; - resetState(); - }); - } - }); + }); + } } void DeveloperModeSettings::connectCommandSignals(PackageKit::Transaction *transaction) @@ -319,19 +398,16 @@ void DeveloperModeSettings::connectCommandSignals(PackageKit::Transaction *trans void DeveloperModeSettings::updateState(int percentage, PackageKit::Transaction::Status status, PackageKit::Transaction::Role role) { - // Do not update progress when finished. - if (status == PackageKit::Transaction::StatusFinished) { - return; - } - // Expected changes from PackageKit when installing packages: - // 1. Change to 'install packages' role + // 1. Change to 'install packages' role or 'install files' if installing from local package file // 2. Status changes: // setup -> refresh cache -> query -> resolve deps -> install (refer to as 'Preparing' status) // -> download ('DownloadingPackages' status) // -> install ('InstallingPackages' status) // -> finished // + // If installing from local package fails, it starts over! + // // Expected changes from PackageKit when removing packages: // 1. Change to 'remove packages' role // 2. Status changes: @@ -347,10 +423,17 @@ void DeveloperModeSettings::updateState(int percentage, PackageKit::Transaction: m_transactionRole = role; m_transactionStatus = status; + // Do not update progress when finished or role is unknown. + if (m_transactionStatus == PackageKit::Transaction::StatusFinished + || m_transactionRole == PackageKit::Transaction::RoleUnknown) { + return; + } + if (percentage >= 0 && percentage <= 100) { int rangeStart = 0; int rangeEnd = 0; - if (m_transactionRole == PackageKit::Transaction::RoleInstallPackages) { + if (m_transactionRole == PackageKit::Transaction::RoleInstallPackages + || m_transactionRole == PackageKit::Transaction::RoleInstallFiles) { switch (m_transactionStatus) { case PackageKit::Transaction::StatusRefreshCache: // 0-10 % rangeStart = 0; @@ -362,7 +445,10 @@ void DeveloperModeSettings::updateState(int percentage, PackageKit::Transaction: rangeEnd = 20; break; case PackageKit::Transaction::StatusDownload: // 20-60 % - workStatus = DownloadingPackages; + // Skip downloading when installing from local file + if (m_transactionRole != PackageKit::Transaction::RoleInstallFiles) { + workStatus = DownloadingPackages; + } rangeStart = 20; rangeEnd = 60; break; diff --git a/src/developermodesettings.h b/src/developermodesettings.h index 35654b3..36bea30 100644 --- a/src/developermodesettings.h +++ b/src/developermodesettings.h @@ -55,6 +55,7 @@ class SYSTEMSETTINGS_EXPORT DeveloperModeSettings : public QObject Q_PROPERTY(bool developerModeEnabled READ developerModeEnabled NOTIFY developerModeEnabledChanged) Q_PROPERTY(enum DeveloperModeSettings::Status workStatus READ workStatus NOTIFY workStatusChanged) Q_PROPERTY(int workProgress READ workProgress NOTIFY workProgressChanged) + Q_PROPERTY(bool repositoryAccessRequired READ repositoryAccessRequired NOTIFY repositoryAccessRequiredChanged) public: explicit DeveloperModeSettings(QObject *parent = NULL); @@ -74,6 +75,7 @@ class SYSTEMSETTINGS_EXPORT DeveloperModeSettings : public QObject bool developerModeEnabled() const; enum DeveloperModeSettings::Status workStatus() const; int workProgress() const; + bool repositoryAccessRequired() const; Q_INVOKABLE void setDeveloperMode(bool enabled); Q_INVOKABLE void setUsbIpAddress(const QString &usbIpAddress); @@ -85,6 +87,7 @@ class SYSTEMSETTINGS_EXPORT DeveloperModeSettings : public QObject void developerModeEnabledChanged(); void workStatusChanged(); void workProgressChanged(); + void repositoryAccessRequiredChanged(); private slots: void reportTransactionErrorCode(PackageKit::Transaction::Error code, const QString &details); @@ -117,6 +120,8 @@ private slots: PackageKit::Transaction::Role m_transactionRole; PackageKit::Transaction::Status m_transactionStatus; bool m_refreshedForInstall; + bool m_localInstallFailed; + QString m_localDeveloperModePackagePath; }; Q_DECLARE_METATYPE(DeveloperModeSettings::Status)