diff --git a/.gitignore b/.gitignore index c3545c787..6696f2c34 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ patchmanager-dialog src/icons/z* src/icons/ss *.list +doc/generated diff --git a/README.md b/README.md index 465217c2c..02b2a3fe9 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ Usually, you can generate such patch file using the following command, with the The metadata file contains information about a Patch. It is a simple JSON file, that must be named `patch.json`. This file contains the title of the Patch, a short description of the Patch, a category, and additional information. For the documentation of this JSON file format see: - - for the [modern format](./doc/example_patch.json.md) - - for the much simpler [legacy format](./doc/example_legacy_patch.json.md) + - for the [modern format](./doc/examples/patch.json.md) + - for the much simpler [legacy format](./doc/examples/legacy_patch.json.md) Either format is supported, but the modern one provides more useful features and is recommended. diff --git a/doc/example_legacy_patch.json.md b/doc/examples/legacy_patch.json.md similarity index 100% rename from doc/example_legacy_patch.json.md rename to doc/examples/legacy_patch.json.md diff --git a/doc/example_patch-rpm.spec b/doc/examples/patch-rpm.spec similarity index 100% rename from doc/example_patch-rpm.spec rename to doc/examples/patch-rpm.spec diff --git a/doc/example_patch.json.md b/doc/examples/patch.json.md similarity index 100% rename from doc/example_patch.json.md rename to doc/examples/patch.json.md diff --git a/doc/qdoc/common.qdocconf b/doc/qdoc/common.qdocconf new file mode 100644 index 000000000..550a70e04 --- /dev/null +++ b/doc/qdoc/common.qdocconf @@ -0,0 +1,34 @@ +# include some Qt defaults +# -- macros for QDoc commands +include($QT_INSTALL_DOCS/global/macros.qdocconf) +# -- needed by C++ projects +include($QT_INSTALL_DOCS/global/qt-cpp-defines.qdocconf) +# -- compatibility macros +include($QT_INSTALL_DOCS/global/compat.qdocconf) +# -- configuration common among QDoc projects +include($QT_INSTALL_DOCS/global/fileextensions.qdocconf) +# -- offline HTML template for documentation shipped to Qt Creator +#include($QT_INSTALL_DOCS/global/qt-html-templates-offline.qdocconf) +include($SAILFISH_INSTALL_DOCS/sailfish-html-templates.qdocconf) + +# Reduce padding around code +codeprefix = "\n" +codesuffix = "\n" + +# override/add some things to the stylesheets: +HTML.stylesheets += ./pm.css + +# assign custom HTML footer to replace Qt's default copyright notice +HTML.footer = "

" \ + "

Patchmanager Documentation\n" \ + "Copyright (c) 2023 Patchmanager for SailfishOS contributors.\n" \ + "This document may be used under the terms of the " \ + "" \ + "Creative Commons Attribution Share Alike 4.0 International " \ + "License.

" \ + "

" + +navigation.homepage = https://github.com/sailfishos-patches/patchmanager/wiki +navigation.hometitle = Patchmanager +navigation.landingpage = Patchmanager Documentation + diff --git a/doc/qdoc/daemon.qdocconf b/doc/qdoc/daemon.qdocconf new file mode 100644 index 000000000..4bdf3ab9d --- /dev/null +++ b/doc/qdoc/daemon.qdocconf @@ -0,0 +1,13 @@ +project = daemon +description = Patchmanager Daemon Reference Documentation +versionsym = +version = 2.0 +url = https://github.com/sailfishos-patches/patchmanager + +include (common.qdocconf) + +headerdirs += ../../src/bin/patchmanager-daemon +headers.fileextensions = "*.h *.hpp" + +sourcedirs += ../../src/bin/patchmanager-daemon +#sources.fileextensions = "*.cpp *.qdoc *.qml" diff --git a/doc/qdoc/dialog.qdocconf b/doc/qdoc/dialog.qdocconf new file mode 100644 index 000000000..7b5a38dea --- /dev/null +++ b/doc/qdoc/dialog.qdocconf @@ -0,0 +1,10 @@ +project = dialog +description = Patchmanager Startup Dialog Documentation +versionsym = +version = 2.0 +url = https://github.com/sailfishos-patches/patchmanager + +include (common.qdocconf) + +sourcedirs = ../../src/bin/dialog + diff --git a/doc/qdoc/general.qdocconf b/doc/qdoc/general.qdocconf new file mode 100644 index 000000000..999de6bd1 --- /dev/null +++ b/doc/qdoc/general.qdocconf @@ -0,0 +1,10 @@ +project = patchmanager +description = Patchmanager + +include (common.qdocconf) + +## Sources +sources += ../../src/index.qdoc +# for quoting +exampledirs += ../../src/bin/patchmanager-daemon + diff --git a/doc/qdoc/master.qdocconf b/doc/qdoc/master.qdocconf new file mode 100644 index 000000000..318f9f728 --- /dev/null +++ b/doc/qdoc/master.qdocconf @@ -0,0 +1,6 @@ +doc/qdoc/general.qdocconf +doc/qdoc/qmlplugin.qdocconf +doc/qdoc/daemon.qdocconf +doc/qdoc/plugin.qdocconf +doc/qdoc/preload.qdocconf +doc/qdoc/dialog.qdocconf diff --git a/doc/qdoc/plugin.qdocconf b/doc/qdoc/plugin.qdocconf new file mode 100644 index 000000000..a010ead14 --- /dev/null +++ b/doc/qdoc/plugin.qdocconf @@ -0,0 +1,11 @@ +project = settings-plugin +description = Patchmanager Settings Plugin Documentation +versionsym = +version = 2.0 +url = https://github.com/sailfishos-patches/patchmanager + +include (common.qdocconf) + +sourcedirs = ../../src/plugin + + diff --git a/doc/qdoc/pm.css b/doc/qdoc/pm.css new file mode 100644 index 000000000..b6759043d --- /dev/null +++ b/doc/qdoc/pm.css @@ -0,0 +1,5 @@ +a:link { color: #8C001A; text-decoration: none; text-align: left; } +a.qa-mark:target:before { content: "***"; color: #ff0000; } +a:hover { color: #8C0060; text-align: left; } +a:visited { color: #8C001A; text-align: left; } +a:visited:hover { color: #8C0060; text-align: left; } diff --git a/doc/qdoc/preload.qdocconf b/doc/qdoc/preload.qdocconf new file mode 100644 index 000000000..91f273e09 --- /dev/null +++ b/doc/qdoc/preload.qdocconf @@ -0,0 +1,13 @@ +project = preload +description = Patchmanager Preload Library +versionsym = +version = 2.0 +url = https://github.com/sailfishos-patches/patchmanager + +include(common.qdocconf) + +sources = ../../src/preload/src/preloadpatchmanager.qdoc +# needed for quoting source code: +exampledirs += ../../src/preload/src + + diff --git a/doc/qdoc/qmlplugin.qdocconf b/doc/qdoc/qmlplugin.qdocconf new file mode 100644 index 000000000..caf05f9a2 --- /dev/null +++ b/doc/qdoc/qmlplugin.qdocconf @@ -0,0 +1,19 @@ +project = qml-plugin +description = Patchmanager QML Plugin Reference Documentation +versionsym = +version = 2.0 +url = https://github.com/sailfishos-patches/patchmanager + +include(common.qdocconf) + +depends += qtcore qtqml + +headerdirs = ../../src/qml +#headers.fileextensions = "*.h *.hpp" + +sourcedirs = ../../src/qml +sources += qmlplugin.qdoc +#sources.fileextensions = "*.cpp *.qdoc *.qml" + +# get rid of warnings about undoicumented stuff in patchmanager.h +Cpp.ignoretokens += Q_PROPERTY diff --git a/makedocs b/makedocs new file mode 100755 index 000000000..22986e0b0 --- /dev/null +++ b/makedocs @@ -0,0 +1,65 @@ +#!/bin/sh +# +# Copyright (c) 2023, Peter G. "nephros" +# Licensed under the terms of the BSD 3-Clause License +# + +# This will build Documentation. +# +# It is intended to be run from within the source tree. +# It is also used in Github Actions. +# +# Note that there are large differnces in the qdoc command between Qt 5.6 +# (SFOS), and later Qt versions. +# In order to enable building in newer Qt versions, changes in both the qdoc +# config files as well as the code comments will be required. + +echo '####### $0 starting... #######' +switched_dirs="false" +if gitroot=$(git rev-parse --show-toplevel 2>/dev/null); then + echo found a git root at $gitroot, running from there... + pushd $gitroot 2>/dev/null || cd $gitroot + switched_dirs="true" +else + echo this is not a git repo, assuming root is \$PWD +fi + +# this is searched relative to the qdocconf file!! +export SAILFISH_INSTALL_DOCS=sailfish-qdoc-template/config + +### verify "build" environment +echo Verifying build environment... +qdoc --version +if [ $? -ne 0 ]; then + echo ERROR: qdoc does not seem to work! Please check your qt5 installation + exit 1 +fi +if [ ! -e doc/qdoc/sailfish-qdoc-template ]; then + echo ERROR: sailfish-qdoc-template does not exist + echo You should "'git clone --depth 1 -b upgrade-4.5.0 https://github.com/sailfishos/sailfish-qdoc-template doc/qdoc/sailfish-qdoc-template'" + exit 1 +fi + +OUT=$PWD/doc/generated + +export QT_VER=5 +export QT_INSTALL_DOCS + +if [ -z "$QT_INSTALL_DOCS" ]; then + if [ -f /usr/share/doc/qt5/global/macros.qdocconf ]; then + QT_INSTALL_DOCS=/usr/share/doc/qt5 + elif [ -f /usr/share/qt5/doc/global/macros.qdocconf ]; then + QT_INSTALL_DOCS=/usr/share/qt5/doc + fi +fi + +echo Generating Docs... +[ -d $OUT ] && rm -r $OUT +qdoc --no-examples --outputdir $OUT --outputformat HTML --installdir $QMAKE_INSTALL_ROOT/doc doc/qdoc/master.qdocconf --single-exec $@ +echo Generated Docs at "$OUT" + +[ $switched_dirs = "true" ] && popd 2>/dev/null || cd - + +echo '####### $0 done. #######' +# always exit 0 for CI runs: +exit 0 diff --git a/src/bin/dialog/dialog.qdoc b/src/bin/dialog/dialog.qdoc new file mode 100644 index 000000000..aebc26cb6 --- /dev/null +++ b/src/bin/dialog/dialog.qdoc @@ -0,0 +1,55 @@ +/*! + \title Patchmanager Documentation: Startup Dialog + \page index.html overview + \indexpage Patchmanager Documentation + + This is a very simple Sailfish OS application that upon start checks the state of the Patchmanager Daemon to see whether it needs to launch a GUI. + + \section1 User Interface: Dialog + + At start it shows a \l {https://sailfishos.org/develop/docs/silica/qml-sailfishsilica-sailfish-silica-remorseitem.html/}{RemorseItem} + with a timeout of 10 seconds. If the user cancels this dialog, the PM deamon + will not activate any patches (with the side-effect of marking all as + disabled.) + + If the Remorse timer runs out, the PM daemon will activate all Patches marked + as enabled, and the Dialog shows a little progress bar while it's + doing that. + + Patches failing to apply will be logged, and reported. + + After ending any of the above operations, a \uicontrol "Quit" button is + activated which will close the application. + + This dialog will only be shown if the option \uicontrol{"Apply on Boot"} is \c off. + + + \section1 Application Startup + + Upon launch, the binary will check the following: + + \list + \li whether \l {PatchManager::applyOnBoot}{applyOnBoot} is set in \c /etc/patchmanager2.conf. + \li the result of a call to the \l {PatchManagerObject::getLoaded}{getLoaded} D-Bus method on the Daemon. + \endlist + + If the first is set to \c true, it will exit. (Because with this setting on, all patches will be activate at boot, without the need of a Startup Dialog.) + + If the reply indicates that PM has not been initialized, it will launch the QML UI. Otherwise it will exit. + + \section2 D-Bus interface + + The dialog implements a D-Bus method \c show i on the root path of the \c org.SfietKonstantin.patchmanager service to invoke the UI: + + \badcode + + + + + + \endcode + + This is used by \l {PatchManagerObject::lipstickChanged} to open the dialog once it has initialized. + + \sa {Patchmanager Services}, {Patchmanager Configuration Files} +*/ diff --git a/src/bin/patchmanager-daemon/daemon.qdoc b/src/bin/patchmanager-daemon/daemon.qdoc new file mode 100644 index 000000000..943de927b --- /dev/null +++ b/src/bin/patchmanager-daemon/daemon.qdoc @@ -0,0 +1,24 @@ +/*! + \page index.html + + \title Patchmanager Documentation: Daemon + \indexpage Patchmanager Documentation + + \section1 Overview + + 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. + + If the \c patchmanager binary is called from command line, it + can also serve as a simple CLI to a running daemon. + See the output of \c{patchmanager --help} and + PatchManagerObject::process() for more information. + + \section2 Documentation: + \generatelist {classesbymodule PatchManagerDaemon} + + +*/ + diff --git a/src/bin/patchmanager-daemon/inotifywatcher.cpp b/src/bin/patchmanager-daemon/inotifywatcher.cpp index 1ca0abe6c..79a632ae2 100644 --- a/src/bin/patchmanager-daemon/inotifywatcher.cpp +++ b/src/bin/patchmanager-daemon/inotifywatcher.cpp @@ -69,6 +69,20 @@ INotifyWatcher::INotifyWatcher(QObject *parent) connect(notifier, &QSocketNotifier::activated, this, &INotifyWatcher::readFromInotify); } +/*! \class INotifyWatcher + \inmodule PatchManagerDaemon + \inherits QSocketNotifier + \brief watches a list of files or directories for changes +*/ +/*! \fn void INotifyWatcher::directoryChanged(const QString &path, bool removed); + \fn void INotifyWatcher::fileChanged(const QString &path, bool removed); + This signal is emitted when a file or directory has changed. + Parameters are the \a path and whether it was \a removed. + */ +/*! \fn void INotifyWatcher::contentChanged(const QString &path, bool created); + This signal is emitted when directory content has changed + Parameters are the \a path and whether something was \a created. + */ INotifyWatcher::~INotifyWatcher() { notifier->setEnabled(false); @@ -78,6 +92,7 @@ INotifyWatcher::~INotifyWatcher() ::close(inotifyFd); } +/*! Add \a paths to the list of paths to be watched. */ QStringList INotifyWatcher::addPaths(const QStringList &paths) { QStringList p = paths; @@ -127,6 +142,7 @@ QStringList INotifyWatcher::addPaths(const QStringList &paths) return p; } +/*! Removes \a paths from the list of paths to be watched. */ QStringList INotifyWatcher::removePaths(const QStringList &paths) { QStringList p = paths; diff --git a/src/bin/patchmanager-daemon/journal.cpp b/src/bin/patchmanager-daemon/journal.cpp index ccde98be6..a1f89257c 100644 --- a/src/bin/patchmanager-daemon/journal.cpp +++ b/src/bin/patchmanager-daemon/journal.cpp @@ -4,6 +4,12 @@ #include #include +/*! \class Journal + \inmodule PatchManagerDaemon +*/ +/*! \fn Journal::matchFound() + Emitted when the fileters found a log entry that matched. +*/ Journal::Journal(QObject *parent) : QObject(parent) { @@ -39,6 +45,7 @@ void Journal::wait() thread->start(); } +/*! Attaches itself to the Journal, filetering for \e Lipstick and \e jolla-settings executables. */ void Journal::init() { qDebug() << Q_FUNC_INFO; @@ -60,6 +67,16 @@ void Journal::init() wait(); } +/*! Read the message from the Journal, look got certain strings, and emit the matchFound signal if found. + + Strings looked for are: + + \list + \li "Type X unavailable" + \li "is not a type" AND "Error while loading page" + \endlist + +*/ void Journal::process() { int next_ret = sd_journal_next(m_sdj); diff --git a/src/bin/patchmanager-daemon/patchmanagerobject.cpp b/src/bin/patchmanager-daemon/patchmanagerobject.cpp index 2b420bbaf..530754a5f 100644 --- a/src/bin/patchmanager-daemon/patchmanagerobject.cpp +++ b/src/bin/patchmanager-daemon/patchmanagerobject.cpp @@ -147,6 +147,40 @@ static const QString SILICA_CODE = QStringLiteral("silica"); static const QString SETTINGS_CODE = QStringLiteral("settings"); static const QString KEYBOARD_CODE = QStringLiteral("keyboard"); +/*! + \class PatchManagerObject + \inmodule PatchManagerDaemon + + \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. + 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. + +*/ + +/*! + \enum PatchManagerObject::NotifyAction + \relates PatchManagerObject::notify() + + This enum specifies the type of notification to emit through \c + PatchManagerObject::notify() + + \value NotifyActionSuccessApply + applying was successful + \value NotifyActionSuccessUnapply + unapplying was successful + \value NotifyActionFailedApply + applying was not successful + \value NotifyActionFailedUnapply + unapplying was not successful + \value NotifyActionUpdateAvailable + one of the patches has an update +*/ + QString getLang() { QString lang = QStringLiteral("en_US.utf8"); @@ -224,6 +258,10 @@ bool PatchManagerObject::makePatch(const QDir &root, const QString &patchPath, Q return true; } +/*! + 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) { qDebug() << Q_FUNC_INFO << patch << action; @@ -303,11 +341,19 @@ void PatchManagerObject::notify(const QString &patch, NotifyAction action) qDebug() << Q_FUNC_INFO << notification.replacesId(); } +/*! + Returns the list of applied patches via getSettings(). + \sa getSettings(), getSettings(), setAppliedPatches() +*/ QSet PatchManagerObject::getAppliedPatches() const { return getSettings(QStringLiteral("applied"), QStringList()).toStringList().toSet(); } +/*! + Saves the list of applied \a patches via \c putSettings(). + \sa getSettings(), getSettings(), getAppliedPatches() +*/ void PatchManagerObject::setAppliedPatches(const QSet &patches) { putSettings(QStringLiteral("applied"), QStringList(patches.toList())); @@ -324,6 +370,12 @@ QStringList PatchManagerObject::getMangleCandidates() return m_mangleCandidates; } +/*! + Reads operating system (\c{VERSION_ID}) version from \c /etc/os-release and sets \c m_osRelease to its value. + Calls lateInitialize() afterwards. + + \sa lateInitialize() +*/ void PatchManagerObject::getVersion() { qDebug() << Q_FUNC_INFO; @@ -546,6 +598,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 +*/ void PatchManagerObject::doPrepareCache(const QString &patchName, bool apply) { qDebug() << Q_FUNC_INFO << patchName << apply; @@ -627,6 +686,13 @@ 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. + Emits \c loadedChanged if successful. +*/ void PatchManagerObject::doStartLocalServer() { qDebug() << Q_FUNC_INFO; @@ -639,6 +705,19 @@ void PatchManagerObject::doStartLocalServer() } } +/*! + \fn void PatchManagerObject::initialize() + \fn void PatchManagerObject::lateInitialize() + + Initialize the engines. + + The initialization sequence consists of: + + - setting up the patch translator + - checking configuration constants and environment + - setting up DBus connections to Lipstick and the Store client + +*/ void PatchManagerObject::initialize() { qInfo() << Q_FUNC_INFO << "Patchmanager version" << qApp->applicationVersion(); @@ -779,6 +858,7 @@ void PatchManagerObject::initialize() getVersion(); } +/*! Returns a pretty name (the \c display_name field) from the metadata of \a patch. */ QString PatchManagerObject::getPatchName(const QString patch) const { if (!m_metadata.contains(patch)) { @@ -789,6 +869,14 @@ QString PatchManagerObject::getPatchName(const QString patch) const return (patchData.contains("display_name") ? patchData["display_name"] : patchData[NAME_KEY]).toString(); } +/*! + \fn void PatchManagerObject::restartLipstick() + \fn void PatchManagerObject::doRestartLipstick() + + Invokes PatchManagerObject::doRestartLipstick() to restart Lipstick + + \sa PatchManagerObject::restartService(const QString &serviceName) +*/ void PatchManagerObject::restartLipstick() { qDebug() << Q_FUNC_INFO; @@ -803,6 +891,11 @@ void PatchManagerObject::doRestartLipstick() restartService(QStringLiteral("lipstick.service")); } +/*! + Invokes ManagerObject::doRestartKeyboard() to restart Maliit + + \sa PatchManagerObject::doRestartKeyboard() +*/ void PatchManagerObject::restartKeyboard() { qDebug() << Q_FUNC_INFO; @@ -817,6 +910,15 @@ void PatchManagerObject::doRestartKeyboard() restartService(QStringLiteral("maliit-server.service")); } +/*! + 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. + +*/ void PatchManagerObject::restartService(const QString &serviceName) { qDebug() << Q_FUNC_INFO << serviceName; @@ -932,6 +1034,9 @@ void PatchManagerObject::clearFakeroot() QDir::root().mkpath(s_patchmanagerCacheRoot); } +/*! + retrieve the RPM name from a full package string +*/ QString PatchManagerObject::getRpmName(const QString &rpm) const { const QString info = rpm.section('-', -2); @@ -939,6 +1044,35 @@ QString PatchManagerObject::getRpmName(const QString &rpm) const return name; } +/*! + + handle command line arguments, and maybe daemonize. + + If called with any other argument other than \c --daemon, call a method + coreesponding to the command line option on the bus and exit. + + Currently supported command line options are: + + \table + \header + \li Argument + \li Options + \li Description + \row + \li \c -a + \li a patch internal name + \li calls the "apply" action for the patch + \row + \li \c -u + \li a patch internal name + \li calls the "unapply" action for the patch + \row + \li \c --unapply-all + \li \e none + \li calls the "unapply" action for all patches + \endtable + +*/ void PatchManagerObject::process() { const QStringList args = QCoreApplication::arguments(); @@ -994,6 +1128,8 @@ void PatchManagerObject::process() } } + +/*! Retrieves a list of patches via D-Bus. */ QVariantList PatchManagerObject::listPatches() { DBUS_GUARD(QVariantList()) @@ -1003,6 +1139,7 @@ QVariantList PatchManagerObject::listPatches() return QVariantList(); } +/*! Returns all versions contained in all patch metadata. */ QVariantMap PatchManagerObject::listVersions() { qDebug() << Q_FUNC_INFO; @@ -1014,12 +1151,17 @@ QVariantMap PatchManagerObject::listVersions() return versionsList; } +/*! Returns whether \a patch is in the list of currently applied patches. */ bool PatchManagerObject::isPatchApplied(const QString &patch) { qDebug() << Q_FUNC_INFO; return m_appliedPatches.contains(patch); } +/*! + Calls the corresponding method over D-Bus to apply \a patch + \warning This function always returns an empty(!) \c QVariantMap +*/ QVariantMap PatchManagerObject::applyPatch(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; @@ -1036,6 +1178,12 @@ QVariantMap PatchManagerObject::applyPatch(const QString &patch) return QVariantMap(); } +/*! + call the corresponding method over D-Bus to unapply \a patch + + Returns a \c QVariantMap with the call results. + +*/ QVariantMap PatchManagerObject::unapplyPatch(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; @@ -1057,6 +1205,10 @@ QVariantMap PatchManagerObject::unapplyPatch(const QString &patch) return QVariantMap(); } +/*! + Calls the corresponding method over D-Bus to unapply all active patches. + Returns \c true if successful. +*/ bool PatchManagerObject::unapplyAllPatches() { qDebug() << Q_FUNC_INFO; @@ -1077,6 +1229,11 @@ bool PatchManagerObject::unapplyAllPatches() return true; } +/*! + Calls the corresponding method over D-Bus to download \a patch from \a url in version \a version and install it. + + Returns \c true if installation was successful. +*/ bool PatchManagerObject::installPatch(const QString &patch, const QString &version, const QString &url) { qDebug() << Q_FUNC_INFO << patch; @@ -1089,6 +1246,11 @@ bool PatchManagerObject::installPatch(const QString &patch, const QString &versi return true; } +/*! + Calls the corresponding method over D-Bus to uninstall (delete) \a patch from system. + + Returns \c true if uninstallation was successful. +*/ bool PatchManagerObject::uninstallPatch(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; @@ -1105,6 +1267,13 @@ bool PatchManagerObject::uninstallPatch(const QString &patch) return true; } +/*! + Calls the corresponding method over D-Bus to reset applied state of \a patch + + Returns \c true if successful + + \sa doResetPatchState +*/ bool PatchManagerObject::resetPatchState(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; @@ -1121,6 +1290,11 @@ bool PatchManagerObject::resetPatchState(const QString &patch) return true; } +/*! + Calls the corresponding method over D-Bus to retrieve a vote for patch \a patch + + \link doCheckVote \endlink +*/ int PatchManagerObject::checkVote(const QString &patch) { DBUS_GUARD(0) @@ -1132,6 +1306,11 @@ int PatchManagerObject::checkVote(const QString &patch) return 0; } +/*! + Calls the corresponding method over D-Bus to send a vote of type \a action for patch \a patch. + + \sa sendVote +*/ void PatchManagerObject::votePatch(const QString &patch, int action) { qDebug() << Q_FUNC_INFO << patch << action; @@ -1140,6 +1319,7 @@ void PatchManagerObject::votePatch(const QString &patch, int action) Q_ARG(int, action)); } +/*! an \internal thing, let's not spoil the eggs! */ QString PatchManagerObject::checkEaster() { DBUS_GUARD(QString()) @@ -1149,6 +1329,7 @@ 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. */ QVariantList PatchManagerObject::downloadCatalog(const QVariantMap ¶ms) { DBUS_GUARD(QVariantList()) @@ -1160,6 +1341,11 @@ QVariantList PatchManagerObject::downloadCatalog(const QVariantMap ¶ms) return QVariantList(); } +/*! + Calls the corresponding method over D-Bus to download metadata for the patch with the name \a name + + \sa requestDownloadPatchInfo +*/ QVariantMap PatchManagerObject::downloadPatchInfo(const QString &name) { DBUS_GUARD(QVariantMap()) @@ -1171,27 +1357,36 @@ QVariantMap PatchManagerObject::downloadPatchInfo(const QString &name) return QVariantMap(); } +/*! + 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() { qDebug() << Q_FUNC_INFO; QMetaObject::invokeMethod(this, NAME(requestCheckForUpdates), Qt::QueuedConnection); } +/*! Returns the list of updated objects from the \l {Patchmanager Web Catalog}{Web Catalog}. */ 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) + + Store setting called \a name to the persistent config, \c s_newConfigLocation, and give it value \a value + + Returns \c true if successful. +*/ bool PatchManagerObject::putSettings(const QString &name, const QDBusVariant &value) { return putSettings(name, value.variant()); } -QDBusVariant PatchManagerObject::getSettings(const QString &name, const QDBusVariant &def) -{ - return QDBusVariant(getSettings(name, def.variant())); -} - bool PatchManagerObject::putSettings(const QString &name, const QVariant &value) { qDebug() << Q_FUNC_INFO << name << value; @@ -1208,6 +1403,19 @@ 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 + + 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. +*/ + +QDBusVariant PatchManagerObject::getSettings(const QString &name, const QDBusVariant &def) +{ + return QDBusVariant(getSettings(name, def.variant())); +} QVariant PatchManagerObject::getSettings(const QString &name, const QVariant &def) const { QString key = QStringLiteral("settings/%1").arg(name); @@ -1216,6 +1424,10 @@ QVariant PatchManagerObject::getSettings(const QString &name, const QVariant &de return value; } +/*! + Compares two dot-separated version strings \a version1 and \a version2, and + returns the semanticly higher one. +*/ QString PatchManagerObject::maxVersion(const QString &version1, const QString &version2) { const QStringList vnums1 = version1.split(QChar('.')); @@ -1250,6 +1462,15 @@ QString PatchManagerObject::maxVersion(const QString &version1, const QString &v return version1; } +/*! + 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. + +*/ void PatchManagerObject::restartServices() { qDebug() << Q_FUNC_INFO << m_toggleServices; @@ -1297,6 +1518,10 @@ 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. +*/ void PatchManagerObject::patchToggleService(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; @@ -1320,26 +1545,37 @@ void PatchManagerObject::patchToggleService(const QString &patch) } } + +/*! Returns the list of services which should be restarted. */ QStringList PatchManagerObject::getToggleServicesList() const { return m_toggleServices.keys(); } +/*! Returns \c true if whether there are services that should be restarted, \c false otherwise. +*/ bool PatchManagerObject::getToggleServices() const { return !m_toggleServices.isEmpty(); } +/*! Returns the internal failure state. */ bool PatchManagerObject::getFailure() const { return m_failed; } +/*! Returns the internal state whether the server thread is running. */ bool PatchManagerObject::getLoaded() const { return m_serverThread->isRunning(); } +/*! + Reset internal failure state and re-initialize. + + \sa loadRequest() +*/ void PatchManagerObject::resolveFailure() { qDebug() << Q_FUNC_INFO; @@ -1358,6 +1594,11 @@ 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. + +*/ void PatchManagerObject::loadRequest(bool apply) { qDebug() << Q_FUNC_INFO << apply; @@ -1379,7 +1620,20 @@ void PatchManagerObject::loadRequest(bool apply) restartLipstick(); } } +/*! + D-Bus Method handler. + Called on Lipstick (Re-)Start. Launched the Patchmanager Dialog app (if enabled). + \a state passed the Lipstick state: + + \list + \li "started" + \li "stopped" + \li "restarted" + \endlist + + See also \c lipstick-patchmanager.service, {Patchmanager Service} +*/ void PatchManagerObject::lipstickChanged(const QString &state) { qDebug() << Q_FUNC_INFO << state; @@ -1395,12 +1649,13 @@ void PatchManagerObject::lipstickChanged(const QString &state) }); } } - +/*! Returns the Patchmanager version string. */ QString PatchManagerObject::getPatchmanagerVersion() const { return QCoreApplication::applicationVersion(); } +/*! Returns the internal value for operating system version. */ QString PatchManagerObject::getOsVersion() const { return m_osRelease; @@ -1444,6 +1699,7 @@ QString PatchManagerObject::getOsVersion() const // refreshPatchList(); //} +/*! 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")) { @@ -1452,6 +1708,12 @@ bool PatchManagerObject::eventFilter(QObject *watched, QEvent *event) return QObject::eventFilter(watched, event); } +/*! + Detect a Lipstick crash, assume it was us, clean up and set ourselves into filed state. + + \sa PatchManagerObject::onFailureOccured() + \sa PatchManagerObject::FailureOccured() +*/ void PatchManagerObject::onLipstickChanged(const QString &, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) { qDebug() << Q_FUNC_INFO << changedProperties << invalidatedProperties; @@ -1922,6 +2184,11 @@ void PatchManagerObject::doPatch(const QVariantMap ¶ms, const QDBusMessage & } } +/*! + Removes \a patch from list of applied patches. Returns the result in \a message. + + \target doResetPatchState +*/ void PatchManagerObject::doResetPatchState(const QString &patch, const QDBusMessage &message) { bool success = m_appliedPatches.remove(patch); @@ -2140,6 +2407,13 @@ int PatchManagerObject::getVote(const QString &patch) return m_settings->value(QStringLiteral("votes/%1").arg(patch), 0).toInt(); } +/*! + \target doCheckVote + + Send a reply \a message regarding the result or \e getVote() for patch \a patch + + \sa getVote() +*/ void PatchManagerObject::doCheckVote(const QString &patch, const QDBusMessage &message) { qDebug() << Q_FUNC_INFO << patch; @@ -2147,6 +2421,12 @@ void PatchManagerObject::doCheckVote(const QString &patch, const QDBusMessage &m sendMessageReply(message, getVote(patch)); } +/*! + Submit a vote for patch \a patch. + \a action can be an integet representing an "upvote" or "downvote" (1) + + \target sendVote +*/ void PatchManagerObject::sendVote(const QString &patch, int action) { qDebug() << Q_FUNC_INFO << patch << action; @@ -2320,6 +2600,11 @@ 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 + + \target requestDownloadPatchInfo + */ void PatchManagerObject::requestDownloadPatchInfo(const QString &name, const QDBusMessage &message) { qDebug() << Q_FUNC_INFO << name; @@ -2357,6 +2642,14 @@ 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. + + \target requestCheckForUpdates +*/ void PatchManagerObject::requestCheckForUpdates() { qDebug() << Q_FUNC_INFO; diff --git a/src/bin/patchmanager-daemon/patchmanagerobject.h b/src/bin/patchmanager-daemon/patchmanagerobject.h index 272a550b7..7f262f4d9 100644 --- a/src/bin/patchmanager-daemon/patchmanagerobject.h +++ b/src/bin/patchmanager-daemon/patchmanagerobject.h @@ -114,10 +114,10 @@ public slots: QVariantMap getUpdates() const; bool putSettings(const QString & name, const QDBusVariant & value); - QDBusVariant getSettings(const QString & name, const QDBusVariant & def); - bool putSettings(const QString & name, const QVariant & value); + QVariant getSettings(const QString & name, const QVariant & def) const; + QDBusVariant getSettings(const QString & name, const QDBusVariant & def); static QString maxVersion(const QString &version1, const QString &version2); diff --git a/src/index.qdoc b/src/index.qdoc new file mode 100644 index 000000000..65d1d60ad --- /dev/null +++ b/src/index.qdoc @@ -0,0 +1,349 @@ +/*! + + \page index.html overview + + \title Patchmanager Documentation + + For User documentation, see \l {https://github.com/sailfishos-patches/patchmanager/#information-for-users}{User Documentation}. + + For Patch Developer documentation, see \l {https://github.com/sailfishos-patches/patchmanager/#information-for-patch-developers}{Developer Documentation}. + + \section1 Internals + + For information on how PM operates internally, see: + + \list + \li \l {Patchmanager Overview} + \li \l {Patchmanager Services} + \li \l {Patchmanager Configuration Files} + \endlist + + \section1 Applications + + The Patchmanager project includes the following 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. + \endlist + + \section2 Infrastructure Applications + \list + \li \l {Patchmanager Documentation: Daemon}{Patchmanager Daemon} + \li \l {Patchmanager Documentation: Preload Library}{Preload Library} + \li \l {Patchmanager Web Catalog}{Web Catalog} + \endlist + +*/ + +/****** Overview page *******/ +/*! + \title Patchmanager Overview + + \page pmoverview.html overview + \indexpage Patchmanager Documentation + \nextpage {Patchmanager Services} + + So the current mode of operation of Patchmanager is something like this: + + \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. + + 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. + + 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. + + \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. + \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. + + Certain paths are blacklisted for these operations to reduce the risk of + critical services, or PM itself, choking on 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. + + \section1 Patch Installation + There are three ways a Patch can end up on the system: + + \list + \li Web Catalog + \li RPM package + \li TAR package + \li manual installation + \endlist + + \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. + + 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}. + + 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 + \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. + + \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}. + + 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. + + \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. + + \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. + + \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. + + \e{to be continued...} + +*/ +/****** Web Catalog page *******/ +/*! + \title Patchmanager Web Catalog + + \page pmwc.html + + The Web Catalog is currently not maintained by the Patchmanager project, and + is documented here briefly for sake of completeness. + + \e{to be continued...} + +*/ + +/****** Services Documentation page *******/ +/*! + \title Patchmanager Services + \previouspage {Patchmanager Overview} + \nextpage {Patchmanager Configuration Files} + + \page pmservices.html overview + + \section1 Installation and Configuration + + \section2 D-Bus System Service + \target dbus-sys + The D-Bus services reserve the bus name \c org.SfietKonstantin.patchmanager + + It is defined as follows: + + \quotefile dbus/org.SfietKonstantin.patchmanager.xml + + Calls can be issued e.g. like this: + + \badcode + dbus-send --print-reply --system --dest=org.SfietKonstantin.patchmanager /org/SfietKonstantin/patchmanager org.SfietKonstantin.patchmanager.checkforUpdates + \endcode + + \section2 Systemd System Units + + \table + \header + \li Name + \li Type + \li Component + \li Description + \row + \li \c UNITDIR/checkForUpdates-org.SfietKonstantin.patchmanager.service + \li service + \li - + \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 + \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 + \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. + \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 + \endtable + + \section2 Systemd User Units + + \table + \header + \li Name + \li Type + \li Component + \li Description + \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 + \row + \li \c $USERUNITDIR/lipstick-patchmanager.service + \li service + \li {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. + \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. + + \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. + + There is also a shell script called \c patchmanager-tool, wrapping the capabilities of \c patchmanager to generate more useful 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/. + + \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"} + + After changing this, the system service must be restarted so it can pick up + the new values. + +*/ + /****** Services Documentation page *******/ +/*! + \title Patchmanager Configuration Files + \previouspage {Patchmanager Services} + + \page pmconfig.html overview + + \section2 \c{/etc/patchmanager2.conf} + + \target inifile + + INI-style configuration file and state storage. + + It currently consists of two sections: \c settings, and \c votes + \section3 \c settings + + \table + \header + \li Key + \li Type + \li default + \li Description + \row + \li applied + \li list of strings, comma-separated + \li empty + \li stores the list of "activated" patches + \row + \li applyOnBoot + \li Boolean + \li \c false + \li Whether to activate Patches at boot, or 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) + \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 + \row + \li notifyOnSuccess + \li Boolean + \li \c true + \li Whether to show success messages in the UI + \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. + \row + \li patchDevelMode + \li Boolesn + \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. + \endtable + + \section3 \c [votes] + + \e{to be written...} + + \section2 \c{/var/lib/environment/patchmanager-dialog/*.conf} + \section2 \c{/var/lib/environment/patchmanager/10-dbus.conf} +*/ diff --git a/src/plugin/patchmanager.qml b/src/plugin/patchmanager.qml index 43a51b797..4f73623c4 100644 --- a/src/plugin/patchmanager.qml +++ b/src/plugin/patchmanager.qml @@ -1,3 +1,11 @@ import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype patchmanager + \inqmlmodule patchmanager + \brief Jolla Settings Plugin Page + \inherits \l {Page} + + This loads the main Patchmanager UI from Jolla Settings. +*/ + PatchManagerPage {} diff --git a/src/plugin/plugin.qdoc b/src/plugin/plugin.qdoc new file mode 100644 index 000000000..4a07a8874 --- /dev/null +++ b/src/plugin/plugin.qdoc @@ -0,0 +1,40 @@ +/*! + \page index.html + \title Patchmanager Documentation: Settings Plugin + \indexpage Patchmanager Documentation + + \section1 Overview + + The Settings Plugin makes up the main Patchmanager GUI Application. + + Jolla Settings loads this page which in turn loads + PatchManagerPage, a part of \l {Patchmanager Documentation: QML Plugin}{Patchmanager QML Plugin}. + + \section2 Configuration + + The Jolla Settings plugin configuration looks like this: + + \badcode + { + "translation_catalog": "settings-patchmanager", + "entries": [ + { + "path": "system_settings/look_and_feel/patchmanager", + "type": "page", + "title": "Patchmanager", + "translation_id": "Patchmanager", + "icon": "image://theme/icon-m-patchmanager2", + "order": 2000, + "params": { + "source": "/usr/share/jolla-settings/pages/patchmanager/patchmanager.qml" + } + } + ] + } + \endcode + + \section2 Types + + This plugin provides no types. +*/ + diff --git a/src/preload/src/preloadpatchmanager.qdoc b/src/preload/src/preloadpatchmanager.qdoc new file mode 100644 index 000000000..de385c81b --- /dev/null +++ b/src/preload/src/preloadpatchmanager.qdoc @@ -0,0 +1,100 @@ +/*! + \page index.html + + \title Patchmanager Documentation: Preload Library + + \indexpage Patchmanager Documentation + + \section1 Overview + + This 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. + \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. + + Certain paths are blacklisted for these operations to reduce the risk of + critical services, or PM itself, choking on these redirections. + + \e{to be continued...} + + \section1 Implementation + + \e{to be continued...} + \section3 Blacklists: + + The following two "blacklists" cause the lib to back off redirecting calls for paths matching one of the entries in the list: + + \quotefromfile preloadpatchmanager.c + Paths that start with one of these patterns are skipped + \skipto static const char *blacklist_paths_startswith[] + \printuntil }; + Paths that match exactly one of these patterns are skipped + \skipto static const char *blacklist_paths_equal[] + \printuntil }; + + \section2 Building + + The following \c{#defines} can be used to alter the library behaviour at compile-time + + \table + \header + \li Define + \li Default + \li Description + \row + \li NO_INTERCEPT + \li undefined + \li Compiles out all of the actual redirection code, leaving the rest in place. Can be used in extreme debugging cases te track what is going on without affecting applications. + \row + \li ALLOW_ALL_USERS + \li defined + \li Only allow preloading for users with an UID >= \c UID_MIN, default 100000 + \endtable + + \section1 Running + \section2 Installation + + Once the library is installed in one of the standard library paths, the following is added to the file \c /etc/ld.so.preload + + \badcode + /usr/lib/libpreloadpatchmanager.so + \endcode + \badcode + /usr/lib64/libpreloadpatchmanager.so + \endcode + + which causes it to be loaded into any binary process that is launched. + + \section2 Environment + + The library reacts on the following environment variables: + + \table + \header + \li Variable + \li Default + \li Description + \row + \li NO_PM_PRELOAD + \li undefined + \li Makes the lib skip finding files to redirect to, effectively diabling it ofr practical purposes. Intended to be set for processes which should operate on unpatched original files. + \row + \li PM_PRELOAD_DEBUG + \li undefined + \li Enables debugging output; messages are prefixed with \c{[pm_name]}, \c{[open]}, or \c{[open64]} depending on operation. + \endtable + + Set them to any value to make them take effect + + \section2 Socket + + The library will try to open a socket at \c{/tmp/patchmanager-socket} in order to communicate with the Patchmanager Daemon + +*/ diff --git a/src/qml/AboutPage.qml b/src/qml/AboutPage.qml index 8954fdb4a..32485c64c 100644 --- a/src/qml/AboutPage.qml +++ b/src/qml/AboutPage.qml @@ -39,6 +39,13 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype AboutPage + + \ingroup qml-plugin-components + \inherits Page + \brief Shows brief information about the app, with links to Credits, Attributions etc. +*/ + Page { SilicaFlickable { id: flick diff --git a/src/qml/DevelopersPage.qml b/src/qml/DevelopersPage.qml index ef11d7da4..279d51f96 100644 --- a/src/qml/DevelopersPage.qml +++ b/src/qml/DevelopersPage.qml @@ -38,6 +38,13 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +/*! \qmltype DevelopersPage + + \ingroup qml-plugin-components + \inherits Page + \brief Shows detailed information about the present and past developers of the App. +*/ + Page { id: container ListModel { diff --git a/src/qml/ItemErrorComponent.qml b/src/qml/ItemErrorComponent.qml index 607f1b8a4..153e60a20 100644 --- a/src/qml/ItemErrorComponent.qml +++ b/src/qml/ItemErrorComponent.qml @@ -33,11 +33,25 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +/*! \qmltype ItemErrorComponent + + \ingroup qml-plugin-components + \brief Shows an error message. +*/ + Rectangle { id: errorMessage anchors.fill: parent color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) + /*! \qmlproperty string text + This property holds the text for the error message. + */ property alias text: titleLabel.text + /*! \qmlproperty int timeout + This property specifies how long the message is shown (in milliseconds) + + \sa {https://doc.qt.io/qt-5/qml-qtqml-timer.html#interval-prop}{Timer::interval} + */ property alias timeout: destroyTimer.interval Component.onCompleted: { if (parent.contentItem) { diff --git a/src/qml/PatchManagerPage.qml b/src/qml/PatchManagerPage.qml index eb810835f..b0ed4ae69 100644 --- a/src/qml/PatchManagerPage.qml +++ b/src/qml/PatchManagerPage.qml @@ -40,6 +40,27 @@ import Sailfish.Silica 1.0 import Nemo.Configuration 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype PatchManagerPage + + \ingroup qml-plugin-components + \brief The main Patchmanager GUI + + Page shown through the Jolla Settings Plugin from the Settings Application. + + It is a both a front-end to the Daemon and a Patch management application. + + It allows to: + + \list + \li View the list of installed and activated Patches. + \li Configure the Application and Daemon settings. + \li Activate or deactivate Patches + \li Install Patches from the \l {Patchmanager Web Catalog}{Web Catalog} + \li Uninstall installed Patches + \endlist + +*/ + Page { id: container @@ -57,6 +78,10 @@ Page { } Component.onCompleted: migrateDevModeSettings() + /*! \qmlmethod migrateDevModeSettings() + Manages migration from legacy \e developerMode setting to the new \e patchDevelMode and \e sfosVersionCheck settings, then sets \e developerMode to \e false. + \internal + */ function migrateDevModeSettings() { if (PatchManager.developerMode === true) { console.info("Migrating settings from deprecated developerMode setting.") @@ -98,6 +123,12 @@ Page { } } + /*! \qmlmethod function showUpdates(manual) + + Flashes the Pulley Menu if updates are available and \a manual is \c false. + Does nothing if \a manual is \c true. + + */ function showUpdates(manual) { if (pageStack.busy) { return @@ -109,6 +140,12 @@ Page { pulleyAnimation.start() } + /*! \qmlproperty real PatchManagerPage::pullDownDistance + \internal + Amount of space the pulley "pops down" when it's showing a hint, e.g the result of showUpdates(). + + \warning This is probably broken in recent SFOS versions (?). + */ property real pullDownDistance: Theme.itemSizeLarge SequentialAnimation { diff --git a/src/qml/PatchObject.cpp b/src/qml/PatchObject.cpp index e5f22096a..4c86b84e3 100644 --- a/src/qml/PatchObject.cpp +++ b/src/qml/PatchObject.cpp @@ -42,6 +42,21 @@ #include #include +/*! \qmltype PatchObject + \instantiates PatchObject + \inqmlmodule org.SfietKonstantin.patchmanager + \brief An element to be used as content of an \l {PatchManagerModel} +*/ +/*! \fn void PatchObject::toBeDestroyed(PatchObject *object); + This signal is emitted when \a object is about to be destroyed. (Duh.) + */ +/*! \qmlsignal PatchObject::toBeDestroyed(PatchObject object); + This signal is emitted when \a object is about to be destroyed. (Duh.) +*/ +/*! \qmlsignal PatchObject::busyChanged(); + \internal +*/ + PatchObject::PatchObject(const QVariantMap &data, QObject *parent) : QObject(parent) , m_details(new QQmlPropertyMap(parent)) @@ -60,22 +75,49 @@ PatchObject::PatchObject(const QVariantMap &data, QObject *parent) setObjectName(m_details->value(QStringLiteral("patch")).toString()); } +/*! \class PatchObject + \inmodule org.SfietKonstantin.patchmanager + \brief An element to be used as content of an \l {PatchManagerModel} +*/ PatchObject::~PatchObject() { qDebug() << Q_FUNC_INFO; emit toBeDestroyed(this); } +/*! \qmlproperty var PatchObject::details + + Holds the Patch metadata. +*/ +/*! \property PatchObject::details + \inheaderfile PatchObject.hpp + + \sa {https://doc.qt.io/qt-5/qqmlpropertymap.html}{QQmlPropertyMap} + */ QQmlPropertyMap *PatchObject::details() { return m_details; } -bool PatchObject:: busy() const +/*! \qmlproperty bool PatchObject::busy + + \c true when an internal operation is in progress. +*/ +/*! \property PatchObject::busy + \inheaderfile PatchObject.hpp + \c true when an internal operation is in progress. + */ +/*! Returns \c true when an internal operation is in progress. */ +bool PatchObject::busy() const { return m_busy; } +/*! + Fills the PatchObject's properties from \a data. + + \note If there is a "display_name" field in \a data, it is used. Otherwise, patch name is used. +*/ void PatchObject::setData(const QVariantMap &data) { for (const QString &key : data.keys()) { @@ -90,6 +132,12 @@ void PatchObject::setData(const QVariantMap &data) } } +/*! + Calls PatchManager::applyPatch with the patch name. If \a callback is callable, calls it afterwards. + Does nothing if the \c "patched" property is \c true. + + \sa PatchManager::applyPatch +*/ void PatchObject::apply(QJSValue callback) { qDebug() << Q_FUNC_INFO; @@ -127,6 +175,12 @@ void PatchObject::apply(QJSValue callback) }); } +/*! + Calls PatchManager::unapplyPatch() with the patch name. If \a callback is callable, calls it afterwards. + Does nothing if the \c "patched" property is \c false. + + \sa PatchManager::unapplyPatch +*/ void PatchObject::unapply(QJSValue callback) { qDebug() << Q_FUNC_INFO; @@ -164,6 +218,7 @@ void PatchObject::unapply(QJSValue callback) }); } +/*! Calls PatchManager::uninstallPatch with the patch name. */ void PatchObject::uninstall() { qDebug() << Q_FUNC_INFO; @@ -185,6 +240,7 @@ void PatchObject::uninstall() }); } +/*! Calls PatchManager::resetState. */ void PatchObject::resetState() { qDebug() << Q_FUNC_INFO; diff --git a/src/qml/RestartServicesDialog.qml b/src/qml/RestartServicesDialog.qml index d8036ab0c..9782bfc4c 100644 --- a/src/qml/RestartServicesDialog.qml +++ b/src/qml/RestartServicesDialog.qml @@ -39,6 +39,19 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype RestartServicesDialog + + \ingroup qml-plugin-components + \brief Service restart confirmation dialog + + This Dialog is shown to the user to confirm restarting of "Services", i.e. + anything that has been affected by activated patches. + + If accepted, these programs will be killed. + + The services to kill are selected via the \c category field of Patch metadata. + +*/ Dialog { id: container diff --git a/src/qml/ScreenshotsPage.qml b/src/qml/ScreenshotsPage.qml index f9a6fcbcf..65a47fd24 100644 --- a/src/qml/ScreenshotsPage.qml +++ b/src/qml/ScreenshotsPage.qml @@ -34,9 +34,28 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype ScreenshotsPage + + \ingroup qml-plugin-components + \inherits Page + \brief Shows Screenshots available for a Patch from \l {Patchmanager Web Catalog}{Web Catalog} in a full-screen view. + + \sa {https://sailfishos.org/develop/docs/silica/qml-sailfishsilica-sailfish-silica-slideshowview.html}{SlideshowView} +*/ + Page { id: page + + /*! \qmlproperty model model + This property holds the model providing data for the slideshow. + \sa {https://sailfishos.org/develop/docs/silica/qml-sailfishsilica-sailfish-silica-slideshowview.html#model}{SlideshowView} + */ property alias model: view.model + + /*! \qmlproperty int currentIndex + The \c index of the currently shown Item + \sa {https://sailfishos.org/develop/docs/silica/qml-sailfishsilica-sailfish-silica-slideshowview.html#model}{SlideshowView} + */ property alias currentIndex: view.currentIndex showNavigationIndicator: !view.interactive diff --git a/src/qml/SettingsPage.qml b/src/qml/SettingsPage.qml index 98dcc5c64..688f35377 100644 --- a/src/qml/SettingsPage.qml +++ b/src/qml/SettingsPage.qml @@ -41,13 +41,27 @@ import Sailfish.Silica 1.0 import Nemo.Configuration 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype SettingsPage + + \ingroup qml-plugin-components + \inherits Page + + \brief Manages Application Settings, both global and user-specific ones + + The usual, system-wide configuration values are set via D-Bus plugin by the + Patchmanager daemon, which stores them in \c{/etc/patchmanager2.conf} + The configuration group \c uisettings is for settings which \e solely affect + the PM GUI application and consequently also are per-user settings. + +*/ Page { - /* - * The usual, system-wide configuration values are set via D-Bus plugin by the - * Patchmanager daemon, which stores them in /etc/patchmanager2.conf - * This configuration group "uisettings" is for settings which *solely* affect - * the PM GUI application and consequently also are per-user settings. + /*! \qmlproperty ConfigurationGroup uisettings + + Manages user-specific properties. + + The DConf path for these is \c /org/SfietKonstantin/patchmanager/uisettings + */ ConfigurationGroup { id: uisettings diff --git a/src/qml/UnifiedPatchPage.qml b/src/qml/UnifiedPatchPage.qml index 54d0c2578..75ac81281 100644 --- a/src/qml/UnifiedPatchPage.qml +++ b/src/qml/UnifiedPatchPage.qml @@ -40,11 +40,34 @@ import Sailfish.Silica 1.0 import Nemo.Notifications 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype UnifiedPatchPage + + \ingroup qml-plugin-components + \inherits Page + \brief Shows details about an installed Patch. +*/ Page { id: container + /*! \qmlproperty var modelData + The model date whose contents are to be displayed. + */ property var modelData + + /*! \qmlproperty var delegate + The delegate Component to use to display modelData + */ property var delegate + + /*! \qmlproperty bool legacyPatch + \c true if the Patch uses the legacy metadata format + + Legacy Patches have less and different properties to display. + */ property bool legacyPatch: !modelData.isNewPatch + + /*! \qmlsignal doPatch + This signal is emitted when the user chooses to activate the Patch. + */ signal doPatch Notification { diff --git a/src/qml/WebCatalogPage.qml b/src/qml/WebCatalogPage.qml index 4c0b25117..28e30a4b7 100644 --- a/src/qml/WebCatalogPage.qml +++ b/src/qml/WebCatalogPage.qml @@ -40,12 +40,54 @@ import Sailfish.Silica 1.0 import org.nemomobile.dbus 2.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype WebCatalogPage + + \ingroup qml-plugin-components + \inherits Page + \brief Shows the list of Patches available in the Web Catalog. + + It also provides a search interface, and can order the list by either + date, or category. + + Per default, displays all Patches. If author is set, displays only Patches + by one author/Patch Developer. + + \sa {Patchmanager Web Catalog}{Web Catalog}, author +*/ + Page { id: container + /*! \qmlproperty string author + This property holds the name of the Patch developer, if WebPatchPage is in "author-only" mode. + + \sa {https://github.com/sailfishos-patches/patchmanager/blob/master/README.md#the-json-metadata-file}{Patch JSON metadata file} + */ property string author + + /*! \qmlproperty var versions + This property holds the versions of all the patches in the list. + It is used for update checking. + \warning exact function of this needs to be researched + */ property var versions + + /*! \qmlproperty string search + This property holds the user-supplied search string + */ property string search + + /*! \qmlproperty bool searchVisible + If \c true shows the search field + */ property bool searchVisible + + /*! \qmlproperty bool sortByDate + If \c true, the list is sorted by Patch \c updated_date. Otherwise it is sorted by \c category. + + Default \c true + + \sa {https://github.com/sailfishos-patches/patchmanager/blob/master/README.md#the-json-metadata-file}{Patch JSON metadata file} + */ property bool sortByDate: true onStatusChanged: { diff --git a/src/qml/WebPatchPage.qml b/src/qml/WebPatchPage.qml index 0befad061..f43963504 100644 --- a/src/qml/WebPatchPage.qml +++ b/src/qml/WebPatchPage.qml @@ -39,18 +39,72 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import org.SfietKonstantin.patchmanager 2.0 +/*! \qmltype WebPatchPage + + \ingroup qml-plugin-components + \inherits Page + \brief Shows details about a Patch from the Web Catalog + + \sa {Patchmanager Web Catalog}{Web Catalog}, {https://github.com/sailfishos-patches/patchmanager/blob/master/README.md#the-json-metadata-file}{Patch JSON metadata file} +*/ + + Page { id: container objectName: "WebPatchPage" + /*! \qmlproperty var modelData + This property holds the metadata from the model + + \sa PatchManagerModel, PatchObject + */ property var modelData + /*! \qmlproperty var versions + This property holds a map of patch \c{[name, [versions]]} + */ property var versions + + /*! \qmlproperty int voteAction + State of the like/dislike voting action + + \table + \header + \li Meaning + \li Value + \row + \li Like + \li 2 + \row + \li Dislike + \li 1 + \row + \li Not Voted + \li 0 + \endtable + + \sa PatchManager::checkVote + */ property int voteAction + /*! \qmlproperty bool isInstalled + \c true if a version of the patch is currently installed + */ property bool isInstalled: !!container.versions && typeof(container.versions[modelData.name]) != "undefined" + /*! \qmlproperty var patchData + This property holds the metadata downloaded from the Web Catalog + + \sa modelData + */ property var patchData + + /*! \qmlproperty bool fetching + + Indicates whether data is currently being downloaded. The WebPatchPage will show a ViewPlaceholder if true. + + Default \c true + */ property bool fetching: true onStatusChanged: { diff --git a/src/qml/patchmanager.cpp b/src/qml/patchmanager.cpp index db291f160..e34dea1f3 100644 --- a/src/qml/patchmanager.cpp +++ b/src/qml/patchmanager.cpp @@ -69,6 +69,13 @@ static const char *noop_strings[] = { QT_TRANSLATE_NOOP("Sections", "keyboard"), }; +/*! \class PatchManager + \inheaderfile patchmanager.h + \inmodule org.SfietKonstantin.patchmanager + + \brief Patchmanager QML Plugin +*/ + PatchManager::PatchManager(QObject *parent) : QObject(parent) , m_nam(new QNetworkAccessManager(this)) @@ -172,6 +179,8 @@ PatchManager::PatchManager(QObject *parent) m_osVersion = QSettings("/etc/os-release", QSettings::IniFormat).value("VERSION_ID").toString(); } +/*! Returns a singleton instance of \e PatchManager, constructing it using \a parent if necessary. + */ PatchManager *PatchManager::GetInstance(QObject *parent) { static PatchManager* lsSingleton = nullptr; @@ -224,11 +233,18 @@ void PatchManager::setSfosVersionCheck(int sfosVersionCheck) } } +/*! \property PatchManager::applyOnBoot + Whether to apply patches on boot or not. +*/ bool PatchManager::applyOnBoot() const { return getSettingsSync(QStringLiteral("applyOnBoot"), false).toBool(); } + +/*! \property PatchManager::mangleCandidates + List of mangle candidates from the config +*/ QStringList PatchManager::mangleCandidates() const { QDBusPendingReply reply = m_interface->getMangleCandidates(); @@ -240,6 +256,10 @@ QStringList PatchManager::mangleCandidates() const return QStringList(); } +/*! + Saves the \e applyOnBoot settings value to \a applyOnBoot + \sa applyOnBootChanged(bool value) +*/ void PatchManager::setApplyOnBoot(bool applyOnBoot) { if (putSettingsSync(QStringLiteral("applyOnBoot"), applyOnBoot)) { @@ -247,6 +267,21 @@ void PatchManager::setApplyOnBoot(bool applyOnBoot) } } +/* \property PatchManager::appsNeedRestart + \inheaderfile: patchmanager.h + Whether services need to be restarted +*/ +/* \qmlproperty bool PatchManager::appsNeedRestart + \inheaderfile: patchmanager.h + Whether services need to be restarted +*/ +/*! \property PatchManager::notifyOnSuccess + Whether to show a popup on successful actions +*/ +/*! \qmlproperty bool PatchManager::notifyOnSuccess + \inheaderfile: patchmanager.h + Whether to show a popup on successful actions +*/ bool PatchManager::notifyOnSuccess() const { return getSettingsSync(QStringLiteral("notifyOnSuccess"), true).toBool(); @@ -259,6 +294,10 @@ void PatchManager::setNotifyOnSuccess(bool notifyOnSuccess) } } +/*! \property PatchManager::bitnessMangle +*/ +/*! \qmlproperty bool PatchManager::bitnessMangle +*/ bool PatchManager::bitnessMangle() const { return getSettingsSync(QStringLiteral("bitnessMangle"), false).toBool(); @@ -276,6 +315,11 @@ PatchManagerModel *PatchManager::installedModel() return m_installedModel; } +/*! + Helper for SectionHeader titles. Looks up translations for \a category. + Returns the translated string. + \sa {https://sailfishos.org/develop/docs/silica/qml-sailfishsilica-sailfish-silica-sectionheader.html/} +*/ QString PatchManager::trCategory(const QString &category) const { const QString section = qApp->translate("Sections", category.toLatin1().constData()); @@ -324,11 +368,16 @@ bool PatchManager::failure() const return m_failed; } +/*! \property PatchManager::loaded +*/ +/*! \qmlproperty bool PatchManager::loaded +*/ bool PatchManager::loaded() const { return m_loaded; } +/*! Calls \a call via D-Bus */ void PatchManager::call(QDBusPendingCallWatcher *call) { connect(call, @@ -359,6 +408,7 @@ void PatchManager::requestListPatches(const QString &patch, bool installed) }); } +/*! Request daemon to apply (activate) the Patch named \a patch */ QDBusPendingCallWatcher* PatchManager::applyPatch(const QString &patch) { qDebug() << Q_FUNC_INFO; @@ -366,6 +416,7 @@ QDBusPendingCallWatcher* PatchManager::applyPatch(const QString &patch) return new QDBusPendingCallWatcher(m_interface->applyPatch(patch), this); } +/*! Request daemon to unapply (deactivate) the Patch named \a patch */ QDBusPendingCallWatcher* PatchManager::unapplyPatch(const QString &patch) { qDebug() << Q_FUNC_INFO; @@ -373,6 +424,11 @@ QDBusPendingCallWatcher* PatchManager::unapplyPatch(const QString &patch) return new QDBusPendingCallWatcher(m_interface->unapplyPatch(patch), this); } +/*! + Request daemon to download and install the Patch named \a patch, version \a version from \a url + + \sa {} {PatchManagerInterface::installPatch(const QString &patch, const QString &version, const QString &url)} +*/ QDBusPendingCallWatcher *PatchManager::installPatch(const QString &patch, const QString &version, const QString &url) { qDebug() << Q_FUNC_INFO; @@ -380,6 +436,7 @@ QDBusPendingCallWatcher *PatchManager::installPatch(const QString &patch, const return new QDBusPendingCallWatcher(m_interface->installPatch(patch, version, url), this); } +/*! Request daemon to uninstall the Patch named \a patch */ QDBusPendingCallWatcher *PatchManager::uninstallPatch(const QString &patch) { qDebug() << Q_FUNC_INFO; @@ -387,6 +444,9 @@ QDBusPendingCallWatcher *PatchManager::uninstallPatch(const QString &patch) return new QDBusPendingCallWatcher(m_interface->uninstallPatch(patch), this); } +/*! Request the daemon to do ... with \a patch + \warning method not investigated, need documentation +*/ QDBusPendingCallWatcher *PatchManager::resetState(const QString &patch) { qDebug() << Q_FUNC_INFO; @@ -394,6 +454,10 @@ QDBusPendingCallWatcher *PatchManager::resetState(const QString &patch) return new QDBusPendingCallWatcher(m_interface->resetState(patch), this); } +/*! + Request daemon to retrieve the \l {Patchmanager Web Catalog}{Web Catalog}. + Can be configred by \a params +*/ QDBusPendingCallWatcher *PatchManager::downloadCatalog(const QVariantMap ¶ms) { qDebug() << Q_FUNC_INFO; @@ -401,6 +465,7 @@ QDBusPendingCallWatcher *PatchManager::downloadCatalog(const QVariantMap ¶ms return new QDBusPendingCallWatcher(m_interface->downloadCatalog(params), this); } +/*! Request daemon to download patch info for patch \a name */ QDBusPendingCallWatcher *PatchManager::downloadPatchInfo(const QString &name) { qDebug() << Q_FUNC_INFO; @@ -408,6 +473,11 @@ QDBusPendingCallWatcher *PatchManager::downloadPatchInfo(const QString &name) return new QDBusPendingCallWatcher(m_interface->downloadPatchInfo(name), this); } +/*! + Request daemon to list patch versions. + + \sa PatchManagerObject::listVersions() +*/ QDBusPendingCallWatcher *PatchManager::listVersions() { qDebug() << Q_FUNC_INFO; @@ -415,6 +485,7 @@ QDBusPendingCallWatcher *PatchManager::listVersions() return new QDBusPendingCallWatcher(m_interface->listVersions(), this); } +/*! Request daemon to unapply (deactivate) all active patches. */ QDBusPendingCallWatcher *PatchManager::unapplyAllPatches() { qDebug() << Q_FUNC_INFO; @@ -422,6 +493,10 @@ QDBusPendingCallWatcher *PatchManager::unapplyAllPatches() return new QDBusPendingCallWatcher(m_interface->unapplyAllPatches(), this); } +/*! Request daemon to initialize itself, and apply patches if \a apply is \e true. + + \sa {PatchManagerObject::loadRequest(bool apply)} +*/ void PatchManager::loadRequest(bool apply) { qDebug() << Q_FUNC_INFO; @@ -429,6 +504,11 @@ void PatchManager::loadRequest(bool apply) m_interface->loadRequest(apply); } +/*! + Request daemon to restart/kill programs affected by changes. + + \sa {} {PatchManagerInterface::restartServices()} +*/ void PatchManager::restartServices() { qDebug() << Q_FUNC_INFO; @@ -436,11 +516,21 @@ void PatchManager::restartServices() m_interface->restartServices(); } +/*! + Returns the name of the patch identified by \a patch. + + \sa {} {PatchManagerModel::patchName(const Qstring &patch)} +*/ QString PatchManager::patchName(const QString &patch) const { return m_installedModel->patchName(patch); } +/*! + Returns the applied state of the patch identified by \a name. + + \sa {} {PatchManagerModel::isApplied(const Qstring &patch)} +*/ bool PatchManager::isApplied(const QString &name) const { return m_installedModel->isApplied(name); @@ -456,6 +546,7 @@ bool PatchManager::isApplied(const QString &name) const // return new QDBusPendingCallWatcher(m_interface->getSettings(name, def), this); //} +/*! Calls \a call via D-Bus, executing \a callback on success, \a errorCallback on error. */ void PatchManager::watchCall(QDBusPendingCallWatcher *call, QJSValue callback, QJSValue errorCallback) { connect(call, @@ -484,16 +575,24 @@ void PatchManager::watchCall(QDBusPendingCallWatcher *call, QJSValue callback, Q }); } +/*! + Installs the translation service for \a patch. + Returns \c true if successful, \c false otherwise. +*/ bool PatchManager::installTranslator(const QString &patch) { return m_translator->installTranslator(patch); } - +/*! + Uninstalls the translation service for \a patch. + Returns \c true if successful, \c false otherwise. +*/ bool PatchManager::removeTranslator(const QString &patch) { return m_translator->removeTranslator(patch); } +/*! Returns the vote count of \a patch from settings. */ int PatchManager::checkVote(const QString &patch) const { qDebug() << Q_FUNC_INFO << patch; @@ -501,6 +600,7 @@ int PatchManager::checkVote(const QString &patch) const return getSettingsSync(QStringLiteral("votes/%1").arg(patch), 0).toInt(); } +/*! Send a vote got \a patch, and record it (\a action) in settings. */ void PatchManager::doVote(const QString &patch, int action) { qDebug() << Q_FUNC_INFO << patch << action; @@ -514,6 +614,7 @@ void PatchManager::doVote(const QString &patch, int action) putSettingsSync(QStringLiteral("votes/%1").arg(patch), action); } +/*! \internal lets not spoil the fun (or the eggs!). */ void PatchManager::checkEaster() { qDebug() << Q_FUNC_INFO; @@ -528,6 +629,7 @@ void PatchManager::checkEaster() }); } +/*! Returns the path or an icon file for patch \a patch, light or dark version depending on \a dark */ QString PatchManager::iconForPatch(const QString &patch, bool dark) const { const QString iconPlaceholder = QStringLiteral("/usr/share/patchmanager/patches/%1/main.%2").arg(patch); @@ -546,6 +648,10 @@ QString PatchManager::iconForPatch(const QString &patch, bool dark) const return QString(); } +/*! + Returns \a filename if it exists, an empty QString otherwise + \warning This is probably dead code, it should be removed. +*/ QString PatchManager::valueIfExists(const QString &filename) const { if (QFile(filename).exists()) { @@ -554,6 +660,7 @@ QString PatchManager::valueIfExists(const QString &filename) const return QString(); } +/*! Request daemon to check for updates. */ void PatchManager::checkForUpdates() { qDebug() << Q_FUNC_INFO; @@ -561,6 +668,13 @@ void PatchManager::checkForUpdates() m_interface->checkForUpdates(); } +/*! + Saves the setting \a name to \a value over DBus. + Returns \c true when done, \c false otherwise. + + \sa {} {PatchManagerObject::putSettings(const QString &name, const QDBusVariant &value)} +*/ + bool PatchManager::putSettingsSync(const QString &name, const QVariant &value) { QDBusPendingReply reply = m_interface->putSettings(name, QDBusVariant(value)); @@ -571,11 +685,21 @@ bool PatchManager::putSettingsSync(const QString &name, const QVariant &value) return false; } +/*! + Saves the setting \a name to \a value over DBus. + + Calls \a callback in success, \a errorCallback on failure. + +*/ void PatchManager::putSettingsAsync(const QString &name, const QVariant &value, QJSValue callback, QJSValue errorCallback) { watchCall(new QDBusPendingCallWatcher(m_interface->putSettings(name, QDBusVariant(value)), this), callback, errorCallback); } +/*! + Returns the setting \a name over DBus. + Defaults to \a def +*/ QVariant PatchManager::getSettingsSync(const QString &name, const QVariant &def) const { QDBusPendingReply reply = m_interface->getSettings(name, QDBusVariant(def)); @@ -586,6 +710,14 @@ QVariant PatchManager::getSettingsSync(const QString &name, const QVariant &def) return QVariant(); } +/*! + Retrieves the setting \a name over DBus. + Defaults to \a def + + Calls \a callback in success, \a errorCallback on failure. + + \sa PatchManagerObject::putSettings(const QString &name, const QDBusVariant &value) +*/ void PatchManager::getSettingsAsync(const QString &name, const QVariant &def, QJSValue callback, QJSValue errorCallback) { watchCall(new QDBusPendingCallWatcher(m_interface->getSettings(name, QDBusVariant(def)), this), callback, errorCallback); @@ -613,6 +745,11 @@ void PatchManager::errorCall(QJSValue errorCallback, const QString &message) errorCallback.call(callbackArguments); } +/*! + Handler for the DBus signal. Sets the internal list to \a updates if different. + + Emits signal /e updatesChanged() +*/ void PatchManager::onUpdatesAvailable(const QVariantMap &updates) { if (m_updates == updates) { @@ -625,6 +762,11 @@ void PatchManager::onUpdatesAvailable(const QVariantMap &updates) emit updatesChanged(); } +/*! + Handler for the DBus signal. Sets the internal list to \a toggle if different. + + Emits signal /e toggleServicesChanged(bool toggle) +*/ void PatchManager::onToggleServicesChanged(bool toggle) { qDebug() << Q_FUNC_INFO << toggle; @@ -637,6 +779,12 @@ void PatchManager::onToggleServicesChanged(bool toggle) emit toggleServicesChanged(m_toggleServices); } + +/*! + Handler for the DBus signal. Sets the internal property to \a failed if different. + + Emits \e failureChanged(bool failed) +*/ void PatchManager::onFailureChanged(bool failed) { qDebug() << Q_FUNC_INFO << failed; @@ -649,6 +797,11 @@ void PatchManager::onFailureChanged(bool failed) emit failureChanged(m_failed); } +/*! + Handler for the DBus signal. Sets the internal list to \a loaded if different. + + Emits \e loadedChanged(bool loaded) +*/ void PatchManager::onLoadedChanged(bool loaded) { qDebug() << Q_FUNC_INFO << loaded; @@ -661,6 +814,7 @@ void PatchManager::onLoadedChanged(bool loaded) emit loadedChanged(m_loaded); } +/*! Calls the \e resolveFailure method on D-Bus */ void PatchManager::resolveFailure() { qDebug() << Q_FUNC_INFO; @@ -668,6 +822,13 @@ void PatchManager::resolveFailure() m_interface->resolveFailure(); } +/*! + Helper to translate a DBus reply object \a val to a valid/usable QVariant + + Recurse up to \a depth (max. 32). + + Returns the result. +*/ QVariant PatchManager::unwind(const QVariant &val, int depth) { /* Limit recursion depth to protect against type conversions @@ -799,12 +960,18 @@ QVariant PatchManager::unwind(const QVariant &val, int depth) return res; } +/*! \class PatchManagerTranslator + \inheaderfile patchmanager.h + \inmodule org.SfietKonstantin.patchmanager + \brief allows patches to include localizations in their shipped QML files. +*/ PatchManagerTranslator::PatchManagerTranslator(QObject *parent) : QObject(parent) { } +/*! Returns a (singleton) instance of \e PatchManagerTranslator, if necessary contructing it using \a parent. */ PatchManagerTranslator *PatchManagerTranslator::GetInstance(QObject *parent) { static PatchManagerTranslator* tsSingleton = nullptr; @@ -814,6 +981,10 @@ PatchManagerTranslator *PatchManagerTranslator::GetInstance(QObject *parent) return tsSingleton; } +/*! Install the translation service for \a patch + + Returns \c true if successful, \c false otherwise. +*/ bool PatchManagerTranslator::installTranslator(const QString &patch) { qDebug() << Q_FUNC_INFO << patch << QLocale::system(); @@ -842,11 +1013,35 @@ bool PatchManagerTranslator::installTranslator(const QString &patch) return true; } +/*! void PatchManager::activation(const QString & patch, const QString & version); + \warning probably dead code, need to investigate + probably \internal, using \a patch and \a version +*/ +void PatchManager::activation(const QString & patch, const QString & version) +{ +} + +/*! void PatchManager::easterReceived(const QString & easterText); + \warning probably dead code, need to investigate + probably \internal, using \a easterText +*/ +void PatchManager::easterReceived(const QString & easterText); +{ +} + +/*! + Returns \e true if \a filename exists, \e false otherwise. + \sa https://doc.qt.io/qt-5/qfile.html#exists-1 +*/ bool PatchManager::fileExists(const QString &filename) { return QFile::exists(filename); } +/*! + Remove the translation service for \a patch + Returns \c true if successful, \c false otherwise. +*/ bool PatchManagerTranslator::removeTranslator(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; diff --git a/src/qml/patchmanagermodel.cpp b/src/qml/patchmanagermodel.cpp index 8e2f4c5ee..d8e60570f 100644 --- a/src/qml/patchmanagermodel.cpp +++ b/src/qml/patchmanagermodel.cpp @@ -37,11 +37,39 @@ #include #include +/*! \qmltype PatchManagerModel + \instantiates PatchManagerModel + \inqmlmodule org.SfietKonstantin.patchmanager + \brief A ListModel containing the metadata of Patches. +*/ PatchManagerModel::PatchManagerModel(QObject *parent) : QAbstractListModel(parent) { } +/*! \class PatchManagerModel + \inheaderfile patchmanagermodel.h + \inmodule org.SfietKonstantin.patchmanager + \brief A ListModel containing the metadata of Patches. + + Currently it defines the following Roles: + \table + \header + \li Qt Role + \li QML Role Name + \row + \li \l{https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum}{Qt::DisplayRole} + \li name + \row + \li \l{https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum}{Qt::DecorationRole} + \li section + \row + \li \l{https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum}{Qt::EditRole} + \li patchObject + \endtable + + \sa {https://doc.qt.io/qt-5/qabstractitemmodel.html}{Qt::QAbstractItemModel} +*/ PatchManagerModel::PatchManagerModel(const QList &data, QObject *parent) : QAbstractListModel(parent) , m_modelData(data) @@ -78,11 +106,16 @@ QHash PatchManagerModel::roleNames() const return r; } +// /*! \qmlproperty var PatchManagerModel::patches +// Contains the list of patches +// */ +/*! Returns the list of patches */ QList PatchManagerModel::patches() const { return m_modelData; } +/*! clears the model data and sets \a patches as new model data. */ void PatchManagerModel::setPatches(const QList &patches) { qDebug() << Q_FUNC_INFO << patches.length(); @@ -102,6 +135,22 @@ void PatchManagerModel::setPatches(const QList &patches) endResetModel(); } +/*! + Does nothing if both \a data and \a patch are empty. + + If \a patch is empty, and we have \a data, extracts the patch name from \a + data and adds the metadata to the model. + + If we have \a patch and \a data, and \a installed is \c true, add the data + to the model. + + If we have \a patch and \a data, and \a installed is \c false, just create + a PatchObject from \a data. + + \warning that last part is not yet documented. + + \sa saveLayout() +*/ void PatchManagerModel::populateData(const QVariantList &data, const QString &patch, bool installed) { qDebug() << Q_FUNC_INFO << data.length(); @@ -190,6 +239,7 @@ void PatchManagerModel::populateData(const QVariantList &data, const QString &pa saveLayout(); } +/*! removes the patch with the name \a patch from the model. */ void PatchManagerModel::removePatch(const QString &patch) { qDebug() << Q_FUNC_INFO << patch; @@ -210,6 +260,7 @@ void PatchManagerModel::removePatch(const QString &patch) saveLayout(); } +/*! Moves an entry from index \a from to index \a to */ void PatchManagerModel::move(int from, int to) { if (from == to) { @@ -220,6 +271,7 @@ void PatchManagerModel::move(int from, int to) endMoveRows(); } +/*! Saves the current order of patch names to Settings. */ void PatchManagerModel::saveLayout() { QStringList patches; @@ -230,6 +282,7 @@ void PatchManagerModel::saveLayout() PatchManager::GetInstance()->putSettingsAsync(QStringLiteral("order"), patches); } +/*! Returns the \e display_name of \a patch. */ QString PatchManagerModel::patchName(const QString &patch) const { if (!m_patchMap.contains(patch)) { @@ -239,6 +292,7 @@ QString PatchManagerModel::patchName(const QString &patch) const return m_patchMap[patch]->details()->value(QStringLiteral("display_name")).toString(); } +/*! Returns /c true if patch \a name is in the list of applied (activated) patches. */ bool PatchManagerModel::isApplied(const QString &name) const { // FIXME: there certainly is a more efficient way, e.g. std::find_if? diff --git a/src/qml/plugin.cpp b/src/qml/plugin.cpp index 9ca2b4e8b..327ed7d0d 100644 --- a/src/qml/plugin.cpp +++ b/src/qml/plugin.cpp @@ -54,6 +54,92 @@ static QObject *patchmanagertransalator_singleton(QQmlEngine *engine, QJSEngine return PatchManagerTranslator::GetInstance(engine); } +/***** PatchManager ******/ +/*! \qmltype PatchManager + \instantiates PatchManager + \inqmlmodule org.SfietKonstantin.patchmanager + + \brief Singleton providing access to the \l [C++]{PatchManager} methods and properties. +*/ +/*! \qmlproperty string PatchManager::appsToRestart + List of applications and services that should be restarted after a patch has been activated. + \sa PatchManager::toggleServicesList +*/ +/*! \qmlproperty bool PatchManager::developerMode + \deprecated + \sa {Patchmanager Configuration Files}, inifile +*/ +/*! \qmlproperty bool PatchManager::failure + If \c true, PM is in Failure Mode. +*/ +/*! \qmlproperty string PatchManager::osVersion + This property holds the Operating System version. This is used for version checking. +*/ +/*! \qmlproperty bool PatchManager::patchDevelMode + \sa {Patchmanager Configuration Files}, inifile +*/ +/*! \qmlproperty string PatchManager::patchmanagerVersion + This property holds our own version +*/ +/*! \qmlproperty string PatchManager::serverMediaUrl + This poperty holds the URL to download screenshots from. +*/ +/*! \qmlproperty int PatchManager::sfosVersionCheck + This property keeps the setting of VersionCheck + \sa PatchManagerVersionCheck::CheckMode +*/ +/*! \qmlproperty var PatchManager::updates + Map of internal names and metadata of patches which can be updated. + \sa PatchManagerObject::getUpdates, dbus-sys +*/ +/*! \qmlproperty var PatchManager::updatesNames + List of display names of patches which can be updated. + \sa PatchManagerObject::getUpdates, dbus-sys +*/ + +/***** PatchManagerTranslator ******/ +/*! \qmltype PatchManagerTranslator + \instantiates PatchManagerTranslator + \inqmlmodule org.SfietKonstantin.patchmanager + + \brief This Singleton allows patches to include localizations in their shipped QML files. + + To use, add: + + \code + import org.SfietKonstantin.patchmanager 2.0 + \endcode + + In the root item add: + + \code + property bool pmTranslationLoaded: PatchManagerTranslator ? PatchManagerTranslator.installTranslator("my-patch-name") : false} + \endcode + + In the first visible text item replace text with following: + + \code + pmTranslationLoaded ? qsTr("Translated text") : "Please update patchmanager!" + \endcode + + If translation is not loaded try using qsTranslate() strings instead: + + \code + pmTranslationLoaded ? qsTranslate("pm", "Translated text") : "Please update patchmanager!" + \endcode + +*/ +/*! \qmlproperty bool PatchManagerTranslator::pmTranslationLoaded + \c true when the PatchManagerTranslator has been loaded/initialized successfully. +*/ + +/***** WebPatchesModel ******/ +/*! \qmltype WebPatchesModel + \instantiates WebPatchesModel + \inqmlmodule org.SfietKonstantin.patchmanager + + \brief Holds elements from the \l {Patchmanager Web Catalog}{Web Catalog}. +*/ class PatchManagerPlugin : public QQmlExtensionPlugin { Q_OBJECT diff --git a/src/qml/qmlplugin.qdoc b/src/qml/qmlplugin.qdoc new file mode 100644 index 000000000..155d9edfa --- /dev/null +++ b/src/qml/qmlplugin.qdoc @@ -0,0 +1,44 @@ +/*! + \page index.html + \title Patchmanager Documentation: QML Plugin + \indexpage Patchmanager Documentation + + \section1 Overview + + The QML Plugin makes up the main Patchmanager GUI Application. + + Jolla Settings loads PatchManagerPage from the QML Plugin. + + It is a both a front-end to the Daemon and a Patch management application. + + It allows to: + + \list + \li View the list of installed and activated Patches. + \li Configure the Application and Daemon settings. + \li Activate or deactivate Patches + \li Install Patches from the Web Catalog + \li Uninstall installed Patches + \endlist + + \section2 QML Components: + + \generatelist{overviews} + + \section2 QML types: + + \generatelist{qmltypesbymodule org.SfietKonstantin.patchmanager} + + \section2 C++ classes: + + \generatelist{classesbymodule org.SfietKonstantin.patchmanager} + +*/ + +/*! + \group qml-plugin-components + \title QML Components + + \brief Pages and other components shipped with the QML Plugin + +*/ diff --git a/src/qml/webdownloader.cpp b/src/qml/webdownloader.cpp index 55a221b43..6fe45fcab 100644 --- a/src/qml/webdownloader.cpp +++ b/src/qml/webdownloader.cpp @@ -33,12 +33,34 @@ #include "webdownloader.h" #include "webcatalog.h" +/*! \qmltype WebDownloader + \instantiates WebDownloader + \inqmlmodule org.SfietKonstantin.patchmanager + \brief Downloads Patch archives from the \l {Patchmanager Web Catalog}{Web Catalog} +*/ +/*! \qmlsignal WebDownloader::downloadFinished(const QString & patch, const QString & fileName) + This signal is emitted when \a patch has finished downloading, resulting in \a fileName. +*/ +/*! \qmlsignal WebDownloader::downloadError() + This signal is emitted when a download failed. +*/ +/*! \class WebDownloader + \inmodule org.SfietKonstantin.patchmanager + \brief Downloads Patch archives from the \l {Patchmanager Web Catalog}{Web Catalog} +*/ +/*! \fn void WebDownloader::downloadError() + This signal is emitted when a download failed. +*/ +/*! \fn void WebDownloader::downloadFinished(const QString &patch, const QString &fileName) + This signal is emitted when \a patch has finished downloading, resulting in \a fileName. +*/ WebDownloader::WebDownloader(QObject *parent) : QObject(parent) { _nam = new QNetworkAccessManager(this); _file = new QFile(this); } +/*! starts the download, using compile-time variable \e MEDIA_URL as source. */ void WebDownloader::start() { _file->setFileName(destination); diff --git a/src/qml/webpatchesmodel.cpp b/src/qml/webpatchesmodel.cpp index 8db426273..270ad2613 100644 --- a/src/qml/webpatchesmodel.cpp +++ b/src/qml/webpatchesmodel.cpp @@ -38,6 +38,48 @@ #include +/*! \class WebPatchesModel + \inmodule org.SfietKonstantin.patchmanager + \brief The WebPatchesModel holds elements from the \l {Patchmanager Web Catalog}{Web Catalog}. + + Roles follow the Patch JSON format, hence the following are defined: + + \table + \header + \li Qt Role + \li QML Role Name + \row + \li {Qt::UserRole} + \li description + \row + \li {Qt::UserRole} + \li last_updated + \row + \li {Qt::UserRole} + \li name + \row + \li {Qt::UserRole} + \li display_name + \row + \li {Qt::UserRole} + \li category + \row + \li {Qt::UserRole} + \li author + \row + \li {Qt::UserRole} + \li rating + \row + \li {Qt::UserRole} + \li total_activations + \endtable + + \sa {https://doc.qt.io/qt-5/qabstractitemmodel.html}{Qt::QAbstractItemModel} +*/ +/*! \fn virtual QHash WebPatchesModel::roleNames() const + Returns the model's role names. + +*/ WebPatchesModel::WebPatchesModel(QObject * parent) : QAbstractListModel(parent) { @@ -63,6 +105,27 @@ WebPatchesModel::~WebPatchesModel() { } +/*! \property WebPatchesModel::sorted + \c true when the model is sorted +*/ +/*! \qmlproperty bool WebPatchesModel::sorted + \c true when the model is sorted + + The corresponding signal is sortedChanged +*/ +/*! \qmlsignal WebPatchesModel::sortedChanged() + Emitted when \c sorted changed +*/ + +/*! \property WebPatchesModel::queryParams + Query parameters +*/ +/*! \qmlproperty var WebPatchesModel::queryParams + Query parameters +*/ +/*! \qmlsignal WebPatchesModel::queryParamsChanged() + Emitted when query parameters change +*/ QVariantMap WebPatchesModel::queryParams() const { return _queryParams; @@ -89,6 +152,7 @@ void WebPatchesModel::setSorted(const bool & sorted) { } } +/*! \warning need to investigate what this is for... */ void WebPatchesModel::classBegin() { @@ -104,6 +168,12 @@ bool compareStrings(const QString &a, const QString &b) return a.compare(b, Qt::CaseInsensitive) < 0; } +/*! + Retrieves the list of patches from the \l {Patchmanager Web Catalog}{Web Catalog}, and populates the model + with its contents, sorting them if necessary. + + \sa setSorted() +*/ void WebPatchesModel::componentComplete() { if (_modelData.size() > 0) { @@ -158,12 +228,14 @@ void WebPatchesModel::componentComplete() }); } +/*! Returns the row count. \note \a parent is unused */ int WebPatchesModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); return _modelData.count(); } +/*! Returns the data stored under the given \a role for the item referred to by the \a index. */ QVariant WebPatchesModel::data(const QModelIndex & index, int role) const { int row = index.row(); @@ -171,3 +243,4 @@ QVariant WebPatchesModel::data(const QModelIndex & index, int role) const return QVariant(); return _modelData[index.row()].toMap()[_roles[role]]; } +