diff options
author | Sergio Ahumada <sergio.ahumada@nokia.com> | 2012-01-31 18:52:45 +0100 |
---|---|---|
committer | Sergio Ahumada <sergio.ahumada@nokia.com> | 2012-01-31 18:52:45 +0100 |
commit | 0fc5c9bc9f113d8783e2b44b6686b5b5a1cba7c7 (patch) | |
tree | c859b20fc2770548d60ec7137a180fae70678dec |
Long live Qt Process Manager!
113 files changed, 10721 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485265a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*~ +*.o +*.a +*# +moc_* +*.dylib +doc/html +.DS_Store +Makefile +TAGS +include/qtprocessmanager/*.h diff --git a/config.pri b/config.pri new file mode 100644 index 0000000..cae9c04 --- /dev/null +++ b/config.pri @@ -0,0 +1,5 @@ +isEmpty(INSTALLBASE): INSTALLBASE = $$(INSTALLBASE) +isEmpty(INSTALLBASE) { + INSTALLBASE=/usr + message("No INSTALLBASE specified, defaulting to $$INSTALLBASE") +} diff --git a/doc/doc.pri b/doc/doc.pri new file mode 100644 index 0000000..5d18bc3 --- /dev/null +++ b/doc/doc.pri @@ -0,0 +1,9 @@ +OTHER_FILES += \ + $$PWD/processmanager.qdocconf + +docs_target.target = docs +docs_target.commands = qdoc3 $$PWD/processmanager.qdocconf + +QMAKE_EXTRA_TARGETS = docs_target +QMAKE_CLEAN += \ + "-r $$PWD/html" diff --git a/doc/images/process_structs.png b/doc/images/process_structs.png Binary files differnew file mode 100644 index 0000000..e3b112d --- /dev/null +++ b/doc/images/process_structs.png diff --git a/doc/images/processbackend_hierarchy.png b/doc/images/processbackend_hierarchy.png Binary files differnew file mode 100644 index 0000000..be8334f --- /dev/null +++ b/doc/images/processbackend_hierarchy.png diff --git a/doc/images/processbackendfactory_hierarchy.png b/doc/images/processbackendfactory_hierarchy.png Binary files differnew file mode 100644 index 0000000..2b106ec --- /dev/null +++ b/doc/images/processbackendfactory_hierarchy.png diff --git a/doc/processmanager.qdocconf b/doc/processmanager.qdocconf new file mode 100644 index 0000000..396c6d9 --- /dev/null +++ b/doc/processmanager.qdocconf @@ -0,0 +1,78 @@ +# Name of the project. +project = ProcessManager + +# Directories in which to search for files to document. +# Paths are relative to the location of this file. +exampledirs += ../examples +headerdirs += ./src ../src/core ../src/declarative ../src/launcher +imagedirs += images +sourcedirs += ./src ../src/core ../src/declarative ../src/launcher + +Cpp.ignoretokens = \ + QT_BEGIN_HEADER \ + QT_END_HEADER \ + Q_INVOKABLE \ + Q_ENUMS \ + QT_BEGIN_NAMESPACE_PROCESSMANAGER \ + QT_END_NAMESPACE_PROCESSMANAGER \ + Q_ADDON_PROCESSMANAGER_EXPORT + +# The following parameters are for creating a qhp file, the qhelpgenerator +# program can convert the qhp file into a qch file which can be opened in +# Qt Assistant and/or Qt Creator. + +# Defines the name of the project. You cannot use operators (+, =, -) in +# the name. Properties for this project are set using a qhp.<projectname>.property +# format. +qhp.projects = ProcessManager + +# Sets the name of the output qhp file. +qhp.ProcessManager.file = ProcessManager.qhp + +# Namespace for the output file. This namespace is used to distinguish between +# different documentation files in Creator/Assistant. The namespace ends with +# a version being a number containing a major, minor and revision element. +# E.g. version 1.0 becomes 100. +qhp.ProcessManager.namespace = com.nokia.processmanager.100 + +# Title for the package, will be the main title for the package in +# Assistant/Creator. +qhp.ProcessManager.indexTitle = Process Manager Reference Documentation + +# Extra files to add to the output which are not linked to from anywhere +# using a qdoc \l command. +qhp.ProcessManager.extraFiles = style/style.css \ + index.html + +# Only update the name of the project for the next variables. +qhp.ProcessManager.virtualFolder = qdoc +qhp.ProcessManager.subprojects = classes +qhp.ProcessManager.subprojects.classes.title = Classes +qhp.ProcessManager.subprojects.classes.selectors = class fake:headerfile +qhp.ProcessManager.subprojects.classes.sortPages = true + + +# Do NOT change the variables after this line unless you know what you are doing. + +outputdir = html +outputformats = HTML + +examples.fileextensions = "*.cpp *.h *.js *.svg *.xml *.ui *.qml" +examples.imageextensions = "*.png *.jpeg *.jpg *.gif *.mng" +headers.fileextensions = "*.h *.ch *.h++ *.hh *.hpp *.hxx" +sources.fileextensions = "*.cpp *.qdoc *.mm *.qml" + +HTML.nobreadcrumbs = "true" + +HTML.templatedir = . +HTML.stylesheets = style/style.css + +HTML.headerstyles = " <link rel=\"stylesheet\" type=\"text/css\" href=\"style/style.css\" />\n" +HTML.endheader = "</head>\n" + +HTML.postheader = \ + " <div class=\"header\">\n" \ + " <div id=\"nav-logo\">\n" \ + " <a href=\"index.html\">Process Manager Reference</a>" \ + " </div>\n" \ + " </div>\n" diff --git a/doc/src/basicpm.qdoc b/doc/src/basicpm.qdoc new file mode 100644 index 0000000..45238c2 --- /dev/null +++ b/doc/src/basicpm.qdoc @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + +\page basicpm.html +\previouspage Understanding ProcessInfo +\contentspage {Backend Process Manager} {Contents} +\nextpage Standard Process Manager + +\title Backend Process Manager + +The basic C++ API for controlling processes uses the ProcessBackendManager. +The ProcessBackendManager contains an ordered list of ProcessBackendFactory objects, +which are used to convert ProcessInfo objects into executable processes. + +The ProcessBackendManager can be used as follows: + +\code + ProcessBackendManager *manager = new ProcessBackendManager; + manager->add(new UnixProcessBackendFactory); + + ProcessInfo info; + info.setName("lstest"); + info.setProgram("/bin/ls"); + info.setWorkingDirectory("/root"); + + ProcessBackend *backend = manager->create(info); + if (backend) { + connect(backend, SIGNAL(started()), this, SLOT(processStarted())); + connect(backend, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(processFinished(int, QProcess::ExitStatus))); + backend->start(); + } +\endcode + +The first step of initializing the ProcessBackendManager is to assign +ProcessBackendFactory objects. The order of the factories is important. +The ProcessBackendManager::create() function asks each factory in turn +if it can handle the ProcessInfo object. The first factory to match +is the one that creates the ProcessBackend object. +The backend object behaves similarly to a QProcess object; one normally +attaches a few signal handlers and then starts the backend running. +Please note the it is the user's responsibility to correctly delete +the backend object. + +In the following example, we've added a GdbProcessBackendFactory. +The GdbProcessBackendFactory will match any +that has a "gdb" property set to "true" in the ProcessInfo record. When +it matches, it rewrites the ProcessInfo record to execute the gdb +program with the appropriate command line arguments. + +\code + ProcessBackendManager *manager = new ProcessBackendManager; + manager->add(new GdbProcessBackendFactory); + manager->add(new UnixProcessBackendFactory); + + ProcessInfo info; + info.setName("lstest"); + info.setProgram("/bin/ls"); + info.setWorkingDirectory("/root"); + info.setValue("gdb", "true"); + + ProcessBackend *backend = manager->create(info); + if (backend) { + connect(backend, SIGNAL(started()), this, SLOT(processStarted())); + connect(backend, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(processFinished(int, QProcess::ExitStatus))); + backend->start(); + } +\endcode + +\section1 Inheritence Hierarchy + +\image processbackend_hierarchy.png {Process Backend Hierarchy} +\caption \i{Process Backend Hierarchy} + +The virtual ProcessBackend object hierarchy is divided into two +sections: the UnixProcessBackend objects contain a QProcess internally +and the RemoteProcessBackend objects communicate with a separate process +"launcher" program. It may sound odd that a process manager would not +launch its own processes, but this mechanism allows the process +manager to not run with setuid privileges. + +\image processbackendfactory_hierarchy.png +\caption \i{Process Backend Factory Hierarchy} + +The ProcessBackendFactory hierarchy closely matches the ProcessBackend +hierarchy. The standard and prelaunch subclasses create standard and +prelaunch process backend objects. The remote subclass is divided +by how it connects to the remote "launcher" program; either over a +pipe connection or a socket connection. + +*/ diff --git a/doc/src/index.qdoc b/doc/src/index.qdoc new file mode 100644 index 0000000..e1f20e1 --- /dev/null +++ b/doc/src/index.qdoc @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\title Process Manager Reference +\page index.html + +The Process Manager is used to start and stop +a collection of child processes. It includes monitoring tools to +measure process memory and CPU use. It +has hooks to create Pipe applications which can be used to +accelerate the launching of certain types of processes. It exposes a +QML-appropriate API so that process manager logic can be controlled +from QML. + +The process manager API has three layers: a basic C++ API for generic +process management, an enhanced C++ API that allows process objects +to be subclassed, and a QML API. + +Table of contents: + +\list +\o \l {Introduction} +\o \l {Understanding ProcessInfo} +\o \l {Backend Process Manager} +\o \l {Standard Process Manager} +\o \l {Declarative Process Manager} +\endlist + + +\section1 C++ Classes + +\generatelist annotatedclasses + +\section1 QML Elements + +\generatelist qmlclasses +*/ diff --git a/doc/src/intro.qdoc b/doc/src/intro.qdoc new file mode 100644 index 0000000..65b5132 --- /dev/null +++ b/doc/src/intro.qdoc @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + +\page intro.html +\contentspage {Introduction} {Contents} +\nextpage Understanding ProcessInfo + +\title Introduction + + +\section1 Basic Concepts + +There are four basic types of objects to work with in the process manager. + +\list + \o ProcessInfo objects specify what program to run, environment variables + to pass to the program, working directory, and permissions. + \o ProcessBackendFactory objects convert ProcessInfo objects into executing + processes. + \o ProcessFrontend / ProcessBackend objects wrap executing processes. They + bear a strong resemblence to QProcess objects, but in fact may not + contain a QProcess. + \o ProcessManager / ProcessBackendManager are singleton objects that + hold a list of factories and provide convenience interfaces for interacting + with ProcessFrontend/ProcessBackend objects. +\endlist + +*/ diff --git a/doc/src/namespace.qdoc b/doc/src/namespace.qdoc new file mode 100644 index 0000000..883df6d --- /dev/null +++ b/doc/src/namespace.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \macro QT_USE_NAMESPACE_PROCESSMANAGER + \inmodule ProcessManager + + This macro expands to using processmanager namespace and makes processmanager namespace + visible to C++ source code. + + \code + #include <processmanager-global.h> + QT_USE_NAMESPACE_PROCESSMANAGER + \endcode + + To declare the class without including the declaration of the class: + + \code + #include <processmanager-global.h> + QT_BEGIN_NAMESPACE_PROCESSMANAGER + class ProcessManager; + QT_END_NAMESPACE_PROCESSMANAGER + QT_USE_NAMESPACE_PROCESSMANAGER + \endcode +*/ + +/*! + \macro QT_BEGIN_NAMESPACE_PROCESSMANAGER + \inmodule ProcessManager + + This macro begins a ProcessManager namespace. All forward declarations of ProcessManager classes need to + be wrapped in \c QT_BEGIN_NAMESPACE_PROCESSMANAGER and \c QT_END_NAMESPACE_PROCESSMANAGER. + + \sa QT_USE_NAMESPACE_PROCESSMANAGER, QT_END_NAMESPACE_PROCESSMANAGER +*/ + +/*! + \macro QT_END_NAMESPACE_PROCESSMANAGER + \inmodule ProcessManager + + This macro ends a ProcessManager namespace. All forward declarations of ProcessManager classes need to + be wrapped in \c QT_BEGIN_NAMESPACE_PROCESSMANAGER and \c QT_END_NAMESPACE_PROCESSMANAGER. + + \sa QT_USE_NAMESPACE_PROCESSMANAGER, QT_BEGIN_NAMESPACE_PROCESSMANAGER +*/ diff --git a/doc/src/processinfo.qdoc b/doc/src/processinfo.qdoc new file mode 100644 index 0000000..b501173 --- /dev/null +++ b/doc/src/processinfo.qdoc @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + +\page understandprocessinfo.html +\previouspage Introduction +\contentspage {Understanding ProcessInfo} {Contents} +\nextpage Backend Process Manager + +\title Understanding ProcessInfo + +An individual Unix process has a large number of properties that can be +set upon process creation. The ProcessInfo object is a ligthweight wrapper +for a QVariantMap containing the more common properties including: + +\list + \o Common name assigned to the object. This name is used when creating + a process identifier. + \o Application to execute + \o Arguments to pass on the command line + \o Working directory + \o User ID (UID) and Group ID (GID) + \o Environment variables + \o Linux-specific \l {http://linux.die.net/man/7/capabilities} {capabilities} + \o Linux-specific \l + {http://www.kernel.org/doc/Documentation/cgroups/cgroups.txt} + {cgroups} + \o The priority level of the process (agents run at lower priority levels) + \o The OOM killer adjustment score (ranges from -1000 to +1000) + \l {http://www.kernel.org/doc/Documentation/filesystems/proc.txt} {[/proc/<pid>/oom_score_adj]} + \o Output text patterns to match to determing if the process has finished initializing. + \o Other machine-specific ways of limiting process + memory/CPU/resource use. +\endlist + +The ProcessInfo object provides accessor functions for accessing the +more common properties along with named string constants. Because it is +based on a QVariantMap, additional properties can be easily added to the +structure. From C++ code, a ProcessInfo object is typically created as follows: + +\code + ProcessInfo info; + info.setName("myprocess"); + info.setProgram("/bin/ls"); + info.setWorkingDirectory("/root"); +\endcode + +*/ diff --git a/doc/src/qmlpm.qdoc b/doc/src/qmlpm.qdoc new file mode 100644 index 0000000..dff3e78 --- /dev/null +++ b/doc/src/qmlpm.qdoc @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + +\page qmlpm.html +\previouspage Standard Process Manager +\contentspage {Declarative Process Manager} {Contents} + +\title Declarative Process Manager + +The declarative process manager is a ProcessManager object with +appropriate declarations so that it can be used from QML. Sample +use: + +\qml + import QtQuick 2.0 + import ProcessManager 1.0 + + DeclarativeProcessManager { + id: myManager + + factories: [ + GdbProcessBackendFactory, + StandardProcessBackendFactory + ] + + onProcessStarted: console.log("Process "+identifier+" started"); + onProcessFinished: console.log("Process "+identifier+" finished"); + + function make(info) { + var process = create(info); + return process.identifier; + } + + function start(id) { + var process = processForIdentifier(id); + process.start(); + } + + function stop(id) { + var process = processForIdentifier(id); + process.stop(); + } + } +\endqml + +*/ diff --git a/doc/src/standardpm.qdoc b/doc/src/standardpm.qdoc new file mode 100644 index 0000000..fef2b46 --- /dev/null +++ b/doc/src/standardpm.qdoc @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of ProcessManager +** +** $QT_BEGIN_LICENSE:FDL$ +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms +** and conditions contained in a signed written agreement between you +** and Nokia. +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + +\page standardpm.html +\previouspage Backend Process Manager +\contentspage {Standard Process Manager} {Contents} +\nextpage Declarative Process Manager + +\title Standard Process Manager + +The standard ProcessManager object wraps the ProcessBackendManager +object, creates ProcessFrontend objects to wrap each ProcessBackend +object, and assigns unique string identifiers to each process. +This approach provides several advantages. First, the ProcessManager can +maintain a list of running processes indexed by identifier. Second, the +ProcessFrontend objects can be subclassed. This provides a convenient +way of adding convenience functions to the ProcessFrontend objects. Third, +instead of connecting to signals from each process, one can subclass +the ProcessManager itself and override the handler functions. + +The unique identifier assigned to each process created takes the form +of "NAME-NUMBER", where NAME comes from the ProcessInfo.name attribute +and NUMBER is a unique integer assigned at process creation. + +\section2 Directly using the ProcessManager + +You can directly use the ProcessManager in your code. +\code +void MyClass::setup() +{ + ProcessManager *manager = new ProcessManager; + manager->addFactory(new UnixProcessBackendFactory); + m_manager = manager; +} + +void MyClass::start(ProcessInfo info) +{ + ProcessFrontend *frontend = m_manager->start(info); + connect(frontend, SIGNAL(started()), SLOT(started())); + connect(frontend, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(finished(int, QProcess::ExitStatus))); +} + +void MyClass::started() +{ + ProcessFrontend *frontend = qobject_cast<ProcessFrontend *>(sender()); + if (frontend) + qDebug() << "Process" << identifier << "started with pid=" << frontend->pid(); +} + +void MyClass::finished(int exitCode, QProcess::ExitStatus exitStatus) +{ + ProcessFrontend *frontend = qobject_cast<ProcessFrontend *>(sender()); + if (frontend) + qDebug() << "Process" << identifier << "stopped with" << exitCode << exitStatus; +} +\endcode + + +\section2 Subclassing ProcessManager + +You can subclass the process manager to gain more control. This +avoids connecting signals to every process you create. + +\code +class Example : public ProcessManager +{ + Q_OBJECT +public: + Example(QObject *parent=0); + +protected slots: + void processFrontendStarted(); + void processFrontendFinished(int, QProcess::ExitStatus); +}; + +void Example::Example(QObject *parent) + : ProcessManager(parent) +{ + addFactory(new UnixProcessBackendFactory); +} + +void Example::processFrontendStarted() +{ + ProcessManager::processFrontendStarted(); + ProcessFrontend *frontend = qobject_cast<ProcessFrontend *>(sender()); + qDebug() << "Process" << frontend->identifier() << "has started"; +} + +void Example::processFrontendFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + ProcessManager::processFrontendFinished(exitCode, exitStatus); + ProcessFrontend *frontend = qobject_cast<ProcessFrontend *>(sender()); + qDebug() << "Process" << frontend->identifier() << "has finished" << exitCode << exitStatus; +} + +\endcode + +\section2 Subclass ProcessFrontend objects + +To subclass ProcessFrontend objects, override the \l +{ProcessManager::} {createFrontend()} function and return an object +that derived from ProcessFrontend. For example: + +\code + +class MyFrontend : public ProcessFrontend { + Q_OBJECT +public: + MyFrontend(ProcessBackend *backend) : ProcessFrontend(backend) {} +protected: + void handleStateChanged(QProcess::ProcessState); +signals: + void stopped(); +}; + +void MyFrontend::handleStateChanged(QProcess::ProcessState state) +{ + if (state == QProcess::NotRunning) + emit stopped(); +} + +class MyManager : public ProcessManager +{ + Q_OBJECT +public: + MyManager(QObject *parent=0) : ProcessManager(parent) {} +protected : + ProcessFrontend *createFrontend(ProcessBackend *backend) { + return new MyFrontend(backend); + } +}; + +\endcode + +In the above example, the custom \c MyFrontend class raises a new \c +stopped() signal when the child process stops running. The \c +MyManager class reimplements \l {ProcessManager::} {createFrontend()} +to return a \c MyFrontend objects for each created process. + +*/ diff --git a/doc/style/style.css b/doc/style/style.css new file mode 100644 index 0000000..24b0e0e --- /dev/null +++ b/doc/style/style.css @@ -0,0 +1,146 @@ +a:link, a:visited { + color: #00732F; + text-decoration: none; + font-weight: bold; +} + +body { + font: normal 400 14px/1.2 Arial; +# margin-top: 85px; +} + +h1 { + margin: 0; +} + +h2 { + font: 500 20px/1.2 Arial; +} + +h3.fn, span.fn { + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; + border-radius: 7px 7px 7px 7px; + background-color: #F6F6F6; + border-width: 1px; + border-style: solid; + border-color: #E6E6E6; + word-spacing: 3px; + padding: 3px 5px; +} + +table, pre { + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; + border-radius: 7px 7px 7px 7px; + background-color: #F6F6F6; + border: 1px solid #E6E6E6; + border-collapse: separate; + font-size: 12px; + line-height: 1.2; + margin-bottom: 25px; + margin-left: 15px; +} + +table td { + padding: 3px 15px 3px 20px; +} + +table tr.even { + background-color: white; + color: #66666E; +} + +table tr.odd { + background-color: #F6F6F6; + color: #66666E; +} + +li { + margin-bottom: 10px; + padding-left: 12px; +} + +.cpp { + display: block; + margin: 10; + overflow: hidden; + overflow-x: hidden; + overflow-y: hidden; + padding: 20px 0 20px 0; +} + +.footer { + margin-top: 50px; +} + +.memItemLeft { + padding-right: 3px; +} + +.memItemRight { + padding: 3px 15px 3px 0; +} + +.qml { + display: block; + margin: 10; + overflow: hidden; + overflow-x: hidden; + overflow-y: hidden; + padding: 20px 0 20px 0; +} + +.qmldefault { + padding-left: 5px; + float: right; + color: red; +} + +.qmlreadonly { + padding-left: 5px; + float: right; + color: #254117; +} + +.rightAlign { + padding: 3px 5px 3px 10px; + text-align: right; +} + +.header { + background-color: #F6F6F6; + border: 1px solid #DDD; + padding: 5px 5px; + margin: 5px 5px; + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; +} + +.title { + background-color: white; + color: #44A51C; + font-family: Verdana; + font-size: 35px; + font-weight: normal; + left: 0; + padding-bottom: 5px; + padding-left: 16px; + padding-top: 5px; +# position: absolute; + right: 0; + top: 0; +} + +.toc { + float: right; + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; + border-radius: 7px 7px 7px 7px; + background-color: #F6F6F6; + border: 1px solid #DDD; + margin: 0 20px 10px 10px; + padding: 20px 15px 20px 20px; + height: auto; + width: 200px; +} diff --git a/include/qtprocessmanager/syncheaders.sh b/include/qtprocessmanager/syncheaders.sh new file mode 100755 index 0000000..a76cde8 --- /dev/null +++ b/include/qtprocessmanager/syncheaders.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +DIR="$(cd $(dirname $0); echo $PWD)" +for i in `ls -1 $DIR/../../src/core/*.h` ; do + header=`basename $i` + echo "#include \"../../src/core/$header\"" > $DIR/$header +done + +for i in `ls -1 $DIR/../../src/declarative/*.h` ; do + header=`basename $i` + echo "#include \"../../src/declarative/$header\"" > $DIR/$header +done + diff --git a/processmanager.pro b/processmanager.pro new file mode 100644 index 0000000..777225d --- /dev/null +++ b/processmanager.pro @@ -0,0 +1,14 @@ +TEMPLATE = subdirs + +module_processmanager_src.subdir = src +module_processmanager_src.target = module-processmanager-src + +module_processmanager_tests.subdir = tests +module_processmanager_tests.target = module-processmanager-tests +module_processmanager_tests.depends += module-processmanager-src + +SUBDIRS += module_processmanager_src #module_processmanager_tests + +include(doc/doc.pri) + +system($$PWD/include/qtprocessmanager/syncheaders.sh) diff --git a/src/core/core-lib.pri b/src/core/core-lib.pri new file mode 100644 index 0000000..361c8c8 --- /dev/null +++ b/src/core/core-lib.pri @@ -0,0 +1,52 @@ +QT += network + +INCLUDEPATH += $$PWD + +PUBLIC_HEADERS += \ + $$PWD/process.h \ + $$PWD/processfrontend.h \ + $$PWD/processbackend.h \ + $$PWD/processbackendfactory.h \ + $$PWD/processbackendmanager.h \ + $$PWD/processinfo.h \ + $$PWD/processmanager.h \ + $$PWD/gdbprocessbackendfactory.h \ + $$PWD/standardprocessbackendfactory.h \ + $$PWD/prelaunchprocessbackendfactory.h \ + $$PWD/remoteprocessbackendfactory.h \ + $$PWD/pipeprocessbackendfactory.h \ + $$PWD/socketprocessbackendfactory.h \ + $$PWD/unixprocessbackend.h \ + $$PWD/standardprocessbackend.h \ + $$PWD/prelaunchprocessbackend.h \ + $$PWD/remoteprocessbackend.h \ + $$PWD/processmanager-global.h + +*linux* { + PUBLIC_HEADERS += $$PWD/procutils.h + SOURCES += $$PWD/procutils.cpp +} + +HEADERS += \ + $$PUBLIC_HEADERS \ + $$PWD/unixsandboxprocess.h + +SOURCES += \ + $$PWD/process.cpp \ + $$PWD/gdbprocessbackendfactory.cpp \ + $$PWD/processfrontend.cpp \ + $$PWD/processbackend.cpp \ + $$PWD/processbackendfactory.cpp \ + $$PWD/processbackendmanager.cpp \ + $$PWD/processinfo.cpp \ + $$PWD/processmanager.cpp \ + $$PWD/unixprocessbackend.cpp \ + $$PWD/standardprocessbackendfactory.cpp \ + $$PWD/standardprocessbackend.cpp \ + $$PWD/unixsandboxprocess.cpp \ + $$PWD/prelaunchprocessbackendfactory.cpp \ + $$PWD/prelaunchprocessbackend.cpp \ + $$PWD/remoteprocessbackend.cpp \ + $$PWD/remoteprocessbackendfactory.cpp \ + $$PWD/pipeprocessbackendfactory.cpp \ + $$PWD/socketprocessbackendfactory.cpp diff --git a/src/core/core.pri b/src/core/core.pri new file mode 100644 index 0000000..f934acc --- /dev/null +++ b/src/core/core.pri @@ -0,0 +1,10 @@ +CONFIG += network + +INCLUDEPATH += $$PWD +LIBS += -L$$PWD -lprocessmanager-core + +mac|unix { + CONFIG += rpath_libdirs + QMAKE_RPATHDIR += $$PWD + QMAKE_LFLAGS += "-Wl,-rpath $$PWD" +} diff --git a/src/core/core.pro b/src/core/core.pro new file mode 100644 index 0000000..343572a --- /dev/null +++ b/src/core/core.pro @@ -0,0 +1,16 @@ +TEMPLATE = lib +TARGET = processmanager-core + +include($$PWD/../../config.pri) +include($$PWD/core-lib.pri) + +mac { + QMAKE_POST_LINK = install_name_tool -id $$PWD/${TARGET} ${TARGET} +} + +target.path = $$INSTALLBASE/lib + +headers.path = $$INSTALLBASE/include/qtprocessmanager +headers.files = $$PUBLIC_HEADERS + +INSTALLS += target headers diff --git a/src/core/gdbprocessbackendfactory.cpp b/src/core/gdbprocessbackendfactory.cpp new file mode 100644 index 0000000..5b25715 --- /dev/null +++ b/src/core/gdbprocessbackendfactory.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "gdbprocessbackendfactory.h" +#include "processinfo.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class GdbProcessBackendFactory + \brief The GdbProcessBackendFactory class creates UnixProcessBackend objects + + This is a simple example class showing how to create a custom factory. + The GdbProcessBackendFactory matches ProcessInfo records with a "gdb" + attribute of "true" (the string, not the boolean value). It rewrites the + process arguments to launch gdb with the passed application. + + In the future this class will be replaced with a general "rewriting" + frontend to modify ProcessInfo arguments. +*/ + +/*! + Construct a GdbProcessBackendFactory with optional \a parent +*/ + +GdbProcessBackendFactory::GdbProcessBackendFactory(QObject *parent) + : StandardProcessBackendFactory(parent) +{ +} + +/*! + GdbProcessBackendFactory will match ProcessInfo \a info objects + with a "gdb" attribute containing the string "true". +*/ + +bool GdbProcessBackendFactory::canCreate(const ProcessInfo& info) const +{ + return (info.value("gdb").toString() == "true"); +} + +/*! + Construct a UnixProcessBackend from a ProcessInfo \a info record and \a parent, + but change the program name to "gdb" and pass the original + program as an argument. +*/ + +ProcessBackend * GdbProcessBackendFactory::create(const ProcessInfo& info, QObject *parent) +{ + ProcessInfo i = info; + QStringList args = i.arguments(); + args.prepend(i.program()); + args.prepend("--"); + i.setProgram("gdb"); + return StandardProcessBackendFactory::create(i, parent); +} + +#include "moc_gdbprocessbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/gdbprocessbackendfactory.h b/src/core/gdbprocessbackendfactory.h new file mode 100644 index 0000000..4ea99ab --- /dev/null +++ b/src/core/gdbprocessbackendfactory.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GDB_PROCESS_BACKEND_FACTORY_H +#define GDB_PROCESS_BACKEND_FACTORY_H + +#include "standardprocessbackendfactory.h" +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class GdbProcessBackendFactory : public StandardProcessBackendFactory +{ + Q_OBJECT + +public: + GdbProcessBackendFactory(QObject *parent=0); + virtual bool canCreate(const ProcessInfo& info) const; + virtual ProcessBackend *create(const ProcessInfo& info, QObject *parent); +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // GDB_PROCESS_BACKEND_FACTORY_H diff --git a/src/core/memorystatistics_p.h b/src/core/memorystatistics_p.h new file mode 100644 index 0000000..4d1d9a1 --- /dev/null +++ b/src/core/memorystatistics_p.h @@ -0,0 +1,73 @@ +#ifndef MEMORYMANAGER_P_H +#define MEMORYMANAGER_P_H + +#include <QString> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class MappedRegion { +public: + quint64 addressStart; + quint64 addressEnd; + quint64 rssSize; + quint64 pssSize; + quint64 privateSize; + QString pathname; + + static QString libBaseName(const QString &pathName); + QString libBaseName() const; +}; + +// proc/pid/stat +class stat { +public: + stat() + : ppid(0) + , pgrp(0) + , session(0) + , it_real_value(0) + , start_time(0) + , vsize(0) + , rss(0) {} + QByteArray comm; + char state; + int ppid; + int pgrp; + int session; + int tty_nr; + int tty_pgrp; + ulong flags; + ulong min_flt; + ulong cmin_flt; + ulong maj_flt; + ulong cmaj_flt; + ulong tms_utime; + ulong tms_stime; + long tms_cutime; + long tms_cstime; + long priority; + long nice; + + long it_real_value; + ulong start_time; + ulong vsize; + long rss; /* you might want to shift this left 3 */ + ulong rlim; + ulong start_code; + ulong end_code; + ulong start_stack; + ulong esp; + ulong eip; + + ulong wchan; + ulong nswap; + ulong cnswap; + int exit_signal; + int processor; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // MEMORYMANAGER_P_H diff --git a/src/core/pipeprocessbackendfactory.cpp b/src/core/pipeprocessbackendfactory.cpp new file mode 100644 index 0000000..39ea8ea --- /dev/null +++ b/src/core/pipeprocessbackendfactory.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "pipeprocessbackendfactory.h" +#include "remoteprocessbackend.h" + +#include <QDebug> +#include <QJsonDocument> +#include <QFileInfo> +#include <QtEndian> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +const int kPipeTimerInterval = 1000; + +/*! + \class PipeProcessBackendFactory + \brief The PipeProcessBackendFactory class forks a new process to launch applications. + + The PipeProcessBackendFactory launches a persistent pipe process. + The factory communicates with the pipe process by sending and receiving + messages over stdin/stdout. +*/ + +/*! + Construct a PipeProcessBackendFactory with optional \a parent. + The \a info ProcessInfo is used to start the pipe process. + The \a program is used to match program names for create() requests. +*/ + +PipeProcessBackendFactory::PipeProcessBackendFactory(const ProcessInfo& info, + const QString& program, + QObject *parent) + : RemoteProcessBackendFactory(parent) + , m_process(NULL) + , m_program(program) +{ + m_process = new QProcess; // Note that we do NOT own the pipe process + m_process->setReadChannel(QProcess::StandardOutput); + connect(m_process, SIGNAL(readyReadStandardOutput()), + this, SLOT(pipeReadyReadStandardOutput())); + connect(m_process, SIGNAL(readyReadStandardError()), + this, SLOT(pipeReadyReadStandardError())); + connect(m_process, SIGNAL(started()), this, SLOT(pipeStarted())); + connect(m_process,SIGNAL(error(QProcess::ProcessError)), + this,SLOT(pipeError(QProcess::ProcessError))); + connect(m_process,SIGNAL(finished(int, QProcess::ExitStatus)), + this,SLOT(pipeFinished(int, QProcess::ExitStatus))); + connect(m_process, SIGNAL(stateChanged(QProcess::ProcessState)), + this,SLOT(pipeStateChanged(QProcess::ProcessState))); + + QProcessEnvironment env; + QMapIterator<QString, QVariant> it(info.environment()); + while (it.hasNext()) { + it.next(); + env.insert(it.key(), it.value().toString()); + } + m_process->setProcessEnvironment(env); + m_process->setWorkingDirectory(info.workingDirectory()); + m_process->start(info.program(), info.arguments()); +} + +/*! + Destroy this and child objects. +*/ + +PipeProcessBackendFactory::~PipeProcessBackendFactory() +{ + // ### Note: The m_process process is NOT a child of the + // factory to avoid stranding grandchildren + // However, we do send it a "stop" message before we exit + if (m_process) { + QJsonObject object; + object.insert(QLatin1String("remote"), QLatin1String("stop")); + m_process->write(QJsonDocument(object).toBinaryData()); + m_process->waitForBytesWritten(); // Block until they have been written + m_process = NULL; + } +} + +/*! + The PipeProcessBackendFactory will match the ProcessInfo \a info + if there is a \c info.pipe attribute set to "true" (the string) and + if the \c info.program attribute matches the program original specified + when creating the factory. +*/ + +bool PipeProcessBackendFactory::canCreate(const ProcessInfo& info) const +{ + QString program = QFileInfo(info.program()).fileName(); + return (m_process && + m_process->state() == QProcess::Running && + info.value("pipe").toString() == "true" && + program == m_program); +} + +/*! + If there is a pipe process running, it will be returned here. + */ + +QList<Q_PID> PipeProcessBackendFactory::internalProcesses() +{ + QList<Q_PID> list; + if (m_process) + list << m_process->pid(); + return list; +} + +/*! + Send \a message to a pipe process. + */ +bool PipeProcessBackendFactory::send(const QJsonObject& message) +{ + qDebug() << Q_FUNC_INFO << message; + + if (m_process->state() != QProcess::Running) { + qCritical("Pipe process not running"); + return false; + } + if (m_process->write(QJsonDocument(message).toBinaryData()) == -1) { + qCritical("Unable to write to pipe process"); + return false; + } + return true; +} + + +void PipeProcessBackendFactory::pipeReadyReadStandardOutput() +{ + m_buffer.append(m_process->readAllStandardOutput()); + while (m_buffer.size() >= 12) { // QJsonDocuments are at least this large + if (QJsonDocument::BinaryFormatTag != *((uint *) m_buffer.data())) + qFatal("ERROR in receive buffer: %s", m_buffer.data()); + qint32 message_size = qFromLittleEndian(((qint32 *)m_buffer.data())[2]) + 8; + if (m_buffer.size() < message_size) + break; + QByteArray msg = m_buffer.left(message_size); + m_buffer = m_buffer.mid(message_size); + receive(QJsonDocument::fromBinaryData(msg).object()); + } +} + +void PipeProcessBackendFactory::pipeReadyReadStandardError() +{ + const QByteArray byteArray = m_process->readAllStandardError(); + QList<QByteArray> lines = byteArray.split('\n'); + foreach (const QByteArray& line, lines) { + if (line.size()) + qDebug() << "PIPE STDERR" << line; + } +} + +void PipeProcessBackendFactory::pipeStarted() +{ + qDebug() << "Pipe process started"; +} + +void PipeProcessBackendFactory::pipeError(QProcess::ProcessError error) +{ + qWarning("Pipe process error: %d", error); +} + +void PipeProcessBackendFactory::pipeFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qCritical("Pipe process died, exit code=%d status=%d", exitCode, exitStatus); + delete m_process; + m_process = NULL; +} + +void PipeProcessBackendFactory::pipeStateChanged(QProcess::ProcessState state) +{ + qDebug() << "Pipe process state change" << state; +} + +#include "moc_pipeprocessbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/pipeprocessbackendfactory.h b/src/core/pipeprocessbackendfactory.h new file mode 100644 index 0000000..20f461b --- /dev/null +++ b/src/core/pipeprocessbackendfactory.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PIPE_PROCESS_BACKEND_FACTORY_H +#define PIPE_PROCESS_BACKEND_FACTORY_H + +#include "remoteprocessbackendfactory.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class PipeProcessBackendFactory : public RemoteProcessBackendFactory +{ + Q_OBJECT +public: + PipeProcessBackendFactory(const ProcessInfo& info, const QString& program, QObject *parent = 0); + virtual ~PipeProcessBackendFactory(); + + virtual bool canCreate(const ProcessInfo& info) const; + virtual QList<Q_PID> internalProcesses(); + +protected: + virtual bool send(const QJsonObject&); + +private slots: + void pipeReadyReadStandardOutput(); + void pipeReadyReadStandardError(); + void pipeStarted(); + void pipeError(QProcess::ProcessError error); + void pipeFinished(int exitCode, QProcess::ExitStatus exitStatus); + void pipeStateChanged(QProcess::ProcessState state); + +private: + QProcess *m_process; + QString m_program; + QByteArray m_buffer; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PIPE_PROCESS_BACKEND_FACTORY_H diff --git a/src/core/prelaunchprocessbackend.cpp b/src/core/prelaunchprocessbackend.cpp new file mode 100644 index 0000000..5408aa5 --- /dev/null +++ b/src/core/prelaunchprocessbackend.cpp @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "prelaunchprocessbackend.h" + +#include <qjsondocument.h> +#include <QUuid> +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class PrelaunchProcessBackend + \brief The PrelaunchProcessBackend class encapsulates a process that is started and later told what to run. + + The PrelaunchProcessBackend class contains an internal QProcess object that is + "prelaunched" by the PrelaunchProcessBackendFactory object. When the factory + is asked to create a new process, the prelaunched backend is returned. When start() is + called, the prelaunched process is passed a ProcessInfo record encoded in QBinaryJson + document format (a serialized JSON object). This record should be used by the + prelaunched process to transform itself into the correctly running application. + */ + +/*! + Construct a PrelaunchProcessBackend with ProcessInfo \a info and optional \a parent. + The \a info ProcessInfo is used to start the internal QProcess. This is different + from the final ProcessInfo which will be directly passed to the running QProcess. + */ + +PrelaunchProcessBackend::PrelaunchProcessBackend(const ProcessInfo &info, QObject *parent) + : UnixProcessBackend(info, parent) + , m_started(false) +{ +} + +/*! + Destroy this and child objects +*/ + +PrelaunchProcessBackend::~PrelaunchProcessBackend() +{ +} + +/*! + Starts the internal QProcess with the prelaunch information. + */ + +void PrelaunchProcessBackend::prestart() +{ + if (createProcess()) + startProcess(); +} + +/*! + Switch the stored ProcessInfo to the final \a info object. + This function also updates the identifier of the process. + */ + +void PrelaunchProcessBackend::setInfo(const ProcessInfo& info) +{ + m_info = info; + createName(); +} + +/*! + Pretend to start the prelaunched process. + The stored ProcessInfo record is written to the internal QProcess as a serialized + JSON object (the internal QProcess receives it from stdin). + Then emit all queued signals that were captured from the QProcess. + */ + +void PrelaunchProcessBackend::start() +{ + if (m_started) { + qWarning() << "Can't restart prelaunched process"; + return; + } + + m_started = true; + // Pass the actual process info to the child process + QByteArray byteArray = QJsonDocument::fromVariant(m_info.toMap()).toBinaryData(); + write(byteArray.data(), byteArray.size()); + + while (m_queue.size()) { + QueuedSignal s = m_queue.takeFirst(); + switch (s.name) { + case QueuedSignal::StateChanged: + emit stateChanged(s.n.state); + break; + case QueuedSignal::Started: + emit started(); + break; + case QueuedSignal::Error: + emit error(s.n.error); + break; + case QueuedSignal::Finished: + emit finished(s.n.f.exitCode, s.n.f.exitStatus); + break; + } + } +} + +/*! + Return the current process state. Until the process has been + officially "started", the state has to be NotRunning. + */ + +QProcess::ProcessState PrelaunchProcessBackend::state() const +{ + if (m_started) + return UnixProcessBackend::state(); + else + return QProcess::NotRunning; +} + +/*! + \internal +*/ +void PrelaunchProcessBackend::handleProcessStarted() +{ + UnixProcessBackend::handleProcessStarted(); + if (m_started) + emit started(); + else { + QueuedSignal s; + s.name = QueuedSignal::Started; + m_queue << s; + } +} + +/*! + \internal +*/ +void PrelaunchProcessBackend::handleProcessError(QProcess::ProcessError processError) +{ + UnixProcessBackend::handleProcessError(processError); + if (m_started) + emit error(processError); + else { + QueuedSignal s; + s.name = QueuedSignal::Error; + s.n.error = processError; + m_queue << s; + } +} + +/*! + \internal +*/ +void PrelaunchProcessBackend::handleProcessFinished(int exitCode, QProcess::ExitStatus status) +{ + UnixProcessBackend::handleProcessFinished(exitCode, status); + if (m_started) + emit finished(exitCode, status); + else { + QueuedSignal s; + s.name = QueuedSignal::Finished; + s.n.f.exitCode = exitCode; + s.n.f.exitStatus = status; + m_queue << s; + } +} + +/*! + \internal +*/ +void PrelaunchProcessBackend::handleProcessStateChanged(QProcess::ProcessState state) +{ + UnixProcessBackend::handleProcessStateChanged(state); + if (m_started) + emit stateChanged(state); + else { + QueuedSignal s; + s.name = QueuedSignal::StateChanged; + s.n.state = state; + m_queue << s; + } +} + +/*! + \class QueuedSignal + \internal + */ + +/*! + \enum QueuedSignal::SignalName + \internal + */ + +#include "moc_prelaunchprocessbackend.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/prelaunchprocessbackend.h b/src/core/prelaunchprocessbackend.h new file mode 100644 index 0000000..ab21da6 --- /dev/null +++ b/src/core/prelaunchprocessbackend.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PRELAUNCHPROCESSBACKEND_H +#define PRELAUNCHPROCESSBACKEND_H + +#include "unixprocessbackend.h" +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class QueuedSignal +{ +public: + enum SignalName { StateChanged, Started, Error, Finished }; + + SignalName name; + union { + QProcess::ProcessError error; + struct { + int exitCode; + QProcess::ExitStatus exitStatus; + } f; + QProcess::ProcessState state; + } n; +}; + +class PrelaunchProcessBackend : public UnixProcessBackend +{ + Q_OBJECT + +public: + PrelaunchProcessBackend(const ProcessInfo& info, QObject *parent); + virtual ~PrelaunchProcessBackend(); + + void prestart(); + void setInfo(const ProcessInfo& info); + + virtual void start(); + virtual QProcess::ProcessState state() const; + +private slots: + void handleProcessStarted(); + void handleProcessError(QProcess::ProcessError error); + void handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void handleProcessStateChanged(QProcess::ProcessState state); + +private: + QList<QueuedSignal> m_queue; + bool m_started; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PRELAUNCHPROCESSBACKEND_H diff --git a/src/core/prelaunchprocessbackendfactory.cpp b/src/core/prelaunchprocessbackendfactory.cpp new file mode 100644 index 0000000..c65dcc3 --- /dev/null +++ b/src/core/prelaunchprocessbackendfactory.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDebug> + +#include "prelaunchprocessbackendfactory.h" +#include "prelaunchprocessbackend.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +const int kPrelaunchTimerInterval = 1000; + +/*! + \class PrelaunchProcessBackendFactory + \brief The PrelaunchProcessBackendFactory class creates PrelaunchProcessBackend objects + + The PrelaunchProcessBackendFactory starts up a PrelaunchProcessBackend using + information passed in the constructor. +*/ + +/*! + Construct a PrelaunchProcessBackendFactory with optional \a parent. + The \a info ProcessInfo is used to start the prelaunched process. This is + different from the final ProcessInfo which will be passed to the prelaunched + process as a QBinaryJson document. +*/ + +PrelaunchProcessBackendFactory::PrelaunchProcessBackendFactory(const ProcessInfo& info, QObject *parent) + : ProcessBackendFactory(parent) + , m_prelaunch(NULL) + , m_info(info) +{ + connect(&m_timer, SIGNAL(timeout()), SLOT(timeout())); + m_timer.setSingleShot(true); + m_timer.setInterval(kPrelaunchTimerInterval); + m_timer.start(); +} + +/*! + Destroy this and child objects. +*/ + +PrelaunchProcessBackendFactory::~PrelaunchProcessBackendFactory() +{ +} + +/*! + The PrelaunchProcessBackendFactory will match the ProcessInfo \a info + if there is a \c info.prelaunch attribute set to "true" (the string) and + if the \c info.program attribute matches the program attribute of the + original ProcessInfo record used to create the PrelaunchProcessBackendFactory. +*/ + +bool PrelaunchProcessBackendFactory::canCreate(const ProcessInfo& info) const +{ + return (info.value("prelaunch").toString() == "true" && + info.program() == m_info.program()); +} + +/*! + Construct a PrelaunchProcessBackend from a ProcessInfo \a info record with \a parent. +*/ + +ProcessBackend * PrelaunchProcessBackendFactory::create(const ProcessInfo& info, QObject *parent) +{ + PrelaunchProcessBackend *prelaunch = m_prelaunch; + + if ( prelaunch && prelaunch->state() != QProcess::NotRunning) { + m_prelaunch = NULL; + m_timer.start(); + prelaunch->setInfo(info); + prelaunch->setParent(parent); + return prelaunch; + } + + qDebug() << "Creating prelaunch from scratch"; + prelaunch = new PrelaunchProcessBackend(m_info, parent); + prelaunch->prestart(); + prelaunch->setInfo(info); + return prelaunch; +} + +/*! + If there is a prelaunched process running, it will be return here. + */ + +QList<Q_PID> PrelaunchProcessBackendFactory::internalProcesses() +{ + QList<Q_PID> list; + if (m_prelaunch) + list << m_prelaunch->pid(); + return list; +} + +/*! + Under memory restriction, terminate the prelaunch process. + */ + +void PrelaunchProcessBackendFactory::handleMemoryRestrictionChange() +{ + if ( m_memoryRestricted ) { + m_timer.stop(); + if (m_prelaunch) { + delete m_prelaunch; // This will kill the child process as well + m_prelaunch = NULL; + } + } else { + Q_ASSERT(m_prelaunch == NULL); + m_timer.start(); + } +} + +void PrelaunchProcessBackendFactory::timeout() +{ + Q_ASSERT(m_prelaunch == NULL); + Q_ASSERT(!m_memoryRestricted); + + m_prelaunch = new PrelaunchProcessBackend(m_info, this); + m_prelaunch->prestart(); +} + +#include "moc_prelaunchprocessbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/prelaunchprocessbackendfactory.h b/src/core/prelaunchprocessbackendfactory.h new file mode 100644 index 0000000..c066026 --- /dev/null +++ b/src/core/prelaunchprocessbackendfactory.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PRELAUNCH_PROCESS_BACKEND_FACTORY_H +#define PRELAUNCH_PROCESS_BACKEND_FACTORY_H + +#include "processbackendfactory.h" +#include "processinfo.h" +#include "processmanager-global.h" +#include <QTimer> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class PrelaunchProcessBackend; + +class PrelaunchProcessBackendFactory : public ProcessBackendFactory +{ + Q_OBJECT +public: + PrelaunchProcessBackendFactory(const ProcessInfo& info, QObject *parent = 0); + virtual ~PrelaunchProcessBackendFactory(); + virtual bool canCreate(const ProcessInfo& info) const; + virtual ProcessBackend *create(const ProcessInfo& info, QObject *parent); + + virtual QList<Q_PID> internalProcesses(); + +protected: + virtual void handleMemoryRestrictionChange(); + +private slots: + void timeout(); + +private: + PrelaunchProcessBackend *m_prelaunch; + ProcessInfo m_info; + QTimer m_timer; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PRELAUNCH_PROCESS_BACKEND_FACTORY_H diff --git a/src/core/process.cpp b/src/core/process.cpp new file mode 100644 index 0000000..f838dc9 --- /dev/null +++ b/src/core/process.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "process.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class Process + \brief The Process class contains common enumaration definitions for the process manager. +*/ + +/*! + \enum Process::ProcessError + + This enum is an exact match for QProcess::ProcessError. It + has been duplicated here so that it can be exposed to QML. + + \value FailedToStart The process failed to start. Either the + invoked program is missing, or you may have insufficient + permissions to invoke the program. + + \value Crashed The process crashed some time after starting + successfully. + + \value Timedout The last waitFor...() function timed out. The + state of QProcess is unchanged, and you can try calling + waitFor...() again. + + \value WriteError An error occurred when attempting to write to the + process. For example, the process may not be running, or it may + have closed its input channel. + + \value ReadError An error occurred when attempting to read from + the process. For example, the process may not be running. + + \value UnknownError An unknown error occurred. This is the default + return value of error(). +*/ + +/*! + \enum Process::ProcessState + + This enum is an exact match for QProcess::ProcessState. It + has been duplicated here so that it can be exposed to QML. + + \value NotRunning The process is not running. + + \value Starting The process is starting, but the program has not + yet been invoked. + + \value Running The process is running and is ready for reading and + writing. +*/ + +/*! + \enum Process::ExitStatus + + This enum is an exact match for QProcess::ExitStatus. It + has been duplicated here so that it can be exposed to QML. + + \value NormalExit The process exited normally. + + \value CrashExit The process crashed. +*/ + + +#include "moc_process.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/process.h b/src/core/process.h new file mode 100644 index 0000000..7f0a751 --- /dev/null +++ b/src/core/process.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef _PROCESS_H +#define _PROCESS_H + +#include "processmanager-global.h" +#include <QObject> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class Process : public QObject +{ + Q_OBJECT + +public: + enum ProcessError { FailedToStart, Crashed, Timedout, ReadError, WriteError, UnknownError }; + enum ProcessState { NotRunning, Starting, Running }; + enum ExitStatus { NormalExit, CrashExit }; + + Q_ENUMS(ProcessError) + Q_ENUMS(ProcessState) + Q_ENUMS(ExitStatus) +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // _PROCESS_H diff --git a/src/core/processbackend.cpp b/src/core/processbackend.cpp new file mode 100644 index 0000000..c3da1a1 --- /dev/null +++ b/src/core/processbackend.cpp @@ -0,0 +1,464 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "processbackend.h" + +#include <QDateTime> +#include <QUuid> +#include <QDebug> +#include <QFile> +#include <QByteArray> +#include <stdio.h> + +/***************************************************************************************/ + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class ProcessBackend + \brief The ProcessBackend class is a generalized representation of a process. + + The ProcessBackend class encapsulates a process whose lifecycle is controlled by the process manager. + It is a generalized representation - in actuality, subclasses of the ProcessBackend class are used + to control processes. These subclasses are used to accelerate process launching, wrap + processes with additional arguments and settings, or fake the existence of a process. + The ProcessBackend class itself cannot be instantiated. + + A ProcessBackend object always contains a ProcessInfo object, which is the static information + used to start and execute the process. + + Because ProcessBackends represent actual or fake processes, they can only + modify a few of the process properties. Currently the ProcessBackend can + only modify the process priority (or niceness) and the oomAdjustment score (for Linux + systems). All other process properties (such as UID, GID, program name) can + be considered fixed. + + Furthermore, please note that setting these dynamic values can take time - + for example, they may require some intra-application communication. For example, + if the process manager has delegated starting applications to some kind of + pipe process, then the process manager itself may not be running with + sufficient permissions to change the priority or oomAdjustment value to the + desired setting. In this case, the process manager must request that the pipe + process make the changes, which will result in some time delay before the + changes are real. In this case, the actualPriority() and desiredPriority() + functions will return different values until the IPC has completed. +*/ + +/*! + \internal + Constructs a ProcessBackend instance with ProcessInfo \a info and optional \a parent +*/ +ProcessBackend::ProcessBackend(const ProcessInfo& info, QObject *parent) + : QObject(parent) + , m_info(info) + , m_echo(ProcessBackend::EchoStdoutStderr) +{ + static int backend_count = 0; + m_id = ++backend_count; + createName(); +} + +/*! + Destroy this process object. +*/ + +ProcessBackend::~ProcessBackend() +{ +} + +/*! + Return the process name +*/ + +QString ProcessBackend::name() const +{ + return m_name; +} + +/*! + Return the process identifier +*/ + +QString ProcessBackend::identifier() const +{ + return m_info.identifier(); +} + +/*! + Return the process program +*/ + +QString ProcessBackend::program() const +{ + return m_info.program(); +} + +/*! + Return the process arguments +*/ + +QStringList ProcessBackend::arguments() const +{ + return m_info.arguments(); +} + +/*! + Return the process environment +*/ + +QVariantMap ProcessBackend::environment() const +{ + return m_info.environment(); +} + +/*! + Return the process working directory +*/ + +QString ProcessBackend::workingDirectory() const +{ + return m_info.workingDirectory(); +} + +/*! + Return the process UID +*/ + +qint64 ProcessBackend::uid() const +{ + return 0; +} + +/*! + Return the process GID +*/ + +qint64 ProcessBackend::gid() const +{ + return 0; +} + +/*! + Returns the PID of this process. If the process has not started up yet properly, its PID will be 0. +*/ +Q_PID ProcessBackend::pid() const +{ + return 0; +} + +/*! + Return the desired process priority. This may not be + the same as the actual priority +*/ + +qint32 ProcessBackend::desiredPriority() const +{ + return m_info.priority(); +} + +/*! + Return the actual process priority. This should be + overriden by a subclass. +*/ + +qint32 ProcessBackend::actualPriority() const +{ + return 0; +} + +/*! + Set the process priority to \a priority. + The base class updates the ProcessInfo object + Subclasses should should override this function. +*/ + +void ProcessBackend::setDesiredPriority(qint32 priority) +{ + m_info.setPriority(priority); +} + +/*! + Return the desired process oomAdjustment +*/ + +qint32 ProcessBackend::desiredOomAdjustment() const +{ + return m_info.oomAdjustment(); +} + +/*! + Return the actual process oomAdjustment + Override this in a subclass that actual touches the oomAdjustment +*/ + +qint32 ProcessBackend::actualOomAdjustment() const +{ + return 0; +} + +/*! + Set the process desired \a oomAdjustment. + The base class updates the ProcessInfo object + Subclasses should override this function. + + Please note that there is no guarantee that the actual + oomAdjustment will match the desired oomAdjustment. +*/ + +void ProcessBackend::setDesiredOomAdjustment(qint32 oomAdjustment) +{ + m_info.setOomAdjustment(oomAdjustment); +} + +/*! + \fn QProcess::ProcessState ProcessBackend::state() const + + Returns the state of the process. +*/ + +/*! + \fn void ProcessBackend::start() + \brief Starts the process. + + After the process is started, the started() signal is emitted. + If a process fails to start (e.g. because it doesn't give any output until timeout) then + the (partly) started process is killed and error(QProcess::FailedToStart) is emitted. + + This function must be overridden in subclasses. + + \sa started() + \sa error() +*/ + +/*! + \fn void ProcessBackend::stop(int timeout) + + Attempts to stop a process by giving it a \a timeout time to die, measured in milliseconds. + If the process does not die in the given time limit, it is killed. + + \sa finished() +*/ + +/*! + Writes at most \a maxSize bytes of data from \a data to the process. + Returns the number of bytes that were actually written, or -1 if an error occurred. +*/ + +qint64 ProcessBackend::write(const char *data, qint64 maxSize) +{ + Q_UNUSED(data); + Q_UNUSED(maxSize); + return -1; +} + +/*! + Writes \a data from a zero-terminated string of 8-bit characters to the process. + Returns the number of bytes that were actually written, or -1 if an error occurred. + This is equivalent to + +\code + ProcessFrontend::write(data, qstrlen(data)); +\endcode + */ + +qint64 ProcessBackend::write(const char *data) +{ + return write(data, qstrlen(data)); +} + +/*! + Writes the content of \a byteArray to the process. + Returns the number of bytes that were actually written, or -1 if an error occurred. + */ + +qint64 ProcessBackend::write(const QByteArray& byteArray) +{ + return write(byteArray.data(), byteArray.length()); +} + +/*! + \enum ProcessBackend::EchoOutput + + This enum is used to control if data should be copied from an individual + process stdout/stderr to the global stdout/stderr. By default, it is + set to \c ProcessBackend::EchoStdoutStderr, which means that all data + from the child process will be copied to stdout and stderr. + + \value EchoNone No data will be displayed + \value EchoStdoutOnly Data from the child's stdout will be displayed + \value EchoStderrOnly Data from the child's stderr will be displayed + \value EchoStdoutStderr All data will be displayed. + */ + +/*! + Return the current echo setting. + */ + +ProcessBackend::EchoOutput ProcessBackend::echo() const +{ + return m_echo; +} + +/*! + Set the \a echo setting + */ + +void ProcessBackend::setEcho( ProcessBackend::EchoOutput echo ) +{ + m_echo = echo; +} + +/*! + Return a copy of the current ProcessInfo + */ + +ProcessInfo ProcessBackend::processInfo() const +{ + return m_info; +} + +/*! + \internal + */ + +static void _writeByteArrayToFd(const QByteArray& data, const QByteArray& prefix, FILE *fh) +{ + QList<QByteArray> lines = data.split('\n'); + if (lines.isEmpty()) + return; + + // If the last item was a separator, there will be an extra blank item at the end + if (lines.last().isEmpty()) + lines.removeLast(); + + if (!lines.isEmpty()) { + QFile f; + f.open(fh, QIODevice::WriteOnly); + foreach (const QByteArray& line, lines) { + f.write(prefix); + f.write(line); + f.write("\n"); + } + f.close(); + } +} + +/*! + Handler for standard output \a byteArray, read from the running process. + + Reimplement this method to provide handling for standard output. +*/ +void ProcessBackend::handleStandardOutput(const QByteArray &byteArray) +{ + if (m_echo == EchoStdoutOnly || m_echo == EchoStdoutStderr) { + QByteArray prefix = QString("%1 [%2]: ").arg(m_name).arg(pid()).toLocal8Bit(); + _writeByteArrayToFd( byteArray, prefix, stderr ); + } + emit standardOutput(byteArray); +} + +/*! + Handler for standard error \a byteArray, read from the running process. + + Reimplement this method to provide handling for standard error output. +*/ +void ProcessBackend::handleStandardError(const QByteArray &byteArray) +{ + if (m_echo == EchoStderrOnly || m_echo == EchoStdoutStderr) { + QByteArray prefix = QString("%1 [%2] ERR: ").arg(m_name).arg(pid()).toLocal8Bit(); + _writeByteArrayToFd( byteArray, prefix, stderr ); + } + emit standardError(byteArray); +} + +/*! + This function set the name of the process to be "NAME-ID". + If you need to change the name of the process (for example, from a prelaunch backend), + then call this function after changing the name. + */ + +void ProcessBackend::createName() +{ + m_name = QString("%1-%2").arg(m_info.identifier().isEmpty() + ? QString("process") + : m_info.identifier()).arg(m_id); +} + +/*! + \fn void ProcessBackend::started() + This signal is emitted when the proces has started successfully. +*/ + +/*! + \fn void ProcessBackend::error(QProcess::ProcessError error) + This signal is emitted on a process error. The \a error argument + is the error emitted by the internal QProcess. +*/ + +/*! + \fn void ProcessBackend::finished(int exitCode, QProcess::ExitStatus exitStatus) + This signal is emitted when the process has stopped and its execution is over. + The \a exitStatus tells you how the process exited; the \a exitCode is the + return value of the process. + + Please note: The \a exitStatus will be QProcess::CrashExit only if the + ProcessManager stops the process externally. A normal process that crashes + will have an \a exitStatus of QProcess::NormalExit with a non-zero \a exitCode. +*/ + +/*! + \fn void ProcessBackend::stateChanged(QProcess::ProcessState newState) + This signal is emitted whenever the state of QProcess changes. The \a newState argument + is the state the internal QProcess changed to. +*/ + +/*! + \fn void ProcessBackend::standardOutput(const QByteArray& data) + This signal is emitted whenever standard output \a data is received from the child. +*/ + +/*! + \fn void ProcessBackend::standardError(const QByteArray& data) + This signal is emitted whenever standard error \a data is received from the child +*/ + +#include "moc_processbackend.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/processbackend.h b/src/core/processbackend.h new file mode 100644 index 0000000..87fdff8 --- /dev/null +++ b/src/core/processbackend.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCESS_BACKEND_H +#define PROCESS_BACKEND_H + +#include <QObject> +#include "processinfo.h" +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessBackend : public QObject +{ + Q_OBJECT + +protected: + ProcessBackend(const ProcessInfo& info, QObject *parent); + +public: + virtual ~ProcessBackend(); + + virtual QString name() const; + virtual QString identifier() const; + virtual QString program() const; + virtual QStringList arguments() const; + virtual QVariantMap environment() const; + virtual QString workingDirectory() const; + + virtual qint64 uid() const; + virtual qint64 gid() const; + virtual Q_PID pid() const; + + virtual qint32 actualPriority() const; + virtual qint32 desiredPriority() const; + virtual void setDesiredPriority(qint32); + + virtual qint32 actualOomAdjustment() const; + virtual qint32 desiredOomAdjustment() const; + virtual void setDesiredOomAdjustment(qint32); + + virtual QProcess::ProcessState state() const = 0; + virtual void start() = 0; + virtual void stop(int timeout = 500) = 0; + virtual qint64 write(const char *data, qint64 maxSize); + + qint64 write(const char *data); + qint64 write(const QByteArray& byteArray); + + enum EchoOutput { EchoNone, EchoStdoutOnly, EchoStderrOnly, EchoStdoutStderr }; + EchoOutput echo() const; + void setEcho(EchoOutput); + + ProcessInfo processInfo() const; + +protected: + virtual void handleStandardOutput(const QByteArray &output); + virtual void handleStandardError(const QByteArray &output); + + void createName(); + +signals: + void started(); + void error(QProcess::ProcessError error); + void finished(int, QProcess::ExitStatus); + void stateChanged(QProcess::ProcessState); + void standardOutput(const QByteArray&); + void standardError(const QByteArray&); + +protected: + QString m_name; + int m_id; + ProcessInfo m_info; + EchoOutput m_echo; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE_PROCESSMANAGER(ProcessBackend)*) + +#endif // PROCESS_BACKEND_H diff --git a/src/core/processbackendfactory.cpp b/src/core/processbackendfactory.cpp new file mode 100644 index 0000000..3fcf710 --- /dev/null +++ b/src/core/processbackendfactory.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "processbackendfactory.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class ProcessBackendFactory + \brief The ProcessBackendFactory class is a virtual class for creating processes. + + Subclass this class to create particular types of processes. +*/ + +/*! + Construct a ProcessBackendFactory with an optional \a parent. +*/ + +ProcessBackendFactory::ProcessBackendFactory(QObject *parent) + : QObject(parent) + , m_memoryRestricted(false) +{ +} + +/*! + Destroy a process factory +*/ + +ProcessBackendFactory::~ProcessBackendFactory() +{ +} + +/*! + Control memory restrictions. Setting \a memoryRestricted to + true requests the factory to free up as much memory as possible. +*/ + +void ProcessBackendFactory::setMemoryRestricted(bool memoryRestricted) +{ + if (memoryRestricted != m_memoryRestricted) { + m_memoryRestricted = memoryRestricted; + handleMemoryRestrictionChange(); + } +} + +/*! + Override this is subclasses to return a list of internal + processes that are contained in this factory. The default + class returns an empty list. + */ + +QList<Q_PID> ProcessBackendFactory::internalProcesses() +{ + return QList<Q_PID>(); +} + +/*! + Override this in subclasses to handle memory restriction changes +*/ + +void ProcessBackendFactory::handleMemoryRestrictionChange() +{ +} + +/*! + \fn bool ProcessBackendFactory::canCreate(const ProcessInfo& info) const + + Return true if this ProcessBackendFactory matches the ProcessInfo \a info + process binding and can create an appropriate process. + + This virtual function must be overridden. +*/ + +/*! + \fn ProcessBackend * ProcessBackendFactory::create(const ProcessInfo& info, QObject *parent) + + Create a ProcessBackend object based on the ProcessInfo \a info and \a parent. +*/ + +#include "moc_processbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/processbackendfactory.h b/src/core/processbackendfactory.h new file mode 100644 index 0000000..9130ce1 --- /dev/null +++ b/src/core/processbackendfactory.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCESS_BACKEND_FACTORY_H +#define PROCESS_BACKEND_FACTORY_H + +#include <QObject> +#include <QProcessEnvironment> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessBackend; +class ProcessInfo; + +class ProcessBackendFactory : public QObject +{ + Q_OBJECT +public: + ProcessBackendFactory(QObject *parent = 0); + virtual ~ProcessBackendFactory(); + virtual bool canCreate(const ProcessInfo& info) const = 0; + virtual ProcessBackend *create(const ProcessInfo& info, QObject *parent) = 0; + + void setMemoryRestricted(bool); + virtual QList<Q_PID> internalProcesses(); + +protected: + virtual void handleMemoryRestrictionChange(); + +protected: + bool m_memoryRestricted; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PROCESS_BACKEND_FACTORY_H diff --git a/src/core/processbackendmanager.cpp b/src/core/processbackendmanager.cpp new file mode 100644 index 0000000..9df3506 --- /dev/null +++ b/src/core/processbackendmanager.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "processbackendmanager.h" +#include "processbackendfactory.h" +#include "processbackend.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class ProcessBackendManager + \brief The ProcessBackendManager class contains a list of ProcessBackendFactory + objects and is used to create ProcessBackend objects. + + The ProcessBackendManager class is a standalone class for creating ProcessBackend + objects from factories. Typical use for the backend manager is like: + + \code + ProcessBackendManager *bm = new ProcessBackendManager; + ProcessInfo info; + info.setProgram("/home/me/myprogram"); + bm->add(new PrelaunchProcessBackendFactory(info); + pm->add(new StandardProcessBackendFactory); + + // ... + + ProcessInfo i; + i.setProgram("/home/me/myprogram"); + i.setWorkingDirectory("/root"); + ProcessBackend *backend = bm->create(i); + \endcode + + The backend manager does not get involved in starting or tracking + the lifetime of a process. In general, you should use the + ProcessManager class for processes, which contains a backend manager object. +*/ + +/*! + Construct a ProcessBackendManager with an optional \a parent +*/ + +ProcessBackendManager::ProcessBackendManager(QObject *parent) + : QObject(parent) + , m_memoryRestricted(false) +{ +} + +/*! + Delete the ProcessBackendManager and terminate all factory processes. +*/ + +ProcessBackendManager::~ProcessBackendManager() +{ +} + +/*! + Create a new ProcessBackend based on ProcessInfo \a info and \a parent. +*/ + +ProcessBackend *ProcessBackendManager::create(const ProcessInfo& info, QObject *parent) +{ + foreach (ProcessBackendFactory *factory, m_factories) { + if (factory->canCreate(info)) + return factory->create(info, parent); + } + return NULL; +} + +/*! + Add a ProcessBackendFactory \a factory to the end of the Factory list. + The factory becomes a child of the backend manager. +*/ + +void ProcessBackendManager::addFactory(ProcessBackendFactory *factory) +{ + m_factories.append(factory); + factory->setParent(this); + factory->setMemoryRestricted(m_memoryRestricted); +} + +/*! + Return a list of all internal processes being used by factories +*/ + +QList<Q_PID> ProcessBackendManager::internalProcesses() +{ + QList<Q_PID> list; + foreach (ProcessBackendFactory *factory, m_factories) + list.append(factory->internalProcesses()); + return list; +} + +/*! + Set memory restrictions. If \a memoryRestricted is true + all factories are requested to minimize memory use. +*/ + +void ProcessBackendManager::setMemoryRestricted(bool memoryRestricted) +{ + if (m_memoryRestricted != memoryRestricted) { + m_memoryRestricted = memoryRestricted; + foreach (ProcessBackendFactory *factory, m_factories) + factory->setMemoryRestricted(memoryRestricted); + } +} + +/*! + Return current memory restriction setting +*/ + +bool ProcessBackendManager::memoryRestricted() const +{ + return m_memoryRestricted; +} + +#include "moc_processbackendmanager.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/processbackendmanager.h b/src/core/processbackendmanager.h new file mode 100644 index 0000000..6c0d993 --- /dev/null +++ b/src/core/processbackendmanager.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCESS_BACKEND_MANAGER_H +#define PROCESS_BACKEND_MANAGER_H + +#include <QObject> +#include <QHash> +#include <QProcessEnvironment> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessFrontend; +class ProcessInfo; +class ProcessBackendFactory; +class ProcessBackend; + +class ProcessBackendManager : public QObject +{ + Q_OBJECT + +public: + explicit ProcessBackendManager(QObject *parent = 0); + virtual ~ProcessBackendManager(); + + ProcessBackend *create(const ProcessInfo& info, QObject *parent=0); + void addFactory(ProcessBackendFactory *factory); + QList<Q_PID> internalProcesses(); + + void setMemoryRestricted(bool); + bool memoryRestricted() const; + +private: + QList<ProcessBackendFactory*> m_factories; + bool m_memoryRestricted; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PROCESS_BACKEND_MANAGER_H diff --git a/src/core/processfrontend.cpp b/src/core/processfrontend.cpp new file mode 100644 index 0000000..4d0b946 --- /dev/null +++ b/src/core/processfrontend.cpp @@ -0,0 +1,525 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "processfrontend.h" +#include "processbackend.h" + +#include <QDateTime> + +/***************************************************************************************/ + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class ProcessFrontend + \brief The ProcessFrontend class is a generalized representation of a process. + + The ProcessFrontend class encapsulates a process whose lifecycle is controlled by the process manager. + It is a generalized representation - in actuality, subclasses of the ProcessFrontend class are used + to control processes. These subclasses are used to accelerate process launching, wrap + processes with additional arguments and settings, or fake the existence of a process. + The ProcessFrontend class itself cannot be instantiated. + + A process object always contains a ProcessInfo object, which is the static information + used to start and execute the process. +*/ + +/*! + \property ProcessFrontend::identifier + \brief the application identifier of the process. +*/ + +/*! + \property ProcessFrontend::name + \brief the application name of the process. +*/ + +/*! + \property ProcessFrontend::program + \brief the filename of the binary executable that is launched to start up the process. +*/ + +/*! + \property ProcessFrontend::arguments + \brief the arguments that will be passed to the program upon process startup +*/ + +/*! + \property ProcessFrontend::environment + \brief a map of the environment variables that will be used by the process +*/ + +/*! + \property ProcessFrontend::workingDirectory + \brief the directory that will be switched to before launching the process +*/ + +/*! + \property ProcessFrontend::uid + \brief the user id (uid) of the process. +*/ + +/*! + \property ProcessFrontend::gid + \brief the group id (gid) of the process. +*/ + +/*! + \property ProcessFrontend::pid + \brief the process id (PID) of the process. + + Returns 0 if the process has not been started or if this is a "fake" process. +*/ + +/*! + \property ProcessFrontend::startTime + \brief the start time of the process, measured in milliseconds since the epoch (1st Jan 1970 00:00). + + Returns 0 if process has not been started. +*/ + +/*! + \property ProcessFrontend::priority + \brief The Unix process priority (niceness). + + Returns the current process priority if the process is running. Otherwise, + it returns the ProcessFrontend priority setting. You can only set the priority once the + process is running. +*/ + +/*! + \property ProcessFrontend::oomAdjustment + \brief The Unix process /proc/<pid>/oom_score_adj (likelihood of being killed) + + Returns the current OOM adjustment score if the process is running. Otherwise, + it returns the ProcessFrontend OOM adjustment score setting. You can only set the OOM adjustment + score when the process is running. +*/ + +/*! + \internal + Constructs a ProcessFrontend instance with ProcessBackend \a process and optional \a parent + The ProcessFrontend takes ownership of the ProcessBackend. +*/ +ProcessFrontend::ProcessFrontend(ProcessBackend *backend, QObject *parent) + : QObject(parent) + , m_startTimeSinceEpoch(0) + , m_backend(backend) +{ + Q_ASSERT(backend); + backend->setParent(this); + connect(backend, SIGNAL(started()), SLOT(handleStarted())); + connect(backend, SIGNAL(error(QProcess::ProcessError)), SLOT(handleError(QProcess::ProcessError))); + connect(backend, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(handleFinished(int, QProcess::ExitStatus))); + connect(backend, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(handleStateChanged(QProcess::ProcessState))); + connect(backend, SIGNAL(standardOutput(const QByteArray&)), + SLOT(handleStandardOutput(const QByteArray&))); + connect(backend, SIGNAL(standardError(const QByteArray&)), + SLOT(handleStandardError(const QByteArray&))); +} + +/*! + Destroy this process object. +*/ + +ProcessFrontend::~ProcessFrontend() +{ +} + +/*! + Return the process identifier +*/ + +QString ProcessFrontend::identifier() const +{ + Q_ASSERT(m_backend); + return m_backend->identifier(); +} + +/*! + Return the process name +*/ + +QString ProcessFrontend::name() const +{ + Q_ASSERT(m_backend); + return m_backend->name(); +} + +/*! + Return the process program +*/ + +QString ProcessFrontend::program() const +{ + Q_ASSERT(m_backend); + return m_backend->program(); +} + +/*! + Return the process arguments +*/ + +QStringList ProcessFrontend::arguments() const +{ + Q_ASSERT(m_backend); + return m_backend->arguments(); +} + +/*! + Return the process environment +*/ + +QVariantMap ProcessFrontend::environment() const +{ + Q_ASSERT(m_backend); + return m_backend->environment(); +} + +/*! + Return the process working directory +*/ + +QString ProcessFrontend::workingDirectory() const +{ + Q_ASSERT(m_backend); + return m_backend->workingDirectory(); +} + +/*! + Return the process UID +*/ + +qint64 ProcessFrontend::uid() const +{ + Q_ASSERT(m_backend); + return m_backend->uid(); +} + +/*! + Return the process GID +*/ + +qint64 ProcessFrontend::gid() const +{ + Q_ASSERT(m_backend); + return m_backend->gid(); +} + +/*! + Returns the PID of this process. If the process has not started up yet properly, its PID will be 0. +*/ +Q_PID ProcessFrontend::pid() const +{ + Q_ASSERT(m_backend); + return m_backend->pid(); +} + +/*! + Return the current process priority. + This may be different than the desired process priority. +*/ + +qint32 ProcessFrontend::priority() const +{ + Q_ASSERT(m_backend); + return m_backend->actualPriority(); +} + +/*! + Set the process priority +*/ + +void ProcessFrontend::setPriority(qint32 priority) +{ + Q_ASSERT(m_backend); + if (priority != m_backend->desiredPriority()) { + m_backend->setDesiredPriority(priority); + emit priorityChanged(); + } +} + +/*! + Return the process actual oomAdjustment +*/ + +qint32 ProcessFrontend::oomAdjustment() const +{ + Q_ASSERT(m_backend); + return m_backend->actualOomAdjustment(); +} + +/*! + Set the process desired oomAdjustment. + This may be different than the actual process oomAdjustment +*/ + +void ProcessFrontend::setOomAdjustment(qint32 oomAdjustment) +{ + Q_ASSERT(m_backend); + if (oomAdjustment != m_backend->desiredOomAdjustment()) { + m_backend->setDesiredOomAdjustment(oomAdjustment); + emit oomAdjustmentChanged(); + } +} + +/*! + Returns the state of the process. + The base class always returns NotRunning. +*/ +QProcess::ProcessState ProcessFrontend::state() const +{ + Q_ASSERT(m_backend); + return m_backend->state(); +} + +/*! + \brief Starts the process. + + After the process is started, the started() signal is emitted. + If a process fails to start (e.g. because it doesn't give any output until timeout) then + the (partly) started process is killed and error() is emitted. + + This function must be overridden in subclasses. + + \sa started() + \sa error() +*/ +void ProcessFrontend::start() +{ + if (state() == QProcess::NotRunning) { + Q_ASSERT(m_backend); + emit aboutToStart(); + m_startTimeSinceEpoch = QDateTime::currentMSecsSinceEpoch(); + m_backend->start(); + } +} + +/*! + Attempts to stop a process by giving it a \a timeout time to die, measured in milliseconds. + + If the process does not die in the given time limit, it is killed. + + \sa finished() +*/ +void ProcessFrontend::stop(int timeout) +{ + Q_ASSERT(m_backend); + emit aboutToStop(); + m_backend->stop(timeout); +} + +/*! + Writes at most \a maxSize bytes of data from \a data to the process. + Returns the number of bytes that were actually written, or -1 if an error occurred. + */ + +qint64 ProcessFrontend::write(const char *data, qint64 maxSize) +{ + return m_backend->write(data, maxSize); +} + +/*! + Writes \a data from a zero-terminated string of 8-bit characters to the process. + Returns the number of bytes that were actually written, or -1 if an error occurred. + This is equivalent to + +\code + ProcessFrontend::write(data, qstrlen(data)); +\endcode + */ + +qint64 ProcessFrontend::write(const char *data) +{ + return m_backend->write(data, qstrlen(data)); +} + +/*! + Writes the content of \a byteArray to the process. + Returns the number of bytes that were actually written, or -1 if an error occurred. + */ + +qint64 ProcessFrontend::write(const QByteArray& byteArray) +{ + return m_backend->write(byteArray.data(), byteArray.length()); +} + +/*! + Returns the start time of the process, measured in milliseconds since the epoch (1st Jan 1970 00:00). +*/ +qint64 ProcessFrontend::startTime() const +{ + return m_startTimeSinceEpoch; +} + +/*! + Returns the ProcessInfo object as a QVariantMap. + + If you use this method to retrieve the ProcessInfo content from the C++ side, you can + wrap it around ProcessInfo() object for convenience. +*/ +QVariantMap ProcessFrontend::processInfo() const +{ + return m_backend->processInfo().toMap(); +} + +/*! + Handle a started() signal from the backend. + The default implementation emits the started() signal. + */ +void ProcessFrontend::handleStarted() +{ + emit started(); +} + +/*! + Handle an error() signal from the backend with \a processError. + The default implementation emits the error signal. + */ +void ProcessFrontend::handleError(QProcess::ProcessError processError) +{ + emit error(processError); +} + +/*! + Handle a finished() signal from the backend with \a exitCode and \a exitStatus. + The default implementation emits the finished() signal + */ +void ProcessFrontend::handleFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + emit finished(exitCode, exitStatus); +} + +/*! + Handle a stateChange() signal from the backend with \a state. + The default implementation emits the stateChanged() signal + */ +void ProcessFrontend::handleStateChanged(QProcess::ProcessState state) +{ + emit stateChanged(state); +} + +/*! + Handle stdout \a data received from the process. + */ +void ProcessFrontend::handleStandardOutput(const QByteArray& data) +{ + emit standardOutput(data); +} + +/*! + Handle stderr \a data received from the process. + */ +void ProcessFrontend::handleStandardError(const QByteArray& data) +{ + emit standardError(data); +} + +/*! + Returns the backend object for this process. +*/ +ProcessBackend *ProcessFrontend::backend() const +{ + return m_backend; +} + + +/*! + \fn void ProcessFrontend::aboutToStart() + This signal is emitted by start() just before the process starts running. +*/ + +/*! + \fn void ProcessFrontend::aboutToStop() + This signal is emitted by stop() just before the process is stopped. +*/ + +/*! + \fn void ProcessFrontend::started() + This signal is emitted when the proces has started successfully. +*/ + +/*! + \fn void ProcessFrontend::error(QProcess::ProcessError error) + This signal is emitted when the process has failed to start or has another \a error. +*/ + +/*! + \fn void ProcessFrontend::finished(int exitCode, QProcess::ExitStatus exitStatus) + This signal is emitted when the process has stopped and its execution is over. + The \a exitStatus tells you how the process exited; the \a exitCode is the + return value of the process. + + Please note: The \a exitStatus will be QProcess::CrashExit only if the + ProcessManager stops the process externally. A normal process that crashes + will have an \a exitStatus of QProcess::NormalExit with a non-zero \a exitCode. +*/ + +/*! + \fn void ProcessFrontend::stateChanged(QProcess::ProcessState newState) + This signal is emitted whenever the state of QProcess changes. The \a newState argument + is the state the internal QProcess changed to. +*/ + +/*! + \fn void ProcessFrontend::standardOutput(const QByteArray& data) + This signal is emitted whenever \a data is received from the stdout of the process. +*/ + +/*! + \fn void ProcessFrontend::standardError(const QByteArray& data) + This signal is emitted whenever \a data is received from the stderr of the process. +*/ + +/*! + \fn void ProcessFrontend::priorityChanged() + This signal is emitted when the process priority has been changed for a running process. +*/ + +/*! + \fn void ProcessFrontend::oomAdjustmentChanged() + This signal is emitted when the process oomAdjustment has been changed for a running process. + Only applicable under Linux. +*/ + +#include "moc_processfrontend.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/processfrontend.h b/src/core/processfrontend.h new file mode 100644 index 0000000..a7a8a6a --- /dev/null +++ b/src/core/processfrontend.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef PROCESS_FRONTEND_H +#define PROCESS_FRONTEND_H + +#include <QObject> +#include "processinfo.h" + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessBackend; + +class ProcessFrontend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString identifier READ identifier CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString program READ program CONSTANT) + Q_PROPERTY(QStringList arguments READ arguments CONSTANT) + Q_PROPERTY(QVariantMap environment READ environment CONSTANT) + Q_PROPERTY(QString workingDirectory READ workingDirectory CONSTANT) + Q_PROPERTY(qint64 uid READ uid NOTIFY started) + Q_PROPERTY(qint64 gid READ gid NOTIFY started) + + Q_PROPERTY(qint64 pid READ pid NOTIFY started) + Q_PROPERTY(qint64 startTime READ startTime NOTIFY started) + + Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged) + Q_PROPERTY(int oomAdjustment READ oomAdjustment WRITE setOomAdjustment NOTIFY oomAdjustmentChanged) + +public: + virtual ~ProcessFrontend(); + + QString identifier() const; + QString name() const; + QString program() const; + QStringList arguments() const; + QVariantMap environment() const; + QString workingDirectory() const; + + qint64 uid() const; + qint64 gid() const; + Q_PID pid() const; + + qint32 priority() const; + void setPriority(qint32); + + qint32 oomAdjustment() const; + void setOomAdjustment(qint32); + + QProcess::ProcessState state() const; + Q_INVOKABLE virtual void start(); + Q_INVOKABLE virtual void stop(int timeout = 500); + + qint64 write(const char *data, qint64 maxSize); + qint64 write(const char *data); + qint64 write(const QByteArray& byteArray); + + qint64 startTime() const; + + Q_INVOKABLE QVariantMap processInfo() const; + +signals: + void aboutToStart(); + void aboutToStop(); + + void started(); + void error(QProcess::ProcessError); + void finished(int, QProcess::ExitStatus); + void stateChanged(QProcess::ProcessState); + void standardOutput(const QByteArray&); + void standardError(const QByteArray&); + + void priorityChanged(); + void oomAdjustmentChanged(); + +protected slots: + void handleStarted(); + void handleError(QProcess::ProcessError); + void handleFinished(int, QProcess::ExitStatus); + void handleStateChanged(QProcess::ProcessState); + +protected: + ProcessFrontend(ProcessBackend *backend, QObject *parent=0); + ProcessBackend *backend() const; + +private slots: + void handleStandardOutput(const QByteArray&); + void handleStandardError(const QByteArray&); + +protected: + qint64 m_startTimeSinceEpoch; + +private: + ProcessBackend *m_backend; + + friend class ProcessManager; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE_PROCESSMANAGER(ProcessFrontend)*) +Q_DECLARE_METATYPE(const QT_PREPEND_NAMESPACE_PROCESSMANAGER(ProcessFrontend)*) + +#endif // PROCESS_FRONTEND_H diff --git a/src/core/processinfo.cpp b/src/core/processinfo.cpp new file mode 100644 index 0000000..1f16954 --- /dev/null +++ b/src/core/processinfo.cpp @@ -0,0 +1,551 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "processinfo.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \namespace ProcessInfoConstants + \target ProcessInfoConstants Namespace + + \brief The ProcessInfoConstants namespace contains constant strings for ProcessInfo. +*/ + +/*! + \class ProcessInfo + \brief The ProcessInfo class is the set of information necessary to start a new process running. + + The ProcessInfo is internally implemented as a simple QVariantMap with a set of pre-defined + keys. This intentionally allows the ProcessInfo to be extended by the user without + changing the API. The predefined keys are: + + \list + \o Identifier + \o Program + \o Arguments + \o Environment + \o WorkingDirectory + \o UID + \o GID + \o Priority + \o OomAdjustment + \endlist +*/ + +/*! + \property ProcessInfo::identifier + \brief the application identifier of the process. The identifier is used to construct + a unique name. +*/ + +/*! + \property ProcessInfo::program + \brief the filename of the binary executable that is launched to start up the process. +*/ + +/*! + \property ProcessInfo::arguments + \brief the arguments that will be passed to the program upon process startup +*/ + +/*! + \property ProcessInfo::environment + \brief a map of the environment variables that will be used by the process +*/ + +/*! + \property ProcessInfo::workingDirectory + \brief the directory that will be switched to before launching the process +*/ + +/*! + \property ProcessInfo::uid + \brief the user id (uid) of the process. +*/ + +/*! + \property ProcessInfo::gid + \brief the group id (gid) of the process. +*/ + +/*! + \property ProcessInfo::priority + \brief the Unix priority "niceness" that the program will run at. +*/ + +/*! + \property ProcessInfo::oomAdjustment + \brief the Unix OOM adjustment that programs run at (+1000 = kill me first) +*/ +/*! + \property ProcessInfo::startOutputPattern + \brief the start output pattern is QByteArray of a line to match. +*/ + + +/*! + Constructs a ProcessInfo instance with optional \a parent. +*/ + +ProcessInfo::ProcessInfo(QObject *parent) + : QObject(parent) +{ +} + +/*! + Copy constructor for ProcessInfo from \a other +*/ + +ProcessInfo::ProcessInfo(const ProcessInfo &other) + : QObject(other.parent()) + , m_info(other.m_info) +{ +} + +/*! + Copy constructor for ProcessInfo from \a map +*/ + +ProcessInfo::ProcessInfo(const QVariantMap& map) + : m_info(map) +{ +} + +/*! + Assignment constructor for ProcessInfo from \a other +*/ + +ProcessInfo &ProcessInfo::operator =(const ProcessInfo &other) +{ + m_info = other.m_info; + return *this; +} + +/*! + Returns the application identifier of the process. The identifier + is used to construct a unique name for the process. +*/ + +QString ProcessInfo::identifier() const +{ + return m_info.value(ProcessInfoConstants::Identifier).toString(); +} + +/*! + Set the application identifier of this process. +*/ + +void ProcessInfo::setIdentifier(const QString &identifier) +{ + setValue(ProcessInfoConstants::Identifier, identifier); +} + +/*! + Returns the filename of the binary executable that is launched. +*/ + +QString ProcessInfo::program() const +{ + return m_info.value(ProcessInfoConstants::Program).toString(); +} + +/*! + Set the filename of the binary executable to be launched. +*/ + +void ProcessInfo::setProgram(const QString &program) +{ + m_info.insert(ProcessInfoConstants::Program, program); +} + +/*! + Return the argument list. +*/ + +QStringList ProcessInfo::arguments() const +{ + return m_info.value(ProcessInfoConstants::Arguments).toStringList(); +} + +/*! + Set the argument list to be passed to the program +*/ + +void ProcessInfo::setArguments(const QStringList &argumentList) +{ + setValue(ProcessInfoConstants::Arguments, argumentList); +} + +/*! + Return the environment map +*/ + +QVariantMap ProcessInfo::environment() const +{ + return m_info.value(ProcessInfoConstants::Environment).toMap(); +} + +/*! + Set the environment map +*/ + +void ProcessInfo::setEnvironment(const QVariantMap &environmentToSet) +{ + setValue(ProcessInfoConstants::Environment, environmentToSet); +} + +/*! + Set the environment map using a QProcessEnvironment +*/ + +void ProcessInfo::setEnvironment(const QProcessEnvironment &environment) +{ + QVariantMap env; + QStringList keys = environment.keys(); + + foreach (const QString &envKey, keys) { + env.insert(envKey, environment.value(envKey)); + } + + setValue(ProcessInfoConstants::Environment, env); +} + +/*! + Return the process working directory +*/ + +QString ProcessInfo::workingDirectory() const +{ + return m_info.value(ProcessInfoConstants::WorkingDirectory).toString(); +} + +/*! + Set the process working directory +*/ + +void ProcessInfo::setWorkingDirectory(const QString &workingDir) +{ + setValue(ProcessInfoConstants::WorkingDirectory, workingDir); +} + +/*! + Return the process UID + + Initializing this value to -1 will result in it being set to the process manager's uid. +*/ + +qint64 ProcessInfo::uid() const +{ + return m_info.value(ProcessInfoConstants::Uid).toLongLong(); +} + +/*! + Set the process UID +*/ + +void ProcessInfo::setUid(qint64 newUid) +{ + setValue(ProcessInfoConstants::Uid, newUid); +} + +/*! + Return the process GID + + Initializing this value to -1 (or leaving it uninitialized) will result in it being + set to the process manager's gid. +*/ + +qint64 ProcessInfo::gid() const +{ + return m_info.value(ProcessInfoConstants::Priority).toLongLong(); +} + +/*! + Set the process GID +*/ + +void ProcessInfo::setGid(qint64 newGid) +{ + setValue(ProcessInfoConstants::Gid, newGid); +} + +/*! + Return the process priority +*/ + +int ProcessInfo::priority() const +{ + return m_info.value(ProcessInfoConstants::Priority).toDouble(); +} + +/*! + Return the OOM adjustment value +*/ + +int ProcessInfo::oomAdjustment() const +{ + return m_info.value(ProcessInfoConstants::OomAdjustment).toDouble(); +} + +/*! + Set the process priority. + + Normally the priority ranges from -20 to +19, but that + can vary based on the type of Unix system. A priority of -20 + delivers most of the CPU to this process; a priority of +19 + is the lowest and means that this process will be starved unless + nothing else is going on in the sytem. + + We do not do any checking on the value set for process priority. +*/ + +void ProcessInfo::setPriority(int newPriority) +{ + if (newPriority != priority()) { + setValue(ProcessInfoConstants::Priority, newPriority); + emit priorityChanged(); + } +} + +/*! + Set the process OOM adjustment. + + In modern Linux kernels, the /proc/<pid>/oom_score_adj value + may range from -1000 to +1000. A -1000 value represents a process + that will never be killed; a 0 is a process that follows normal + OOM killer values, and a +1000 represents a process that will + always be killed first. + + We do not do any checking on the value set for process priority. +*/ + +void ProcessInfo::setOomAdjustment(int newOomAdjustment) +{ + if (newOomAdjustment != oomAdjustment()) { + setValue(ProcessInfoConstants::OomAdjustment, newOomAdjustment); + emit oomAdjustmentChanged(); + } +} + +/*! + Returns the start output pattern. + + \sa setStartOutputPattern +*/ +QByteArray ProcessInfo::startOutputPattern() const +{ + return m_info.value(ProcessInfoConstants::StartOutputPattern).toByteArray(); +} + +/*! + Sets the start output pattern to \a outputPattern. + + The start output pattern is a string that the process should print when + it considers itself ready. Typically, a process is ready after it has + started up and performed its initialization successfully. +*/ +void ProcessInfo::setStartOutputPattern(const QByteArray &outputPattern) +{ + setValue(ProcessInfoConstants::StartOutputPattern, outputPattern); +} + +/*! + Returns the keys for which values have been set in this ProcessInfo object. +*/ +QStringList ProcessInfo::keys() const +{ + return m_info.keys(); +} + +/*! + Return true if a value has been set for this \a key +*/ + +bool ProcessInfo::contains(const QString &key) const +{ + return m_info.contains(key); +} + +/*! + Return the value of this \a key +*/ + +QVariant ProcessInfo::value(const QString &key) const +{ + return m_info.value(key); +} + +/*! + Set \a key to \a value and emit appropriate change signals +*/ + +void ProcessInfo::setValue(const QString &key, const QVariant &value) +{ + if (key.isEmpty()) + return; + + QVariant oldValue = m_info.value(key); + if (oldValue != value) { + m_info.insert(key, value); + emitChangeSignal(key); + } +} + +/*! + Sets the data provided by this ProcessInfo object to \a data. + + Overwrites all existing values. +*/ +void ProcessInfo::setData(const QVariantMap &data) +{ + m_info = data; +} + +/*! + Applies data provided by \a data on top of the existing data. + + Overwrites existing values if \a data contains the same keys as the + original data. Leaves existing values with keys not contained by \a data + untouched. +*/ +void ProcessInfo::insert(const QVariantMap &data) +{ + QStringList keys = data.keys(); + foreach (const QString &key, keys) { + m_info.insert(key, data.value(key)); + } +} + +/*! + Return the ProcessInfo object as a QVariantMap +*/ +QVariantMap ProcessInfo::toMap() const +{ + return m_info; +} + +/*! + \internal +*/ + +void ProcessInfo::emitChangeSignal(const QString &key) +{ + if (key == ProcessInfoConstants::Identifier) { + emit identifierChanged(); + } else if (key == ProcessInfoConstants::Program) { + emit programChanged(); + } else if (key == ProcessInfoConstants::Arguments) { + emit argumentsChanged(); + } else if (key == ProcessInfoConstants::Environment) { + emit environmentChanged(); + } else if (key == ProcessInfoConstants::WorkingDirectory) { + emit workingDirectoryChanged(); + } else if (key == ProcessInfoConstants::Uid) { + emit uidChanged(); + } else if (key == ProcessInfoConstants::Gid) { + emit gidChanged(); + } else if (key == ProcessInfoConstants::Priority) { + emit priorityChanged(); + } else if (key == ProcessInfoConstants::OomAdjustment) { + emit oomAdjustmentChanged(); + } else if (key == ProcessInfoConstants::StartOutputPattern) { + emit startOutputPatternChanged(); + } +} + +/*! + Add \a newEnvironment to the current environment settings. +*/ + +void ProcessInfo::insertEnvironment(const QVariantMap &newEnvironment) +{ + QVariantMap env = environment(); + QMapIterator<QString, QVariant> it(newEnvironment); + while (it.hasNext()) { + it.next(); + env.insert(it.key(), it.value()); + } + setEnvironment(env); +} + +/*! + \fn void ProcessInfo::identifierChanged() + This signal is emitted when the process Identifier has been changed +*/ +/*! + \fn void ProcessInfo::programChanged() + This signal is emitted when the process Program has been changed +*/ +/*! + \fn void ProcessInfo::argumentsChanged() + This signal is emitted when the process Arguments has been changed +*/ +/*! + \fn void ProcessInfo::environmentChanged() + This signal is emitted when the process Environment has been changed +*/ +/*! + \fn void ProcessInfo::workingDirectoryChanged() + This signal is emitted when the process WorkingDirectory has been changed +*/ +/*! + \fn void ProcessInfo::uidChanged() + This signal is emitted when the process UID has been changed +*/ +/*! + \fn void ProcessInfo::gidChanged() + This signal is emitted when the process GID has been changed +*/ +/*! + \fn void ProcessInfo::priorityChanged() + This signal is emitted when the process Priority has been changed +*/ +/*! + \fn void ProcessInfo::oomAdjustmentChanged() + This signal is emitted when the process OOM adjustment has been changed +*/ +/*! + \fn void ProcessInfo::startOutputPatternChanged() + This signal is emitted when the startOutputPattern has been changed. +*/ + +#include "moc_processinfo.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/processinfo.h b/src/core/processinfo.h new file mode 100644 index 0000000..cae23b3 --- /dev/null +++ b/src/core/processinfo.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCESSINFO_H +#define PROCESSINFO_H + +#include <QObject> +#include <QVariant> +#include <QStringList> +#include <QProcessEnvironment> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +namespace ProcessInfoConstants { +const QLatin1String Identifier = QLatin1String("identifier"); +const QLatin1String Program = QLatin1String("program"); +const QLatin1String Arguments = QLatin1String("arguments"); +const QLatin1String Environment = QLatin1String("environment"); +const QLatin1String WorkingDirectory = QLatin1String("workingDirectory"); +const QLatin1String Uid = QLatin1String("uid"); +const QLatin1String Gid = QLatin1String("gid"); +const QLatin1String Priority = QLatin1String("priority"); +const QLatin1String OomAdjustment = QLatin1String("oomAdjustment"); +const QLatin1String StartOutputPattern = QLatin1String("startOutputPattern"); +} + +class ProcessInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) + Q_PROPERTY(QString program READ program WRITE setProgram NOTIFY programChanged) + Q_PROPERTY(QStringList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) + Q_PROPERTY(QVariantMap environment READ environment WRITE setEnvironment NOTIFY environmentChanged) + Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged) + Q_PROPERTY(qint64 uid READ uid WRITE setUid NOTIFY uidChanged) + Q_PROPERTY(qint64 gid READ gid WRITE setGid NOTIFY gidChanged) + Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged) + Q_PROPERTY(int oomAdjustment READ oomAdjustment WRITE setOomAdjustment NOTIFY oomAdjustmentChanged) + Q_PROPERTY(QByteArray startOutputPattern READ startOutputPattern WRITE setStartOutputPattern NOTIFY startOutputPatternChanged) +public: + explicit ProcessInfo(QObject *parent = 0); + ProcessInfo(const ProcessInfo &other); + ProcessInfo(const QVariantMap& map); + ProcessInfo &operator =(const ProcessInfo &other); + + QString identifier() const; + void setIdentifier(const QString &identifier); + + QString program() const; + void setProgram(const QString &program); + + QStringList arguments() const; + void setArguments(const QStringList &arguments); + + QVariantMap environment() const; + void setEnvironment(const QProcessEnvironment &environment); + Q_INVOKABLE void setEnvironment(const QVariantMap &environment); + Q_INVOKABLE void insertEnvironment(const QVariantMap &environment); + + QString workingDirectory() const; + void setWorkingDirectory(const QString &workingDir); + + qint64 uid() const; + void setUid(qint64 uid); + + qint64 gid() const; + void setGid(qint64 gid); + + int priority() const; + void setPriority(int priority); + + int oomAdjustment() const; + void setOomAdjustment(int oomAdjustment); + + QByteArray startOutputPattern() const; + void setStartOutputPattern(const QByteArray &outputPattern); + + Q_INVOKABLE QStringList keys() const; + Q_INVOKABLE bool contains(const QString &key) const; + Q_INVOKABLE QVariant value(const QString &key) const; + Q_INVOKABLE void setValue(const QString &key, const QVariant &value); + Q_INVOKABLE void setData(const QVariantMap &data); + Q_INVOKABLE void insert(const QVariantMap &data); + QVariantMap toMap() const; + +signals: + void identifierChanged(); + void programChanged(); + void argumentsChanged(); + void environmentChanged(); + void workingDirectoryChanged(); + void uidChanged(); + void gidChanged(); + void priorityChanged(); + void oomAdjustmentChanged(); + void startOutputPatternChanged(); + +public slots: + +protected: + virtual void emitChangeSignal(const QString &key); + +private: + QVariantMap m_info; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE_PROCESSMANAGER(ProcessInfo)*) + +#endif // PROCESSINFO_H diff --git a/src/core/processmanager-global.h b/src/core/processmanager-global.h new file mode 100644 index 0000000..34a6144 --- /dev/null +++ b/src/core/processmanager-global.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtAddOn.ProcessManager module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef PROCESSMANAGER_GLOBAL_H +#define PROCESSMANAGER_GLOBAL_H + +#include "qglobal.h" + +#if defined(QT_ADDON_PROCESSMANAGER_LIB) +# define Q_ADDON_PROCESSMANAGER_EXPORT Q_DECL_EXPORT +#else +# define Q_ADDON_PROCESSMANAGER_EXPORT Q_DECL_IMPORT +#endif + +#if defined(QT_NAMESPACE) +# define QT_BEGIN_NAMESPACE_PROCESSMANAGER namespace QT_NAMESPACE { namespace QtAddOn { namespace ProcessManager { +# define QT_END_NAMESPACE_PROCESSMANAGER } } } +# define QT_USE_NAMESPACE_PROCESSMANAGER using namespace QT_NAMESPACE::QtAddOn::ProcessManager; +# define QT_PREPEND_NAMESPACE_PROCESSMANAGER(name) ::QT_NAMESPACE::QtAddOn::ProcessManager::name +#else +# define QT_BEGIN_NAMESPACE_PROCESSMANAGER namespace QtAddOn { namespace ProcessManager { +# define QT_END_NAMESPACE_PROCESSMANAGER } } +# define QT_USE_NAMESPACE_PROCESSMANAGER using namespace QtAddOn::ProcessManager; +# define QT_PREPEND_NAMESPACE_PROCESSMANAGER(name) ::QtAddOn::ProcessManager::name +#endif + +// a workaround for moc - if there is a header file that doesn't use processmanager +// namespace, we still force moc to do "using namespace" but the namespace have to +// be defined, so let's define an empty namespace here +QT_BEGIN_NAMESPACE_PROCESSMANAGER +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PROCESSMANAGER_GLOBAL_H diff --git a/src/core/processmanager.cpp b/src/core/processmanager.cpp new file mode 100644 index 0000000..d4af3b1 --- /dev/null +++ b/src/core/processmanager.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "processmanager.h" +#include "processfrontend.h" +#include "processbackendfactory.h" +#include "processbackendmanager.h" +#include "processbackend.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class ProcessManager + \brief The ProcessManager class encapsulates ways of creating and tracking processes. +*/ + +/*! + \property ProcessManager::memoryRestricted + \brief the current setting of memory restriction +*/ + +/*! + Construct a ProcessManager with an optional \a parent +*/ + +ProcessManager::ProcessManager(QObject *parent) + : QObject(parent) +{ + m_backend = new ProcessBackendManager(this); +} + +/*! + Delete the ProcessManager and terminate all processes + The ProcessManager is the parent of all factories and process frontends. +*/ + +ProcessManager::~ProcessManager() +{ +} + +/*! + Create a new ProcessFrontend based on ProcessInfo \a info + The parent of the ProcessFrontend is the ProcessManager. +*/ + +ProcessFrontend *ProcessManager::create(const ProcessInfo& info) +{ + ProcessBackend *backend = m_backend->create(info); + if (!backend) + return NULL; + ProcessFrontend *frontend = createFrontend(backend); + frontend->setParent(this); + m_processlist.append(frontend); + connect(frontend, SIGNAL(aboutToStart()), SLOT(processFrontendAboutToStart())); + connect(frontend, SIGNAL(aboutToStop()), SLOT(processFrontendAboutToStop())); + connect(frontend, SIGNAL(started()), SLOT(processFrontendStarted())); + connect(frontend, SIGNAL(error(QProcess::ProcessError)), SLOT(processFrontendError(QProcess::ProcessError))); + connect(frontend, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(processFrontendFinished(int, QProcess::ExitStatus))); + connect(frontend, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(processFrontendStateChanged(QProcess::ProcessState))); + connect(frontend, SIGNAL(destroyed()), SLOT(processFrontendDestroyed())); + return frontend; +} + +/*! + Create a new ProcessFrontend based on QVariantMap \a info + (which should look a lot like ProcessInfo). +*/ + +ProcessFrontend *ProcessManager::create(const QVariantMap& info) +{ + return create(ProcessInfo(info)); +} + +/*! + Create a new ProcessFrontend for a \a backend. + Override this function if you need to subclass ProcessFrontend to + store additional process information or functions. +*/ + +ProcessFrontend *ProcessManager::createFrontend(ProcessBackend *backend) +{ + return new ProcessFrontend(backend); +} + +/*! + Returns a process that matches the name \a name. + + Each process started with the ProcessManager has uniqut application name. + If the method returns 0, + the name is either invalid or the process has been killed. +*/ +ProcessFrontend *ProcessManager::processForName(const QString &name) const +{ + foreach (ProcessFrontend *frontend, m_processlist) + if (frontend->name() == name) + return frontend; + return NULL; +} + +/*! + Returns a process that matches the process id \a pid. Returns NULL if no such process was found. + Returns NULL if \a pid is 0. + If more than one process matches the same ID, this method will return the first one find. +*/ + +ProcessFrontend *ProcessManager::processForPID(qint64 pid) const +{ + if (!pid) + return NULL; + + foreach (ProcessFrontend *frontend, m_processlist) + if (frontend->pid() == pid) + return frontend; + return NULL; +} + +/*! + Returns the number of processes. +*/ + +int ProcessManager::size() const +{ + return m_processlist.size(); +} + +/*! + Add a ProcessBackendFactory \a factory to the end of the Factory list. +*/ + +void ProcessManager::addBackendFactory(ProcessBackendFactory *factory) +{ + m_backend->addFactory(factory); +} + +/*! + Return a list of current process names +*/ + +QStringList ProcessManager::names() const +{ + QStringList result; + foreach (ProcessFrontend *frontend, m_processlist) + result << frontend->name(); + return result; +} + +/*! + Return a list of all internal processes being used by factories +*/ + +QList<Q_PID> ProcessManager::internalProcesses() +{ + return m_backend->internalProcesses(); +} + +/*! + Set memory restrictions. If \a memoryRestricted is true + all factories are requested to minimize memory use. +*/ + +void ProcessManager::setMemoryRestricted(bool memoryRestricted) +{ + m_backend->setMemoryRestricted(memoryRestricted); + emit memoryRestrictedChanged(); +} + +/*! + Return current memory restriction setting +*/ + +bool ProcessManager::memoryRestricted() const +{ + return m_backend->memoryRestricted(); +} + +/*! + Raise the processAboutToStart() signal. +*/ + +void ProcessManager::processFrontendAboutToStart() +{ +} + +/*! + Raise the processAboutToStop() signal. +*/ + +void ProcessManager::processFrontendAboutToStop() +{ +} + +/*! + Raise the processStarted() signal. +*/ + +void ProcessManager::processFrontendStarted() +{ +} + +/*! + Raise the processError() signal. + Pass through the \a error value. +*/ + +void ProcessManager::processFrontendError(QProcess::ProcessError error) +{ + Q_UNUSED(error); +} + +/*! + Raise the processFinished() signal. Pass through the + \a exitCode and \a exitStatus values. +*/ + +void ProcessManager::processFrontendFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + Q_UNUSED(exitCode); + Q_UNUSED(exitStatus); +} + +/*! + Raise the processStateChanged() signal. Pass through the + \a state value. +*/ + +void ProcessManager::processFrontendStateChanged(QProcess::ProcessState state) +{ + Q_UNUSED(state); +} + +/*! + Track a process being destroyed. Remote this process from our + list of valid process. Also emit the processDestroyed() signal. + If you override this function in a subclass, be sure to call + the parent function. +*/ + +void ProcessManager::processFrontendDestroyed() +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + m_processlist.removeAll(frontend); +} + +/*! + \fn void ProcessManager::memoryRestrictedChanged() + This signal is emitted when the memory restriction is changed +*/ + +#include "moc_processmanager.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/processmanager.h b/src/core/processmanager.h new file mode 100644 index 0000000..ff3020a --- /dev/null +++ b/src/core/processmanager.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCESSMANAGER_H +#define PROCESSMANAGER_H + +#include <QObject> +#include <QHash> +#include <QProcessEnvironment> + +#include "processmanager-global.h" +#include "process.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessFrontend; +class ProcessInfo; +class ProcessBackendFactory; +class ProcessBackendManager; +class ProcessBackend; + +class ProcessManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool memoryRestricted READ memoryRestricted + WRITE setMemoryRestricted NOTIFY memoryRestrictedChanged) +public: + explicit ProcessManager(QObject *parent = 0); + virtual ~ProcessManager(); + + Q_INVOKABLE ProcessFrontend *create(const ProcessInfo& info); + Q_INVOKABLE ProcessFrontend *create(const QVariantMap& info); + + Q_INVOKABLE QStringList names() const; + + Q_INVOKABLE ProcessFrontend *processForName(const QString &name) const; + Q_INVOKABLE ProcessFrontend *processForPID(qint64 PID) const; + Q_INVOKABLE int size() const; + + Q_INVOKABLE void addBackendFactory(ProcessBackendFactory *factory); + Q_INVOKABLE QList<Q_PID> internalProcesses(); + + void setMemoryRestricted(bool); + bool memoryRestricted() const; + +signals: + void memoryRestrictedChanged(); + +protected slots: + virtual void processFrontendAboutToStart(); + virtual void processFrontendAboutToStop(); + virtual void processFrontendStarted(); + virtual void processFrontendError(QProcess::ProcessError); + virtual void processFrontendFinished(int, QProcess::ExitStatus); + virtual void processFrontendStateChanged(QProcess::ProcessState); + virtual void processFrontendDestroyed(); + +protected: + virtual ProcessFrontend *createFrontend(ProcessBackend *backend); + +protected: + QList<ProcessFrontend*> m_processlist; + ProcessBackendManager *m_backend; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PROCESSMANAGER_H diff --git a/src/core/procutils.cpp b/src/core/procutils.cpp new file mode 100644 index 0000000..f6965b3 --- /dev/null +++ b/src/core/procutils.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "procutils.h" + +#include <cstdio> +#include <dirent.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <QFile> +#include <QFileInfo> +#include <QLocalSocket> + +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +ProcUtils::ProcUtils() +{ +} + +QString ProcUtils::execNameForPid(qint64 pid) +{ + enum { BUFFERSIZE = 1024 }; + char buf[BUFFERSIZE]; + QString fn = QLatin1String("/proc/") + QString::number(pid).toLatin1() + QLatin1String("/exe"); + ssize_t len = readlink(fn.toLatin1(), buf, sizeof(buf) - 1); + + if (len != -1) { + buf[len] = '\0'; + } else { + return QString(); + } + + return QString(buf); +} + +qint64 ProcUtils::ppidForPid(pid_t pid) +{ + QFile statFile(QLatin1String("/proc/") + QString::number(pid) + "/stat"); + statFile.open(QIODevice::ReadOnly); + + QByteArray contents = statFile.readAll(); + statFile.close(); + // 954 (ofono-jdb-daemo) S 1 + int readPid = 0; + int ppid = 0; + char strDummy[64]; + char state; + sscanf(contents.constData(), "%d %s %c %d %s", &readPid, strDummy, &state, &ppid, strDummy); + return ppid; +} + +qint64 ProcUtils::pidForFilename(const QString &filename) +{ + QFile file(filename); + if (!file.exists()) + return 0; + QFileInfo fileInfo(file); + + DIR *d = opendir(QLatin1String("/proc").latin1()); + if (!d) { + qWarning() << "failed to open proc"; + return 0; + } + + while (dirent *dirent = readdir(d)) { + QString dirname = QString::fromLatin1(dirent->d_name); + bool ok = false; + qint64 pid = dirname.toLongLong(&ok, 10); + if (ok) { + const QString execFilename = execNameForPid(pid); + if (execFilename == fileInfo.fileName()) + return pid; + } + } + closedir(d); + return 0; +} + +qint64 ProcUtils::pidForLocalSocket(const QLocalSocket *socket) +{ + if (socket) { + struct ucred cr; + socklen_t len = sizeof(struct ucred); + int r = ::getsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cr, &len); + if (r == 0) { + return (qint64)cr.pid; + } + } + return 0; +} + +QByteArray ProcUtils::cmdlineForPid(qint64 pid) +{ + QByteArray cmdline; + if (pid) { + QFile file(QLatin1String("/proc/") + QString::number(pid) + QLatin1String("/cmdline")); + file.open(QIODevice::ReadOnly); + cmdline = file.readAll(); + } + return cmdline; +} + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/procutils.h b/src/core/procutils.h new file mode 100644 index 0000000..4fe5d6f --- /dev/null +++ b/src/core/procutils.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCUTILS_H +#define PROCUTILS_H + +#include <QString> + +QT_FORWARD_DECLARE_CLASS(QLocalSocket) + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcUtils +{ + ProcUtils(); + Q_DISABLE_COPY(ProcUtils) +public: + static QString execNameForPid(qint64 pid); + static qint64 ppidForPid(pid_t pid); + static qint64 pidForFilename(const QString &filename); + static qint64 pidForLocalSocket(const QLocalSocket *socket); + static QByteArray cmdlineForPid(qint64 pid); +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PROCUTILS_H diff --git a/src/core/remoteprocessbackend.cpp b/src/core/remoteprocessbackend.cpp new file mode 100644 index 0000000..ce6db11 --- /dev/null +++ b/src/core/remoteprocessbackend.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "remoteprocessbackend.h" +#include <sys/resource.h> +#include <errno.h> +#include <signal.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +static const QLatin1String kCommand("command"); +static const QLatin1String kId("id"); +static const QLatin1String kSignal("signal"); + +/*! + \class RemoteProcessBackend + + \brief The RemoteProcessBackend class handles a process started by another process. + + The RemoteProcessBackend is used to control a process that was started by + a separate "controller" process. The RemoteProcessBackendFactory object handles + the pipe or socket communication with the remote controller. Communication is + carried over serialized JSON messages. +*/ + +/*! + Construct a RemoteProcessBackend with ProcessInfo \a info, \a factory, + \a id, and optional \a parent. +*/ + +RemoteProcessBackend::RemoteProcessBackend(const ProcessInfo& info, + RemoteProcessBackendFactory *factory, + int id, + QObject *parent) + : ProcessBackend(info, parent) + , m_factory(factory) + , m_state(QProcess::NotRunning) + , m_pid(-1) + , m_id(id) +{ + Q_ASSERT(factory); +} + +/*! + Destroy this process object. +*/ + +RemoteProcessBackend::~RemoteProcessBackend() +{ + if (m_factory) + m_factory->backendDestroyed(m_id); +} + +/*! + Returns the PID of this process if it is starting or running. + Return the default value if it is not. +*/ + +Q_PID RemoteProcessBackend::pid() const +{ + if (m_pid != -1) + return m_pid; + return ProcessBackend::pid(); +} + +/*! + Return the actual process priority (if running) +*/ + +qint32 RemoteProcessBackend::actualPriority() const +{ + if (m_pid != -1) { + errno = 0; // getpriority can return -1, so we clear errno + int result = getpriority(PRIO_PROCESS, m_pid); + if (!errno) + return result; + } + return ProcessBackend::actualPriority(); +} + +/*! + Set the process priority to \a priority +*/ + +void RemoteProcessBackend::setDesiredPriority(qint32 priority) +{ + ProcessBackend::setDesiredPriority(priority); + if (m_factory && m_pid != -1) { + QJsonObject object; + object.insert(kCommand, QLatin1String("set")); + object.insert(kId, m_id); + object.insert("key", QLatin1String("priority")); + object.insert("value", priority); + m_factory->send(object); + } +} + +#if defined(Q_OS_LINUX) + +/*! + Return the process oomAdjustment +*/ + +qint32 RemoteProcessBackend::actualOomAdjustment() const +{ + if (m_pid != -1) { + // ### TODO: Read correctly from /proc/<pid>/oom_score_adj + } + return ProcessBackend::actualOomAdjustment(); +} + +/*! + Set the process /proc/<pid>/oom_score_adj to \a oomAdjustment +*/ + +void RemoteProcessBackend::setDesiredOomAdjustment(qint32 oomAdjustment) +{ + ProcessBackend::setDesiredOomAdjustment(oomAdjustment); + if (m_factory && m_pid != -1) { + QJsonObject object; + object.insert(kCommand, QLatin1String("set")); + object.insert(kId, m_id); + object.insert("key", QLatin1String("oomAdjustment")); + object.insert("value", oomAdjustment); + m_factory->send(object); + } +} + +#endif // defined(Q_OS_LINUX) + + +/*! + Returns the state of the process. +*/ +QProcess::ProcessState RemoteProcessBackend::state() const +{ + return m_state; +} + +/*! + Start the process running +*/ + +void RemoteProcessBackend::start() +{ + if (m_factory) { + QJsonObject object; + object.insert(kCommand, QLatin1String("start")); + object.insert(kId, m_id); + object.insert(QLatin1String("info"), QJsonValue::fromVariant(m_info.toMap())); + m_factory->send(object); + } +} + +/*! + Attempts to stop a process by giving it a \a timeout time to die, measured in milliseconds. + If the process does not die in the given time limit, it is killed. + \sa finished() +*/ + +void RemoteProcessBackend::stop(int timeout) +{ + if (m_factory) { + QJsonObject object; + object.insert(kCommand, QLatin1String("stop")); + object.insert(kId, m_id); + object.insert(QLatin1String("timeout"), timeout); + } +} + +/*! + Writes at most \a maxSize bytes of data from \a data to the device. + Returns the number of bytes that were actually written, or -1 if an error occurred. + + This function isn't quite correct, because we have a JSON encoding problem. + For the moment we'll pretend that any data sent is actually something in + standard ASCII. +*/ +qint64 RemoteProcessBackend::write(const char *data, qint64 maxSize) +{ + if (m_factory) { + QJsonObject object; + object.insert(kCommand, QLatin1String("write")); + object.insert(kId, m_id); + object.insert(QLatin1String("data"), QString::fromLocal8Bit(data, maxSize)); + if (m_factory->send(object)) + return maxSize; + } + return -1; +} + + +/*! + Message received from the remote. + The \a message is encoded in JSON format. +*/ + +void RemoteProcessBackend::receive(const QJsonObject& message) +{ + QString event = message.value("event").toString(); + qDebug() << Q_FUNC_INFO << message; + if (event == "started") { + m_pid = message.value("pid").toDouble(); + emit started(); + } + else if (event == "error") { + emit error(static_cast<QProcess::ProcessError>(message.value("error").toDouble())); + } + else if (event == "finished") { + emit finished(message.value("exitCode").toDouble(), + static_cast<QProcess::ExitStatus>(message.value("exitStatus").toDouble())); + } + else if (event == "stateChanged") { + m_state = static_cast<QProcess::ProcessState>(message.value("stateChanged").toDouble()); + emit stateChanged(m_state); + } + else if (event == "output") { + if (message.contains("stdout")) { + handleStandardOutput(message.value("stdout").toString().toLocal8Bit()); + } + if (message.contains("stderr")) { + handleStandardError(message.value("stderr").toString().toLocal8Bit()); + } + } + else + qDebug() << Q_FUNC_INFO << "unrecognized message"; +} + +/*! + \internal +*/ + +void RemoteProcessBackend::killTimeout() +{ + if (m_factory) { + QJsonObject object; + object.insert(kCommand, kSignal); + object.insert(kId, m_id); + object.insert(kSignal, SIGKILL); + m_factory->send(object); + } +} + +/*! + \internal +*/ + +void RemoteProcessBackend::factoryDestroyed() +{ + m_factory = NULL; + if (m_state != QProcess::NotRunning) { + m_state = QProcess::NotRunning; + emit error(QProcess::UnknownError); + } +} + +#include "moc_remoteprocessbackend.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/remoteprocessbackend.h b/src/core/remoteprocessbackend.h new file mode 100644 index 0000000..371047a --- /dev/null +++ b/src/core/remoteprocessbackend.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef REMOTE_PROCESS_BACKEND_H +#define REMOTE_PROCESS_BACKEND_H + +#include <QJsonObject> + +#include "processmanager-global.h" +#include "processbackend.h" +#include "remoteprocessbackendfactory.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class RemoteProcessBackend : public ProcessBackend +{ + Q_OBJECT + +public: + RemoteProcessBackend(const ProcessInfo& info, RemoteProcessBackendFactory *factory, + int id, QObject *parent); + virtual ~RemoteProcessBackend(); + + virtual Q_PID pid() const; + virtual qint32 actualPriority() const; + virtual void setDesiredPriority(qint32); +#if defined(Q_OS_LINUX) + virtual qint32 actualOomAdjustment() const; + virtual void setDesiredOomAdjustment(qint32); +#endif + + virtual QProcess::ProcessState state() const; + virtual void start(); + virtual void stop(int timeout = 500); + virtual qint64 write(const char *data, qint64 maxSize); + +private: + friend class RemoteProcessBackendFactory; + void killTimeout(); + void receive(const QJsonObject&); + void factoryDestroyed(); + +private: + RemoteProcessBackendFactory *m_factory; + QProcess::ProcessState m_state; + Q_PID m_pid; + qint32 m_id; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // REMOTE_PROCESS_BACKEND_H diff --git a/src/core/remoteprocessbackendfactory.cpp b/src/core/remoteprocessbackendfactory.cpp new file mode 100644 index 0000000..149effb --- /dev/null +++ b/src/core/remoteprocessbackendfactory.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "remoteprocessbackendfactory.h" +#include "remoteprocessbackend.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +const int kRemoteTimerInterval = 1000; + +/*! + \class RemoteProcessBackendFactory + \brief The RemoteProcessBackendFactory class creates RemoteProcessBackend objects + + The RemoteProcessBackendFactory communicates with a remote process to control + other, child processes. + The factory communicates with the remote process by sending and receiving + JSON-formated messages. The remote process should respond to the following + commands: + + \table + \header + \o Command + \o Action + \row + \o \c{{ "command": "start", "id": NUM, "info": PROCESSINFO }} + \o Start a new process running. The \bold{token} attribute will + be used to refer to this process in the future. + \row + \o \c{{ "command": "stop", "id": NUM, "timeout": NUM }} + \o Stop the remote process with an optional timeout. + The remote process should be sent a SIGTERM, followed by + a SIGKILL after 'timeout' milliseconds + \row + \o \c{{ "command": "set", "id": NUM, "key": STRING, "value": NUM }} + \o Set a process key/value pair. Currently the \bold{key} can be + either "priority" or "oomAdjustment". + \row + \o \c{{ "command": "write", "id": NUM, "data": STRING }} + \o Write a data string to the remote process. We assume that the + data string is a valid local 8 bit string. + \endtable + + The following are events that are sent by the remote process + to the RemoteProcessBackendFactory: + + \table + \header + \o Event + \o Description + \row + \o \c{{ "event": "started", "id": NUM, "pid": NUM }} + \o This process has started. This maps to the QProcess::started() + signal, but also includes the PID of the new process. This should + be the first event returned after a \bold{start} command + \row + \o \c{{ "event": "finished", "id": NUM, "exitCode": NUM, "exitStatus": NUM }} + \o The process has exited. This is the last event returned for a given + process number. + \row + \o \c{{ "event": "error", "id": NUM, "error": NUM }} + \o The process has an error that maps to QProcess::ProcessError + \row + \o \c{{ "event": "stateChanged", "id": NUM, "state": NUM }} + \o The process has an event that maps to a QProcess::ProcessState change. + \row + \o \c{{ "event": "output", "id": NUM, "stdout": STRING, "stderr": STRING }} + \o The process has written data to stdout and/or stderr. + \endtable +*/ + +/*! + Construct a RemoteProcessBackendFactory with optional \a parent. +*/ + +RemoteProcessBackendFactory::RemoteProcessBackendFactory(QObject *parent) + : ProcessBackendFactory(parent) + , m_idCount(100) +{ +} + +/*! + Destroy this and child objects. +*/ + +RemoteProcessBackendFactory::~RemoteProcessBackendFactory() +{ + foreach (RemoteProcessBackend *backend, m_backendMap) + backend->factoryDestroyed(); +} + +/*! + Construct a RemoteProcessBackend from a ProcessInfo \a info record with \a parent. +*/ + +ProcessBackend * RemoteProcessBackendFactory::create(const ProcessInfo& info, QObject *parent) +{ + int id = m_idCount++; + RemoteProcessBackend *backend = new RemoteProcessBackend(info, this, id, parent); + m_backendMap.insert(id, backend); + return backend; +} + +/*! + Receive a remote \a message and dispatch it to the correct recipient. + Call this function from your subclass to properly dispatch messages. + */ + +void RemoteProcessBackendFactory::receive(const QJsonObject& message) +{ + qDebug() << Q_FUNC_INFO << message; + int id = message.value(QLatin1String("id")).toDouble(); + if (m_backendMap.contains(id)) + m_backendMap.value(id)->receive(message); +} + +/*! + \fn bool RemoteProcessBackendFactory::send(const QJsonObject& message) + + Send a \a message to the remote process. This method must be implemented in a + child process. Return true if the message can be sent. + */ + +/*! + \internal + */ + +void RemoteProcessBackendFactory::backendDestroyed(int id) +{ + if (m_backendMap.remove(id) != 1) + qCritical("Missing remote process backend"); +} + +#include "moc_remoteprocessbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/remoteprocessbackendfactory.h b/src/core/remoteprocessbackendfactory.h new file mode 100644 index 0000000..82c4fb0 --- /dev/null +++ b/src/core/remoteprocessbackendfactory.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef REMOTE_PROCESS_BACKEND_FACTORY_H +#define REMOTE_PROCESS_BACKEND_FACTORY_H + +#include "processbackendfactory.h" +#include <QJsonObject> +#include <QMap> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class RemoteProcessBackend; + +class RemoteProcessBackendFactory : public ProcessBackendFactory +{ + Q_OBJECT +public: + RemoteProcessBackendFactory(QObject *parent = 0); + virtual ~RemoteProcessBackendFactory(); + + virtual ProcessBackend *create(const ProcessInfo& info, QObject *parent); + +protected: + virtual bool send(const QJsonObject&) = 0; + void receive(const QJsonObject&); + +private: + void backendDestroyed(int); + friend class RemoteProcessBackend; + +protected: + int m_idCount; + QMap<int, RemoteProcessBackend*> m_backendMap; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // REMOTE_PROCESS_BACKEND_FACTORY_H diff --git a/src/core/socketprocessbackendfactory.cpp b/src/core/socketprocessbackendfactory.cpp new file mode 100644 index 0000000..a87ffdd --- /dev/null +++ b/src/core/socketprocessbackendfactory.cpp @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDebug> +#include <QLocalSocket> +#include <QJsonDocument> +#include <QtEndian> + +#include "socketprocessbackendfactory.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class SocketProcessBackendFactory + \brief The SocketProcessBackendFactory class creates RemoteProcessBackend objects + + The SocketProcessBackendFactory connects over a Unix local socket to + an already-running "launcher" server. +*/ + +/*! + Construct a SocketProcessBackendFactory with optional \a parent. + Connect to an application launcher listening on Unix local socket \a socketname +*/ + +SocketProcessBackendFactory::SocketProcessBackendFactory(const QString& socketname, + QObject *parent) + : RemoteProcessBackendFactory(parent) +{ + m_socket = new QLocalSocket(this); + connect(m_socket, SIGNAL(disconnected()), SLOT(disconnected())); + connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead())); + m_socket->connectToServer(socketname); +} + +/*! + Destroy this and child objects. +*/ + +SocketProcessBackendFactory::~SocketProcessBackendFactory() +{ +} + +/*! + The SocketProcessBackendFactory will match the ProcessInfo \a info + if there is a \c info.launcher attribute set to "true" (the string) and + if the \c info.program attribute matches the program original specified + when creating the factory. +*/ + +bool SocketProcessBackendFactory::canCreate(const ProcessInfo& info) const +{ + Q_UNUSED(info); + return m_socket->isValid(); +} + +/*! + Read data from the socket +*/ + +void SocketProcessBackendFactory::readyRead() +{ + m_buffer.append(m_socket->readAll()); + while (m_buffer.size() >= 12) { // QJsonDocuments are at least this large + qint32 message_size = qFromLittleEndian(((qint32 *)m_buffer.data())[2]) + 8; + if (m_buffer.size() < message_size) + break; + QByteArray msg = m_buffer.left(message_size); + m_buffer = m_buffer.mid(message_size); + receive(QJsonDocument::fromBinaryData(msg).object()); + } +} + +/*! + \internal + */ + +void SocketProcessBackendFactory::disconnected() +{ + qWarning("Launcher process socket disconnected"); +} + +/*! + \internal + */ + +bool SocketProcessBackendFactory::send(const QJsonObject& message) +{ + return (m_socket->isValid() && + m_socket->write(QJsonDocument(message).toBinaryData()) != -1); +} + +#include "moc_socketprocessbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/socketprocessbackendfactory.h b/src/core/socketprocessbackendfactory.h new file mode 100644 index 0000000..ac7ad19 --- /dev/null +++ b/src/core/socketprocessbackendfactory.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SOCKET_PROCESS_BACKEND_FACTORY_H +#define SOCKET_PROCESS_BACKEND_FACTORY_H + +#include "remoteprocessbackendfactory.h" + +class QLocalSocket; + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class SocketProcessBackendFactory : public RemoteProcessBackendFactory +{ + Q_OBJECT +public: + SocketProcessBackendFactory(const QString& socketname, QObject *parent = 0); + virtual ~SocketProcessBackendFactory(); + + virtual bool canCreate(const ProcessInfo& info) const; + +protected: + virtual bool send(const QJsonObject&); + +private slots: + void readyRead(); + void disconnected(); + +private: + QLocalSocket *m_socket; + QByteArray m_buffer; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // SOCKET_PROCESS_BACKEND_FACTORY_H diff --git a/src/core/standardprocessbackend.cpp b/src/core/standardprocessbackend.cpp new file mode 100644 index 0000000..94d7c90 --- /dev/null +++ b/src/core/standardprocessbackend.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "standardprocessbackend.h" +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class StandardProcessBackend + \brief The StandardProcessBackend class wraps a single QProcess object +*/ + +/*! + Construct a StandardProcessBackend with ProcessInfo \a info and optional \a parent +*/ + +StandardProcessBackend::StandardProcessBackend(const ProcessInfo& info, QObject *parent) + : UnixProcessBackend(info, parent) +{ +} + +/*! + \brief Starts the process. + + After the process is started, the started() signal is emitted or + an error(QProcess::FailedToStart) will be emitted. +*/ + +void StandardProcessBackend::start() +{ + if (createProcess()) + startProcess(); +} + +/*! + Calls the parent class function and emits the started() signal. +*/ + +void StandardProcessBackend::handleProcessStarted() +{ + UnixProcessBackend::handleProcessStarted(); + emit started(); +} + +/*! + Calls the parent class function and emits the error() signal with \a err. +*/ + +void StandardProcessBackend::handleProcessError(QProcess::ProcessError err) +{ + UnixProcessBackend::handleProcessError(err); + emit error(err); +} + +/*! + Calls the parent class function and emits the finished() signal with + the \a exitCode and \a exitStatus. +*/ + +void StandardProcessBackend::handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + UnixProcessBackend::handleProcessFinished(exitCode, exitStatus); + emit finished(exitCode, exitStatus); +} + +/*! + Calls the parent class function and emits the stateChanged() signal with \a state. +*/ + +void StandardProcessBackend::handleProcessStateChanged(QProcess::ProcessState state) +{ + UnixProcessBackend::handleProcessStateChanged(state); + emit stateChanged(state); +} + +#include "moc_standardprocessbackend.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/standardprocessbackend.h b/src/core/standardprocessbackend.h new file mode 100644 index 0000000..452d192 --- /dev/null +++ b/src/core/standardprocessbackend.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef STANDARD_PROCESS_BACKEND_H +#define STANDARD_PROCESS_BACKEND_H + +#include "unixprocessbackend.h" + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class StandardProcessBackend : public UnixProcessBackend +{ + Q_OBJECT + +public: + StandardProcessBackend(const ProcessInfo& info, QObject *parent); + virtual void start(); + +protected: + virtual void handleProcessStarted(); + virtual void handleProcessError(QProcess::ProcessError error); + virtual void handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + virtual void handleProcessStateChanged(QProcess::ProcessState state); +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // STANDARD_PROCESS_BACKEND_H diff --git a/src/core/standardprocessbackendfactory.cpp b/src/core/standardprocessbackendfactory.cpp new file mode 100644 index 0000000..5e6ad0e --- /dev/null +++ b/src/core/standardprocessbackendfactory.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "standardprocessbackendfactory.h" +#include "standardprocessbackend.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class StandardProcessBackendFactory + \brief The StandardProcessBackendFactory class creates StandardProcessBackend objects +*/ + +/*! + Construct a StandardProcessBackendFactory with optional \a parent +*/ + +StandardProcessBackendFactory::StandardProcessBackendFactory(QObject *parent) + : ProcessBackendFactory(parent) +{ +} + +/*! + StandardProcessBackendFactory can create any type of process + using the ProcessInfo \a info record. +*/ + +bool StandardProcessBackendFactory::canCreate(const ProcessInfo& info) const +{ + Q_UNUSED(info); + return true; +} + +/*! + Construct a StandardProcessBackend from a ProcessInfo \a info record + with \a parent. +*/ + +ProcessBackend * StandardProcessBackendFactory::create(const ProcessInfo& info, QObject *parent) +{ + return new StandardProcessBackend(info, parent); +} + +#include "moc_standardprocessbackendfactory.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/standardprocessbackendfactory.h b/src/core/standardprocessbackendfactory.h new file mode 100644 index 0000000..98a7526 --- /dev/null +++ b/src/core/standardprocessbackendfactory.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef STANDARD_PROCESS_BACKEND_FACTORY_H +#define STANDARD_PROCESS_BACKEND_FACTORY_H + +#include "processbackendfactory.h" + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class StandardProcessBackendFactory : public ProcessBackendFactory +{ + Q_OBJECT + +public: + StandardProcessBackendFactory(QObject *parent=0); + virtual bool canCreate(const ProcessInfo& info) const; + virtual ProcessBackend *create(const ProcessInfo& info, QObject *parent); +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // STANDARD_PROCESS_BACKEND_FACTORY_H diff --git a/src/core/unixprocessbackend.cpp b/src/core/unixprocessbackend.cpp new file mode 100644 index 0000000..c9bf3d6 --- /dev/null +++ b/src/core/unixprocessbackend.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +#include "unixprocessbackend.h" +#include "unixsandboxprocess.h" +#include <sys/resource.h> +#include <errno.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class UnixProcessBackend + \brief The UnixProcessBackend class wraps a QProcess object +*/ + +/*! + Construct a UnixProcessBackend with ProcessInfo \a info and optional \a parent +*/ + +UnixProcessBackend::UnixProcessBackend(const ProcessInfo& info, QObject *parent) + : ProcessBackend(info, parent) + , m_process(0) +{ +} + +/*! + Destroy this process object. + Any created QProcess is a child of this object, so it will be automatically terminated. +*/ + +UnixProcessBackend::~UnixProcessBackend() +{ +} + +/*! + Returns the PID of this process. If the process has not started up yet properly, its PID will be 0. +*/ +Q_PID UnixProcessBackend::pid() const +{ + if (m_process) + return m_process->pid(); + return 0; +} + +/*! + Return the actual process priority (if running) +*/ + +qint32 UnixProcessBackend::actualPriority() const +{ + if (m_process) { + errno = 0; // getpriority can return -1, so we clear errno + int result = getpriority(PRIO_PROCESS, m_process->pid()); + if (!errno) + return result; + } + return ProcessBackend::actualPriority(); +} + +/*! + Set the process priority to \a priority +*/ + +void UnixProcessBackend::setDesiredPriority(qint32 priority) +{ + ProcessBackend::setDesiredPriority(priority); + if (m_process) { + if (setpriority(PRIO_PROCESS, m_process->pid(), priority)) + qWarning() << "Failed to set process priority from " << actualPriority() << + "to" << priority << " : errno = " << errno; + } +} + +#if defined(Q_OS_LINUX) + +/*! + Return the process oomAdjustment +*/ + +qint32 UnixProcessBackend::actualOomAdjustment() const +{ + if (m_process) { + // ### TODO: Read correctly from /proc/<pid>/oom_score_adj + } + return ProcessBackend::actualOomAdjustment(); +} + +/*! + Set the process /proc/<pid>/oom_score_adj to \a oomAdjustment +*/ + +void UnixProcessBackend::setDesiredOomAdjustment(qint32 oomAdjustment) +{ + ProcessBackend::setDesiredOomAdjustment(oomAdjustment); + if (m_process) { + // ### Write to /proc/<pid>/oom_score_adj + } +} + +#endif // defined(Q_OS_LINUX) + +/*! + Returns the state of the process. + The base class always returns NotRunning. +*/ +QProcess::ProcessState UnixProcessBackend::state() const +{ + return m_process ? m_process->state() : QProcess::NotRunning; +} + +/*! + Internal function to create the QProcess. + Returns true if a process was created. +*/ +bool UnixProcessBackend::createProcess() +{ + if (m_process) { + qWarning() << "Can't restart process!"; + return false; + } + + if (m_info.contains(ProcessInfoConstants::Uid) || m_info.contains(ProcessInfoConstants::Gid)) + m_process = new UnixSandboxProcess(m_info.uid(), m_info.gid(), this); + else + m_process = new QProcess(this); + + m_process->setReadChannel(QProcess::StandardOutput); + connect(m_process, SIGNAL(readyReadStandardOutput()), + this, SLOT(readyReadStandardOutput())); + connect(m_process, SIGNAL(readyReadStandardError()), + this, SLOT(readyReadStandardError())); + connect(&m_killTimer, SIGNAL(timeout()), this, SLOT(killTimeout())); + + connect(m_process, SIGNAL(started()), this, SLOT(unixProcessStarted())); + connect(m_process,SIGNAL(error(QProcess::ProcessError)), + this,SLOT(unixProcessError(QProcess::ProcessError))); + connect(m_process,SIGNAL(finished(int, QProcess::ExitStatus)), + this,SLOT(unixProcessFinished(int, QProcess::ExitStatus))); + connect(m_process, SIGNAL(stateChanged(QProcess::ProcessState)), + this,SLOT(unixProcessStateChanged(QProcess::ProcessState))); + return true; +} + +/*! + Internal function to start the QProcess running. +*/ + +void UnixProcessBackend::startProcess() +{ + QProcessEnvironment env; + QMapIterator<QString, QVariant> it(m_info.environment()); + while (it.hasNext()) { + it.next(); + env.insert(it.key(), it.value().toString()); + } + m_process->setProcessEnvironment(env); + m_process->setWorkingDirectory(m_info.workingDirectory()); + m_process->start(m_info.program(), m_info.arguments()); +} + +/*! + Attempts to stop a process by giving it a \a timeout time to die, measured in milliseconds. + + If the process does not die in the given time limit, it is killed. + + \sa finished() +*/ + +void UnixProcessBackend::stop(int timeout) +{ + Q_ASSERT(m_process); + + if (m_process->state() != QProcess::NotRunning) { + if (timeout > 0) { + m_process->terminate(); + m_killTimer.start(timeout); + } + else + m_process->kill(); + } +} + +/*! + Writes at most \a maxSize bytes of data from \a data to the device. + Returns the number of bytes that were actually written, or -1 if an error occurred. +*/ +qint64 UnixProcessBackend::write(const char *data, qint64 maxSize) +{ + if (m_process) + return m_process->write(data, maxSize); + return 0; +} + +/*! + Override this in subclasses. Make sure you call the parent class. + Your subclass should emit \sa started() +*/ +void UnixProcessBackend::handleProcessStarted() +{ + if (m_info.contains("priority") && setpriority(PRIO_PROCESS, m_process->pid(), m_info.priority())) + qWarning() << "Failed to set process priority at startup from " << actualPriority() << + "to" << m_info.priority() << " : errno = " << errno; +} +/*! + Override this in subclasses. Make sure you call the parent class with \a error. + Your subclass should emit \sa error() +*/ +void UnixProcessBackend::handleProcessError(QProcess::ProcessError error) +{ + Q_UNUSED(error); +} + +/*! + Override this in subclasses. Make sure you call the parent class with \a exitCode + and \a exitStatus. Your subclass should emit \sa finished() +*/ +void UnixProcessBackend::handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + Q_UNUSED(exitCode); + Q_UNUSED(exitStatus); + m_killTimer.stop(); +} + +/*! + Override this in subclasses. Make sure you call the parent class with \a state. + Your subclass should emit \sa stateChanged() +*/ +void UnixProcessBackend::handleProcessStateChanged(QProcess::ProcessState state) +{ + Q_UNUSED(state); + m_killTimer.stop(); +} + +/*! + \internal +*/ +void UnixProcessBackend::unixProcessStarted() +{ + handleProcessStarted(); +} + +/*! + \internal +*/ +void UnixProcessBackend::unixProcessError(QProcess::ProcessError error) +{ + handleProcessError(error); +} + +/*! + \internal +*/ +void UnixProcessBackend::unixProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + handleProcessFinished(exitCode, exitStatus); +} + +/*! + \internal +*/ +void UnixProcessBackend::unixProcessStateChanged(QProcess::ProcessState state) +{ + handleProcessStateChanged(state); +} + +/*! + \internal +*/ +void UnixProcessBackend::killTimeout() +{ + if (m_process && m_process->state() == QProcess::Running) + m_process->kill(); +} + +/*! + \internal +*/ +void UnixProcessBackend::readyReadStandardOutput() +{ + handleStandardOutput(m_process->readAllStandardOutput()); +} + +/*! + \internal +*/ +void UnixProcessBackend::readyReadStandardError() +{ + handleStandardError(m_process->readAllStandardError()); +} + +#include "moc_unixprocessbackend.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/unixprocessbackend.h b/src/core/unixprocessbackend.h new file mode 100644 index 0000000..ae68ffb --- /dev/null +++ b/src/core/unixprocessbackend.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef UNIX_PROCESS_BACKEND_H +#define UNIX_PROCESS_BACKEND_H + +#include "processbackend.h" +#include <QTimer> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class UnixProcessBackend : public ProcessBackend +{ + Q_OBJECT + +public: + UnixProcessBackend(const ProcessInfo& info, QObject *parent=0); + virtual ~UnixProcessBackend(); + + virtual Q_PID pid() const; + + virtual qint32 actualPriority() const; + virtual void setDesiredPriority(qint32); + +#if defined(Q_OS_LINUX) + virtual qint32 actualOomAdjustment() const; + virtual void setDesiredOomAdjustment(qint32); +#endif + + virtual QProcess::ProcessState state() const; + virtual void stop(int timeout = 500); + + virtual qint64 write(const char *data, qint64 maxSize); + +protected: + bool createProcess(); + void startProcess(); + + virtual void handleProcessStarted(); + virtual void handleProcessError(QProcess::ProcessError error); + virtual void handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + virtual void handleProcessStateChanged(QProcess::ProcessState state); + +private slots: + void unixProcessStarted(); + void unixProcessError(QProcess::ProcessError error); + void unixProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void unixProcessStateChanged(QProcess::ProcessState state); + + void killTimeout(); + void readyReadStandardOutput(); + void readyReadStandardError(); + +protected: + QProcess *m_process; + QTimer m_killTimer; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // UNIX_PROCESS_BACKEND_H diff --git a/src/core/unixsandboxprocess.cpp b/src/core/unixsandboxprocess.cpp new file mode 100644 index 0000000..c7c8889 --- /dev/null +++ b/src/core/unixsandboxprocess.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "unixsandboxprocess.h" +#include <sys/stat.h> + +#if defined(Q_OS_LINUX) +#include <sys/types.h> +#include <unistd.h> +#include <grp.h> +#endif + +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class UnixSandboxProcess + \brief The UnixSandboxProcess class sets the UID and GID on a Unix process +*/ + +/*! + Construct a UnixProcessBackend with \a uid, \a gid, and optional \a parent. +*/ + +UnixSandboxProcess::UnixSandboxProcess(qint64 uid, qint64 gid, QObject *parent) + : QProcess(parent) + , m_uid(uid) + , m_gid(gid) +{ +} + +/*! + \internal Set up the user and group id +*/ + +void UnixSandboxProcess::setupChildProcess() +{ + qDebug() << "Setting up child process" << m_uid << m_gid; + ::setgroups(0,0); + ::setgid(m_gid); + ::setuid(m_uid); + ::umask(S_IWGRP | S_IWOTH); +} + +#include "moc_unixsandboxprocess.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/unixsandboxprocess.h b/src/core/unixsandboxprocess.h new file mode 100644 index 0000000..797bc34 --- /dev/null +++ b/src/core/unixsandboxprocess.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef UNIX_SANDBOX_PROCESS_H +#define UNIX_SANDBOX_PROCESS_H + +#include <QProcess> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class UnixSandboxProcess : public QProcess +{ + Q_OBJECT + +public: + UnixSandboxProcess(qint64 uid, qint64 gid, QObject *parent=0); + +protected: + void setupChildProcess(); + +private: + qint64 m_uid; + qint64 m_gid; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // UNIX_SANDBOX_PROCESS_H diff --git a/src/declarative/.gitignore b/src/declarative/.gitignore new file mode 100644 index 0000000..0ff1d61 --- /dev/null +++ b/src/declarative/.gitignore @@ -0,0 +1 @@ +plugin.moc diff --git a/src/declarative/declarative-lib.pri b/src/declarative/declarative-lib.pri new file mode 100644 index 0000000..8c253e8 --- /dev/null +++ b/src/declarative/declarative-lib.pri @@ -0,0 +1,12 @@ +QT += declarative +CONFIG += network + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/declarativeprocessmanager.h \ + $$PWD/processinfotemplate.h + +SOURCES += \ + $$PWD/declarativeprocessmanager.cpp \ + $$PWD/processinfotemplate.cpp diff --git a/src/declarative/declarative.pri b/src/declarative/declarative.pri new file mode 100644 index 0000000..34a2ab1 --- /dev/null +++ b/src/declarative/declarative.pri @@ -0,0 +1,12 @@ +include(../core/core.pri) + +CONFIG += network + +INCLUDEPATH += $$PWD +LIBS += -L$$PWD -lprocessmanager-declarative + +mac|unix { + CONFIG += rpath_libdirs + QMAKE_RPATHDIR += $$PWD + QMAKE_LFLAGS += "-Wl,-rpath $$PWD" +} diff --git a/src/declarative/declarative.pro b/src/declarative/declarative.pro new file mode 100644 index 0000000..051ef27 --- /dev/null +++ b/src/declarative/declarative.pro @@ -0,0 +1,25 @@ +TEMPLATE = lib +TARGET = processmanager-declarative + +LIBS += -L../core + +include($$PWD/../../config.pri) +include(../core/core.pri) +include(declarative-lib.pri) + +SOURCES += \ + plugin.cpp + +mac:!staticlib { + QMAKE_POST_LINK = install_name_tool -id $$PWD/${TARGET} ${TARGET} +} + +qmldir.path = $$INSTALLBASE/imports/com/nokia/QtProcessManager +qmldir.files += $$PWD/qmldir + +headers.path = $$INSTALLBASE/include/qtprocessmanager +headers.files = $$HEADERS + +target.path = $$INSTALLBASE/lib + +INSTALLS += target headers qmldir diff --git a/src/declarative/declarativeprocess.cpp b/src/declarative/declarativeprocess.cpp new file mode 100644 index 0000000..47e3524 --- /dev/null +++ b/src/declarative/declarativeprocess.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 Nokia Corporation + */ + +#include "declarativeprocess.h" + +#include <QDateTime> + +/***************************************************************************************/ + +/*! + \class DeclarativeProcess + \brief The DeclarativeProcess class is a generalized representation of a process. +*/ + +/*! + \property DeclarativeProcess::identifier + \brief the application identifier of the process. +*/ + +/*! + \property DeclarativeProcess::program + \brief the filename of the binary executable that is launched to start up the process. +*/ + +/*! + \property DeclarativeProcess::arguments + \brief the arguments that will be passed to the program upon process startup +*/ + +/*! + \property DeclarativeProcess::environment + \brief a map of the environment variables that will be used by the process +*/ + +/*! + \property DeclarativeProcess::workingDirectory + \brief the directory that will be switched to before launching the process +*/ + +/*! + \property DeclarativeProcess::uid + \brief the user id (uid) of the process. +*/ + +/*! + \property DeclarativeProcess::gid + \brief the group id (gid) of the process. +*/ + +/*! + \property DeclarativeProcess::pid + \brief the process id (PID) of the process. + + Returns 0 if the process has not been started or if this is a "fake" process. +*/ + +/*! + \property DeclarativeProcess::startTime + \brief the start time of the process, measured in milliseconds since the epoch (1st Jan 1970 00:00). + + Returns 0 if process has not been started. +*/ + +/*! + \property DeclarativeProcess::priority + \brief The Unix process priority (niceness). + + Returns the current process priority if the process is running. Otherwise, + it returns the DeclarativeProcess priority setting. You can only set the priority once the + process is running. +*/ + +#if defined(Q_OS_LINUX) +/*! + \property DeclarativeProcess::oomAdjustment + \brief The Unix process /proc/<pid>/oom_score_adj (likelihood of being killed) + + Returns the current OOM adjustment score if the process is running. Otherwise, + it returns the DeclarativeProcess OOM adjustment score setting. You can only set the OOM adjustment + score when the process is running. +*/ +#endif + +/*! + \internal + Constructs a DeclarativeProcess instance with DeclarativeProcess \a info and optional \a parent +*/ +DeclarativeProcess::DeclarativeProcess(ProcessBackend *process, QObject *parent) + : ProcessFrontend(process, parent) +{ +} + +/*! + Destroy this process object. +*/ + +DeclarativeProcess::~DeclarativeProcess() +{ +} diff --git a/src/declarative/declarativeprocess.h b/src/declarative/declarativeprocess.h new file mode 100644 index 0000000..296ae3e --- /dev/null +++ b/src/declarative/declarativeprocess.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 Nokia Corporation + */ + +#ifndef DECLARATIVE_PROCESS_H +#define DECLARATIVE_PROCESS_H + +#include "processfrontend.h" + +class DeclarativeProcess : public ProcessFrontend +{ + Q_OBJECT + + Q_PROPERTY(QString identifier READ identifier CONSTANT) + Q_PROPERTY(QString program READ program CONSTANT) + Q_PROPERTY(QStringList arguments READ arguments CONSTANT) + Q_PROPERTY(QVariantMap environment READ environment CONSTANT) + Q_PROPERTY(QString workingDirectory READ workingDirectory CONSTANT) + Q_PROPERTY(qint64 uid READ uid CONSTANT) + Q_PROPERTY(qint64 gid READ gid CONSTANT) + + Q_PROPERTY(qint64 pid READ pid NOTIFY started) + Q_PROPERTY(qint64 startTime READ startTime NOTIFY started) + + Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged) +#if defined(Q_OS_LINUX) + Q_PROPERTY(int oomAdjustment READ oomAdjustment WRITE setOomAdjustment NOTIFY oomAdjustmentChanged) +#endif + +public: + DeclarativeProcess(ProcessBackend *process, QObject *parent=0); + virtual ~DeclarativeProcess(); +}; + +Q_DECLARE_METATYPE(DeclarativeProcess*) + +#endif // DECLARATIVE_PROCESS_H diff --git a/src/declarative/declarativeprocessmanager.cpp b/src/declarative/declarativeprocessmanager.cpp new file mode 100644 index 0000000..2f43a7b --- /dev/null +++ b/src/declarative/declarativeprocessmanager.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "declarativeprocessmanager.h" +#include "processbackendfactory.h" +#include "processfrontend.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \qmlclass ProcessManager DeclarativeProcessManager + \brief The ProcessManager class encapsulates ways of creating and tracking processes. + + Only a single ProcessManager class should be loaded at one time. + + Typical use of the ProcessManager class is as follows: + + \code + import QtQuick 2.0 + import ProcessManager 1.0 + + ProcessManager { + id: myProcessManager + + factories: [ + GdbProcessBackendFactory {}, + StandardProcessBackendFactory {} + ] + } + \endcode + +*/ + +/*! + \qmlproperty list<ProcessBackendFactory> ProcessManager::factories + \brief The factories assigned to this process manager + + The factories property is an ordered list of ProcessBackendFactory objects. +*/ + + +/*! + Construct a DeclarativeProcessManager with an optional \a parent +*/ + +DeclarativeProcessManager::DeclarativeProcessManager(QObject *parent) + : ProcessManager(parent) +{ +} + +/*! + \internal +*/ +void DeclarativeProcessManager::classBegin() +{ +} + +/*! + \internal +*/ +void DeclarativeProcessManager::componentComplete() +{ +} + +/*! + \internal +*/ +void append_factory(QDeclarativeListProperty<ProcessBackendFactory> *list, + ProcessBackendFactory *value) +{ + DeclarativeProcessManager *manager = static_cast<DeclarativeProcessManager *>(list->object); + ProcessBackendFactory *factory = qobject_cast<ProcessBackendFactory *>(value); + if (factory) + manager->addBackendFactory(factory); +} + +/*! + \internal + */ + +QDeclarativeListProperty<ProcessBackendFactory> DeclarativeProcessManager::factories() +{ + return QDeclarativeListProperty<ProcessBackendFactory>(this, NULL, append_factory); +} + +/*! + Raise the processAboutToStart() signal. +*/ + +void DeclarativeProcessManager::processFrontendAboutToStart() +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processAboutToStart(frontend->name()); + ProcessManager::processFrontendAboutToStart(); +} + +/*! + Raise the processAboutToStop() signal. +*/ + +void DeclarativeProcessManager::processFrontendAboutToStop() +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processAboutToStop(frontend->name()); + ProcessManager::processFrontendAboutToStop(); +} + +/*! + Raise the processStarted() signal. +*/ + +void DeclarativeProcessManager::processFrontendStarted() +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processStarted(frontend->name()); + ProcessManager::processFrontendStarted(); +} + +/*! + Raise the processError() signal. + Pass through the \a error value. +*/ + +void DeclarativeProcessManager::processFrontendError(QProcess::ProcessError error) +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processError(frontend->name(), static_cast<int>(error)); + ProcessManager::processFrontendError(error); +} + +/*! + Raise the processFinished() signal. Pass through the + \a exitCode and \a exitStatus values. +*/ + +void DeclarativeProcessManager::processFrontendFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processFinished(frontend->name(), exitCode, exitStatus); + ProcessManager::processFrontendFinished(exitCode, exitStatus); +} + +/*! + Raise the processStateChanged() signal. Pass through the + \a state value. +*/ + +void DeclarativeProcessManager::processFrontendStateChanged(QProcess::ProcessState state) +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processStateChanged(frontend->name(), state); + ProcessManager::processFrontendStateChanged(state); +} + +/*! + Raise the processFrontendDestroyed() signal. +*/ + +void DeclarativeProcessManager::processFrontendDestroyed() +{ + ProcessFrontend *frontend = static_cast<ProcessFrontend *>(sender()); + if (frontend) + emit processDestroyed(frontend->name()); + ProcessManager::processFrontendDestroyed(); +} + +/*! + \fn void DeclarativeProcessManager::processAboutToStart(const QString& name) + This signal is emitted when a process is about to start. + The \a name may be used to retrieve the ProcessFrontend + object. + + \sa processForName() +*/ + +/*! + \fn void DeclarativeProcessManager::processAboutToStop(const QString& name) + This signal is emitted when a process is about to stop + The \a name may be used to retrieve the ProcessFrontend + object. + + \sa processForName() +*/ + +/*! + \fn void DeclarativeProcessManager::processStarted(const QString& name) + This signal is emitted once a process has started. + The \a name may be used to retrieve the ProcessFrontend + object. + + \sa processForName() +*/ + +/*! + \fn void DeclarativeProcessManager::processError(const QString& name, int error) + This signal is emitted when a process experiences an \a error. + The \a name may be used to retrieve the ProcessFrontend + object. The \a error value can be compared to the QProcess::ProcessError + enumeration (it has been cast to an integer to resolve a QML issue). + + \sa processForName() +*/ + +/*! + \fn void DeclarativeProcessManager::processFinished(const QString& name, int exitCode, int exitStatus) + This signal is emitted when a process finishes. The \a exitCode + and \a exitStatus match the QProcess values. + The \a name may be used to retrieve the ProcessFrontend + object. The \a exitStatus value can be compared with a QProcess::ExitStatus + enumeration (it has been cast to an integer to resolve a QML issue). + + \sa processForName() +*/ + +/*! + \fn void DeclarativeProcessManager::processStateChanged(const QString& name, int state) + This signal is emitted when a process has a state change to \a state. + The \a name may be used to retrieve the ProcessFrontend + object. The \a state value can be compared with QProcess::ProcessState values + (it has been cast to an integer to resolve a QML issue). + + \sa processForName() +*/ + +/*! + \fn void DeclarativeProcessManager::processDestroyed(const QString &name) + This signal is emitted when a process has been destroyed + The \a name cannot be used to retrieve the ProcessFrontend + object because it no longer exists. +*/ + + + +#include "moc_declarativeprocessmanager.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/declarative/declarativeprocessmanager.h b/src/declarative/declarativeprocessmanager.h new file mode 100644 index 0000000..51c38ca --- /dev/null +++ b/src/declarative/declarativeprocessmanager.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DECLARATIVE_PROCESS_MANAGER_H +#define DECLARATIVE_PROCESS_MANAGER_H + +#include "processmanager.h" +#include <QDeclarativeListProperty> +#include <QDeclarativeParserStatus> +#include <qdeclarative.h> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class DeclarativeProcessManager : public ProcessManager, public QDeclarativeParserStatus +{ + Q_OBJECT + Q_INTERFACES(QDeclarativeParserStatus) + Q_PROPERTY(QDeclarativeListProperty<ProcessBackendFactory> factories READ factories) + +public: + DeclarativeProcessManager(QObject *parent=0); + QDeclarativeListProperty<ProcessBackendFactory> factories(); + + void classBegin(); + void componentComplete(); + +signals: + void processAboutToStart(const QString& name); + void processAboutToStop(const QString& name); + void processStarted(const QString& name); + void processError(const QString& name, int error); + void processFinished(const QString& name, int exitCode, int exitStatus); + void processStateChanged(const QString& name, int state); + void processDestroyed(const QString &name); + +protected slots: + virtual void processFrontendAboutToStart(); + virtual void processFrontendAboutToStop(); + virtual void processFrontendStarted(); + virtual void processFrontendError(QProcess::ProcessError); + virtual void processFrontendFinished(int, QProcess::ExitStatus); + virtual void processFrontendStateChanged(QProcess::ProcessState); + virtual void processFrontendDestroyed(); + +private: + friend void append_factory(QDeclarativeListProperty<ProcessBackendFactory>*, + ProcessBackendFactory*); +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +QML_DECLARE_TYPE(QT_PREPEND_NAMESPACE_PROCESSMANAGER(DeclarativeProcessManager)) + +#endif // DECLARATIVE_PROCESS_MANAGER_H diff --git a/src/declarative/plugin.cpp b/src/declarative/plugin.cpp new file mode 100644 index 0000000..93a7a4c --- /dev/null +++ b/src/declarative/plugin.cpp @@ -0,0 +1,28 @@ +#include "processinfotemplate.h" +#include "declarativeprocessmanager.h" + +#include <QtDeclarative/qdeclarative.h> +#include <QtDeclarative/QDeclarativeExtensionPlugin> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessManagerPlugin : public QDeclarativeExtensionPlugin +{ + Q_OBJECT +public: + void registerTypes(const char *uri) { + qmlRegisterType<ProcessInfoTemplate>(uri, 1, 0, "ProcessInfoTemplate"); + qmlRegisterType<DeclarativeProcessManager>(uri, 1, 0, "ProcessManager"); + } + + void initializeEngine(QDeclarativeEngine *engine, const char *uri) { + Q_UNUSED(engine); + Q_UNUSED(uri); + } +}; + +#include "plugin.moc" + +Q_EXPORT_PLUGIN2(processmanagertemplateplugin, ProcessManagerPlugin) + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/declarative/processinfotemplate.cpp b/src/declarative/processinfotemplate.cpp new file mode 100644 index 0000000..8afd3c0 --- /dev/null +++ b/src/declarative/processinfotemplate.cpp @@ -0,0 +1,507 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "processinfotemplate.h" +#include "processinfo.h" + +#include <QtDeclarative/QDeclarativeExpression> + +#include <QFileInfo> +#include <QDir> + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +const QString ProcessInfoTemplate::kCustomValuesStr = QString::fromLatin1("_customValues"); + +/*! + \class ProcessInfoTemplate + \brief The ProcessInfoTemplate class stores template information used to create ProcessInfo objects + + ProcessInfoTemplate is a declarative class used to store template information that will be used to + create ProcessInfo objects. These objects are created by calling \l createProcessInfo(). + + Values stored in the template properties are not evaluated until \l createProcessInfo() is executed. + Values can use \l bind() to bind to values that are contained in a dictionary that is specified when + \l createProcessInfo() is executed. +*/ + +/*! + \property ProcessInfoTemplate::identifier + \brief unique identifier for this template + + Specifies a unique identifier for this template. +*/ + +/*! + \property ProcessInfoTemplate::programName + \brief unique programName for this template + + Specifies a unique programName for this template. +*/ + +/*! + \property ProcessInfoTemplate::uid + \brief unique uid for this template + + Specifies a unique uid for this template. +*/ + +/*! + \property ProcessInfoTemplate::gid + \brief unique gid for this template + + Specifies a unique gid for this template. +*/ + +/*! + \property ProcessInfoTemplate::workingDirectory + \brief unique workingDirectory for this template + + Specifies a unique workingDirectory for this template. +*/ + +/*! + \property ProcessInfoTemplate::environment + \brief unique environment for this template + + Specifies a unique environment for this template. +*/ + +/*! + \property ProcessInfoTemplate::arguments + \brief unique arguments for this template + + Specifies a unique arguments for this template. +*/ + +/*! + \property ProcessInfoTemplate::startOutputPattern + \brief unique startOutputPattern for this template + + Specifies a unique startOutputPattern for this template. +*/ + +/*! + \property ProcessInfoTemplate::priority + \brief unique priority for this template + + Specifies a unique priority for this template. +*/ + +/*! + \property ProcessInfoTemplate::customValues + \brief unique customValues for this template + + Specifies a unique customValues for this template. +*/ + +/*! + Constructs a ProcessInfoTemplate with optional \a parent. + */ + +ProcessInfoTemplate::ProcessInfoTemplate(QObject *parent) : + QObject(parent) +{ +} + +/*! + Return the template identifier +*/ + +QString ProcessInfoTemplate::identifier() const +{ + return m_templateData.value(ProcessInfoConstants::Identifier).toString(); +} + +/*! + Return the template programName +*/ + +QDeclarativeScriptString ProcessInfoTemplate::programName() const +{ + return scriptString(ProcessInfoConstants::Program); +} + +/*! + Return the template uid +*/ + +QDeclarativeScriptString ProcessInfoTemplate::uid() const +{ + return scriptString(ProcessInfoConstants::Uid); +} + +/*! + Return the template gid +*/ + +QDeclarativeScriptString ProcessInfoTemplate::gid() const +{ + return scriptString(ProcessInfoConstants::Gid); +} + +/*! + Return the template workingDirectory +*/ + +QDeclarativeScriptString ProcessInfoTemplate::workingDirectory() const +{ + return scriptString(ProcessInfoConstants::WorkingDirectory); +} + +/*! + Return the template environment +*/ + +QDeclarativeScriptString ProcessInfoTemplate::environment() const +{ + return scriptString(ProcessInfoConstants::Environment); +} + +/*! + Return the template arguments +*/ + +QDeclarativeScriptString ProcessInfoTemplate::arguments() const +{ + return scriptString(ProcessInfoConstants::Arguments); +} + +/*! + Return the template startOutputPattern +*/ + +QDeclarativeScriptString ProcessInfoTemplate::startOutputPattern() const +{ + return scriptString(ProcessInfoConstants::StartOutputPattern); +} + +/*! + Return the template priority +*/ + +QDeclarativeScriptString ProcessInfoTemplate::priority() const +{ + return scriptString(ProcessInfoConstants::Priority); +} + +/*! + Return the template customValues +*/ + +QDeclarativeScriptString ProcessInfoTemplate::customValues() const +{ + return scriptString(kCustomValuesStr); +} + +/*! + Set the template \a identifier +*/ + +void ProcessInfoTemplate::setIdentifier(const QString &identifier) +{ + m_templateData.insert(ProcessInfoConstants::Identifier, QVariant(identifier)); + emit identifierChanged(); +} + +/*! + Set the template \a programName +*/ + +void ProcessInfoTemplate::setProgramName(const QDeclarativeScriptString &programName) +{ + setScriptString(ProcessInfoConstants::Program, programName); + emit programNameChanged(); +} + +/*! + Set the template \a uid +*/ + +void ProcessInfoTemplate::setUid(const QDeclarativeScriptString &uid) +{ + setScriptString(ProcessInfoConstants::Uid, uid); + emit uidChanged(); +} + +/*! + Set the template \a gid +*/ + +void ProcessInfoTemplate::setGid(const QDeclarativeScriptString &gid) +{ + setScriptString(ProcessInfoConstants::Gid, gid); + emit gidChanged(); +} + +/*! + Set the template \a workingDirectory +*/ + +void ProcessInfoTemplate::setWorkingDirectory(const QDeclarativeScriptString &workingDirectory) +{ + setScriptString(ProcessInfoConstants::WorkingDirectory, workingDirectory); + emit workingDirectoryChanged(); +} + +/*! + Set the template \a env environment +*/ + +void ProcessInfoTemplate::setEnvironment(QDeclarativeScriptString environment) +{ + setScriptString(ProcessInfoConstants::Environment, environment); + emit environmentChanged(); +} + +/*! + Set the template \a arguments +*/ + +void ProcessInfoTemplate::setArguments(const QDeclarativeScriptString &arguments) +{ + setScriptString(ProcessInfoConstants::Arguments, arguments); + emit argumentsChanged(); +} + +/*! + Set the template \a startOutputPattern +*/ + +void ProcessInfoTemplate::setStartOutputPattern(const QDeclarativeScriptString &startOutputPattern) +{ + setScriptString(ProcessInfoConstants::StartOutputPattern, startOutputPattern); + emit startOutputPatternChanged(); +} + +/*! + Set the template \a priority +*/ + +void ProcessInfoTemplate::setPriority(const QDeclarativeScriptString &priority) +{ + setScriptString(ProcessInfoConstants::Priority, priority); + emit priorityChanged(); +} + +/*! + Set the template \a customValues +*/ + +void ProcessInfoTemplate::setCustomValues(const QDeclarativeScriptString &customValues) +{ + setScriptString(kCustomValuesStr, customValues); + emit customValuesChanged(); +} + +/*! + * Attempts to bind \a tag to a value in a previously specified dictionary. Returns the + * bound value, or \a defaultValue if a binding for \a tag does not exist in the dictionary. + * This method is intended to be used in QML when specifying properties for the template. + * + * \sa createProcessInfo() + */ + +QVariant ProcessInfoTemplate::bind(const QString &tag, const QVariant &defaultValue) +{ + return m_dict.value(tag, defaultValue); +} + +/*! + Creates and returns a new ProcessInfo object. All property value scripts are evaluated + (bound) with \a dict. + */ +ProcessInfo *ProcessInfoTemplate::createProcessInfo(const QVariantMap &dict) +{ + QVariantMap boundData = bindData(dict); + ProcessInfo *processInfo = new ProcessInfo(boundData); + return processInfo; +} + +/*! + Return the script string bound to \a name + */ + +QDeclarativeScriptString ProcessInfoTemplate::scriptString(const QString &name) const +{ + QVariant var = m_templateData.value(name); + if (var.canConvert<QDeclarativeScriptString>()) + return var.value<QDeclarativeScriptString>(); + else + return QDeclarativeScriptString(); +} + +/*! + Set the script string \a script to value \a name + */ + +void ProcessInfoTemplate::setScriptString(const QString &name, const QDeclarativeScriptString &script) +{ + m_templateData.insert(name, QVariant::fromValue<QDeclarativeScriptString>(script)); +} + +/*! + Returns the evaluated expression using \a script data. + */ + +QVariant ProcessInfoTemplate::bindScript(const QDeclarativeScriptString &script) +{ + if (script.context() && !script.script().isEmpty()) { + // This is necessary because QDeclarativeExpression tries to return any JS array script + // as a QList<QObject *>, and that type is not useful to us (the returned JS array will + // only contain null values. So we wrap everything in a simple JSON object, and then pick + // out the real value after evaluating it. + + QString scriptStr = "script"; + QString jsonWrapper = QString("{\"%1\": %2}").arg(scriptStr).arg(script.script()); + QDeclarativeExpression expr(script.context(), this, jsonWrapper); + + return expr.evaluate().toMap().value(scriptStr); + } + else + return QVariant(); +} + +/*! + Returns the bindScript value evaluated from slot \a name + */ + +QVariant ProcessInfoTemplate::bindValue(const QString &name) +{ + return bindScript(scriptString(name)); +} + +/*! + Set the template dictionary to \a dict +*/ + +void ProcessInfoTemplate::setDict(const QVariantMap &dict) +{ + m_dict = dict; +} + + +/*! + Returns the absolute file path that consists of \a url and \a filename. + + If \a filename is an absolute path by itself, \a url is ignored. + If \a url is empty, only filename is used to resolve the path. +*/ +QString ProcessInfoTemplate::absoluteFilePath(const QString &url, const QString &filename) const +{ + if (url.isEmpty()) + return QFileInfo(filename).absoluteFilePath(); + return QFileInfo(QDir(QUrl(url).toLocalFile()), filename).absoluteFilePath(); +} + +/*! + Returns the boundData from binding \a dict. + */ + +QVariantMap ProcessInfoTemplate::bindData(const QVariantMap &dict) +{ + setDict(dict); + + QVariantMap boundData; + QMapIterator<QString, QVariant> it(m_templateData); + while (it.hasNext()) { + it.next(); + QVariant value = it.value(); + if (value.canConvert<QDeclarativeScriptString>()) { + boundData.insert(it.key(), bindScript(value.value<QDeclarativeScriptString>())); + } else { + boundData.insert(it.key(), value.toString()); + } + } + return boundData; +} + +/*! + \fn void ProcessInfoTemplate::identifierChanged() + Emitted when the template identifier changes +*/ + +/*! + \fn void ProcessInfoTemplate::programNameChanged() + Emitted when the template programName changes +*/ + +/*! + \fn void ProcessInfoTemplate::uidChanged() + Emitted when the template uid changes +*/ + +/*! + \fn void ProcessInfoTemplate::gidChanged() + Emitted when the template gid changes +*/ + +/*! + \fn void ProcessInfoTemplate::workingDirectoryChanged() + Emitted when the template workingDirectory changes +*/ + +/*! + \fn void ProcessInfoTemplate::environmentChanged() + Emitted when the template environment changes +*/ + +/*! + \fn void ProcessInfoTemplate::argumentsChanged() + Emitted when the template arguments change +*/ + +/*! + \fn void ProcessInfoTemplate::startOutputPatternChanged() + Emitted when the template startOutputPattern changes +*/ + +/*! + \fn void ProcessInfoTemplate::priorityChanged() + Emitted when the template priority changes +*/ + +/*! + \fn void ProcessInfoTemplate::customValuesChanged() + Emitted when the template customValues change +*/ + +#include "moc_processinfotemplate.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/declarative/processinfotemplate.h b/src/declarative/processinfotemplate.h new file mode 100644 index 0000000..0183929 --- /dev/null +++ b/src/declarative/processinfotemplate.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROCESSINFOTEMPLATE_H +#define PROCESSINFOTEMPLATE_H + +#include <QObject> +#include <QVariant> +#include <QVariantMap> +#include <QtDeclarative/QDeclarativeScriptString> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessInfo; + +class ProcessInfoTemplate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) + Q_PROPERTY(QDeclarativeScriptString programName READ programName WRITE setProgramName NOTIFY programNameChanged) + Q_PROPERTY(QDeclarativeScriptString uid READ uid WRITE setUid NOTIFY uidChanged) + Q_PROPERTY(QDeclarativeScriptString gid READ gid WRITE setGid NOTIFY gidChanged) + Q_PROPERTY(QDeclarativeScriptString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged) + Q_PROPERTY(QDeclarativeScriptString environment READ environment WRITE setEnvironment NOTIFY environmentChanged) + Q_PROPERTY(QDeclarativeScriptString arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) + Q_PROPERTY(QDeclarativeScriptString startOutputPattern READ startOutputPattern WRITE setStartOutputPattern NOTIFY startOutputPatternChanged) + Q_PROPERTY(QDeclarativeScriptString priority READ priority WRITE setPriority NOTIFY priorityChanged) + Q_PROPERTY(QDeclarativeScriptString customValues READ customValues WRITE setCustomValues NOTIFY customValuesChanged) +public: + ProcessInfoTemplate(QObject *parent = 0); + + QString identifier() const; + QDeclarativeScriptString programName() const; + QDeclarativeScriptString uid() const; + QDeclarativeScriptString gid() const; + QDeclarativeScriptString workingDirectory() const; + QDeclarativeScriptString environment() const; + QDeclarativeScriptString arguments() const; + QDeclarativeScriptString startOutputPattern() const; + QDeclarativeScriptString priority() const; + QDeclarativeScriptString customValues() const; + + Q_INVOKABLE virtual ProcessInfo *createProcessInfo(const QVariantMap &dict); + + Q_INVOKABLE QVariant bind(const QString &tag, const QVariant &defaultValue = QVariant()); + Q_INVOKABLE QString absoluteFilePath(const QString &url, const QString &filename = QString()) const; + +public slots: + void setIdentifier(const QString &identifier); + void setProgramName(const QDeclarativeScriptString &programName); + void setUid(const QDeclarativeScriptString &uid); + void setGid(const QDeclarativeScriptString &gid); + void setWorkingDirectory(const QDeclarativeScriptString &dir); + void setEnvironment(QDeclarativeScriptString env); + void setArguments(const QDeclarativeScriptString &args); + void setStartOutputPattern(const QDeclarativeScriptString &startOutputPattern); + void setPriority(const QDeclarativeScriptString &value); + void setCustomValues(const QDeclarativeScriptString &vals); + +signals: + void identifierChanged(); + void programNameChanged(); + void uidChanged(); + void gidChanged(); + void workingDirectoryChanged(); + void environmentChanged(); + void argumentsChanged(); + void startOutputPatternChanged(); + void priorityChanged(); + void customValuesChanged(); + +protected: + virtual QVariantMap bindData(const QVariantMap &dict); + + QDeclarativeScriptString scriptString(const QString &name) const; + void setScriptString(const QString &name, const QDeclarativeScriptString &script); + + QVariant bindScript(const QDeclarativeScriptString &script); + Q_INVOKABLE QVariant bindValue(const QString &name); + Q_INVOKABLE void setDict(const QVariantMap &dict); + + static const QString kCustomValuesStr; + +private: + QVariantMap m_templateData; + QVariantMap m_dict; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PROCESSINFOTEMPLATE_H diff --git a/src/declarative/qmldir b/src/declarative/qmldir new file mode 100644 index 0000000..e6c5423 --- /dev/null +++ b/src/declarative/qmldir @@ -0,0 +1 @@ +plugin processmanager diff --git a/src/launcher/launcher-lib.pri b/src/launcher/launcher-lib.pri new file mode 100644 index 0000000..23be8fe --- /dev/null +++ b/src/launcher/launcher-lib.pri @@ -0,0 +1,14 @@ +QT += network +CONFIG += network + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/launcherclient.h \ + $$PWD/pipelauncher.h \ + $$PWD/socketlauncher.h + +SOURCES += \ + $$PWD/launcherclient.cpp \ + $$PWD/pipelauncher.cpp \ + $$PWD/socketlauncher.cpp diff --git a/src/launcher/launcher.pri b/src/launcher/launcher.pri new file mode 100644 index 0000000..1a7ddf9 --- /dev/null +++ b/src/launcher/launcher.pri @@ -0,0 +1,13 @@ +include(../core/core.pri) + +CONFIG += network +QT += jsonstream + +INCLUDEPATH += $$PWD +LIBS += -L$$PWD -lprocessmanager-launcher + +mac|unix { + CONFIG += rpath_libdirs + QMAKE_RPATHDIR += $$PWD + QMAKE_LFLAGS += "-Wl,-rpath $$PWD" +} diff --git a/src/launcher/launcher.pro b/src/launcher/launcher.pro new file mode 100644 index 0000000..9070918 --- /dev/null +++ b/src/launcher/launcher.pro @@ -0,0 +1,20 @@ +TEMPLATE = lib +TARGET = processmanager-launcher + +QT += jsonstream +LIBS += -L../core + +include($$PWD/../../config.pri) +include(../core/core.pri) +include(launcher-lib.pri) + +mac:!staticlib { + QMAKE_POST_LINK = install_name_tool -id $$PWD/${TARGET} ${TARGET} +} + +headers.path = $$INSTALLBASE/include/qtprocessmanager +headers.files = $$HEADERS + +target.path = $$INSTALLBASE/lib + +INSTALLS += target headers diff --git a/src/launcher/launcherclient.cpp b/src/launcher/launcherclient.cpp new file mode 100644 index 0000000..e56dfd1 --- /dev/null +++ b/src/launcher/launcherclient.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDebug> + +#include "launcherclient.h" +#include "processbackend.h" +#include "processbackendmanager.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class LauncherClient + \brief The LauncherClient class handles a single Launcher client. + */ + +/*! + Create a new LauncherClient with ProcessBackendManager \a manager + */ + +LauncherClient::LauncherClient(ProcessBackendManager *manager) + : QObject(manager) + , m_manager(manager) +{ +} + +/*! + Process an incoming \a message + */ + +void LauncherClient::receive(const QJsonObject& message) +{ + qDebug() << Q_FUNC_INFO << message; + QString cmd = message.value("command").toString(); + int id = message.value("id").toDouble(); + if ( cmd == "start" ) { + ProcessInfo info(message.value("info").toObject().toVariantMap()); + ProcessBackend *backend = m_manager->create(info, this); + if (backend) { + connect(backend, SIGNAL(started()), SLOT(started())); + connect(backend, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(finished(int, QProcess::ExitStatus))); + connect(backend, SIGNAL(error(QProcess::ProcessError)), SLOT(error(QProcess::ProcessError))); + connect(backend, SIGNAL(stateChanged(QProcess::ProcessState)), + SLOT(stateChanged(QProcess::ProcessState))); + connect(backend, SIGNAL(standardOutput(const QByteArray&)), + SLOT(standardOutput(const QByteArray&))); + connect(backend, SIGNAL(standardError(const QByteArray&)), + SLOT(standardError(const QByteArray&))); + m_idToBackend.insert(id, backend); + m_backendToId.insert(backend, id); + backend->start(); + } + } + else if ( cmd == "stop" ) { + ProcessBackend *backend = m_idToBackend.value(id); + if (backend) { + int timeout = message.value("timeout").toDouble(); + backend->stop(timeout); + } + } + else if ( cmd == "set" ) { + ProcessBackend *backend = m_idToBackend.value(id); + if (backend) { + QString key = message.value("key").toString(); + int value = message.value("value").toDouble(); + if (key == "priority") + backend->setDesiredPriority(value); + else if (key == "oomAdjustment") + backend->setDesiredOomAdjustment(value); + } + } + else if ( cmd == "write" ) { + QByteArray data = message.value("data").toString().toLocal8Bit(); + ProcessBackend *backend = m_idToBackend.value(id); + if (backend) + backend->write(data); + } +} + +static const QLatin1String kEvent("event"); +static const QLatin1String kId("id"); + +/*! + \internal + */ + +void LauncherClient::started() +{ + ProcessBackend *backend = qobject_cast<ProcessBackend *>(sender()); + QJsonObject msg; + msg.insert(kEvent, QLatin1String("started")); + msg.insert(kId, m_backendToId.value(backend)); + msg.insert("pid", (double) backend->pid()); + emit send(msg); +} + +/*! + \internal + */ + +void LauncherClient::finished(int exitCode, QProcess::ExitStatus exitStatus) +{ + ProcessBackend *backend = qobject_cast<ProcessBackend *>(sender()); + QJsonObject msg; + msg.insert(kEvent, QLatin1String("finished")); + msg.insert(kId, m_backendToId.value(backend)); + msg.insert("exitCode", exitCode); + msg.insert("exitStatus", exitStatus); + emit send(msg); +} + +/*! + \internal + */ + +void LauncherClient::error(QProcess::ProcessError err) +{ + ProcessBackend *backend = qobject_cast<ProcessBackend *>(sender()); + QJsonObject msg; + msg.insert(kEvent, QLatin1String("error")); + msg.insert(kId, m_backendToId.value(backend)); + msg.insert("error", err); + emit send(msg); +} + +/*! + \internal + */ + +void LauncherClient::stateChanged(QProcess::ProcessState state) +{ + ProcessBackend *backend = qobject_cast<ProcessBackend *>(sender()); + QJsonObject msg; + msg.insert(kEvent, QLatin1String("stateChanged")); + msg.insert(kId, m_backendToId.value(backend)); + msg.insert("stateChanged", state); + emit send(msg); +} + +/*! + \internal + */ + +void LauncherClient::standardOutput(const QByteArray& data) +{ + ProcessBackend *backend = qobject_cast<ProcessBackend *>(sender()); + QJsonObject msg; + msg.insert(kEvent, QLatin1String("standardOutput")); + msg.insert(kId, m_backendToId.value(backend)); + msg.insert(QLatin1String("data"), QString::fromLocal8Bit(data.data(), data.size())); + emit send(msg); +} + +/*! + \internal + */ + +void LauncherClient::standardError(const QByteArray& data) +{ + ProcessBackend *backend = qobject_cast<ProcessBackend *>(sender()); + QJsonObject msg; + msg.insert(kEvent, QLatin1String("standardError")); + msg.insert(kId, m_backendToId.value(backend)); + msg.insert(QLatin1String("data"), QString::fromLocal8Bit(data.data(), data.size())); + emit send(msg); +} + +/*! + \fn void LauncherClient::send(const QJsonObject& message) + + Send a \a message to the remote controller. +*/ + +#include "moc_launcherclient.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/launcher/launcherclient.h b/src/launcher/launcherclient.h new file mode 100644 index 0000000..13dd8e8 --- /dev/null +++ b/src/launcher/launcherclient.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef _LAUNCHER_CLIENT_H +#define _LAUNCHER_CLIENT_H + +#include <QObject> +#include <QJsonObject> +#include <QProcess> +#include <QMap> + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class ProcessBackend; +class ProcessBackendManager; + +class LauncherClient : public QObject { + Q_OBJECT +public: + LauncherClient(ProcessBackendManager *manager); + void receive(const QJsonObject& message); + +signals: + void send(const QJsonObject& message); + +private slots: + void started(); + void finished(int, QProcess::ExitStatus); + void error(QProcess::ProcessError); + void stateChanged(QProcess::ProcessState); + void standardOutput(const QByteArray&); + void standardError(const QByteArray&); + +private: + ProcessBackendManager *m_manager; + QMap<int, ProcessBackend *> m_idToBackend; + QMap<ProcessBackend *, int> m_backendToId; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // _LAUNCHER_CLIENT_H diff --git a/src/launcher/main.cpp b/src/launcher/main.cpp new file mode 100644 index 0000000..db32f14 --- /dev/null +++ b/src/launcher/main.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QCoreApplication> +#include "socketlauncher.h" +#include "standardprocessbackendfactory.h" + +QT_USE_NAMESPACE_PROCESSMANAGER + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + SocketLauncher launcher; + launcher.addFactory(new StandardProcessBackendFactory); + launcher.listen("/tmp/launcher"); + return app.exec(); +} diff --git a/src/launcher/pipelauncher.cpp b/src/launcher/pipelauncher.cpp new file mode 100644 index 0000000..6060c8e --- /dev/null +++ b/src/launcher/pipelauncher.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <signal.h> +#include <QDebug> +#include <QtEndian> +#include <QJsonDocument> + +#include "pipelauncher.h" +#include "launcherclient.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class PipeLauncher + \brief The PipeLauncher class accepts input from STDIN and writes data to STDOUT + */ + +/*! + Construct a PipeLauncher with optional \a parent. + The PipeLauncher reads JSON-formatted messages from stdin and + writes replies on stdout. +*/ + +PipeLauncher::PipeLauncher(QObject *parent) + : ProcessBackendManager(parent) +{ + m_in = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Read, this ); + connect(m_in, SIGNAL(activated(int)), SLOT(inReady(int))); + m_in->setEnabled(true); + m_out = new QSocketNotifier( STDOUT_FILENO, QSocketNotifier::Write, this ); + connect(m_out, SIGNAL(activated(int)), SLOT(outReady(int))); + m_out->setEnabled(false); + + m_client = new LauncherClient(this); + connect(m_client, SIGNAL(send(const QJsonObject&)), + SLOT(send(const QJsonObject&))); +} + +/*! + \internal + */ +void PipeLauncher::inReady(int fd) +{ + qDebug() << Q_FUNC_INFO; + m_in->setEnabled(false); + const int bufsize = 1024; + uint oldSize = m_inbuf.size(); + m_inbuf.resize(oldSize + bufsize); + int n = ::read(fd, m_inbuf.data()+oldSize, bufsize); + if (n > 0) + m_inbuf.resize(oldSize+n); + else + m_inbuf.resize(oldSize); + // Could check for an error here + // Check for a complete JSON object + while (m_inbuf.size() >= 12) { + qint32 message_size = qFromLittleEndian(((qint32 *)m_inbuf.data())[2]) + 8; + if (m_inbuf.size() < message_size) + break; + QByteArray msg = m_inbuf.left(message_size); + m_inbuf = m_inbuf.mid(message_size); + QJsonObject message = QJsonDocument::fromBinaryData(msg).object(); + if (message.value("remote").toString() == "stop") + deleteLater(); + m_client->receive(message); + } + m_in->setEnabled(true); +} + +/*! + \internal + */ +void PipeLauncher::outReady(int fd) +{ + qDebug() << Q_FUNC_INFO; + m_out->setEnabled(false); + if (m_outbuf.size()) { + int n = ::write(fd, m_outbuf.data(), m_outbuf.size()); + if (n == -1) { + qDebug() << "Failed to write to stdout"; + exit(-1); + } + if (n < m_outbuf.size()) + m_outbuf = m_outbuf.mid(n); + else + m_outbuf.clear(); + } + if (m_outbuf.size()) + m_out->setEnabled(true); +} + +/*! + \internal + */ +void PipeLauncher::send(const QJsonObject& object) +{ + QByteArray data = QJsonDocument(object).toBinaryData(); + m_outbuf.append(data); + m_out->setEnabled(true); +} + +#include "moc_pipelauncher.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/launcher/pipelauncher.h b/src/launcher/pipelauncher.h new file mode 100644 index 0000000..765449d --- /dev/null +++ b/src/launcher/pipelauncher.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PIPE_LAUNCHER_H +#define PIPE_LAUNCHER_H + +#include <QObject> +#include <QJsonObject> +#include <QSocketNotifier> + +#include "processbackendmanager.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class LauncherClient; + +class PipeLauncher : public ProcessBackendManager { + Q_OBJECT + +public: + PipeLauncher(QObject *parent=0); + +private slots: + void inReady(int fd); + void outReady(int fd); + void send(const QJsonObject& object); + +private: + QSocketNotifier *m_in; + QSocketNotifier *m_out; + QByteArray m_inbuf; + QByteArray m_outbuf; + LauncherClient *m_client; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // PIPE_LAUNCHER_H diff --git a/src/launcher/socketlauncher.cpp b/src/launcher/socketlauncher.cpp new file mode 100644 index 0000000..bb6561f --- /dev/null +++ b/src/launcher/socketlauncher.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <signal.h> + +#include "socketlauncher.h" +#include "launcherclient.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +/*! + \class SocketLauncher + \brief The SocketLauncher class accepts incoming socket connections and starts processes. + + The SocketLauncher class accepts incoming socket connections and starts and stops + processes based on JSON-formatted messages. Each connection gets its own set of + processes. When a connection is dropped, all processes associated with that connection + are killed. + */ + +/*! + Construct a SocketLauncher with optional \a parent. + The socket launcher opens a JsonServer object. + */ +SocketLauncher::SocketLauncher(QObject *parent) + : ProcessBackendManager(parent) +{ + m_server = new QtAddOn::JsonStream::JsonServer(this); + + connect(m_server, SIGNAL(messageReceived(const QString&, const QJsonObject&)), + SLOT(messageReceived(const QString&, const QJsonObject&))); + connect(m_server, SIGNAL(connectionAdded(const QString&)), + SLOT(connectionAdded(const QString&))); + connect(m_server, SIGNAL(connectionRemoved(const QString&)), + SLOT(connectionRemoved(const QString&))); +} + +/*! + Listen to TCP socket connections on \a port. + The optional \a authority object is used to authenticate incoming connections. + Return true if the port can be used. +*/ +bool SocketLauncher::listen(int port, QtAddOn::JsonStream::JsonAuthority *authority) +{ + return m_server->listen(port, authority); +} + +/*! + Listen to Unix local socket connections on \a socketname. + The optional \a authority object is used to authenticate incoming connections. + Return true if the server can be started. +*/ +bool SocketLauncher::listen(const QString& socketname, QtAddOn::JsonStream::JsonAuthority *authority) +{ + return m_server->listen(socketname, authority); +} + +/*! + \internal +*/ +void SocketLauncher::connectionAdded(const QString& identifier) +{ + LauncherClient *client = new LauncherClient(this); + connect(client, SIGNAL(send(const QJsonObject&)), SLOT(send(const QJsonObject&))); + m_idToClient.insert(identifier, client); + m_clientToId.insert(client, identifier); +} + +/*! + \internal +*/ +void SocketLauncher::connectionRemoved(const QString& identifier) +{ + LauncherClient *client = m_idToClient.take(identifier); + if (client) { + m_clientToId.take(client); + delete client; + } +} + +/*! + \internal +*/ +void SocketLauncher::messageReceived(const QString& identifier, const QJsonObject& message) +{ + LauncherClient *client = m_idToClient.value(identifier); + if (client) + client->receive(message); +} + +/*! + \internal +*/ +void SocketLauncher::send(const QJsonObject& message) +{ + LauncherClient *client = qobject_cast<LauncherClient *>(sender()); + Q_ASSERT(client); + Q_ASSERT(m_server); + m_server->send(m_clientToId.value(client), message); +} + +#include "moc_socketlauncher.cpp" + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/launcher/socketlauncher.h b/src/launcher/socketlauncher.h new file mode 100644 index 0000000..2d6ac74 --- /dev/null +++ b/src/launcher/socketlauncher.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SOCKET_LAUNCHER_H +#define SOCKET_LAUNCHER_H + +#include <QObject> +#include <QJsonObject> +#include <jsonserver.h> + +#include "processbackendmanager.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +class LauncherClient; + +class SocketLauncher : public ProcessBackendManager { + Q_OBJECT + +public: + SocketLauncher(QObject *parent=0); + bool listen(int port, QtAddOn::JsonStream::JsonAuthority *authority = 0); + bool listen(const QString& socketname, QtAddOn::JsonStream::JsonAuthority *authority=0); + +private slots: + void connectionAdded(const QString& identifier); + void connectionRemoved(const QString& identifier); + void messageReceived(const QString& identifier, const QJsonObject& message); + void send(const QJsonObject& message); + +private: + QtAddOn::JsonStream::JsonServer *m_server; + QMap<QString, LauncherClient*> m_idToClient; + QMap<LauncherClient*, QString> m_clientToId; +}; + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // SOCKET_LAUNCHER_H diff --git a/src/src.pri b/src/src.pri new file mode 100644 index 0000000..f934acc --- /dev/null +++ b/src/src.pri @@ -0,0 +1,10 @@ +CONFIG += network + +INCLUDEPATH += $$PWD +LIBS += -L$$PWD -lprocessmanager-core + +mac|unix { + CONFIG += rpath_libdirs + QMAKE_RPATHDIR += $$PWD + QMAKE_LFLAGS += "-Wl,-rpath $$PWD" +} diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..8daef63 --- /dev/null +++ b/src/src.pro @@ -0,0 +1,17 @@ +TEMPLATE = subdirs + +module_processmanager_core.subdir = core +module_processmanager_core.target = module-processmanager-core + +module_processmanager_declarative.subdir = declarative +module_processmanager_declarative.target = module-processmanager-declarative +module_processmanager_declarative.depends += module-processmanager-core + +module_processmanager_launcher.subdir = launcher +module_processmanager_launcher.target = module-processmanager-launcher +module_processmanager_launcher.depends += module-processmanager-core + +SUBDIRS += \ + module_processmanager_core \ + module_processmanager_declarative \ + module_processmanager_launcher diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro new file mode 100644 index 0000000..fc983ed --- /dev/null +++ b/tests/auto/auto.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = processmanager declarative diff --git a/tests/auto/declarative/.gitignore b/tests/auto/declarative/.gitignore new file mode 100644 index 0000000..55e4154 --- /dev/null +++ b/tests/auto/declarative/.gitignore @@ -0,0 +1,2 @@ +tst_declarative +*.moc diff --git a/tests/auto/declarative/data/testfrontend.qml b/tests/auto/declarative/data/testfrontend.qml new file mode 100644 index 0000000..ec6a07d --- /dev/null +++ b/tests/auto/declarative/data/testfrontend.qml @@ -0,0 +1,37 @@ +import QtQuick 2.0 +import Test 1.0 + +DeclarativeProcessManager { + id: foo + + factories: [ + StandardProcessBackendFactory { id: foo2 } + ] + + onProcessStarted: console.log("Process started "+name) + onProcessStateChanged: { + function stateToString(s) { + if (s == Process.NotRunning) return "Not running"; + if (s == Process.Running) return "Running"; + if (s == Process.Starting) return "Starting"; + return "Unknown"; + } + console.log("state changed for "+name+" to "+stateToString(state)); + } + + function makeProcess() { + var a = create({"program": "testDeclarative/testDeclarative","name": "test-client"}); + return a.name; + } + + function startProcess(name) { + var p = processForName(name); + console.log("Starting process '"+name+"' ="+p); + p.start(); + } + + function stopProcess(name) { + var p = processForName(name); + p.stop(); + } +} diff --git a/tests/auto/declarative/declarative.pri b/tests/auto/declarative/declarative.pri new file mode 100644 index 0000000..d797178 --- /dev/null +++ b/tests/auto/declarative/declarative.pri @@ -0,0 +1 @@ +TESTCASE_NAME = tst_declarative diff --git a/tests/auto/declarative/declarative.pro b/tests/auto/declarative/declarative.pro new file mode 100644 index 0000000..f331ac2 --- /dev/null +++ b/tests/auto/declarative/declarative.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS = testDeclarative test diff --git a/tests/auto/declarative/test/test.pro b/tests/auto/declarative/test/test.pro new file mode 100644 index 0000000..58bd541 --- /dev/null +++ b/tests/auto/declarative/test/test.pro @@ -0,0 +1,18 @@ +CONFIG += testcase +macx:CONFIG -= app_bundle + +QT += core network declarative testlib + +LIBS += -L../../../../src/core -L../../../../src/declarative + +include(../declarative.pri) +include(../../../../src/declarative/declarative.pri) + +SOURCES = ../tst_declarative.cpp +TARGET = ../$$TESTCASE_NAME + +OTHER_FILES += + +testDataFiles.files = data +testDataFiles.path = . +DEPLOYMENT += testDatafiles diff --git a/tests/auto/declarative/testDeclarative/.gitignore b/tests/auto/declarative/testDeclarative/.gitignore new file mode 100644 index 0000000..89af73b --- /dev/null +++ b/tests/auto/declarative/testDeclarative/.gitignore @@ -0,0 +1 @@ +testDeclarative diff --git a/tests/auto/declarative/testDeclarative/main.cpp b/tests/auto/declarative/testDeclarative/main.cpp new file mode 100644 index 0000000..d61b1ad --- /dev/null +++ b/tests/auto/declarative/testDeclarative/main.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <string.h> + +const int kBufSize = 100; + +ssize_t writeline(char *buffer, int len) +{ + char *b = buffer; + while ( len > 0 ) { + ssize_t r = write(STDOUT_FILENO, b, len); + if (r < 0) + return r; + b += r; + len -= r; + } + return 0; +} + +ssize_t readline(char *buffer, int max_len) +{ + int len = 0; + char c = 0; + while (len < max_len && c != '\r' && c != '\n') { + ssize_t count = read(STDIN_FILENO, &c, 1); + if (count <= 0) + return count; + *buffer++ = c; + len++; + } + *buffer = 0; + return len; +} + +int +main(int argc, char **argv) +{ + char buffer[kBufSize+1]; + + while (1) { + ssize_t count = readline(buffer, kBufSize); + if (count < 0) + return 1; + if (count == 0) + return 0; + + if (strncmp("stop", buffer, 4) == 0) + return 0; + if (strncmp("crash", buffer, 5) == 0) + return 2; + + ssize_t result = writeline(buffer, count); + if (result < 0) + return 2; + } +} diff --git a/tests/auto/declarative/testDeclarative/testDeclarative.pro b/tests/auto/declarative/testDeclarative/testDeclarative.pro new file mode 100644 index 0000000..f7b5f57 --- /dev/null +++ b/tests/auto/declarative/testDeclarative/testDeclarative.pro @@ -0,0 +1,9 @@ +CONFIG -= app_bundle +include(../declarative.pri) + +DESTDIR = ./ +SOURCES = main.cpp +TARGET = testDeclarative + +target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testDeclarative +INSTALLS += target diff --git a/tests/auto/declarative/tst_declarative.cpp b/tests/auto/declarative/tst_declarative.cpp new file mode 100644 index 0000000..bb106e6 --- /dev/null +++ b/tests/auto/declarative/tst_declarative.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QtCore/QMetaType> +#include <QFileInfo> +#include <QDeclarativeEngine> +#include <QDeclarativeComponent> +#include <QDeclarativeProperty> + +#include "declarativeprocessmanager.h" +#include "standardprocessbackendfactory.h" +#include "processfrontend.h" +#include "process.h" + +QT_USE_NAMESPACE_PROCESSMANAGER + +Q_DECLARE_METATYPE(QProcess::ExitStatus); +Q_DECLARE_METATYPE(QProcess::ProcessState); +Q_DECLARE_METATYPE(QProcess::ProcessError); + +// QML_DECLARE_TYPE(ProcessBackendFactory) +// QML_DECLARE_TYPE(StandardProcessBackendFactory) + +class tst_DeclarativeProcessManager : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void basic(); +}; + + +void tst_DeclarativeProcessManager::initTestCase() +{ + qDebug() << "Registering types"; + const char *uri = "Test"; + qmlRegisterType<ProcessBackendFactory>(); + qmlRegisterType<StandardProcessBackendFactory>(uri, 1, 0, "StandardProcessBackendFactory"); + qmlRegisterType<ProcessFrontend>(); + qmlRegisterType<DeclarativeProcessManager>(uri, 1, 0, "DeclarativeProcessManager"); + qmlRegisterUncreatableType<Process>(uri, 1, 0, "Process", "Don't try to make this"); + + qRegisterMetaType<QProcess::ProcessState>(); + qRegisterMetaType<QProcess::ExitStatus>(); + qRegisterMetaType<QProcess::ProcessError>(); +} + + +class Spy { +public: + Spy(ProcessFrontend *process) + : stateSpy(process, SIGNAL(stateChanged(QProcess::ProcessState))) + , startSpy(process, SIGNAL(started())) + , errorSpy(process, SIGNAL(error(QProcess::ProcessError))) + , finishedSpy(process, SIGNAL(finished(int, QProcess::ExitStatus))) {} + + void check( int startCount, int errorCount, int finishedCount, int stateCount ) { + QVERIFY(startSpy.count() == startCount); + QVERIFY(errorSpy.count() == errorCount); + QVERIFY(finishedSpy.count() == finishedCount); + QVERIFY(stateSpy.count() == stateCount); + bool failedToStart = false; + for (int i = 0 ; i < errorSpy.count() ; i++) + if (qVariantValue<QProcess::ProcessError>(errorSpy.at(i).at(0)) == QProcess::FailedToStart) + failedToStart = true; + + if (failedToStart) + QVERIFY(stateCount <=2); + if (stateCount > 0) + QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(0).at(0)), QProcess::Starting); + if (stateCount > 1) + QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(1).at(0)), + (failedToStart ? QProcess::NotRunning : QProcess::Running)); + if (stateCount > 2) + QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(2).at(0)), QProcess::NotRunning); + } + + void waitStart(int timeout=5000) { + stopWatch.restart(); + forever { + if (startSpy.count()) + break; + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + } + } + + void waitFailedStart(int timeout=5000) { + stopWatch.restart(); + forever { + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + if (errorSpy.count()) + break; + } + } + + void waitFinished(int timeout=5000) { + stopWatch.restart(); + forever { + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + if (finishedSpy.count()) + break; + } + } + + void checkExitCode(int exitCode) { + QVERIFY(finishedSpy.count() == 1); + QCOMPARE(qVariantValue<int>(finishedSpy.at(0).at(0)), exitCode); + } + + void checkExitStatus(QProcess::ExitStatus exitStatus) { + QVERIFY(finishedSpy.count() == 1); + QCOMPARE(qVariantValue<QProcess::ExitStatus>(finishedSpy.at(0).at(1)), exitStatus); + } + + void checkErrors(const QList<QProcess::ProcessError>& list) { + QCOMPARE(errorSpy.count(), list.count()); + for (int i = 0 ; i < errorSpy.count() ; i++) + QCOMPARE(qVariantValue<QProcess::ProcessError>(errorSpy.at(i).at(0)), list.at(i)); + } + + QTime stopWatch; + QSignalSpy stateSpy; + QSignalSpy startSpy; + QSignalSpy errorSpy; + QSignalSpy finishedSpy; +}; + +void tst_DeclarativeProcessManager::basic() +{ + QDeclarativeEngine engine; + QDeclarativeComponent component(&engine, QUrl::fromLocalFile("data/testfrontend.qml")); + if (component.isError()) + qWarning() << component.errors(); + DeclarativeProcessManager *manager = qobject_cast<DeclarativeProcessManager*>(component.create()); + QVERIFY(manager != NULL); + + QVariant name; + QVERIFY(QMetaObject::invokeMethod(manager, "makeProcess", Q_RETURN_ARG(QVariant, name))); + ProcessFrontend *frontend = manager->processForName(name.toString()); + QVERIFY(frontend); + qDebug() << "Internal frontend object" << frontend; + + Spy spy(frontend); + QVERIFY(QMetaObject::invokeMethod(manager, "startProcess", Q_ARG(QVariant, name))); + spy.waitStart(); + return; + + QVERIFY(QMetaObject::invokeMethod(manager, "stopProcess", Q_ARG(QVariant, name))); + spy.waitFinished(); + spy.check(1, 1, 1, 3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::CrashExit); + spy.checkErrors(QList<QProcess::ProcessError>() << QProcess::Crashed); +} + + +QTEST_MAIN(tst_DeclarativeProcessManager) + +#include "tst_declarative.moc" diff --git a/tests/auto/processmanager/.gitignore b/tests/auto/processmanager/.gitignore new file mode 100644 index 0000000..d2adfd1 --- /dev/null +++ b/tests/auto/processmanager/.gitignore @@ -0,0 +1,2 @@ +tst_processmanager +*.moc diff --git a/tests/auto/processmanager/data/testfrontend.qml b/tests/auto/processmanager/data/testfrontend.qml new file mode 100644 index 0000000..ecf73c6 --- /dev/null +++ b/tests/auto/processmanager/data/testfrontend.qml @@ -0,0 +1,19 @@ +import QtQuick 2.0 +import Test 1.0 + +TestManager { + id: foo + magic: "fuzzy" + + function makeProcess() { + var a = create({"program": "testClient/testClient","identifier": "a"}); + console.log("Type="+typeof(a)); + var p = processForIdentifier("a"); + console.log("Process: "+p); + p.onStarted.connect(foo.itStarted); + } + + function itStarted() { + console.log("It started"); + } +} diff --git a/tests/auto/processmanager/processmanager.pri b/tests/auto/processmanager/processmanager.pri new file mode 100644 index 0000000..fb3dc7a --- /dev/null +++ b/tests/auto/processmanager/processmanager.pri @@ -0,0 +1 @@ +TESTCASE_NAME = tst_processmanager diff --git a/tests/auto/processmanager/processmanager.pro b/tests/auto/processmanager/processmanager.pro new file mode 100644 index 0000000..864fbf9 --- /dev/null +++ b/tests/auto/processmanager/processmanager.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS = testClient testPrelaunch testPipeLauncher testSocketLauncher test diff --git a/tests/auto/processmanager/test/test.pro b/tests/auto/processmanager/test/test.pro new file mode 100644 index 0000000..0f2cb6e --- /dev/null +++ b/tests/auto/processmanager/test/test.pro @@ -0,0 +1,19 @@ +CONFIG += testcase +macx:CONFIG -= app_bundle + +QT += core network declarative testlib +QT -= gui + +LIBS += -L../../../../src/core + +include(../processmanager.pri) +include(../../../../src/launcher/launcher.pri) + +SOURCES = ../tst_processmanager.cpp +TARGET = ../$$TESTCASE_NAME + +OTHER_FILES += + +testDataFiles.files = data +testDataFiles.path = . +DEPLOYMENT += testDatafiles diff --git a/tests/auto/processmanager/testClient/.gitignore b/tests/auto/processmanager/testClient/.gitignore new file mode 100644 index 0000000..9640c79 --- /dev/null +++ b/tests/auto/processmanager/testClient/.gitignore @@ -0,0 +1 @@ +testClient diff --git a/tests/auto/processmanager/testClient/main.cpp b/tests/auto/processmanager/testClient/main.cpp new file mode 100644 index 0000000..d61b1ad --- /dev/null +++ b/tests/auto/processmanager/testClient/main.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <string.h> + +const int kBufSize = 100; + +ssize_t writeline(char *buffer, int len) +{ + char *b = buffer; + while ( len > 0 ) { + ssize_t r = write(STDOUT_FILENO, b, len); + if (r < 0) + return r; + b += r; + len -= r; + } + return 0; +} + +ssize_t readline(char *buffer, int max_len) +{ + int len = 0; + char c = 0; + while (len < max_len && c != '\r' && c != '\n') { + ssize_t count = read(STDIN_FILENO, &c, 1); + if (count <= 0) + return count; + *buffer++ = c; + len++; + } + *buffer = 0; + return len; +} + +int +main(int argc, char **argv) +{ + char buffer[kBufSize+1]; + + while (1) { + ssize_t count = readline(buffer, kBufSize); + if (count < 0) + return 1; + if (count == 0) + return 0; + + if (strncmp("stop", buffer, 4) == 0) + return 0; + if (strncmp("crash", buffer, 5) == 0) + return 2; + + ssize_t result = writeline(buffer, count); + if (result < 0) + return 2; + } +} diff --git a/tests/auto/processmanager/testClient/testClient.pro b/tests/auto/processmanager/testClient/testClient.pro new file mode 100644 index 0000000..27abe70 --- /dev/null +++ b/tests/auto/processmanager/testClient/testClient.pro @@ -0,0 +1,11 @@ +CONFIG -= app_bundle +include(../processmanager.pri) + +LIBS += -L../../../../src/core + +DESTDIR = ./ +SOURCES = main.cpp +TARGET = testClient + +target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testClient +INSTALLS += target diff --git a/tests/auto/processmanager/testPipeLauncher/.gitignore b/tests/auto/processmanager/testPipeLauncher/.gitignore new file mode 100644 index 0000000..74e89f1 --- /dev/null +++ b/tests/auto/processmanager/testPipeLauncher/.gitignore @@ -0,0 +1 @@ +testPipeLauncher diff --git a/tests/auto/processmanager/testPipeLauncher/main.cpp b/tests/auto/processmanager/testPipeLauncher/main.cpp new file mode 100644 index 0000000..44ebccc --- /dev/null +++ b/tests/auto/processmanager/testPipeLauncher/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QCoreApplication> +#include "pipelauncher.h" +#include "standardprocessbackendfactory.h" + +QT_USE_NAMESPACE_PROCESSMANAGER + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + PipeLauncher launcher; + launcher.addFactory(new StandardProcessBackendFactory); + return app.exec(); +} diff --git a/tests/auto/processmanager/testPipeLauncher/testPipeLauncher.pro b/tests/auto/processmanager/testPipeLauncher/testPipeLauncher.pro new file mode 100644 index 0000000..ed72ecf --- /dev/null +++ b/tests/auto/processmanager/testPipeLauncher/testPipeLauncher.pro @@ -0,0 +1,12 @@ +CONFIG -= app_bundle +LIBS += -L../../../../src/core + +include(../processmanager.pri) +include(../../../../src/launcher/launcher.pri) + +DESTDIR = ./ +SOURCES += main.cpp +TARGET = testPipeLauncher + +target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testPipeLauncher +INSTALLS += target diff --git a/tests/auto/processmanager/testPrelaunch/.gitignore b/tests/auto/processmanager/testPrelaunch/.gitignore new file mode 100644 index 0000000..d6d4098 --- /dev/null +++ b/tests/auto/processmanager/testPrelaunch/.gitignore @@ -0,0 +1 @@ +testPrelaunch diff --git a/tests/auto/processmanager/testPrelaunch/main.cpp b/tests/auto/processmanager/testPrelaunch/main.cpp new file mode 100644 index 0000000..08a2279 --- /dev/null +++ b/tests/auto/processmanager/testPrelaunch/main.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QCoreApplication> +#include <QSocketNotifier> +#include <QTimer> +#include <QDebug> +#include "qjsondocument.h" +#include "processinfo.h" + +QT_USE_NAMESPACE_PROCESSMANAGER + +class Container : public QObject +{ + Q_OBJECT + +public: + Container() : count(0) { + notifier = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Read, this ); + connect(notifier, SIGNAL(activated(int)), SLOT(dataReady())); + notifier->setEnabled(true); + } + + void handleMessage(const QVariantMap& map) { + if (!count) { + ProcessInfo info(map); + qDebug() << "Received process info" << info.toMap(); + } + else { + QString cmd = map.value("command").toString(); + qDebug() << "Received command" << cmd; + if (cmd == "stop") { + qDebug() << "Stopping"; + exit(0); + } + } + count++; + } + +public slots: + void dataReady() { + qDebug() << Q_FUNC_INFO; + notifier->setEnabled(false); + const int bufsize = 1024; + uint oldSize = buffer.size(); + buffer.resize(oldSize + bufsize); + int n = ::read( STDIN_FILENO, buffer.data()+oldSize, bufsize); + if (n > 0) + buffer.resize(oldSize+n); + else + buffer.resize(oldSize); + // Could check for an error here + // Check for a complete JSON object + while (buffer.size() >= 12) { + qint32 message_size = ((qint32 *)buffer.data())[2] + 8; // Should use 'sizeof(Header)' + if (buffer.size() < message_size) + break; + QByteArray msg = buffer.left(message_size); + buffer = buffer.mid(message_size); + handleMessage(QJsonDocument::fromBinaryData(msg).toVariant().toMap()); + } + notifier->setEnabled(true); + } + +private: + QSocketNotifier *notifier; + QByteArray buffer; + int count; +}; + +int +main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + Container c; + qDebug() << "testPrelaunch running"; + return app.exec(); +} + +#include "main.moc" diff --git a/tests/auto/processmanager/testPrelaunch/testPrelaunch.pro b/tests/auto/processmanager/testPrelaunch/testPrelaunch.pro new file mode 100644 index 0000000..f9be8ca --- /dev/null +++ b/tests/auto/processmanager/testPrelaunch/testPrelaunch.pro @@ -0,0 +1,12 @@ +CONFIG -= app_bundle +include(../processmanager.pri) +include(../../../../src/core/core.pri) + +LIBS += -L../../../../src/core + +DESTDIR = ./ +SOURCES = main.cpp +TARGET = testPrelaunch + +target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testPrelaunch +INSTALLS += target diff --git a/tests/auto/processmanager/testSocketLauncher/.gitignore b/tests/auto/processmanager/testSocketLauncher/.gitignore new file mode 100644 index 0000000..9064b8c --- /dev/null +++ b/tests/auto/processmanager/testSocketLauncher/.gitignore @@ -0,0 +1 @@ +testSocketLauncher diff --git a/tests/auto/processmanager/testSocketLauncher/main.cpp b/tests/auto/processmanager/testSocketLauncher/main.cpp new file mode 100644 index 0000000..33b1bab --- /dev/null +++ b/tests/auto/processmanager/testSocketLauncher/main.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QCoreApplication> +#include "socketlauncher.h" +#include "standardprocessbackendfactory.h" + +QT_USE_NAMESPACE_PROCESSMANAGER + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + SocketLauncher launcher; + launcher.addFactory(new StandardProcessBackendFactory); + launcher.listen("/tmp/socketlauncher"); + return app.exec(); +} diff --git a/tests/auto/processmanager/testSocketLauncher/testSocketLauncher.pro b/tests/auto/processmanager/testSocketLauncher/testSocketLauncher.pro new file mode 100644 index 0000000..d971606 --- /dev/null +++ b/tests/auto/processmanager/testSocketLauncher/testSocketLauncher.pro @@ -0,0 +1,12 @@ +CONFIG -= app_bundle +LIBS += -L../../../../src/core + +include(../processmanager.pri) +include(../../../../src/launcher/launcher.pri) + +DESTDIR = ./ +SOURCES += main.cpp +TARGET = testSocketLauncher + +target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testSocketLauncher +INSTALLS += target diff --git a/tests/auto/processmanager/tst_processmanager.cpp b/tests/auto/processmanager/tst_processmanager.cpp new file mode 100644 index 0000000..1f440ff --- /dev/null +++ b/tests/auto/processmanager/tst_processmanager.cpp @@ -0,0 +1,854 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <QtCore/QMetaType> +#include <QFileInfo> +#include <QDeclarativeEngine> +#include <QDeclarativeComponent> +#include <QDeclarativeProperty> + +#include "process.h" +#include "processbackendmanager.h" +#include "processmanager.h" +#include "processinfo.h" +#include "prelaunchprocessbackendfactory.h" +#include "standardprocessbackendfactory.h" +#include "processbackend.h" +#include "processfrontend.h" +#include "qjsondocument.h" +#include "pipeprocessbackendfactory.h" +#include "socketprocessbackendfactory.h" + +QT_USE_NAMESPACE_PROCESSMANAGER + +Q_DECLARE_METATYPE(QProcess::ExitStatus); +Q_DECLARE_METATYPE(QProcess::ProcessState); +Q_DECLARE_METATYPE(QProcess::ProcessError); + +class tst_ProcessManager : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void startAndKill(); + void failToStart(); + void startAndStop(); + void startAndCrash(); + void echo(); + void priorityChangeBeforeStart(); + void priorityChangeAfterStart(); + void prelaunch(); + void prelaunchRestricted(); + void pipeLauncher(); + void pipeLauncherCrash(); + void socketLauncher(); + void socketLauncherCrash(); + void frontend(); + void subclassFrontend(); +}; + +const char *exitStatusToString[] = { + "NormalExit", + "CrashExit" +}; + +const char *stateToString[] = { + "NotRunning", + "Starting", + "Running" +}; + +const char *errorToString[] = { + "FailedToStart", + "Crashed", + "Timedout", + "WriteError", + "ReadError", + "UnknownError" +}; + +class Spy { +public: + Spy(ProcessBackend *process) + : stateSpy(process, SIGNAL(stateChanged(QProcess::ProcessState))) + , startSpy(process, SIGNAL(started())) + , errorSpy(process, SIGNAL(error(QProcess::ProcessError))) + , finishedSpy(process, SIGNAL(finished(int, QProcess::ExitStatus))) + , stdoutSpy(process, SIGNAL(standardOutput(const QByteArray&))) + , stderrSpy(process, SIGNAL(standardError(const QByteArray&))) {} + + Spy(ProcessFrontend *process) + : stateSpy(process, SIGNAL(stateChanged(QProcess::ProcessState))) + , startSpy(process, SIGNAL(started())) + , errorSpy(process, SIGNAL(error(QProcess::ProcessError))) + , finishedSpy(process, SIGNAL(finished(int, QProcess::ExitStatus))) + , stdoutSpy(process, SIGNAL(standardOutput(const QByteArray&))) + , stderrSpy(process, SIGNAL(standardError(const QByteArray&))) {} + + void check( int startCount, int errorCount, int finishedCount, int stateCount ) { + QVERIFY(startSpy.count() == startCount); + QVERIFY(errorSpy.count() == errorCount); + QVERIFY(finishedSpy.count() == finishedCount); + QVERIFY(stateSpy.count() == stateCount); + bool failedToStart = false; + for (int i = 0 ; i < errorSpy.count() ; i++) + if (qVariantValue<QProcess::ProcessError>(errorSpy.at(i).at(0)) == QProcess::FailedToStart) + failedToStart = true; + + if (failedToStart) + QVERIFY(stateCount <=2); + if (stateCount > 0) + QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(0).at(0)), QProcess::Starting); + if (stateCount > 1) + QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(1).at(0)), + (failedToStart ? QProcess::NotRunning : QProcess::Running)); + if (stateCount > 2) + QCOMPARE(qVariantValue<QProcess::ProcessState>(stateSpy.at(2).at(0)), QProcess::NotRunning); + } + + void checkStdout(const QByteArray s) { + qDebug() << "Checking stdout for" << s; + for (int i = 0 ; i < stdoutSpy.count() ; i++) { + QByteArray b = stdoutSpy.at(i).at(0).toByteArray(); + if (b == s) + return; + } + QFAIL("String not found"); + } + + void waitStart(int timeout=5000) { + stopWatch.restart(); + forever { + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + if (startSpy.count()) + break; + } + } + + void waitFailedStart(int timeout=5000) { + stopWatch.restart(); + forever { + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + if (errorSpy.count()) + break; + } + } + + void waitFinished(int timeout=5000) { + stopWatch.restart(); + forever { + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + if (finishedSpy.count()) + break; + } + } + + void waitStdout(int timeout=5000) { + stopWatch.restart(); + int count = stdoutSpy.count(); + forever { + QTestEventLoop::instance().enterLoop(1); + if (stopWatch.elapsed() >= timeout) + QFAIL("Timed out"); + if (stdoutSpy.count() != count) { + break; + } + } + } + + void checkExitCode(int exitCode) { + QVERIFY(finishedSpy.count() == 1); + QCOMPARE(qVariantValue<int>(finishedSpy.at(0).at(0)), exitCode); + } + + void checkExitStatus(QProcess::ExitStatus exitStatus) { + QVERIFY(finishedSpy.count() == 1); + QCOMPARE(qVariantValue<QProcess::ExitStatus>(finishedSpy.at(0).at(1)), exitStatus); + } + + void checkErrors(const QList<QProcess::ProcessError>& list) { + QCOMPARE(errorSpy.count(), list.count()); + for (int i = 0 ; i < errorSpy.count() ; i++) + QCOMPARE(qVariantValue<QProcess::ProcessError>(errorSpy.at(i).at(0)), list.at(i)); + } + + void dump() { + qDebug() << "================================ SPY DUMP =============================="; + qDebug() << "Start count=" << startSpy.count(); + qDebug() << "Finished count=" << finishedSpy.count(); + for (int i = 0 ; i < finishedSpy.count() ; i++) { + qDebug() << "....(" << qvariant_cast<int>(finishedSpy.at(i).at(0)) + << ")" << exitStatusToString[qvariant_cast<QProcess::ExitStatus>(finishedSpy.at(i).at(0))]; + } + qDebug() << "State count=" << stateSpy.count(); + for (int i = 0 ; i < stateSpy.count() ; i++) { + qDebug() << "...." << stateToString[qvariant_cast<QProcess::ProcessState>(stateSpy.at(i).at(0))]; + } + qDebug() << "Error count=" << errorSpy.count(); + for (int i = 0 ; i < errorSpy.count() ; i++) { + qDebug() << "...." << errorToString[qvariant_cast<QProcess::ProcessError>(errorSpy.at(i).at(0))]; + } + qDebug() << "================================ ======== =============================="; + } + + QTime stopWatch; + QSignalSpy stateSpy; + QSignalSpy startSpy; + QSignalSpy errorSpy; + QSignalSpy finishedSpy; + QSignalSpy stdoutSpy; + QSignalSpy stderrSpy; +}; + +bool canCheckProcessState() +{ + QFileInfo finfo("/bin/ps"); + return finfo.exists(); +} + +bool isProcessRunning(Q_PID pid) +{ + QProcess p; + p.start("ps", QStringList() << "-o" << "pid=" << "-p" << QString::number(pid)); + if (p.waitForStarted() && p.waitForFinished()) + return p.readAll().split('\n').at(0).toDouble() == pid; + + return false; +} + +bool isProcessStopped(Q_PID pid) +{ + QProcess p; + p.start("/bin/ps", QStringList() << "-o" << "pid=" << "-p" << QString::number(pid)); + if (p.waitForStarted() && p.waitForFinished()) { + QList<QByteArray> plist = p.readAll().split('\n'); + return plist.size() == 1 && !plist.at(0).size(); + } + return false; +} + +/*********/ + +void tst_ProcessManager::initTestCase() +{ + qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus"); + qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState"); + qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessError"); +} + +void tst_ProcessManager::startAndKill() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + process->stop(); + spy.waitFinished(); + spy.check(1, 1, 1, 3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::CrashExit); + spy.checkErrors(QList<QProcess::ProcessError>() << QProcess::Crashed); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + + +void tst_ProcessManager::failToStart() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "thisProgramDoesntExist"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitFailedStart(); + spy.check(0,1,0,2); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + + +void tst_ProcessManager::startAndStop() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + process->write("echo\n"); + + // Now send a "stop" message + process->write("stop\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::startAndCrash() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + process->write("crash\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(2); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::echo() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + + process->write("echotest\n"); + spy.waitStdout(); + spy.checkStdout("echotest\n"); + + process->write("stop\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::priorityChangeBeforeStart() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + info.setValue("priority", 19); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + + QVERIFY(process->actualPriority() == 19); + + process->write("stop\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::priorityChangeAfterStart() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + + process->setDesiredPriority(19); + QVERIFY(process->actualPriority() == 19); + + process->write("stop\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::prelaunch() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + ProcessInfo info; + info.setValue("program", "testPrelaunch/testPrelaunch"); + manager->addFactory(new PrelaunchProcessBackendFactory(info)); + + // The factory should not have launched a prelaunch yet + QVERIFY(manager->memoryRestricted() == false); + QVERIFY(manager->internalProcesses().count() == 0); + + qDebug() << "Waiting for 2 seconds"; + QTime waitTime; + waitTime.start(); + while (waitTime.elapsed() < 2000) + QTestEventLoop::instance().enterLoop(1); + + // Verify that there is a prelaunched process + QVERIFY(manager->memoryRestricted() == false); + QVERIFY(manager->internalProcesses().count() == 1); + + info.setValue("prelaunch", "true"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + QVERIFY(process->state() == QProcess::NotRunning); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + QVERIFY(process->state() == QProcess::Running); + + QVariantMap map; + map.insert("command", "stop"); + process->write(QJsonDocument::fromVariant(map).toBinaryData()); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::prelaunchRestricted() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + manager->setMemoryRestricted(true); + + ProcessInfo info; + info.setValue("program", "testPrelaunch/testPrelaunch"); + manager->addFactory(new PrelaunchProcessBackendFactory(info)); + + // The factory should not have launched a prelaunch yet + QVERIFY(manager->memoryRestricted() == true); + QVERIFY(manager->internalProcesses().count() == 0); + + qDebug() << "Waiting for 2 seconds"; + QTime waitTime; + waitTime.start(); + while (waitTime.elapsed() < 2000) + QTestEventLoop::instance().enterLoop(1); + + // The factory should still not have prelaunched anything + QVERIFY(manager->memoryRestricted() == true); + QVERIFY(manager->internalProcesses().count() == 0); + + info.setValue("prelaunch", "true"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + QVariantMap map; + map.insert("command", "stop"); + process->write(QJsonDocument::fromVariant(map).toBinaryData()); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + +void tst_ProcessManager::pipeLauncher() +{ + ProcessBackendManager *manager = new ProcessBackendManager; + ProcessInfo info; + info.setValue("program", "testPipeLauncher/testPipeLauncher"); + manager->addFactory(new PipeProcessBackendFactory(info, "testClient")); + + qDebug() << "Waiting for 500 ms let pipe start"; + QTime waitTime; + waitTime.start(); + while (waitTime.elapsed() < 500) + QTestEventLoop::instance().enterLoop(1); + + // The factory should have launched a pipe by now + QVERIFY(manager->internalProcesses().count() == 1); + + ProcessInfo info2; + info2.setValue("program", "testClient/testClient"); + info2.setValue("pipe", "true"); + ProcessBackend *process = manager->create(info2); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + + qDebug() << "Checking post started"; + spy.check(1,0,0,2); + + qDebug() << "Sending echo command"; + process->write("echo\n"); + + qDebug() << "Sending stop command"; + process->write("stop\n"); + + qDebug() << "Waiting for finished"; + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; +} + + +void tst_ProcessManager::pipeLauncherCrash() +{ + QString socketname = "/tmp/tst_socket"; + ProcessBackendManager *manager = new ProcessBackendManager; + ProcessInfo info; + info.setValue("program", "testPipeLauncher/testPipeLauncher"); + manager->addFactory(new PipeProcessBackendFactory(info, "testClient")); + + qDebug() << "Waiting for 500 ms let pipe start"; + QTime waitTime; + waitTime.start(); + while (waitTime.elapsed() < 500) + QTestEventLoop::instance().enterLoop(1); + + // The factory should have launched a pipe by now + QVERIFY(manager->internalProcesses().count() == 1); + + ProcessInfo info2; + info2.setValue("program", "testClient/testClient"); + info2.setValue("pipe", "true"); + ProcessBackend *process = manager->create(info2); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + + qDebug() << "Checking post started"; + spy.check(1,0,0,2); + + QList<Q_PID> plist = manager->internalProcesses(); + QCOMPARE(plist.count(), 1); + + if (canCheckProcessState()) { + qDebug() << "Verifying that all processes are running"; + foreach (Q_PID pid, plist) + QVERIFY(isProcessRunning(pid)); + } + + qDebug() << "Deleting manager process"; + delete manager; + + qDebug() << "Waiting for 1000 ms to let the pipe stop"; + waitTime.restart(); + while (waitTime.elapsed() < 1000) + QTestEventLoop::instance().enterLoop(1); + + if (canCheckProcessState()) { + foreach (Q_PID pid, plist) { + qDebug() << "Checking process" << pid; + QVERIFY(isProcessStopped(pid)); + } + } + + delete process; + qDebug() << "Deleted process"; +} + + +void tst_ProcessManager::socketLauncher() +{ + QProcess *remote = new QProcess; + remote->setProcessChannelMode(QProcess::ForwardedChannels); + remote->start("testSocketLauncher/testSocketLauncher"); + QVERIFY(remote->waitForStarted()); + + qDebug() << "Waiting for 500 ms to let testSocketLauncher start"; + QTime waitTime; + waitTime.start(); + while (waitTime.elapsed() < 500) + QTestEventLoop::instance().enterLoop(1); + + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new SocketProcessBackendFactory("/tmp/socketlauncher")); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + + qDebug() << "Checking post started"; + spy.check(1,0,0,2); + + qDebug() << "Sending echo command"; + process->write("echo\n"); + + qDebug() << "Sending stop command"; + process->write("stop\n"); + + qDebug() << "Waiting for finished"; + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; + delete remote; +} + +void tst_ProcessManager::socketLauncherCrash() +{ + QProcess *remote = new QProcess; + remote->setProcessChannelMode(QProcess::ForwardedChannels); + remote->start("testSocketLauncher/testSocketLauncher"); + QVERIFY(remote->waitForStarted()); + + qDebug() << "Waiting for 500 ms to let testSocketLauncher start"; + QTime waitTime; + waitTime.start(); + while (waitTime.elapsed() < 500) + QTestEventLoop::instance().enterLoop(1); + + ProcessBackendManager *manager = new ProcessBackendManager; + manager->addFactory(new SocketProcessBackendFactory("/tmp/socketlauncher")); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessBackend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + process->write("crash\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(2); + spy.checkExitStatus(QProcess::NormalExit); + + QVERIFY(process->parent() == NULL); + delete process; + delete manager; + delete remote; +} + + +void tst_ProcessManager::frontend() +{ + QString socketname = "/tmp/tst_socket"; + ProcessManager *manager = new ProcessManager; + manager->addBackendFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessFrontend *process = manager->create(info); + QVERIFY(process); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + // Now send a "stop" message + process->write("stop\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QCOMPARE(manager->size(), 1); + delete process; + QCOMPARE(manager->size(), 0); + delete manager; +} + + +class TestProcess : public ProcessFrontend { + Q_OBJECT + Q_PROPERTY(QString magic READ magic WRITE setMagic NOTIFY magicChanged) +public: + TestProcess(ProcessBackend *backend, QObject *parent=0) : ProcessFrontend(backend, parent) {} + + QString magic() const { return m_magic; } + void setMagic(const QString&s) { if (m_magic != s) { m_magic=s; emit magicChanged(); }} +signals: + void magicChanged(); +private: + QString m_magic; +}; + +class TestManager : public ProcessManager { + Q_OBJECT + Q_PROPERTY(QString magic READ magic WRITE setMagic NOTIFY magicChanged) +public: + TestManager(QObject *parent=0) : ProcessManager(parent) {} + virtual Q_INVOKABLE TestProcess *createFrontend(ProcessBackend *backend) {return new TestProcess(backend);} + QString magic() const { return m_magic; } + void setMagic(const QString&s) { if (m_magic != s) { m_magic=s; emit magicChanged(); }} +signals: + void magicChanged(); +private: + QString m_magic; +}; + +void tst_ProcessManager::subclassFrontend() +{ + QString socketname = "/tmp/tst_socket"; + TestManager *manager = new TestManager; + manager->addBackendFactory(new StandardProcessBackendFactory); + + ProcessInfo info; + info.setValue("program", "testClient/testClient"); + ProcessFrontend *process = manager->create(info); + QVERIFY(process); + + QVERIFY(process->setProperty("magic", 42)); + QCOMPARE(process->property("magic").toDouble(), 42.0); + + Spy spy(process); + process->start(); + spy.waitStart(); + spy.check(1,0,0,2); + + // Now send a "stop" message + process->write("stop\n"); + spy.waitFinished(); + spy.check(1,0,1,3); + spy.checkExitCode(0); + spy.checkExitStatus(QProcess::NormalExit); + + QCOMPARE(manager->size(), 1); + delete process; + QCOMPARE(manager->size(), 0); + delete manager; +} + +QTEST_MAIN(tst_ProcessManager) + +#include "tst_processmanager.moc" diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 0000000..7fbc8a9 --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = auto |