From ee4254cbc93d8ff64ed663488b3101ecb188ff39 Mon Sep 17 00:00:00 2001 From: olf Date: Tue, 26 Nov 2024 15:08:16 +0100 Subject: [PATCH 1/2] [patchmanager.spec] Specify icon paths more strictly (#471) --- rpm/patchmanager.spec | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rpm/patchmanager.spec b/rpm/patchmanager.spec index ba04e0d0..00da1c77 100644 --- a/rpm/patchmanager.spec +++ b/rpm/patchmanager.spec @@ -1,4 +1,3 @@ - # These macros should already be defined in the RPMbuild environment, see: rpm --showrc %{!?qtc_qmake5:%define qtc_qmake5 %qmake5} %{!?qtc_make:%define qtc_make make} @@ -290,12 +289,12 @@ exit 0 %{_datadir}/jolla-settings/entries/%{name}.json %{_datadir}/%{name}/icons/icon-m-patchmanager.png -# pre 4.6: +# On SailfishOS < 4.6: # /usr/share/themes/sailfish-default/meegotouch/zX.Y/icons/*.png -# 4.6 and higher: +# On SailfishOS >= 4.6: # /usr/share/themes/sailfish-default/silica/zX.Y/icons/*.png # /usr/share/themes/sailfish-default/silica/zX.Y/icons-monochrome/*.png -%{_datadir}/themes/sailfish-default/*/*/*/*.png +%{_datadir}/themes/sailfish-default/*/z*/icons*/*.png %{_datadir}/icons/hicolor/scalable/apps/*.svg %changelog From 8fb48d15184677e31c0e397cc52dbf920f01a23e Mon Sep 17 00:00:00 2001 From: "Peter G." Date: Wed, 27 Nov 2024 09:24:24 +0000 Subject: [PATCH 2/2] Implement a Last-Known-Good feature (#437) * If auto applying succeded, save a known good state * Add option to load last-know-good patch list * Add call to save current set as KnownGood * Add "save as good" to command line options * Add "load known-good" to command line options Contributes-To: #277 --------- Co-authored-by: nephros Co-authored-by: olf --- .../dbus/org.SfietKonstantin.patchmanager.xml | 6 + src/bin/patchmanager-daemon/main.cpp | 12 +- .../patchmanagerobject.cpp | 214 ++++++++++++------ .../patchmanager-daemon/patchmanagerobject.h | 5 + src/index.qdoc | 159 ++++++------- src/qml/PatchManagerPage.qml | 6 + src/qml/patchmanager.cpp | 16 ++ src/qml/patchmanager.h | 2 + src/tools/patchmanager-tool | 12 + 9 files changed, 259 insertions(+), 173 deletions(-) diff --git a/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml b/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml index ae7e5b19..b024cc97 100644 --- a/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml +++ b/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml @@ -107,6 +107,12 @@ + + + + + + diff --git a/src/bin/patchmanager-daemon/main.cpp b/src/bin/patchmanager-daemon/main.cpp index 8176d768..86ccadd4 100644 --- a/src/bin/patchmanager-daemon/main.cpp +++ b/src/bin/patchmanager-daemon/main.cpp @@ -57,11 +57,13 @@ void help() { std::cout << "Patchmanager " << BUILD_VERSION << std::endl; std::cout << "Usage:" << std::endl; - std::cout << " patchmanager [--help] : Print this help text" << std::endl; - std::cout << " patchmanager -a : Enable and activate a Patch" << std::endl; - std::cout << " patchmanager -u : Deactivate and disable (unapply) a Patch" << std::endl; - std::cout << " patchmanager --unapply-all : Deactivate and disable (unapply) all Patches" << std::endl; - std::cout << " patchmanager --daemon : Start Patchmanager as daemon" << std::endl; + std::cout << " patchmanager [--help] : Print this help text" << std::endl; + std::cout << " patchmanager -a : Enable and activate a Patch" << std::endl; + std::cout << " patchmanager -u : Deactivate and disable (unapply) a Patch" << std::endl; + std::cout << " patchmanager --unapply-all : Deactivate and disable (unapply) all Patches" << std::endl; + std::cout << " patchmanager --backup-working : Save list of enabled Patches as \"working\"" << std::endl; + std::cout << " patchmanager --restore-working : Enable backup list of \"working\" Patches" << std::endl; + std::cout << " patchmanager --daemon : Start Patchmanager as daemon" << std::endl; } int main(int argc, char **argv) diff --git a/src/bin/patchmanager-daemon/patchmanagerobject.cpp b/src/bin/patchmanager-daemon/patchmanagerobject.cpp index 67eb2218..ac641110 100644 --- a/src/bin/patchmanager-daemon/patchmanagerobject.cpp +++ b/src/bin/patchmanager-daemon/patchmanagerobject.cpp @@ -151,14 +151,14 @@ static const QString KEYBOARD_CODE = QStringLiteral("keyboard"); \class PatchManagerObject \inmodule PatchManagerDaemon - \brief The Patchmanager Daemon. + \brief The Patchmanager daemon. A D-Bus activated background service which manages patch un/installation, listing, de/actvation, and communication with the preload library. - PatchManager is usually launched by its DBus service. + Patchmanager is usually launched by its D-Bus service. The binary can also serve as a simple command-line client to a running - daemon. See the output of \c{patchmanager --help} for more information. + daemon. See the output of \c{patchmanager --help} for more information. */ @@ -178,7 +178,7 @@ static const QString KEYBOARD_CODE = QStringLiteral("keyboard"); \value NotifyActionFailedUnapply unapplying was not successful \value NotifyActionUpdateAvailable - one of the patches has an update + one of the Patches has an update */ QString getLang() @@ -259,7 +259,7 @@ bool PatchManagerObject::makePatch(const QDir &root, const QString &patchPath, Q } /*! - Sends a notification to the user. \a patch is the patch name, \a action one of: + Sends a notification to the user. \a patch is the Patch name, \a action one of: \sa PatchManagerObject::NotifyAction */ void PatchManagerObject::notify(const QString &patch, NotifyAction action) @@ -342,8 +342,8 @@ void PatchManagerObject::notify(const QString &patch, NotifyAction action) } /*! - Returns the list of applied patches via getSettings(). - \sa getSettings(), getSettings(), setAppliedPatches() + Returns the list of applied \a patches via \c getSettings(). + \sa putSettings(), setAppliedPatches() */ QSet PatchManagerObject::getAppliedPatches() const { @@ -351,14 +351,41 @@ QSet PatchManagerObject::getAppliedPatches() const } /*! - Saves the list of applied \a patches via \c putSettings(). - \sa getSettings(), getSettings(), getAppliedPatches() + Stores a list of applied \a patches via \c putSettings(). + \sa getSettings(), getAppliedPatches() */ void PatchManagerObject::setAppliedPatches(const QSet &patches) { putSettings(QStringLiteral("applied"), QStringList(patches.toList())); } +/*! + Returns the list of successfully auto-applied \a patches via \c getSettings(). + \sa putSettings(), setWorkingPatches() +*/ +QSet PatchManagerObject::getWorkingPatches() const +{ + return getSettings(QStringLiteral("workingPatches"), QStringList()).toStringList().toSet(); +} + +/*! + Stores a list of successfully auto-applied \a patches via \c putSettings(). + \sa getSettings(), getWorkingPatches() +*/ +void PatchManagerObject::setWorkingPatches(const QSet &patches) +{ + putSettings(QStringLiteral("workingPatches"), QStringList(patches.toList())); +} + +/*! + Stores a list of currently applied \a patches as "last-known-good" / working via \c setWorkingPatches(). + \sa getSettings(), getWorkingPatches() +*/ +void PatchManagerObject::setWorking() +{ + setWorkingPatches(getAppliedPatches()); +} + QStringList PatchManagerObject::getMangleCandidates() { if (m_mangleCandidates.empty()) { @@ -451,7 +478,7 @@ void PatchManagerObject::lateInitialize() // connect(additionalWatcher, &INotifyWatcher::contentChanged, [this](const QString &path, bool created) { // qDebug() << Q_FUNC_INFO << "Content in" << path << "changed; is newly created (bool):" << created; // refreshPatchList(); - // }); +// }); registerDBus(); checkForUpdates(); @@ -544,6 +571,24 @@ void PatchManagerObject::doRegisterDBus() m_dbusRegistered = true; } +/*! + \fn void PatchManagerObject::prepareCacheRoot() + + Despite its name, it does not actually prepare the cache root! + Instead, this is the main "auto-apply" function. + + \list + \li First, apply all enabled Patches which are listend in the \l{order}{inifile} settings key. + \li Second, apply all enabled Patches which remain (if any). + \li If applying any Patch fails, the local \c success variable will be set to \c false, but the applying run will continue. + \li At the end of the process, if \c success is \c true, calls setWorkingPatches() + \li At the end of the process, if \c success is \c false, calls refreshPatchList() + \endlist +() + Emits signals \c autoApplyingStarted(), \c autoApplyingPatch(), \c autoApplyingFailed(), autoApplyingFinished(), depending on state. + + \sa PatchManagerObject::doPrepareCache(), {Patchmanager Configuration Files}, inifile, refreshPatchList(), setWorkingPatches() +*/ void PatchManagerObject::doPrepareCacheRoot() { qDebug() << Q_FUNC_INFO; @@ -592,6 +637,10 @@ void PatchManagerObject::doPrepareCacheRoot() emit m_adaptor->autoApplyingFinished(success); } + if (success) { + setWorkingPatches(m_appliedPatches); + } + if (!success) { setAppliedPatches(m_appliedPatches); refreshPatchList(); @@ -600,10 +649,13 @@ void PatchManagerObject::doPrepareCacheRoot() /*! \fn void PatchManagerObject::doPrepareCache(const QString &patchName, bool apply = true) - \fn void PatchManagerObject::prepareCacheRoot() Creates the cache directory where patched files will be stored - and read from when passed to the preload library + and read from when passed to the preload library. + + \c It will create the cache for the Patch \a patchName, and optionally \a apply it. + + \sa PatchManagerObject::prepareCacheRoot() */ void PatchManagerObject::doPrepareCache(const QString &patchName, bool apply) { @@ -690,7 +742,7 @@ void PatchManagerObject::doPrepareCache(const QString &patchName, bool apply) \fn void PatchManagerObject::doStartLocalServer() \fn void PatchManagerObject::startLocalServer() - Starts the internal Server thread if not already started. + Starts the internal server thread if not already started. Emits \c loadedChanged if successful. */ void PatchManagerObject::doStartLocalServer() @@ -709,13 +761,13 @@ void PatchManagerObject::doStartLocalServer() \fn void PatchManagerObject::initialize() \fn void PatchManagerObject::lateInitialize() - Initialize the engines. + Initialise the engines. - The initialization sequence consists of: + The initialisation sequence comprises: - setting up the patch translator - checking configuration constants and environment - - setting up DBus connections to Lipstick and the Store client + - setting up D-Bus connections to Lipstick and the Store client */ void PatchManagerObject::initialize() @@ -820,7 +872,7 @@ void PatchManagerObject::initialize() m_originalWatcher = new QFileSystemWatcher(this); connect(m_originalWatcher, &QFileSystemWatcher::fileChanged, this, &PatchManagerObject::onOriginalFileChanged); - m_localServer = new QLocalServer(nullptr); // controlled by separate thread + m_localServer = new QLocalServer(nullptr); // controlled by a separate thread m_localServer->setSocketOptions(QLocalServer::WorldAccessOption); m_localServer->setMaxPendingConnections(2147483647); @@ -916,7 +968,7 @@ void PatchManagerObject::doRestartKeyboard() For regular processes, \c killall will be performed on them. - For SystemD services, they will be restarted via D-Bus call, or if that fails, via \c systemctl-user. + Systemd services will be restarted via D-Bus call, or if that fails, via \c systemctl-user. */ void PatchManagerObject::restartService(const QString &serviceName) @@ -1035,7 +1087,7 @@ void PatchManagerObject::clearFakeroot() } /*! - retrieve the RPM name from a full package string + Retrieve the RPM name from a full package string. */ QString PatchManagerObject::getRpmName(const QString &rpm) const { @@ -1045,11 +1097,10 @@ QString PatchManagerObject::getRpmName(const QString &rpm) const } /*! - - handle command line arguments, and maybe daemonize. + Handle command line arguments, and may daemonise. If called with any other argument other than \c --daemon, call a method - coreesponding to the command line option on the bus and exit. + corresponding to the command line option on the bus and exit. Currently supported command line options are: @@ -1061,15 +1112,15 @@ QString PatchManagerObject::getRpmName(const QString &rpm) const \row \li \c -a \li a patch internal name - \li calls the "apply" action for the patch + \li Calls the "apply" action for a Patch. \row \li \c -u \li a patch internal name - \li calls the "unapply" action for the patch + \li Calls the "unapply" action for a Patch. \row \li \c --unapply-all \li \e none - \li calls the "unapply" action for all patches + \li Calls the "unapply" action for all Patches. \endtable */ @@ -1113,6 +1164,10 @@ void PatchManagerObject::process() } } else if (args[1] == QStringLiteral("--unapply-all")) { method = QStringLiteral("unapplyAllPatches"); + } else if (args[1] == QStringLiteral("--backup-working")) { + method = QStringLiteral("backupWorkingPatchList"); + } else if (args[1] == QStringLiteral("--restore-working")) { + method = QStringLiteral("restorePatchList"); } else { return; } @@ -1129,7 +1184,7 @@ void PatchManagerObject::process() } -/*! Retrieves a list of patches via D-Bus. */ +/*! Retrieves a list of Patches via D-Bus. */ QVariantList PatchManagerObject::listPatches() { DBUS_GUARD(QVariantList()) @@ -1139,7 +1194,7 @@ QVariantList PatchManagerObject::listPatches() return QVariantList(); } -/*! Returns all versions contained in all patch metadata. */ +/*! Returns all versions contained in all Patch metadata. */ QVariantMap PatchManagerObject::listVersions() { qDebug() << Q_FUNC_INFO; @@ -1151,7 +1206,7 @@ QVariantMap PatchManagerObject::listVersions() return versionsList; } -/*! Returns whether \a patch is in the list of currently applied patches. */ +/*! Returns whether \a patch is in the list of currently active (applied) Patches. */ bool PatchManagerObject::isPatchApplied(const QString &patch) { qDebug() << Q_FUNC_INFO; @@ -1159,7 +1214,8 @@ bool PatchManagerObject::isPatchApplied(const QString &patch) } /*! - Calls the corresponding method over D-Bus to apply \a patch + Calls the corresponding method over D-Bus to activate (apply) \a patch + \warning This function always returns an empty(!) \c QVariantMap */ QVariantMap PatchManagerObject::applyPatch(const QString &patch) @@ -1179,10 +1235,9 @@ QVariantMap PatchManagerObject::applyPatch(const QString &patch) } /*! - call the corresponding method over D-Bus to unapply \a patch + Call the corresponding method over D-Bus to deactivate (unapply) \a patch Returns a \c QVariantMap with the call results. - */ QVariantMap PatchManagerObject::unapplyPatch(const QString &patch) { @@ -1206,7 +1261,8 @@ QVariantMap PatchManagerObject::unapplyPatch(const QString &patch) } /*! - Calls the corresponding method over D-Bus to unapply all active patches. + Calls the corresponding method over D-Bus to deactivate (unapply) all active Patches. + Returns \c true if successful. */ bool PatchManagerObject::unapplyAllPatches() @@ -1247,9 +1303,9 @@ bool PatchManagerObject::installPatch(const QString &patch, const QString &versi } /*! - Calls the corresponding method over D-Bus to uninstall (delete) \a patch from system. + Calls the corresponding method over D-Bus to remove (uninstall / delete) \a patch. - Returns \c true if uninstallation was successful. + Returns \c true if removal (deinstallation) was successful. */ bool PatchManagerObject::uninstallPatch(const QString &patch) { @@ -1270,7 +1326,7 @@ bool PatchManagerObject::uninstallPatch(const QString &patch) /*! Calls the corresponding method over D-Bus to reset applied state of \a patch - Returns \c true if successful + Returns \c true if successful. \sa doResetPatchState */ @@ -1291,7 +1347,7 @@ bool PatchManagerObject::resetPatchState(const QString &patch) } /*! - Calls the corresponding method over D-Bus to retrieve a vote for patch \a patch + Calls the corresponding method over D-Bus to retrieve a vote for Patch \a patch \link doCheckVote \endlink */ @@ -1307,7 +1363,7 @@ int PatchManagerObject::checkVote(const QString &patch) } /*! - Calls the corresponding method over D-Bus to send a vote of type \a action for patch \a patch. + Calls the corresponding method over D-Bus to send a vote of type \a action for Patch \a patch. \sa sendVote */ @@ -1319,7 +1375,7 @@ void PatchManagerObject::votePatch(const QString &patch, int action) Q_ARG(int, action)); } -/*! an \internal thing, let's not spoil the eggs! */ +/*! An \internal thing, let's not spoil the eggs! */ QString PatchManagerObject::checkEaster() { DBUS_GUARD(QString()) @@ -1329,7 +1385,10 @@ QString PatchManagerObject::checkEaster() return QString(); } -/*! Calls the corresponding method over D-Bus to update the \l {Patchmanager Web Catalog}{Web Catalog} Metadata. \a params stores the connection properties. */ +/*! + Calls the corresponding method over D-Bus to update the \l {Patchmanager Web Catalog}{Web Catalog} + metadata. \a params stores the connection properties. +*/ QVariantList PatchManagerObject::downloadCatalog(const QVariantMap ¶ms) { DBUS_GUARD(QVariantList()) @@ -1342,7 +1401,7 @@ QVariantList PatchManagerObject::downloadCatalog(const QVariantMap ¶ms) } /*! - Calls the corresponding method over D-Bus to download metadata for the patch with the name \a name + Calls the corresponding method over D-Bus to download metadata for a Patch with the name \a name \sa requestDownloadPatchInfo */ @@ -1358,10 +1417,10 @@ QVariantMap PatchManagerObject::downloadPatchInfo(const QString &name) } /*! - Calls the corresponding method over D-Bus to check whether the \l {Patchmanager Web Catalog}{Web Catalog} contains updated patch entries. + Calls the corresponding method over D-Bus to check whether the + \l {Patchmanager Web Catalog}{Web Catalog} contains updated patch entries. \sa requestCheckForUpdates - */ void PatchManagerObject::checkForUpdates() { @@ -1375,10 +1434,11 @@ QVariantMap PatchManagerObject::getUpdates() const return m_updates; } -/*! \fn bool PatchManagerObject::putSettings(const QString &name, const QDBusVariant &value) - \fn bool PatchManagerObject::putSettings(const QString &name, const QVariant &value) +/*! + \fn bool PatchManagerObject::putSettings(const QString &name, const QDBusVariant &value) + \fn bool PatchManagerObject::putSettings(const QString &name, const QVariant &value) - Store setting called \a name to the persistent config, \c s_newConfigLocation, and give it value \a value + Store setting called \a name to the persistent config, \c s_newConfigLocation, and give it value \a value. Returns \c true if successful. */ @@ -1403,10 +1463,11 @@ bool PatchManagerObject::putSettings(const QString &name, const QVariant &value) return false; } -/*! \fn QDBusVariant PatchManagerObject::getSettings(const QString &name, const QDBusVariant &def) - \fn QVariant PatchManagerObject::getSettings(const QString &name, const QVariant &def) const +/*! + \fn QDBusVariant PatchManagerObject::getSettings(const QString &name, const QDBusVariant &def) + \fn QVariant PatchManagerObject::getSettings(const QString &name, const QVariant &def) const - Retrieve a setting called \a name from the persistent config, \c s_newConfigLocation. + Retrieve a setting called \a name from the persistent config, \c s_newConfigLocation Use \a def as the default value if not present. Returns a \c QDBusVariant or \c QVariant if successful. @@ -1426,7 +1487,7 @@ QVariant PatchManagerObject::getSettings(const QString &name, const QVariant &de /*! Compares two dot-separated version strings \a version1 and \a version2, and - returns the semanticly higher one. + returns the semantically higher one. */ QString PatchManagerObject::maxVersion(const QString &version1, const QString &version2) { @@ -1463,13 +1524,12 @@ QString PatchManagerObject::maxVersion(const QString &version1, const QString &v } /*! - Stops, Restarts, or kills running processes belonging to a category which + Stops, restarts, or kills running processes belonging to a category which has been marked as to-be-restarted. For regular processes, \c killall will be performed on them. - For SystemD services, they will be restarted via D-Bus call, or if that fails, via \c systemctl-user. - + Systemd services will be restarted via D-Bus call, or if that fails, via \c systemctl-user. */ void PatchManagerObject::restartServices() { @@ -1520,7 +1580,7 @@ void PatchManagerObject::restartServices() /*! Checks the category of \a patch for membership in a category. - If found appends its service toggles to the service toggle list. + If found, append its service toggles to the service toggle list. */ void PatchManagerObject::patchToggleService(const QString &patch) { @@ -1552,8 +1612,7 @@ QStringList PatchManagerObject::getToggleServicesList() const return m_toggleServices.keys(); } -/*! Returns \c true if whether there are services that should be restarted, \c false otherwise. -*/ +/*! Returns \c true if there are services that should be restarted, \c false otherwise. */ bool PatchManagerObject::getToggleServices() const { return !m_toggleServices.isEmpty(); @@ -1565,12 +1624,23 @@ bool PatchManagerObject::getFailure() const return m_failed; } -/*! Returns the internal state whether the server thread is running. */ +/*! Returns the internal state whether the server thread is running. */ bool PatchManagerObject::getLoaded() const { return m_serverThread->isRunning(); } +/*! Retrieves the list of "last-known-good" / working Patches if available, and applies them. */ +void PatchManagerObject::restorePatchList() +{ + QSet patches = getWorkingPatches(); + if (!patches.empty()) { + m_appliedPatches = patches; + setAppliedPatches(patches); + refreshPatchList(); + } +} + /*! Reset internal failure state and re-initialize. @@ -1595,9 +1665,8 @@ void PatchManagerObject::resolveFailure() } /*! - If \a apply is \c true, prepare all internals, start the server, apply all patches and restart Lipstick. - If \a apply is \c false, start the server, unapply all patches. - + If \a apply is \c true, prepare all internals, start the server, activate all enabled Patches and restart Lipstick. + If \a apply is \c false, start the server, deactivate all Patches. */ void PatchManagerObject::loadRequest(bool apply) { @@ -1621,9 +1690,9 @@ void PatchManagerObject::loadRequest(bool apply) } } /*! - D-Bus Method handler. + D-Bus method handler - Called on Lipstick (Re-)Start. Launched the Patchmanager Dialog app (if enabled). + Called on Lipstick (re-)start. Launches the Patchmanager Dialog app (if enabled). \a state passed the Lipstick state: \list @@ -1699,7 +1768,7 @@ QString PatchManagerObject::getOsVersion() const // refreshPatchList(); //} -/*! Return the result of calling \c QObject::eventFilter() on \a watched, \a event */ +/*! Return the result of calling \c QObject::eventFilter() on \a watched, \a event */ bool PatchManagerObject::eventFilter(QObject *watched, QEvent *event) { if (qEnvironmentVariableIsSet("PM_DEBUG_EVENTFILTER")) { @@ -1709,7 +1778,7 @@ bool PatchManagerObject::eventFilter(QObject *watched, QEvent *event) } /*! - Detect a Lipstick crash, assume it was us, clean up and set ourselves into filed state. + Detect a Lipstick crash, assume it was our fault, clean up and set ourselves into failed state. \sa PatchManagerObject::onFailureOccured() \sa PatchManagerObject::FailureOccured() @@ -1882,7 +1951,7 @@ void PatchManagerObject::doRefreshPatchList() { qDebug() << Q_FUNC_INFO; - // Create mangling replacement tokens. + // create mangling replacement tokens QStringList toManglePaths{}, mangledPaths{}; if(getSettings(QStringLiteral("bitnessMangle"), false).toBool()) { toManglePaths = getMangleCandidates(); @@ -1986,7 +2055,7 @@ void PatchManagerObject::doRefreshPatchList() QList patches = listPatchesFromDir(PATCHES_DIR, existingPatches); patches.append(listPatchesFromDir(PATCHES_ADDITIONAL_DIR, existingPatches, false)); qDebug() << Q_FUNC_INFO << "patches:" << patches.count(); -// std::sort(patches.begin(), patches.end(), patchSort); +// std::sort(patches.begin(), patches.end(), patchSort); // fill patch conflicts and rpm names @@ -2087,7 +2156,6 @@ void PatchManagerObject::doListPatches(const QDBusMessage &message) } // for (const QVariantMap &patch : m_metadata) { - // result.append(patch); // } sendMessageReply(message, result); @@ -2163,7 +2231,7 @@ void PatchManagerObject::doPatch(const QVariantMap ¶ms, const QDBusMessage & } } - // is this parameter used anywhere?? + // Is this parameter used anywhere?? if (!params.value(QStringLiteral("dont_notify"), false).toBool()) { if (ok) { bool donotify = getSettings(QStringLiteral("notifyOnSuccess"), true).toBool(); @@ -2185,7 +2253,7 @@ void PatchManagerObject::doPatch(const QVariantMap ¶ms, const QDBusMessage & } /*! - Removes \a patch from list of applied patches. Returns the result in \a message. + Removes \a patch from the list of applied Patches. Returns the result in \a message. \target doResetPatchState */ @@ -2410,7 +2478,7 @@ int PatchManagerObject::getVote(const QString &patch) /*! \target doCheckVote - Send a reply \a message regarding the result or \e getVote() for patch \a patch + Send a reply \a message regarding the result or \e getVote() for Patch \a patch \sa getVote() */ @@ -2423,7 +2491,7 @@ void PatchManagerObject::doCheckVote(const QString &patch, const QDBusMessage &m /*! Submit a vote for patch \a patch. - \a action can be an integet representing an "upvote" or "downvote" (1) + \a action can be an integer representing an "upvote" or "downvote" (1) \target sendVote */ @@ -2601,7 +2669,7 @@ void PatchManagerObject::requestDownloadCatalog(const QVariantMap ¶ms, const } /*! - Retrieve patch metadata from the \l {Patchmanager Web Catalog}{Web Catalog} got patch \a name, reply with message \a message + Retrieve Patch metadata from the \l {Patchmanager Web Catalog}{Web Catalog}, use Patch \a name, reply with message \a message \target requestDownloadPatchInfo */ @@ -2646,9 +2714,9 @@ void PatchManagerObject::requestDownloadPatchInfo(const QString &name, const QDB Connects to the \l {Patchmanager Web Catalog}{Web Catalog} to check for Patch updates. Updates internal state with any results. - Emits updatesAvailable() if yes. + Emits updatesAvailable() if true. - \target requestCheckForUpdates + \target requestCheckForUpdates */ void PatchManagerObject::requestCheckForUpdates() { diff --git a/src/bin/patchmanager-daemon/patchmanagerobject.h b/src/bin/patchmanager-daemon/patchmanagerobject.h index 7f262f4d..70a4612f 100644 --- a/src/bin/patchmanager-daemon/patchmanagerobject.h +++ b/src/bin/patchmanager-daemon/patchmanagerobject.h @@ -129,6 +129,7 @@ public slots: bool getFailure() const; bool getLoaded() const; void resolveFailure(); + void restorePatchList(); void loadRequest(bool apply); void lipstickChanged(const QString &state); @@ -216,6 +217,10 @@ private slots: QSet getAppliedPatches() const; void setAppliedPatches(const QSet &patches); + QSet getWorkingPatches() const; + void setWorkingPatches(const QSet &patches); + void setWorking(); + void getVersion(); void lateInitialize(); diff --git a/src/index.qdoc b/src/index.qdoc index 65d1d60a..3b86f03e 100644 --- a/src/index.qdoc +++ b/src/index.qdoc @@ -10,7 +10,7 @@ \section1 Internals - For information on how PM operates internally, see: + For information on how Patchmanager operates internally, see: \list \li \l {Patchmanager Overview} @@ -20,13 +20,13 @@ \section1 Applications - The Patchmanager project includes the following applications: + The Patchmanager project includes these applications: \section2 GUI Applications \list - \li \l {Patchmanager Documentation: Settings Plugin}{Patchmanager Settings Plugin}, a plugin for the Jolla Settings to launch the application - \li \l {Patchmanager Documentation: QML Plugin}{Patchmanager QML Plugin}, the main UI Application - \li \l {Patchmanager Documentation: Startup Dialog}{Patchmanager Startup Dialog}, a UI shown at Lipstick startup. + \li \l {Patchmanager Documentation: Settings Plugin}{Patchmanager Settings Plugin}, a plugin for the Jolla Settings app to launch the Patchmanager application + \li \l {Patchmanager Documentation: QML Plugin}{Patchmanager QML Plugin}, the main GUI Application + \li \l {Patchmanager Documentation: Startup Dialog}{Patchmanager Startup Dialog}, a GUI shown at Lipstick startup \endlist \section2 Infrastructure Applications @@ -40,58 +40,46 @@ /****** Overview page *******/ /*! - \title Patchmanager Overview + \title Overview of Patchmanager (PM) \page pmoverview.html overview \indexpage Patchmanager Documentation \nextpage {Patchmanager Services} - So the current mode of operation of Patchmanager is something like this: + The workflow of operations Patchmanager performs: \section1 Operation Flow \section2 1. A Patch is "activated" - When a user activates a patch via the App, a signal is sent to the daemon. + When a user activates a Patch via the GUI, a signal is sent to the Patchmanager daemon. The daemon will then: - For each file the patch manipulates, a copy of the original file is put into - a cache dir in \c /tmp, and the changes are applied there instead of on the - original file. + For each file a Patch alters, copy the original file into a cache directory in \c /tmp and apply the changes to this copy instead of the original. + + If paths or files are referenced by a Patch which do not yet exist on the filesystem, these will be created in the cache directory, and symlinks pointing to them are placed in the original filesystem. - If there are paths or files involved in the patch which do not exist yet - in the filesystem, they will be created in the cache dir, and a symlink - pointing to them is placed in the original filesystem. \section2 2. A patched application is launched. - Through library preloading, the \c libpreloadpatchmanager.so library is - injected into the launching binary. + Through library preloading, the \c libpreloadpatchmanager.so library is injected into the launching binary. \section2 3. The Preload Library: \list \li Intercepts calls to \l {https://www.man7.org/linux/man-pages/man2/open.2.html}{\c{open()} (or \c{open64()})}, - \li analyzes which files the call was meant to open - \li asks the Patchmanager daemon (via socket) whether it knows of a patched version. + \li analyses which files the call was meant to open + \li asks the Patchmanager daemon (via socket) if it is aware of a patched version of any of these files. \endlist - If yes, the daemon will return a path to its cachedir, and the library - redirects the call to that file instead of the original. Otherwise, the - \c{open()} is executed on the original file. + If so, the daemon will return a path to its cache directory, and the library redirects the call to that file instead of the original. Otherwise, the \c{open()} call is executed on the original file. - Certain paths are blacklisted for these operations to reduce the risk of - critical services, or PM itself, choking on these redirections. + Certain paths are blacklisted for these operations to reduce the risk of critical services, or the Patchmanager binaries proper, stumbling over these redirections. - \note After activating a Patch , the daemon may also inform the UI that some - apps or services need restart. The UI client is expected to issue the command - to restart these soon. - As long as the corresponding preocesses are not restarted, the effect of the - applied patch will not show, or may only show partially, depending on the "patch - history" of the respective process. + \note After activating a Patch, the Patchmanager daemon may inform the Patchmanager GUI that some apps or services need to be restarted. As long as the corresponding processes are not restarted, the applied Patch will be not at all or only partially in effect, depending on the "patch history" of the respective process. \section1 Patch Installation - There are three ways a Patch can end up on the system: + There are four ways a Patch can be deployed: \list \li Web Catalog @@ -102,52 +90,38 @@ \section2 Patch folder structure - All Patches managed by PM are installed under \c /usr/share/patchmanager/patches/NAME, - where NAME is a directory having a unique name. + All Patches managed by PM are installed under \c /usr/share/patchmanager/patches/NAME with NAME being a uniquely named directory. - That directory must contain at least two files: a \c JSON file called - \c patch.json containing metadata, and a \c diff file called \c{unified_diff.patch}. + This directory must contain at least two files: a \c JSON file called \c patch.json containing metadata, and a \c diff file called \c{unified_diff.patch}. It may optionally also contain: \list \li Qt translation (.qm) files \li icon files in PNG format \li a QML file called \c main.qml (whose root element is a Sailfish Silica \c{Page}). - \li other QML-compatible files which are referenced/loaded from \c main.qml + \li other QML-compatible files which are referenced / loaded from \c main.qml \endlist - \note the directory NAME is usually and by convention the same as the patch - metadata field \c name, but that is not a requirement. NAME should be - reasonably unique though, to avoid name clashes with Patches from others. + \note The directory NAME is usually and by convention the same as the Patch metadata field \c name, but this is not a strict requirement. NAME should be reasonably unique though, in order to avoid naming conflicts with other Patches. \section2 Web Catalog - Installation via the Patchmanager Web Catalog is the most common and recomended way of installing a patch. - This is done through the Patchmanager UI, by selecting \uicontrol install on the \l {WebPatchPage}{Patch Page}. + Installation via Patchmanager's Web Catalog is the most common and recommended way of deploying Patches. + This is carried out in the Patchmanager GUI by selecting \uicontrol install on the \l {WebPatchPage}{Patch Page}. - PM will download metadata and a tarball from the Web Catalog, extract the - tarball into the patch storage location, and generate the necessary JSON file - from the metadata. + PM will download metadata and a tar archive from the Web Catalog, extract the archive into the Patch storage location, and generate the necessary JSON file from the metadata. \section2 RPM package - Patches may be distributed as RPM files, which assure the placement of files - according to the structure explained above. RPM also provides advanced - features like dependencies and scriptlets which may be necessary for correct - operation or installation of a Patch. + Patches may be distributed as RPM files, which assure the placement of files according to the structure explained above. RPM files also provide advanced features as specifying dependencies and scriptlets which may be necessary for the correct operation or installation of a Patch. \section2 TAR package - Patch developers may sometimes distribute Patches in \c tar format. Usually - they also contain the necessary files, but the contents of the tarball may - differ, and recipients of them must assure the layout given above is set up - correctly. + Patch developers may offer a Patch as a "naked" \c tar archive. Although it should contain all necessary files, all checks to assure that the layout and content of the files in the archive are correct must be carried out by the user, in contrast to tar archives uploaded to the Web Catalog. - \section2 manual installation + \section2 Manual installation - Patch developers or users may want to quickly place a Patch under PM's - supervision without going through the hassle of packaging and installing. - This can be done easily, as long as the formats given above are followed. + Patch developers or users may want to quickly place a Patch under PM's supervision without the effort of packaging and installing this Patch properly. This can be easily done, as long the aforementioned requirements are obeyed. \e{to be continued...} @@ -158,8 +132,7 @@ \page pmwc.html - The Web Catalog is currently not maintained by the Patchmanager project, and - is documented here briefly for sake of completeness. + The Web Catalog is currently not maintained by the Patchmanager project proper and hence is only tersely documented here. \e{to be continued...} @@ -183,7 +156,7 @@ \quotefile dbus/org.SfietKonstantin.patchmanager.xml - Calls can be issued e.g. like this: + Calls can be issued this way, for example: \badcode dbus-send --print-reply --system --dest=org.SfietKonstantin.patchmanager /org/SfietKonstantin/patchmanager org.SfietKonstantin.patchmanager.checkforUpdates @@ -201,27 +174,27 @@ \li \c UNITDIR/checkForUpdates-org.SfietKonstantin.patchmanager.service \li service \li - - \li calls the \c checkForUpdates() method via D-Bus + \li Calls the \c checkForUpdates() method via D-Bus. \row \li \c UNITDIR/checkForUpdates-org.SfietKonstantin.patchmanager.timer \li timer \li - - \li runs the service above, every two hours + \li Runs the service above, every two hours. \row \li \c UNITDIR/dbus-org.SfietKonstantin.patchmanager.service \li dbus-activated service \li \l {Patchmanager Documentation: Daemon}{Daemon} - \li starts/activates the D-Bus service + \li Starts / activates the D-Bus service. \row \li \c /var/lib/environment/patchmanager/10-dbus.conf \li environment file \li \l {Patchmanager Documentation: Daemon}{Daemon} - \li Configures the environment the Systemd service is started in. + \li Configures the environment in which the Systemd service is started. \row \li \c /etc/dbus-1/system.d/org.SfietKonstantin.patchmanager.conf \li policy file \li \l {Patchmanager Documentation: Daemon}{Daemon} - \li Configures access policy for the D-Bus service + \li Configures the access policy for the D-Bus service. \endtable \section2 Systemd User Units @@ -235,45 +208,35 @@ \row \li \c $USERUNITDIR/dbus-org.SfietKonstantin.patchmanager.service \li dbus-activated service - \li {Patchmanager Documentation: Startup Dialog}{Dialog} - \li starts the Startup Dialog if necessary + \li \l {Patchmanager Documentation: Startup Dialog}{Dialog} + \li Starts the Startup Dialog, if necessary. \row \li \c $USERUNITDIR/lipstick-patchmanager.service \li service - \li {Patchmanager Documentation: Startup Dialog}{Dialog} - \li calls the \c lipstickChanged() method via D-Bus + \li \l {Patchmanager Documentation: Startup Dialog}{Dialog} + \li Calls the \c lipstickChanged() method via D-Bus. \row \li \c /var/lib/environment/patchmanager-dialog/*.conf \li environment file - \li {Patchmanager Documentation: Startup Dialog}{Dialog} - \li Configures the environment the dialog service is started in. + \li \l {Patchmanager Documentation: Startup Dialog}{Dialog} + \li Configures the environment in which the dialog service is started. \endtable - \c lipstick-patchmanager.service watches the state of the Lipstick service. - If Lipstick crashes, Patchmanager Daemon assumes it was caused by a patch - and goes into \c failed state. In this state, all enabled services are - disabled, and PM must be reactivated via the GUI. + \c lipstick-patchmanager.service watches the state of the Lipstick service. If Lipstick crashes, the Patchmanager daemon assumes it was caused by a Patch and goes into \c failed state. In this state, all enabled services are disabled, and PM must be reactivated via the GUI. \section1 Manual Invocation - Apart from using the appropriate tools manipulating tools like \c systemctl, - \c busctl, or \c dbus-send, the patchmanager binary can serve as a CLI as - well if called from command line. - See the output of \c{patchmanager --help} and PatchManagerObject::process() - for more information. + Aside of using appropriate tools as \c systemctl, \c busctl, or \c dbus-send to invoke Patchmanager's functions, the patchmanager binary can also serve as CLI, if called from command line. See the output of \c{patchmanager --help} and PatchManagerObject::process() for more information. - There is also a shell script called \c patchmanager-tool, wrapping the capabilities of \c patchmanager to generate more useful functions. + Additionally a shell script called \c patchmanager-tool exists, which builds upon the capabilities of \c patchmanager to provide more sophisticated and generic functions. - \note At the time this was written, \c patchmanager-tool is not yet distributed with the default packages. You can find it in the source repository though, under \c src/tools/. + \note At the time of compiling this documentation, \c patchmanager-tool is not yet distributed as part of the regular RPM packages. It can be obtained from the directory \c src/tools/ in Patchmanager's source code repository. \section1 Logging and Debugging - Debug logging of the daemon can be configured using the environment file - located in \c /var/lib/environment/patchmanager/10-dbus.conf setting the \c - QT_LOGGING_RULES variable to e.g. \c{"*.debug=true"} + Debug logging by the Patchmananger daemon can be configured by setting the \c QT_LOGGING_RULES variable to, e.g., \c{"*.debug=true"} in the environment file \c /var/lib/environment/patchmanager/10-dbus.conf - After changing this, the system service must be restarted so it can pick up - the new values. + After changing this, Patchmanager's system service must be restarted for evaluating the new values. */ /****** Services Documentation page *******/ @@ -287,9 +250,10 @@ \target inifile - INI-style configuration file and state storage. + An INI-style configuration file and state storage. + + It consists of two sections: \c settings and \c votes - It currently consists of two sections: \c settings, and \c votes \section3 \c settings \table @@ -302,42 +266,47 @@ \li applied \li list of strings, comma-separated \li empty - \li stores the list of "activated" patches + \li Stores the list of "activated" Patches. + \row + \li workingPatches + \li list of strings, comma-separated + \li empty + \li Stores a list of Patches, which represent a "last-known-good" / working set of enabled Patches. This list is automatically copied from \c applied after each successful run of auto-apply. \row \li applyOnBoot \li Boolean \li \c false - \li Whether to activate Patches at boot, or show the {Patchmanager Documentation: Startup Dialog}{Startup Dialog} + \li Whether to activate all enabled Patches during OS startup or to show the {Patchmanager Documentation: Startup Dialog}{Startup Dialog}. \row \li bitnessMangle \li Boolean \li \c false - \li Convert patch contents so they can apply on the local architecture (e.g. \c lib vs. \c lib64 path segments) + \li Convert Patch contents so they are applicable to the local architecture (e.g., \c lib versus \c lib64 path elements). \row \li developerMode \li Boolean \li \c false - \li \warning \b deprecated. Pre-Patchmanager v3.2.7, this was used to store the settings for "Developer Mode". This has now been split into \c patchDevelMode and \c sfosVersionCheck + \li \warning \b deprecated. Before Patchmanager 3.2.7, this was used to store the settings for the "Developer Mode". This mode has been split into \c patchDevelMode and \c sfosVersionCheck \row \li notifyOnSuccess \li Boolean \li \c true - \li Whether to show success messages in the UI + \li Whether to show notifications about successful actions on the GUI. \row \li order \li list of strings, comma-separated \li empty - \li The order of the list of patches, bith in the UI as well as the order they will be applied/activated. + \li The order of Patches, both in the list on the GUI, and the sequence in which they will be activated and hence their patch files applied. \row \li patchDevelMode - \li Boolesn + \li Boolean \li \c false \li \row \li sfosVersionCheck \li Integer (Enum) \li 0 (strict) - \li Whether to relax the version checking in the PM GUI. The default will only allow Patches to be downloaded, or activated, whose compatibility field matches the currently running OS version exactly. + \li Whether to relax the version checking in the Patchmanager GUI. The default setting will only allow Patches to be downloaded or activated, of which an entry in their compatibility field exactly matches the currently installed OS version. \endtable \section3 \c [votes] diff --git a/src/qml/PatchManagerPage.qml b/src/qml/PatchManagerPage.qml index 4bae477e..1d12c74f 100644 --- a/src/qml/PatchManagerPage.qml +++ b/src/qml/PatchManagerPage.qml @@ -239,6 +239,12 @@ Page { onClicked: pageStack.push(Qt.resolvedUrl("RestartServicesDialog.qml")) } + MenuItem { + text: qsTranslate("", "Restore prior enabled list") + visible: PatchManager.failure + onClicked: menuRemorse.execute( text, function() { PatchManager.call(PatchManager.restorePatchList()) } ) + } + MenuItem { text: qsTranslate("", "Resolve failure") visible: PatchManager.failure diff --git a/src/qml/patchmanager.cpp b/src/qml/patchmanager.cpp index 17a780c5..49d1f2b0 100644 --- a/src/qml/patchmanager.cpp +++ b/src/qml/patchmanager.cpp @@ -817,6 +817,22 @@ void PatchManager::onLoadedChanged(bool loaded) emit loadedChanged(m_loaded); } +/*! Calls the \e restorePatchList method on D-Bus */ +void PatchManager::restorePatchList() +{ + qDebug() << Q_FUNC_INFO; + + m_interface->restorePatchList(); +} + +/*! Calls the \e backupWorkingPatchList method on D-Bus */ +void PatchManager::backupWorkingPatchList() +{ + qDebug() << Q_FUNC_INFO; + + m_interface->backupWorkingPatchList(); +} + /*! Calls the \e resolveFailure method on D-Bus */ void PatchManager::resolveFailure() { diff --git a/src/qml/patchmanager.h b/src/qml/patchmanager.h index 5f1e72bf..3527df74 100644 --- a/src/qml/patchmanager.h +++ b/src/qml/patchmanager.h @@ -182,6 +182,8 @@ public slots: void onFailureChanged(bool failed); void onLoadedChanged(bool loaded); + void backupWorkingPatchList(); + void restorePatchList(); void resolveFailure(); signals: diff --git a/src/tools/patchmanager-tool b/src/tools/patchmanager-tool index d7993161..b4d217c9 100644 --- a/src/tools/patchmanager-tool +++ b/src/tools/patchmanager-tool @@ -23,6 +23,8 @@ function usage() { printf "\t-A | --activate-all\t\tActivate / apply all Patches formerly marked as active.\n" printf "\t-d | --deactivate\t\tDeactivate / unapply Patches from list or file (via -f).\n" printf "\t-D | --deactivate-all\t\tDeactivate / unapply all Patches.\n" + printf "\t-B | --backup-working\t\tSave list of enabled Patches as \"working\"\n" + printf "\t-R | --restore-working\t\tEnable backup list of \"working\" Patches\n" printf "\t-f | --file \t\tUse for the list of Patches.\n" printf "\t-e | --export\t\t\tExport list of Patches marked as active either to STDout or (via -f) to a file.\n" printf "\t-? | --help\t\t\tPrint this help.\n" @@ -93,6 +95,16 @@ case "$1" in shift surplus "$*" ;; + -B|--backup-working) + operation="--backup-working" + shift + surplus "$*" + ;; + -R|--restore-working) + operation="--restore-working" + shift + surplus "$*" + ;; --reset-system) operation="--reset-system" shift