Commit 13f988fe authored by Tomi Leppänen's avatar Tomi Leppänen

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).
parent ceaca903
......@@ -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
......
......@@ -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<QString,QString> 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;
......
......@@ -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 @@ public:
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 @@ signals:
void developerModeEnabledChanged();
void workStatusChanged();
void workProgressChanged();
void repositoryAccessRequiredChanged();
private slots:
void reportTransactionErrorCode(PackageKit::Transaction::Error code, const QString &details);
......@@ -117,6 +120,8 @@ private:
PackageKit::Transaction::Role m_transactionRole;
PackageKit::Transaction::Status m_transactionStatus;
bool m_refreshedForInstall;
bool m_localInstallFailed;
QString m_localDeveloperModePackagePath;
};
Q_DECLARE_METATYPE(DeveloperModeSettings::Status)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment