├── tests ├── auto │ ├── auto.pro │ ├── backgroundprocess │ │ ├── backgroundprocess.pro │ │ └── MasterTest │ │ │ ├── MasterTest.pro │ │ │ ├── processhelper.h │ │ │ ├── processhelper.cpp │ │ │ └── tst_master.cpp │ └── cmake │ │ ├── cmake.pro │ │ └── CMakeLists.txt ├── global │ └── global.cfg └── tests.pro ├── doc ├── images │ └── GitHub_Logo.png ├── doc.pro ├── doxme.py ├── globalterminal.dox ├── makedoc.sh ├── terminal.dox ├── gh_header.html └── app.dox ├── examples ├── backgroundprocess │ ├── backgroundprocess.pro │ └── DemoApp │ │ ├── DemoApp.pro │ │ ├── testapp.h │ │ ├── testapp.cpp │ │ └── README.md └── examples.pro ├── src ├── src.pro └── backgroundprocess │ ├── systemd.service │ ├── qtbackgroundprocess_global.h │ ├── qpmx.json │ ├── masterconnecter_p.h │ ├── terminal_p.h │ ├── backgroundprocess.pro │ ├── globalterminal.h │ ├── terminal.cpp │ ├── masterconnecter.cpp │ ├── terminal.h │ ├── terminal_p.cpp │ ├── globalterminal.cpp │ ├── app_p.h │ ├── app.cpp │ ├── app.h │ ├── translations │ ├── qtbackgroundprocess_template.ts │ └── qtbackgroundprocess_de.ts │ └── app_p.cpp ├── .qmake.conf ├── qtbackgroundprocess.pro ├── sync.profile ├── repogen.sh ├── .gitignore ├── appveyor.yml ├── LICENSE ├── .travis.yml └── README.md /tests/auto/auto.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += cmake backgroundprocess 4 | -------------------------------------------------------------------------------- /tests/auto/backgroundprocess/backgroundprocess.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | MasterTest 5 | -------------------------------------------------------------------------------- /doc/images/GitHub_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/QtBackgroundProcess/master/doc/images/GitHub_Logo.png -------------------------------------------------------------------------------- /examples/backgroundprocess/backgroundprocess.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | QT_FOR_CONFIG += core 3 | 4 | SUBDIRS += DemoApp 5 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS = backgroundprocess 4 | 5 | docTarget.target = doxygen 6 | QMAKE_EXTRA_TARGETS += docTarget -------------------------------------------------------------------------------- /tests/global/global.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/src.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | CONFIG += ordered 3 | 4 | SUBDIRS += backgroundprocess 5 | 6 | docTarget.target = doxygen 7 | QMAKE_EXTRA_TARGETS += docTarget 8 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += no_docs_target 4 | 5 | SUBDIRS += auto 6 | 7 | docTarget.target = doxygen 8 | QMAKE_EXTRA_TARGETS += docTarget 9 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | load(qt_build_config) 2 | 3 | CONFIG += warning_clean exceptions 4 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS 5 | 6 | MODULE_VERSION = 1.6.0 7 | -------------------------------------------------------------------------------- /tests/auto/cmake/cmake.pro: -------------------------------------------------------------------------------- 1 | 2 | # Cause make to do nothing. 3 | TEMPLATE = subdirs 4 | 5 | CMAKE_QT_MODULES_UNDER_TEST = backgroundprocess 6 | 7 | CONFIG += ctest_testcase 8 | -------------------------------------------------------------------------------- /qtbackgroundprocess.pro: -------------------------------------------------------------------------------- 1 | load(qt_parts) 2 | 3 | SUBDIRS += doc 4 | 5 | docTarget.target = doxygen 6 | docTarget.CONFIG += recursive 7 | docTarget.recurse_target = doxygen 8 | QMAKE_EXTRA_TARGETS += docTarget 9 | 10 | DISTFILES += .qmake.conf 11 | -------------------------------------------------------------------------------- /sync.profile: -------------------------------------------------------------------------------- 1 | %modules = ( 2 | "QtBackgroundProcess" => "$basedir/src/backgroundprocess", 3 | ); 4 | 5 | %classnames = ( 6 | "app.h" => "NotAllowedInRunningStateException,App", 7 | "globalterminal.h" => "GlobalTerminal", 8 | "terminal.h" => "Terminal", 9 | ); 10 | -------------------------------------------------------------------------------- /tests/auto/cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | project(qmake_cmake_files) 5 | 6 | enable_testing() 7 | 8 | find_package(Qt5Core REQUIRED) 9 | 10 | include("${_Qt5CTestMacros}") 11 | 12 | test_module_includes( 13 | BackgroundProcess QBackgroundProcess 14 | ) 15 | -------------------------------------------------------------------------------- /src/backgroundprocess/systemd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description= 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/bin/ '__qbckgrndprcss$start#master~' --no-daemon 8 | ExecStop=/usr/bin/ stop 9 | Restart=on-abnormal 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /src/backgroundprocess/qtbackgroundprocess_global.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_GLOBAL_H 2 | #define QTBACKGROUNDPROCESS_GLOBAL_H 3 | 4 | #include 5 | 6 | #if defined(QT_BUILD_BACKGROUNDPROCESS_LIB) 7 | # define Q_BACKGROUNDPROCESS_EXPORT Q_DECL_EXPORT 8 | #else 9 | # define Q_BACKGROUNDPROCESS_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | #endif // QBACKGROUNDPROCESS_GLOBAL_H 13 | -------------------------------------------------------------------------------- /src/backgroundprocess/qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "package": "de.skycoder42.qctrlsignals", 5 | "provider": "qpm", 6 | "version": "1.1.1" 7 | }, 8 | { 9 | "package": "de.skycoder42.qconsole", 10 | "provider": "qpm", 11 | "version": "1.1.1" 12 | } 13 | ], 14 | "license": { 15 | "file": "", 16 | "name": "" 17 | }, 18 | "prcFile": "", 19 | "priFile": "", 20 | "priIncludes": [ 21 | ], 22 | "publishers": { 23 | }, 24 | "source": true 25 | } 26 | -------------------------------------------------------------------------------- /repogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # IMPORTANT: Adjust path to script of https://github.com/Skycoder42/QtModules (repogen.py) 3 | # $1 path to module binaries 4 | # $2 Version 5 | 6 | myDir=$(dirname "$0") 7 | qtDir=${1?First parameter must be set to the dir to install} 8 | version=${2?Set the version as second parameter} 9 | 10 | "$myDir/../QtModules/deploy/repogen.py" "$qtDir" BackgroundProcess "" "" "A Library to create background applications with simple, automated foreground control." "$version" "$myDir/LICENSE" BSD-3-Clause "android_armv7,android_x86,ios,winrt_x86_msvc2017,winrt_x64_msvc2017,winrt_armv7_msvc2017" 11 | -------------------------------------------------------------------------------- /doc/doc.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | OTHER_FILES += Doxyfile \ 4 | makedoc.sh \ 5 | doxme.py \ 6 | ../README.md \ 7 | *.dox \ 8 | snippets/*.cpp \ 9 | images/* 10 | 11 | system($$QMAKE_MKDIR $$shell_quote($$shell_path($$OUT_PWD/qtbackgroundprocess))) 12 | 13 | docTarget.target = doxygen 14 | docTarget.commands = $$PWD/makedoc.sh "$$PWD" "$$MODULE_VERSION" "$$[QT_INSTALL_BINS]" "$$[QT_INSTALL_HEADERS]" "$$[QT_INSTALL_DOCS]" 15 | QMAKE_EXTRA_TARGETS += docTarget 16 | 17 | docInst1.path = $$[QT_INSTALL_DOCS] 18 | docInst1.files = $$OUT_PWD/qtbackgroundprocess.qch 19 | docInst1.CONFIG += no_check_exist 20 | docInst2.path = $$[QT_INSTALL_DOCS] 21 | docInst2.files = $$OUT_PWD/qtbackgroundprocess 22 | INSTALLS += docInst1 docInst2 23 | -------------------------------------------------------------------------------- /tests/auto/backgroundprocess/MasterTest/MasterTest.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2017-08-21T11:21:46 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += testlib backgroundprocess network #explicitly? 8 | QT -= gui 9 | 10 | TARGET = tst_master 11 | CONFIG += console 12 | CONFIG -= app_bundle 13 | 14 | TEMPLATE = app 15 | 16 | HEADERS += \ 17 | processhelper.h 18 | 19 | SOURCES += \ 20 | tst_master.cpp \ 21 | processhelper.cpp 22 | 23 | DEFINES += SRCDIR=\\\"$$PWD/\\\" 24 | DEFINES += OUTDIR=\\\"$$OUT_PWD/\\\" 25 | CONFIG(release, debug|release): DEFINES += RMODE=\\\"release\\\" 26 | CONFIG(debug, debug|release): DEFINES += RMODE=\\\"debug\\\" 27 | 28 | mac: QMAKE_LFLAGS += '-Wl,-rpath,\'$$OUT_PWD/../../../../lib\'' 29 | -------------------------------------------------------------------------------- /examples/backgroundprocess/DemoApp/DemoApp.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core backgroundprocess network 4 | QT -= gui 5 | CONFIG += c++11 console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = DemoApp 9 | 10 | SOURCES += \ 11 | testapp.cpp 12 | 13 | HEADERS += \ 14 | testapp.h 15 | 16 | DISTFILES += \ 17 | README.md 18 | 19 | target.path = $$[QT_INSTALL_EXAMPLES]/backgroundprocess/DemoApp 20 | INSTALLS += target 21 | 22 | #not found by linker? 23 | unix:!mac { 24 | LIBS += -L$$OUT_PWD/../../../lib #required to make this the first place to search 25 | LIBS += -L$$[QT_INSTALL_LIBS] -licudata 26 | LIBS += -L$$[QT_INSTALL_LIBS] -licui18n 27 | LIBS += -L$$[QT_INSTALL_LIBS] -licuuc 28 | } 29 | 30 | #add lib dir and qt install libs to rpath 31 | mac: QMAKE_LFLAGS += '-Wl,-rpath,\'$$OUT_PWD/../../../lib\'' '-Wl,-rpath,\'$$[QT_INSTALL_LIBS]\'' 32 | -------------------------------------------------------------------------------- /doc/doxme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # $1 The readme to be transformed 3 | # $pwd: dest dir 4 | 5 | import sys 6 | 7 | def readFirst(line, out): 8 | if line[0:2] != "# ": 9 | raise ValueError("Expected first line to start with '# '") 10 | # skip the first line 11 | out.write("[TOC]\n\n") 12 | 13 | readCounter = 0 14 | def readMore(line, out): 15 | global readCounter 16 | if line[0:2] == "##": 17 | out.write(line[1:] + " {{#qtbackgroundprocess_readme_label_{}}}\n".format(readCounter)) 18 | readCounter += 1 19 | else: 20 | out.write(line + "\n") 21 | 22 | #read args 23 | readme = sys.argv[1] 24 | doxme = "./README.md" 25 | 26 | inFile = open(readme, "r") 27 | outFile = open(doxme, "w") 28 | 29 | isFirst = True 30 | for line in inFile: 31 | if isFirst: 32 | readFirst(line[:-1], outFile) 33 | isFirst = False 34 | else: 35 | readMore(line[:-1], outFile) 36 | 37 | inFile.close(); 38 | outFile.close(); 39 | -------------------------------------------------------------------------------- /examples/backgroundprocess/DemoApp/testapp.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTAPP_H 2 | #define TESTAPP_H 3 | 4 | #include 5 | 6 | class TestApp : public QtBackgroundProcess::App 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit TestApp(int &argc, char **argv); 12 | 13 | void parseTerminalOptions(); 14 | 15 | // App interface 16 | protected: 17 | int startupApp(const QCommandLineParser &parser) override; 18 | bool requestAppShutdown(QtBackgroundProcess::Terminal *terminal, int &) override; 19 | void setupParser(QCommandLineParser &parser, bool useShortOptions) override; 20 | 21 | private slots: 22 | void handleCommand(QSharedPointer parser, bool starter); 23 | void addTerminal(QtBackgroundProcess::Terminal *terminal); 24 | 25 | private: 26 | QtBackgroundProcess::GlobalTerminal *statusTerm; 27 | 28 | void doCommand(const QCommandLineParser &parser); 29 | }; 30 | 31 | #endif // TESTAPP_H 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /src/backgroundprocess/masterconnecter_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_MASTERCONNECTER_P_H 2 | #define QTBACKGROUNDPROCESS_MASTERCONNECTER_P_H 3 | 4 | #include "qtbackgroundprocess_global.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | class QConsole; 12 | 13 | namespace QtBackgroundProcess { 14 | 15 | class Q_BACKGROUNDPROCESS_EXPORT MasterConnecter : public QObject 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit MasterConnecter(const QString &socketName, 21 | const QStringList &arguments, 22 | bool isStarter, 23 | QObject *parent = nullptr); 24 | ~MasterConnecter(); 25 | 26 | private Q_SLOTS: 27 | void connected(); 28 | void disconnected(); 29 | void error(QLocalSocket::LocalSocketError socketError); 30 | void socketReady(); 31 | 32 | void stdinReady(); 33 | 34 | private: 35 | const QStringList arguments; 36 | const bool isStarter; 37 | 38 | QLocalSocket *socket; 39 | QConsole *console; 40 | QFile *outFile; 41 | }; 42 | 43 | } 44 | 45 | #endif // QTBACKGROUNDPROCESS_MASTERCONNECTER_P_H 46 | -------------------------------------------------------------------------------- /src/backgroundprocess/terminal_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_TERMINAL_P_H 2 | #define QTBACKGROUNDPROCESS_TERMINAL_P_H 3 | 4 | #include "terminal.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace QtBackgroundProcess { 13 | 14 | class Q_BACKGROUNDPROCESS_EXPORT TerminalPrivate : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit TerminalPrivate(QLocalSocket *socket, QObject *parent); 20 | 21 | QLocalSocket *socket; 22 | QJsonObject status; 23 | QSharedPointer parser; 24 | bool autoDelete; 25 | 26 | bool loadParser(); 27 | void beginSoftDisconnect(); 28 | 29 | Q_SIGNALS: 30 | void statusLoadComplete(TerminalPrivate *terminal, bool successful); 31 | 32 | void dataReady(); 33 | 34 | private Q_SLOTS: 35 | void disconnected(); 36 | void error(QLocalSocket::LocalSocketError socketError); 37 | void readyRead(); 38 | void writeReady(); 39 | 40 | private: 41 | bool isLoading; 42 | bool disconnecting; 43 | QDataStream stream; 44 | }; 45 | 46 | } 47 | 48 | #endif // QTBACKGROUNDPROCESS_TERMINAL_P_H 49 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | - Visual Studio 2015 4 | 5 | version: build-{build} 6 | 7 | environment: 8 | QT_VER: 5.10.0 9 | 10 | matrix: 11 | - PLATFORM: msvc2017_64 12 | - PLATFORM: msvc2015_64 13 | - PLATFORM: msvc2015 14 | - PLATFORM: mingw53_32 15 | 16 | matrix: 17 | exclude: 18 | - PLATFORM: msvc2017_64 19 | image: Visual Studio 2015 20 | - PLATFORM: mingw53_32 21 | image: Visual Studio 2015 22 | - PLATFORM: msvc2015_64 23 | image: Visual Studio 2017 24 | - PLATFORM: msvc2015 25 | image: Visual Studio 2017 26 | 27 | install: 28 | - git clone https://github.com/Skycoder42/QtModules.git .\qtmodules-travis 29 | - .\qtmodules-travis\ci\win\setup.bat 30 | 31 | build_script: 32 | - .\qtmodules-travis\ci\win\build.bat 33 | 34 | after_build: 35 | - .\qtmodules-travis\ci\win\upload-prepare.bat 36 | 37 | artifacts: 38 | - path: install\build_*_%QT_VER%.zip 39 | 40 | deploy: 41 | provider: GitHub 42 | auth_token: 43 | secure: Cp5GRQku2ZWnKPE12NB5q11ZO0Fr5mlzdUTjnLpYJr/dki4LPVqm231edFggogy8 44 | artifact: /.*\.zip/ 45 | force_update: false 46 | on: 47 | appveyor_repo_tag: true 48 | 49 | cache: 50 | - 'C:\Users\appveyor\AppData\Local\Skycoder42\qpmx\cache -> appveyor.yml' 51 | -------------------------------------------------------------------------------- /doc/globalterminal.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtBackgroundProcess::GlobalTerminal 3 | 4 | The global terminal simply forwards everything written to it to all currently connected 5 | terminals. As such, it's a write-only device. 6 | 7 | The GlobalTerminal implements the QIODevice interface. This allows easy integration into the 8 | application. The terminal is a sequential write-only device. 9 | 10 | @sa Terminal, App::forwardMasterLog 11 | */ 12 | 13 | /*! 14 | @fn QtBackgroundProcess::GlobalTerminal::GlobalTerminal 15 | 16 | @param parent The parent object for the global terminal 17 | @param enableBootBuffer Allows you to buffer input until terminals are connected 18 | 19 | By default, the global terminal simply forwards all input to all connected terminals, 20 | unbuffered. However, to make shure all messages are delivered, The terminal can be told to 21 | buffer all messages, until the first terminal connects. As soon as that happens, the buffer is 22 | written to the terminal, cleared, and disabled. The buffer existst at most 10 seconds, until 23 | it's cleared automatically. This is an internal feature, and should only be used if you have 24 | global messages on your terminal, that are sent before the first terminal can connect. One 25 | example would be the startup function 26 | 27 | @sa App::startupApp 28 | */ 29 | -------------------------------------------------------------------------------- /src/backgroundprocess/backgroundprocess.pro: -------------------------------------------------------------------------------- 1 | TARGET = QtBackgroundProcess 2 | 3 | QT = core network 4 | 5 | HEADERS += \ 6 | app.h \ 7 | globalterminal.h \ 8 | terminal.h \ 9 | app_p.h \ 10 | masterconnecter_p.h \ 11 | terminal_p.h \ 12 | qtbackgroundprocess_global.h 13 | 14 | SOURCES += \ 15 | app_p.cpp \ 16 | app.cpp \ 17 | globalterminal.cpp \ 18 | masterconnecter.cpp \ 19 | terminal_p.cpp \ 20 | terminal.cpp 21 | 22 | TRANSLATIONS += translations/qtbackgroundprocess_de.ts \ 23 | translations/qtbackgroundprocess_template.ts 24 | 25 | load(qt_module) 26 | 27 | win32 { 28 | QMAKE_TARGET_PRODUCT = "QtBackgroundProcess" 29 | QMAKE_TARGET_COMPANY = "Skycoder42" 30 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 31 | 32 | LIBS += -lUser32 33 | } else:mac { 34 | QMAKE_TARGET_BUNDLE_PREFIX = "de.skycoder42." 35 | } 36 | 37 | DISTFILES += \ 38 | systemd.service \ 39 | $$TRANSLATIONS 40 | 41 | qpmx_ts_target.path = $$[QT_INSTALL_TRANSLATIONS] 42 | qpmx_ts_target.depends += lrelease 43 | !win32-g++: INSTALLS += qpmx_ts_target 44 | 45 | !ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.) 46 | else: include($$OUT_PWD/qpmx_generated.pri) 47 | 48 | #replace template qm by ts 49 | qpmx_ts_target.files -= $$OUT_PWD/$$QPMX_WORKINGDIR/qtbackgroundprocess_template.qm 50 | qpmx_ts_target.files += translations/qtbackgroundprocess_template.ts 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /doc/makedoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: $$SRCDIR 3 | # $2: $$VERSION 4 | # $3: $$[QT_INSTALL_BINS] 5 | # $4: $$[QT_INSTALL_HEADERS] 6 | # $5: $$[QT_INSTALL_DOCS] 7 | # $pwd: dest dir 8 | set -e 9 | 10 | scriptDir=$(dirname "$0") 11 | destDir="$(pwd)" 12 | srcDir=$1 13 | version=$2 14 | verTag=$(echo "$version" | sed -e 's/\.//g') 15 | qtBins=$3 16 | qtHeaders=$4 17 | qtDocs=$5 18 | doxyTemplate="$srcDir/Doxyfile" 19 | doxyRes=Doxyfile.generated 20 | readme="$destDir/README.md" 21 | doxme="$scriptDir/doxme.py" 22 | 23 | python3 "$doxme" "$srcDir/../README.md" 24 | 25 | cat "$doxyTemplate" > $doxyRes 26 | echo "PROJECT_NUMBER = \"$version\"" >> $doxyRes 27 | echo "INPUT += \"$readme\"" >> $doxyRes 28 | echo "USE_MDFILE_AS_MAINPAGE = \"$readme\"" >> $doxyRes 29 | echo "OUTPUT_DIRECTORY = \"$destDir\"" >> $doxyRes 30 | echo "QHP_NAMESPACE = \"de.skycoder42.qtbackgroundprocess.$verTag\"" >> $doxyRes 31 | echo "QHP_CUST_FILTER_NAME = \"BackgroundProcess $version\"" >> $doxyRes 32 | echo "QHP_CUST_FILTER_ATTRS = \"qtbackgroundprocess $version\"" >> $doxyRes 33 | echo "QHG_LOCATION = \"$qtBins/qhelpgenerator\"" >> $doxyRes 34 | echo "INCLUDE_PATH += \"$qtHeaders\"" >> $doxyRes 35 | echo "GENERATE_TAGFILE = \"$destDir/qtbackgroundprocess/qtbackgroundprocess.tags\"" >> $doxyRes 36 | if [ "$DOXY_STYLE" ]; then 37 | echo "HTML_STYLESHEET = \"$DOXY_STYLE\"" >> $doxyRes 38 | fi 39 | if [ "$DOXY_STYLE_EXTRA" ]; then 40 | echo "HTML_EXTRA_STYLESHEET = \"$DOXY_STYLE_EXTRA\"" >> $doxyRes 41 | fi 42 | 43 | for tagFile in $(find "$qtDocs" -name *.tags); do 44 | if [ $(basename "$tagFile") != "qtbackgroundprocess.tags" ]; then 45 | echo "TAGFILES += \"$tagFile=https://doc.qt.io/qt-5\"" >> $doxyRes 46 | fi 47 | done 48 | 49 | cd "$srcDir" 50 | doxygen "$destDir/$doxyRes" 51 | -------------------------------------------------------------------------------- /src/backgroundprocess/globalterminal.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_GLOBALTERMINAL_H 2 | #define QTBACKGROUNDPROCESS_GLOBALTERMINAL_H 3 | 4 | #include "QtBackgroundProcess/qtbackgroundprocess_global.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace QtBackgroundProcess { 10 | 11 | class App; 12 | class Terminal; 13 | class GlobalTerminalPrivate; 14 | //! A virtual terminal that forwards input to all connected terminals 15 | class Q_BACKGROUNDPROCESS_EXPORT GlobalTerminal : public QIODevice 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | //! Creates a new global terminal 21 | explicit GlobalTerminal(QObject *parent = nullptr, bool enableBootBuffer = false); 22 | //! Destructor 23 | ~GlobalTerminal(); 24 | 25 | //! @inherit{QIODevice::isSequential} 26 | bool isSequential() const override; 27 | //! @inherit{QIODevice::canReadLine} 28 | bool canReadLine() const override; 29 | 30 | public Q_SLOTS: 31 | //! Writes the given line on all connected terminals @sa Terminal::writeLine 32 | void writeLine(const QByteArray &line, bool doFlush = true); 33 | //! Performs a flush on all connected terminals @sa Terminal::flush 34 | void flush(); 35 | 36 | protected: 37 | //! @inherit{QIODevice::readData} 38 | qint64 readData(char *data, qint64 maxlen) override; 39 | //! @inherit{QIODevice::writeData} 40 | qint64 writeData(const char *data, qint64 len) override; 41 | 42 | private Q_SLOTS: 43 | bool tryPushBuffer(QList terms); 44 | 45 | private: 46 | QScopedPointer d; 47 | 48 | bool open(OpenMode mode) override; 49 | void close() override; 50 | 51 | void pushBuffer(QList terms); 52 | void discardBuffer(); 53 | }; 54 | 55 | } 56 | 57 | #endif // QTBACKGROUNDPROCESS_GLOBALTERMINAL_H 58 | -------------------------------------------------------------------------------- /tests/auto/backgroundprocess/MasterTest/processhelper.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESSHELPER_H 2 | #define PROCESSHELPER_H 3 | 4 | #include 5 | #include 6 | 7 | class ProcessHelper : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | static const char Stamp; 13 | 14 | static QString binPath(); 15 | 16 | explicit ProcessHelper(QObject *parent = nullptr); 17 | 18 | void setExitCode(int code); 19 | void start(const QByteArrayList &commands, bool logpath = false, int timeout = 1000); 20 | 21 | void send(const QByteArray &message); 22 | void termMaster(); 23 | 24 | void waitForFinished(bool terminate = true); 25 | bool verifyLog(const QByteArrayList &log, bool isError, const char *file, int line); 26 | static void waitForFinished(const QList &helpers); 27 | 28 | static void clearLog(); 29 | static bool verifyMasterLog(const QByteArrayList &log, const char *file, int line); 30 | 31 | private Q_SLOTS: 32 | void errorOccurred(QProcess::ProcessError error); 33 | void finished(int exitCode, QProcess::ExitStatus exitStatus); 34 | 35 | private: 36 | QProcess *process; 37 | int exitCode; 38 | 39 | static QString logPath(); 40 | static bool testLog(const QByteArrayList &log, const QByteArrayList &device, bool fullEqual); 41 | }; 42 | 43 | #define QVERIFYOUTLOG(helper, ...) \ 44 | do {\ 45 | if (!helper->verifyLog({__VA_ARGS__}, false, __FILE__, __LINE__))\ 46 | return;\ 47 | } while (0) 48 | 49 | #define QVERIFYERRLOG(helper, ...) \ 50 | do {\ 51 | if (!helper->verifyLog({__VA_ARGS__}, true, __FILE__, __LINE__))\ 52 | return;\ 53 | } while (0) 54 | 55 | #define QVERIFYMASTERLOG(...) \ 56 | do {\ 57 | if (!ProcessHelper::verifyMasterLog({__VA_ARGS__}, __FILE__, __LINE__))\ 58 | return;\ 59 | } while (0) 60 | 61 | #endif // PROCESSHELPER_H 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | services: 4 | - docker 5 | 6 | sudo: required 7 | 8 | env: 9 | global: 10 | - QT_VER=5.10.0 11 | 12 | matrix: 13 | include: 14 | - os: linux 15 | dist: trusty 16 | env: 17 | - PLATFORM=gcc_64 18 | - BUILD_DOC=true 19 | - os: osx 20 | osx_image: xcode9.1 21 | env: 22 | - PLATFORM=clang_64 23 | 24 | before_install: 25 | - git clone https://github.com/Skycoder42/QtModules.git ./qtmodules-travis 26 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then travis_wait 40 ./qtmodules-travis/ci/$TRAVIS_OS_NAME/setup.sh; else ./qtmodules-travis/ci/$TRAVIS_OS_NAME/setup.sh; fi 27 | 28 | script: 29 | - ./qtmodules-travis/ci/$TRAVIS_OS_NAME/build.sh 30 | 31 | before_deploy: 32 | - ./qtmodules-travis/ci/$TRAVIS_OS_NAME/upload-prepare.sh 33 | deploy: 34 | provider: releases 35 | skip_cleanup: true 36 | api_key: 37 | secure: J0aULmVBovEKxvkx/Y5kkC9SFfcCvqiWC1mjkFabjaUJDbk42ZrkbFkn7+d4FdD1fGKfbknXGGMLwyRhcWra6+NpAzDu8dSairC8WnOge27fgzc+yHRbrPonykqypa3S/0muFAMyuswLNUL3+AYqqNSxi/Lhf5866iJm8DI58rupLtVqwnxW7ekuneUCpjv2b7yEpn44JnQXTp6XLko3RRVHzd8ZmZkW5lTSLoTvu1SdyQLcPSXjP0YKrq7piJpMnDGEZ5pZqVxO9Pr+9kTn/Gmr2iTrvUoF+i7vByXFNepk3deEXCiSOSmNgZErYXVeWnzFeXQj5vx3jH0+IbTm4a4+tvgQs2t171xORe/nXM1AB3EWhp/4rWmzkQ5v8Bd8a+VZy0j0GqILnWMoxUY+ygx8q12sapi2KSrsPVOz2fa8RHu08Bi2McMrFKzWSxeXJ8hUL6mvuitSz9hL+W5MOm4qf4C2/CJTGRUC6EpT9HVOCNSU0P8ddkHyFhxUZXDnGV8I7I0CsPQRbV7bPRJKVddQU+H83uEXYWKDeff2fncxR30VMaLWCo8J0c9FmTpEAc6ztJXWsq9dZ2923esLybNV9pung2b+EYuOjgXJSYymHM/jT1PUn8VIeplbd+f7AUa3LVzJJBqNqj8FbMdoasu/glFuVMRDVj7PCM80vOk= 38 | file_glob: true 39 | file: install/opt/build_*_$QT_VER.tar.xz 40 | on: 41 | repo: Skycoder42/QtBackgroundProcess 42 | tags: true 43 | 44 | before_cache: 45 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 46 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 47 | cache: 48 | directories: 49 | - $HOME/.cache/Skycoder42/qpmx 50 | - $HOME/.gradle/caches/ 51 | - $HOME/.gradle/wrapper/ 52 | - $HOME/.android/build-cache 53 | -------------------------------------------------------------------------------- /doc/terminal.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtBackgroundProcess::Terminal 3 | 4 | For every connected terminal, one instance of the Terminal class is created, and provided by 5 | the app. The terminal allows interaction with each connected terminal, by receiving it's 6 | stdin and sending data to it's stdout. 7 | 8 | The Terminal implements the QIODevice interface. This allows easy integration into the 9 | application. The terminal is a sequential device. 10 | 11 | @sa App, App::connectedTerminals, GlobalTerminal 12 | */ 13 | 14 | /*! 15 | @property QtBackgroundProcess::Terminal::starter 16 | 17 | @default{undefined} 18 | 19 | There will always be only one starter terminal per master process. It's arguments are passed 20 | to the startup function. 21 | 22 | @accessors{ 23 | @readAc{isStarter()} 24 | @constantAc 25 | } 26 | 27 | @sa App::startupApp 28 | */ 29 | 30 | /*! 31 | @property QtBackgroundProcess::Terminal::parser 32 | 33 | @default{A parser setup by the app and filled with the sent arguments.} 34 | 35 | You can use the parser to get the received options and arguments. It is wrapped inside a 36 | QSharedPointer, because QCommandlineParser does not provide a copy constructor 37 | 38 | @accessors{ 39 | @readAc{parser()} 40 | @constantAc 41 | } 42 | 43 | @sa App::setupParser, App::commandReceived 44 | */ 45 | 46 | /*! 47 | @property QtBackgroundProcess::Terminal::autoDelete 48 | 49 | @default{App::autoDeleteTerminals} 50 | 51 | If set to `true` the terminal will be automatically deleted, as soon as the terminal was 52 | disconnected. If you need to access the terminal even after closing, you can disable it. 53 | 54 | @accessors{ 55 | @readAc{isAutoDelete()} 56 | @writeAc{setAutoDelete()} 57 | } 58 | 59 | @sa App::autoDeleteTerminals, Terminal::disconnectTerminal, Terminal::terminalDisconnected 60 | */ 61 | 62 | /*! 63 | @fn QtBackgroundProcess::Terminal::disconnectTerminal 64 | 65 | Softly disconnects the terminal by flushing it first and then closing it, waiting up to 500 66 | milliseconds for data to be written. 67 | 68 | @note disconnecting the terminal will **not** be closed! You can still read remaining data 69 | from the terminal after disconnecting. For a full close, use the close() method. 70 | 71 | @sa Terminal::close 72 | */ 73 | -------------------------------------------------------------------------------- /doc/gh_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | $treeview 15 | $search 16 | $mathjax 17 | 18 | $extrastylesheet 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
33 |
$projectname 34 |  $projectnumber 35 |
36 |
$projectbrief
37 |
42 |
$projectbrief
43 |
$searchbox
54 | 55 | 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /src/backgroundprocess/terminal.cpp: -------------------------------------------------------------------------------- 1 | #include "terminal.h" 2 | #include "terminal_p.h" 3 | #include "app_p.h" 4 | 5 | using namespace QtBackgroundProcess; 6 | 7 | Terminal::Terminal(TerminalPrivate *d_ptr, QObject *parent) : 8 | QIODevice(parent), 9 | d(d_ptr) 10 | { 11 | d->setParent(this); 12 | open(QIODevice::ReadWrite | QIODevice::Unbuffered); 13 | 14 | connect(d->socket, &QLocalSocket::disconnected, 15 | this, &Terminal::terminalDisconnected); 16 | connect(d->socket, QOverload::of(&QLocalSocket::error), 17 | this, [this](QLocalSocket::LocalSocketError e) { 18 | if(e != QLocalSocket::PeerClosedError) { 19 | setErrorString(d->socket->errorString()); 20 | emit terminalError((int)e); 21 | } 22 | }); 23 | 24 | connect(d->socket, &QLocalSocket::channelReadyRead, 25 | this, &Terminal::channelReadyRead); 26 | connect(d->socket, &QLocalSocket::readyRead, 27 | this, &Terminal::readyRead); 28 | } 29 | 30 | Terminal::~Terminal() 31 | { 32 | if(d->socket->isOpen()) 33 | d->socket->close(); 34 | } 35 | 36 | bool Terminal::isStarter() const 37 | { 38 | return d->status[QStringLiteral("isStarter")].toBool(); 39 | } 40 | 41 | QSharedPointer Terminal::parser() const 42 | { 43 | return d->parser; 44 | } 45 | 46 | bool Terminal::isAutoDelete() const 47 | { 48 | return d->autoDelete; 49 | } 50 | 51 | bool Terminal::isSequential() const 52 | { 53 | return true; 54 | } 55 | 56 | void Terminal::close() 57 | { 58 | d->socket->close(); 59 | QIODevice::close(); 60 | } 61 | 62 | qint64 Terminal::bytesAvailable() const 63 | { 64 | return QIODevice::bytesAvailable() + d->socket->bytesAvailable(); 65 | } 66 | 67 | void Terminal::disconnectTerminal() 68 | { 69 | d->beginSoftDisconnect(); 70 | } 71 | 72 | void Terminal::setAutoDelete(bool autoDelete) 73 | { 74 | d->autoDelete = autoDelete; 75 | } 76 | 77 | void Terminal::writeLine(const QByteArray &line, bool flush) 78 | { 79 | d->socket->write(line + '\n'); 80 | if(flush) 81 | d->socket->flush(); 82 | } 83 | 84 | void Terminal::flush() 85 | { 86 | d->socket->flush(); 87 | } 88 | 89 | qint64 Terminal::readData(char *data, qint64 maxlen) 90 | { 91 | return d->socket->read(data, maxlen); 92 | } 93 | 94 | qint64 Terminal::writeData(const char *data, qint64 len) 95 | { 96 | return d->socket->write(data, len); 97 | } 98 | 99 | bool Terminal::open(QIODevice::OpenMode mode) 100 | { 101 | return QIODevice::open(mode); 102 | } 103 | -------------------------------------------------------------------------------- /src/backgroundprocess/masterconnecter.cpp: -------------------------------------------------------------------------------- 1 | #include "masterconnecter_p.h" 2 | #include "app_p.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using namespace QtBackgroundProcess; 13 | 14 | MasterConnecter::MasterConnecter(const QString &socketName, const QStringList &arguments, bool isStarter, QObject *parent) : 15 | QObject(parent), 16 | arguments(arguments), 17 | isStarter(isStarter), 18 | socket(new QLocalSocket(this)), 19 | console(new QConsole(this)), 20 | outFile(QConsole::qStdOut(this)) 21 | { 22 | connect(socket, &QLocalSocket::connected, 23 | this, &MasterConnecter::connected); 24 | connect(socket, &QLocalSocket::disconnected, 25 | this, &MasterConnecter::disconnected); 26 | connect(socket, QOverload::of(&QLocalSocket::error), 27 | this, &MasterConnecter::error); 28 | connect(socket, &QLocalSocket::readyRead, 29 | this, &MasterConnecter::socketReady, 30 | Qt::QueuedConnection); //queued connection, because of "socket not ready" errors on win 31 | 32 | connect(console, &QConsole::readyRead, 33 | this, &MasterConnecter::stdinReady); 34 | 35 | socket->connectToServer(socketName); 36 | } 37 | 38 | MasterConnecter::~MasterConnecter() 39 | { 40 | console->close(); 41 | } 42 | 43 | void MasterConnecter::connected() 44 | { 45 | //send over the status 46 | QJsonObject status; 47 | status[QStringLiteral("isStarter")] = isStarter; 48 | status[QStringLiteral("arguments")] = QJsonArray::fromStringList(arguments); 49 | 50 | QDataStream connectData(socket); 51 | connectData << QJsonDocument(status).toBinaryData(); 52 | socket->flush(); 53 | 54 | //begin stdin reading 55 | console->open(); 56 | } 57 | 58 | void MasterConnecter::disconnected() 59 | { 60 | socket->close(); 61 | qApp->quit(); 62 | } 63 | 64 | void MasterConnecter::error(QLocalSocket::LocalSocketError socketError) 65 | { 66 | if(socketError != QLocalSocket::PeerClosedError) { 67 | qCritical() << tr("Connection to Master process failed with error:") 68 | << socket->errorString(); 69 | socket->disconnectFromServer(); 70 | qApp->exit(EXIT_FAILURE); 71 | } 72 | } 73 | 74 | void MasterConnecter::socketReady() 75 | { 76 | auto data = socket->readAll(); 77 | outFile->write(data); 78 | outFile->flush(); 79 | } 80 | 81 | void MasterConnecter::stdinReady() 82 | { 83 | auto bytes = console->bytesAvailable(); 84 | auto data = console->read(bytes); 85 | socket->write(data); 86 | socket->flush(); 87 | } 88 | -------------------------------------------------------------------------------- /src/backgroundprocess/terminal.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_TERMINAL_H 2 | #define QTBACKGROUNDPROCESS_TERMINAL_H 3 | 4 | #include "QtBackgroundProcess/qtbackgroundprocess_global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class QLocalSocket; 11 | 12 | namespace QtBackgroundProcess { 13 | 14 | class TerminalPrivate; 15 | //! Represents a connection to a terminal in the master process 16 | class Q_BACKGROUNDPROCESS_EXPORT Terminal : public QIODevice 17 | { 18 | Q_OBJECT 19 | 20 | //! Specifies, whether this terminal is the one that started the master process 21 | Q_PROPERTY(bool starter READ isStarter CONSTANT) 22 | //! Returns a reference to the parser that parsed this terminals arguments 23 | Q_PROPERTY(QSharedPointer parser READ parser CONSTANT) 24 | //! If true, the terminal will delete itself as soon as the connection has been closed 25 | Q_PROPERTY(bool autoDelete READ isAutoDelete WRITE setAutoDelete) 26 | 27 | public: 28 | //! Creates a new terminal from it's private implementation 29 | explicit Terminal(TerminalPrivate *d_ptr, QObject *parent = nullptr); 30 | //! Destructor 31 | ~Terminal(); 32 | 33 | //! @readAcFn{App::starter} 34 | bool isStarter() const; 35 | //! @readAcFn{App::parser} 36 | QSharedPointer parser() const; 37 | //! @readAcFn{App::autoDelete} 38 | bool isAutoDelete() const; 39 | 40 | //! @inherit{QIODevice::isSequential} 41 | bool isSequential() const override; 42 | //! @inherit{QIODevice::close} @sa Terminal::disconnectTerminal 43 | void close() override; 44 | //! @inherit{QIODevice::bytesAvailable} 45 | qint64 bytesAvailable() const override; 46 | 47 | public Q_SLOTS: 48 | //! Disconnects the terminal from the master 49 | void disconnectTerminal(); 50 | //! @writeAcFn{App::autoDelete} 51 | void setAutoDelete(bool autoDelete); 52 | 53 | //! Writes the given line, appends a newline and optionally flushes 54 | void writeLine(const QByteArray &line, bool flush = true); 55 | //! Flushes the terminal 56 | void flush(); 57 | 58 | Q_SIGNALS: 59 | //! Will be emitted after the terminal has been disconnected 60 | void terminalDisconnected(); 61 | //! Will be emitted if an error occured 62 | void terminalError(int errorCode); 63 | 64 | protected: 65 | //! @inherit{QIODevice::readData} 66 | qint64 readData(char *data, qint64 maxlen) override; 67 | //! @inherit{QIODevice::writeData} 68 | qint64 writeData(const char *data, qint64 len) override; 69 | 70 | private: 71 | TerminalPrivate *d; 72 | 73 | bool open(OpenMode mode) override; 74 | }; 75 | 76 | } 77 | 78 | #endif // QTBACKGROUNDPROCESS_TERMINAL_H 79 | -------------------------------------------------------------------------------- /src/backgroundprocess/terminal_p.cpp: -------------------------------------------------------------------------------- 1 | #include "terminal_p.h" 2 | #include "app_p.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace QtBackgroundProcess; 9 | 10 | TerminalPrivate::TerminalPrivate(QLocalSocket *socket, QObject *parent) : 11 | QObject(parent), 12 | socket(socket), 13 | status(), 14 | parser(), 15 | autoDelete(false), 16 | isLoading(true), 17 | disconnecting(false), 18 | stream(socket) 19 | { 20 | socket->setParent(this); 21 | 22 | connect(socket, &QLocalSocket::disconnected, 23 | this, &TerminalPrivate::disconnected); 24 | connect(socket, QOverload::of(&QLocalSocket::error), 25 | this, &TerminalPrivate::error); 26 | connect(socket, &QLocalSocket::readyRead, 27 | this, &TerminalPrivate::readyRead); 28 | connect(socket, &QLocalSocket::bytesWritten, 29 | this, &TerminalPrivate::writeReady); 30 | } 31 | 32 | bool TerminalPrivate::loadParser() 33 | { 34 | auto array = status[QStringLiteral("arguments")].toArray(); 35 | QStringList lst(QCoreApplication::applicationFilePath()); 36 | foreach(auto value, array) 37 | lst.append(value.toString()); 38 | return parser->parse(lst); 39 | } 40 | 41 | void TerminalPrivate::beginSoftDisconnect() 42 | { 43 | disconnecting = true; 44 | if(socket->bytesToWrite() > 0) { 45 | QTimer::singleShot(500, this, [=](){ 46 | if(socket->state() == QLocalSocket::ConnectedState) 47 | socket->disconnectFromServer(); 48 | }); 49 | socket->flush(); 50 | } else 51 | socket->disconnectFromServer(); 52 | } 53 | 54 | void TerminalPrivate::disconnected() 55 | { 56 | if(isLoading) { 57 | emit statusLoadComplete(this, false); 58 | socket->close(); 59 | } else if(autoDelete && parent()) 60 | parent()->deleteLater(); 61 | } 62 | 63 | void TerminalPrivate::error(QLocalSocket::LocalSocketError socketError) 64 | { 65 | if(socketError != QLocalSocket::PeerClosedError) { 66 | if(isLoading) { 67 | qWarning() << tr("Terminal closed due to connection error while loading terminal status:") 68 | << socket->errorString(); 69 | } 70 | socket->disconnectFromServer(); 71 | } 72 | } 73 | 74 | void TerminalPrivate::readyRead() 75 | { 76 | if(isLoading) { 77 | stream.startTransaction(); 78 | QByteArray data; 79 | stream >> data; 80 | if(stream.commitTransaction()) { 81 | auto doc = QJsonDocument::fromBinaryData(data); 82 | if(doc.isNull()) { 83 | qWarning() << tr("Invalid Terminal status received. Data is corrupted. Terminal will be disconnected"); 84 | socket->disconnectFromServer(); 85 | } else { 86 | status = doc.object(); 87 | isLoading = false; 88 | emit statusLoadComplete(this, true); 89 | } 90 | } 91 | } 92 | } 93 | 94 | void TerminalPrivate::writeReady() 95 | { 96 | if(disconnecting && socket->bytesToWrite() == 0) { 97 | QTimer::singleShot(500, this, [=](){ 98 | if(socket->state() == QLocalSocket::ConnectedState) 99 | socket->disconnectFromServer(); 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/backgroundprocess/globalterminal.cpp: -------------------------------------------------------------------------------- 1 | #include "globalterminal.h" 2 | #include "app_p.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace QtBackgroundProcess { 8 | 9 | class GlobalTerminalPrivate { 10 | public: 11 | App *app; 12 | QBuffer *buffer; 13 | 14 | GlobalTerminalPrivate(bool enableBootBuffer, GlobalTerminal *q_ptr) : 15 | app(qApp), 16 | buffer(enableBootBuffer ? new QBuffer(q_ptr) : nullptr) 17 | {} 18 | }; 19 | 20 | } 21 | 22 | using namespace QtBackgroundProcess; 23 | 24 | GlobalTerminal::GlobalTerminal(QObject *parent, bool enableBootBuffer) : 25 | QIODevice(parent), 26 | d(new GlobalTerminalPrivate(enableBootBuffer, this)) 27 | { 28 | if(enableBootBuffer) { 29 | connect(d->app, &App::connectedTerminalsChanged, 30 | this, &GlobalTerminal::tryPushBuffer); 31 | d->buffer->open(QIODevice::WriteOnly); 32 | 33 | QTimer::singleShot(10*1000, this, [this](){//wait up to 10 seconds for the first terminal to connect 34 | if(d->buffer && !tryPushBuffer(d->app->connectedTerminals())) 35 | discardBuffer(); 36 | }); 37 | } 38 | 39 | open(QIODevice::WriteOnly | QIODevice::Unbuffered); 40 | } 41 | 42 | GlobalTerminal::~GlobalTerminal() 43 | { 44 | QIODevice::close(); 45 | } 46 | 47 | bool GlobalTerminal::isSequential() const 48 | { 49 | return true; 50 | } 51 | 52 | bool GlobalTerminal::canReadLine() const 53 | { 54 | return false; 55 | } 56 | 57 | void GlobalTerminal::writeLine(const QByteArray &line, bool doFlush) 58 | { 59 | write(line + '\n'); 60 | if(doFlush) 61 | flush(); 62 | } 63 | 64 | void GlobalTerminal::flush() 65 | { 66 | auto terms = d->app->connectedTerminals(); 67 | if(d->buffer && !terms.isEmpty()) 68 | pushBuffer(terms); 69 | foreach(auto term, terms) 70 | term->flush(); 71 | } 72 | 73 | qint64 GlobalTerminal::readData(char *data, qint64 maxlen) 74 | { 75 | Q_UNUSED(data); 76 | Q_UNUSED(maxlen); 77 | return 0; 78 | } 79 | 80 | qint64 GlobalTerminal::writeData(const char *data, qint64 len) 81 | { 82 | auto terms = d->app->connectedTerminals(); 83 | if(d->buffer) { 84 | if(terms.isEmpty()) 85 | return d->buffer->write(data, len); 86 | else 87 | pushBuffer(terms); 88 | } 89 | 90 | foreach(auto term, terms) 91 | term->write(data, len); 92 | return len; 93 | } 94 | 95 | bool GlobalTerminal::tryPushBuffer(QList terms) 96 | { 97 | if(d->buffer && !terms.isEmpty()) { 98 | pushBuffer(terms); 99 | return true; 100 | } else 101 | return false; 102 | } 103 | 104 | bool GlobalTerminal::open(QIODevice::OpenMode mode) 105 | { 106 | return QIODevice::open(mode); 107 | } 108 | 109 | void GlobalTerminal::close() {} 110 | 111 | void GlobalTerminal::pushBuffer(QList terms) 112 | { 113 | const auto &data = d->buffer->data(); 114 | if(!data.isEmpty()) { 115 | foreach(auto term, terms) 116 | term->write(data); 117 | } 118 | discardBuffer(); 119 | } 120 | 121 | void GlobalTerminal::discardBuffer() 122 | { 123 | d->buffer->close(); 124 | d->buffer->deleteLater(); 125 | d->buffer = nullptr; 126 | disconnect(d->app, &App::connectedTerminalsChanged, 127 | this, &GlobalTerminal::tryPushBuffer); 128 | } 129 | -------------------------------------------------------------------------------- /src/backgroundprocess/app_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_APP_P_H 2 | #define QTBACKGROUNDPROCESS_APP_P_H 3 | 4 | #include "app.h" 5 | 6 | #include "masterconnecter_p.h" 7 | #include "terminal_p.h" 8 | #include "globalterminal.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace QtBackgroundProcess { 21 | 22 | class Q_BACKGROUNDPROCESS_EXPORT AppPrivate : public QObject 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | static const QString masterArgument; 28 | static const QString purgeArgument; 29 | static const QString startArgument; 30 | static const QString restartArgument; 31 | 32 | static const QString terminalMessageFormat; 33 | static const QString masterMessageFormat; 34 | 35 | static AppPrivate *p_ptr(); 36 | 37 | static void qbackProcMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); 38 | 39 | static bool p_valid; 40 | 41 | bool running; 42 | bool masterLogging; 43 | bool autoStart; 44 | bool ignoreExtraStart; 45 | bool autoDelete; 46 | bool autoKill; 47 | 48 | QString instanceId; 49 | bool globalInstance; 50 | 51 | QScopedPointer masterLock; 52 | QLocalServer *masterServer; 53 | QList activeTerminals; 54 | std::function parserFunc; 55 | std::function startupFunc; 56 | std::function shutdownFunc; 57 | 58 | MasterConnecter *master; 59 | 60 | QPointer debugTerm; 61 | QPointer logFile; 62 | 63 | AppPrivate(App *q_ptr); 64 | 65 | QString generateSingleId(const QString &seed = QString()); 66 | void setInstanceId(const QString &id); 67 | QString socketName() const; 68 | 69 | void setupDefaultParser(QCommandLineParser &parser, bool useShortOptions = true); 70 | void updateLoggingMode(int level); 71 | void updateLoggingPath(const QString &path); 72 | 73 | public Q_SLOTS: 74 | int initControlFlow(const QCommandLineParser &parser); 75 | 76 | private Q_SLOTS: 77 | int makeMaster(const QCommandLineParser &parser); 78 | int startMaster(bool isAutoStart = false, bool isRestart = false); 79 | int restartMaster(const QCommandLineParser &parser); 80 | int commandMaster(); 81 | int purgeMaster(const QCommandLineParser &parser); 82 | 83 | void newTerminalConnected(); 84 | void terminalLoaded(TerminalPrivate *terminal, bool success); 85 | void stopMaster(Terminal *term); 86 | void doExit(int code); 87 | 88 | void beginMasterConnect(const QStringList &arguments, bool isStarter); 89 | 90 | private: 91 | App *q; 92 | }; 93 | 94 | Q_DECLARE_LOGGING_CATEGORY(loggingCategory) 95 | 96 | //custom logging operators 97 | #undef qInfo 98 | #define qInfo() qCInfo(loggingCategory).noquote() 99 | #undef qDebug 100 | #define qDebug() qCDebug(loggingCategory).noquote() 101 | #undef qWarning 102 | #define qWarning() qCWarning(loggingCategory).noquote() 103 | #undef qCritical 104 | #define qCritical() qCCritical(loggingCategory).noquote() 105 | 106 | } 107 | 108 | #endif // QTBACKGROUNDPROCESS_APP_P_H 109 | -------------------------------------------------------------------------------- /examples/backgroundprocess/DemoApp/testapp.cpp: -------------------------------------------------------------------------------- 1 | #include "testapp.h" 2 | 3 | #include 4 | using namespace QtBackgroundProcess; 5 | 6 | TestApp::TestApp(int &argc, char **argv) : 7 | App(argc, argv), 8 | statusTerm(nullptr) 9 | {} 10 | 11 | void TestApp::parseTerminalOptions() 12 | { 13 | QCommandLineParser parser; 14 | setupParser(parser, true); 15 | parser.process(*this); 16 | setAutoStartMaster(parser.isSet(QStringLiteral("a"))); 17 | setIgnoreMultiStarts(parser.isSet(QStringLiteral("i"))); 18 | } 19 | 20 | int TestApp::startupApp(const QCommandLineParser &parser) 21 | { 22 | doCommand(parser); 23 | qDebug() << "App Master started with arguments:" 24 | << parser.positionalArguments() 25 | << "and options:" 26 | << parser.optionNames(); 27 | 28 | connect(this, &TestApp::commandReceived, 29 | this, &TestApp::handleCommand); 30 | 31 | if(parser.isSet(QStringLiteral("m"))) { 32 | if(parser.value(QStringLiteral("m")) == QStringLiteral("echo")) { 33 | connect(this, &TestApp::newTerminalConnected, 34 | this, &TestApp::addTerminal); 35 | qDebug() << "Master started in echo mode!"; 36 | } else if(parser.value(QStringLiteral("m")) == QStringLiteral("status")){ 37 | statusTerm = new GlobalTerminal(this); 38 | qDebug() << "Master started in status mode!"; 39 | } else if(parser.value(QStringLiteral("m")) == QStringLiteral("scream")){ 40 | auto term = new GlobalTerminal(this); 41 | auto timer = new QTimer(this); 42 | timer->setInterval(500); 43 | qsrand(QDateTime::currentMSecsSinceEpoch()); 44 | connect(timer, &QTimer::timeout, this, [term](){ 45 | static const QByteArray strings="qwertzuiopasdfghjklyxcvbnm\n "; 46 | auto idx = (qrand() / (double)RAND_MAX) * (strings.size() - 1); 47 | term->write(strings.mid(idx, 1)); 48 | term->flush(); 49 | }); 50 | timer->start(); 51 | qDebug() << "Master started in scream mode!"; 52 | } else if(parser.value(QStringLiteral("m")) == QStringLiteral("pid")) { 53 | qDebug() << "Master started in pid mode!"; 54 | connect(this, &TestApp::newTerminalConnected, this, [this](Terminal *term){ 55 | disconnect(this, &TestApp::newTerminalConnected, 56 | this, nullptr); 57 | term->write(QByteArray::number(QCoreApplication::applicationPid())); 58 | term->flush(); 59 | term->disconnectTerminal(); 60 | qDebug() << "pid written"; 61 | }); 62 | auto term = new GlobalTerminal(this); 63 | term->writeLine(QByteArray::number(QCoreApplication::applicationPid())); 64 | term->flush(); 65 | } else 66 | qWarning() << "Unknown mode! Will be ignored"; 67 | } 68 | 69 | connect(this, &App::aboutToQuit, this, [](){ 70 | qDebug() << "I am quitting!"; 71 | }); 72 | 73 | return EXIT_SUCCESS; 74 | } 75 | 76 | bool TestApp::requestAppShutdown(Terminal *terminal, int &) 77 | { 78 | qDebug() << "stop requested with" 79 | << terminal->parser()->positionalArguments() 80 | << "and options:" 81 | << terminal->parser()->optionNames(); 82 | return true; 83 | } 84 | 85 | void TestApp::setupParser(QCommandLineParser &parser, bool useShortOptions) 86 | { 87 | App::setupParser(parser, useShortOptions); 88 | 89 | parser.addOption({ 90 | {QStringLiteral("a"), QStringLiteral("autostart")}, 91 | QStringLiteral("Starts the master automatically, if not already running.") 92 | }); 93 | parser.addOption({ 94 | {QStringLiteral("i"), QStringLiteral("ignoreStart")}, 95 | QStringLiteral("If start is called a second time, the arguments will be omitted.") 96 | }); 97 | parser.addOption({ 98 | {QStringLiteral("f"), QStringLiteral("forward")}, 99 | QStringLiteral("forwards master debug output to all terminals if = 1, disables it if = 0."), 100 | QStringLiteral("active"), 101 | QStringLiteral("1") 102 | }); 103 | parser.addOption({ 104 | {QStringLiteral("m"), QStringLiteral("mode")}, 105 | QStringLiteral("Tells the master to run . Can be \"echo\" to simply echo all terminals, " 106 | "\"status\" to simply broadcast new arguments to all terminals, \"scream\" to permanently " 107 | "print stuff to all terminals, or \"pid\" to print the process id to all terminals. Unless explicitly set, nothing will be done"), 108 | QStringLiteral("mode") 109 | }); 110 | } 111 | 112 | void TestApp::handleCommand(QSharedPointer parser, bool starter) 113 | { 114 | if(starter) { 115 | qDebug() << "skipping starter args:" 116 | << parser->positionalArguments() 117 | << "and options:" 118 | << parser->optionNames(); 119 | } else { 120 | doCommand(*parser.data()); 121 | qDebug() << "received new command:" 122 | << parser->positionalArguments() 123 | << "and options:" 124 | << parser->optionNames(); 125 | } 126 | 127 | if(statusTerm) 128 | statusTerm->writeLine('[' + parser->positionalArguments().join(QStringLiteral(", ")).toUtf8() + ']'); 129 | } 130 | 131 | void TestApp::addTerminal(Terminal *terminal) 132 | { 133 | //add a simple echo to all terminals 134 | connect(terminal, &Terminal::readyRead, terminal, [terminal](){ 135 | terminal->write(terminal->readAll()); 136 | }); 137 | } 138 | 139 | void TestApp::doCommand(const QCommandLineParser &parser) 140 | { 141 | if(parser.isSet(QStringLiteral("f"))) 142 | setForwardMasterLog(parser.value(QStringLiteral("f")).toInt()); 143 | } 144 | 145 | int main(int argc, char *argv[]) 146 | { 147 | TestApp a(argc, argv); 148 | TestApp::setApplicationVersion(QStringLiteral("4.2.0")); 149 | 150 | a.parseTerminalOptions(); 151 | return a.exec(); 152 | } 153 | -------------------------------------------------------------------------------- /src/backgroundprocess/app.cpp: -------------------------------------------------------------------------------- 1 | #include "app.h" 2 | #include "app_p.h" 3 | 4 | #include 5 | 6 | using namespace QtBackgroundProcess; 7 | 8 | App::App(int &argc, char **argv, int flags) : 9 | QCoreApplication(argc, argv, flags), 10 | d(new AppPrivate(this)) 11 | { 12 | AppPrivate::p_valid = true; 13 | 14 | qSetMessagePattern(AppPrivate::terminalMessageFormat); 15 | qInstallMessageHandler(AppPrivate::qbackProcMessageHandler); 16 | 17 | QCtrlSignalHandler::instance()->setAutoQuitActive(true); 18 | } 19 | 20 | App::~App() 21 | { 22 | if(d->logFile) 23 | d->logFile->close(); 24 | AppPrivate::p_valid = false; 25 | } 26 | 27 | QString App::instanceID() const 28 | { 29 | return d->instanceId; 30 | } 31 | 32 | bool App::globalInstance() const 33 | { 34 | return d->globalInstance; 35 | } 36 | 37 | bool App::forwardMasterLog() const 38 | { 39 | return d->masterLogging; 40 | } 41 | 42 | bool App::autoStartMaster() const 43 | { 44 | return d->autoStart; 45 | } 46 | 47 | bool App::ignoreMultiStarts() const 48 | { 49 | return d->ignoreExtraStart; 50 | } 51 | 52 | bool App::autoDeleteTerminals() const 53 | { 54 | return d->autoDelete; 55 | } 56 | 57 | bool App::autoKillTerminals() const 58 | { 59 | return d->autoKill; 60 | } 61 | 62 | void App::setParserSetupFunction(const std::function &function) 63 | { 64 | d->parserFunc = function; 65 | } 66 | 67 | void App::setStartupFunction(const std::function &function) 68 | { 69 | d->startupFunc = function; 70 | } 71 | 72 | void App::setShutdownRequestFunction(const std::function &function) 73 | { 74 | d->shutdownFunc = [function](Terminal *t, int &r){ 75 | return function(*t->parser().data(), r); 76 | }; 77 | } 78 | 79 | void App::setShutdownRequestFunction(const std::function &function) 80 | { 81 | d->shutdownFunc = function; 82 | } 83 | 84 | int App::exec() 85 | { 86 | d->running = true; 87 | 88 | //process arguments 89 | QCommandLineParser parser; 90 | setupParser(parser); 91 | parser.process(*this); 92 | 93 | //update terminal logging 94 | d->updateLoggingMode(parser.value(QStringLiteral("terminallog")).toInt()); 95 | 96 | //generate the single id (temporary disable running) 97 | d->running = false; 98 | createDefaultInstanceID(false); 99 | d->running = true; 100 | 101 | auto res = d->initControlFlow(parser); 102 | if(res == -1)//special case 103 | return EXIT_SUCCESS; 104 | else if(res != EXIT_SUCCESS) 105 | return res; 106 | res = QCoreApplication::exec(); 107 | d->running = false; 108 | return res; 109 | } 110 | 111 | QList App::connectedTerminals() const 112 | { 113 | return d->activeTerminals; 114 | } 115 | 116 | void App::createDefaultInstanceID(bool overwrite) 117 | { 118 | if(overwrite || d->instanceId.isNull()) 119 | d->setInstanceId(d->generateSingleId()); 120 | } 121 | 122 | void App::setInstanceID(QString instanceID, bool useAsSeed) 123 | { 124 | if(useAsSeed) 125 | d->setInstanceId(d->generateSingleId(instanceID)); 126 | else 127 | d->setInstanceId(instanceID); 128 | } 129 | 130 | void App::setGlobalInstance(bool globalInstance) 131 | { 132 | d->globalInstance = globalInstance; 133 | } 134 | 135 | void App::setForwardMasterLog(bool forwardMasterLog) 136 | { 137 | if(forwardMasterLog == d->masterLogging) 138 | return; 139 | 140 | d->masterLogging = forwardMasterLog; 141 | if(d->masterLock && d->masterLock->isLocked()) {//I am master 142 | if(forwardMasterLog) 143 | d->debugTerm = new GlobalTerminal(d, true); 144 | else { 145 | d->debugTerm->deleteLater(); 146 | d->debugTerm.clear(); 147 | } 148 | } 149 | } 150 | 151 | void App::setAutoStartMaster(bool autoStartMaster) 152 | { 153 | d->autoStart = autoStartMaster; 154 | } 155 | 156 | void App::setIgnoreMultiStarts(bool ignoreMultiStarts) 157 | { 158 | d->ignoreExtraStart = ignoreMultiStarts; 159 | } 160 | 161 | void App::setAutoDeleteTerminals(bool autoDeleteTerminals, bool changeCurrent) 162 | { 163 | d->autoDelete = autoDeleteTerminals; 164 | if(changeCurrent) { 165 | foreach(auto terminal, d->activeTerminals) 166 | terminal->setAutoDelete(autoDeleteTerminals); 167 | } 168 | } 169 | 170 | void App::setAutoKillTerminals(bool autoKillTerminals, bool killCurrent) 171 | { 172 | d->autoKill = autoKillTerminals; 173 | if(killCurrent) { 174 | foreach(auto terminal, d->activeTerminals) { 175 | terminal->setAutoDelete(true); 176 | terminal->disconnectTerminal(); 177 | } 178 | } 179 | } 180 | 181 | void App::setupParser(QCommandLineParser &parser, bool useShortOptions) 182 | { 183 | d->setupDefaultParser(parser, useShortOptions); 184 | if(d->parserFunc) 185 | d->parserFunc(parser); 186 | } 187 | 188 | int App::startupApp(const QCommandLineParser &parser) 189 | { 190 | if(d->startupFunc) 191 | return d->startupFunc(parser); 192 | else 193 | return EXIT_SUCCESS; 194 | } 195 | 196 | bool App::requestAppShutdown(Terminal *terminal, int &exitCode) 197 | { 198 | if(d->shutdownFunc) 199 | return d->shutdownFunc(terminal, exitCode); 200 | else 201 | return true; 202 | } 203 | 204 | 205 | 206 | NotAllowedInRunningStateException::NotAllowedInRunningStateException() : 207 | QException() 208 | {} 209 | 210 | const char *NotAllowedInRunningStateException::what() const noexcept 211 | { 212 | return "You are not allowed to perform this operation while the application is running!"; 213 | } 214 | 215 | void NotAllowedInRunningStateException::raise() const 216 | { 217 | throw *this; 218 | } 219 | 220 | QException *NotAllowedInRunningStateException::clone() const 221 | { 222 | return new NotAllowedInRunningStateException(); 223 | } 224 | -------------------------------------------------------------------------------- /tests/auto/backgroundprocess/MasterTest/processhelper.cpp: -------------------------------------------------------------------------------- 1 | #include "processhelper.h" 2 | #include 3 | #ifdef Q_OS_UNIX 4 | #include 5 | #include 6 | #endif 7 | 8 | const char ProcessHelper::Stamp = '%'; 9 | 10 | QString ProcessHelper::binPath() 11 | { 12 | #if defined(Q_OS_WIN) 13 | return QStringLiteral(OUTDIR) + QStringLiteral("../../../../examples/backgroundprocess/DemoApp/") + QStringLiteral(RMODE) + QStringLiteral("/DemoApp"); 14 | #else 15 | return QStringLiteral(OUTDIR) + QStringLiteral("../../../../examples/backgroundprocess/DemoApp/DemoApp"); 16 | #endif 17 | } 18 | 19 | ProcessHelper::ProcessHelper(QObject *parent) : 20 | QObject(parent), 21 | process(new QProcess(this)), 22 | exitCode(EXIT_SUCCESS) 23 | { 24 | process->setProgram(binPath()); 25 | 26 | connect(process, &QProcess::errorOccurred, 27 | this, &ProcessHelper::errorOccurred); 28 | connect(process, QOverload::of(&QProcess::finished), 29 | this, &ProcessHelper::finished); 30 | } 31 | 32 | void ProcessHelper::setExitCode(int code) 33 | { 34 | exitCode = code; 35 | } 36 | 37 | void ProcessHelper::start(const QByteArrayList &commands, bool logpath, int timeout) 38 | { 39 | QStringList s; 40 | foreach(auto c, commands) 41 | s.append(QString::fromUtf8(c)); 42 | if(logpath) { 43 | s.append({QStringLiteral("--logpath"), logPath()}); 44 | s.append({QStringLiteral("--loglevel"), QStringLiteral("4")}); 45 | } 46 | s.append({QStringLiteral("--terminallog"), QStringLiteral("4")}); 47 | 48 | process->setArguments(s); 49 | process->start(QIODevice::ReadWrite); 50 | QVERIFY2(process->waitForStarted(5000), qUtf8Printable(process->errorString())); 51 | QThread::msleep(timeout); 52 | } 53 | 54 | void ProcessHelper::send(const QByteArray &message) 55 | { 56 | process->write(message + '\n'); 57 | QCoreApplication::processEvents(); 58 | } 59 | 60 | void ProcessHelper::termMaster() 61 | { 62 | auto line = process->readAll(); 63 | auto ok = false; 64 | auto pid = line.toInt(&ok); 65 | QVERIFY2(ok, line.constData()); 66 | 67 | #ifdef Q_OS_UNIX 68 | kill(pid, SIGTERM); 69 | #endif 70 | } 71 | 72 | void ProcessHelper::waitForFinished(bool terminate) 73 | { 74 | if(!process->waitForFinished(5000)) { 75 | if(terminate) 76 | process->terminate(); 77 | QVERIFY2(false, "Process did not stop by itself"); 78 | } 79 | } 80 | 81 | bool ProcessHelper::verifyLog(const QByteArrayList &log, bool isError, const char *file, int line) 82 | { 83 | QByteArray data; 84 | if(isError) 85 | data = process->readAllStandardError(); 86 | else 87 | data = process->readAllStandardOutput(); 88 | auto dataList = data.split('\n'); 89 | 90 | if(!testLog(log, dataList, isError)) { 91 | QByteArray resStr = "Log does not contain device output for "; 92 | if(isError) 93 | resStr += "stderr:\n"; 94 | else 95 | resStr += "stdout:\n"; 96 | resStr += "Expected Log:"; 97 | foreach(auto line, log) 98 | resStr += "\n\t" + line; 99 | resStr += "\nActual Log:"; 100 | foreach(auto line, dataList) 101 | resStr += "\n\t" + line; 102 | QTest::qFail(resStr.constData(), file, line); 103 | return false; 104 | } else 105 | return true; 106 | } 107 | 108 | void ProcessHelper::waitForFinished(const QList &helpers) 109 | { 110 | foreach (auto h, helpers) 111 | h->waitForFinished(); 112 | } 113 | 114 | void ProcessHelper::clearLog() 115 | { 116 | auto logFile = logPath(); 117 | if(QFile::exists(logFile)) 118 | QVERIFY(QFile::remove(logFile)); 119 | } 120 | 121 | bool ProcessHelper::verifyMasterLog(const QByteArrayList &log, const char *file, int line) 122 | { 123 | auto logFile = logPath(); 124 | QFile masterFile(logFile); 125 | if (!QTest::qVerify(masterFile.exists(), "masterLog.exists", "File does not exist", file, line)) 126 | return false; 127 | if (!QTest::qVerify(masterFile.open(QIODevice::ReadOnly | QIODevice::Text), "masterLog.open", qUtf8Printable(masterFile.errorString()), file, line)) 128 | return false; 129 | 130 | auto dataList = masterFile.readAll().split('\n'); 131 | auto res = testLog(log, dataList, true); 132 | if(!res) { 133 | QByteArray resStr = "Master Log is not as expected:\nExpected Log:"; 134 | foreach(auto line, log) 135 | resStr += "\n\t" + line; 136 | resStr += "\nActual Log:"; 137 | foreach(auto line, dataList) 138 | resStr += "\n\t" + line; 139 | QTest::qFail(resStr.constData(), file, line); 140 | } 141 | 142 | masterFile.close(); 143 | if (!QTest::qVerify(masterFile.remove(), "masterLog.remove", qUtf8Printable(masterFile.errorString()), file, line)) 144 | return false; 145 | 146 | return res; 147 | } 148 | 149 | void ProcessHelper::errorOccurred(QProcess::ProcessError error) 150 | { 151 | Q_UNUSED(error) 152 | QFAIL(qUtf8Printable(process->errorString())); 153 | } 154 | 155 | void ProcessHelper::finished(int exitCode, QProcess::ExitStatus exitStatus) 156 | { 157 | QCOMPARE(exitStatus, QProcess::NormalExit); 158 | QCOMPARE(exitCode, this->exitCode); 159 | } 160 | 161 | QString ProcessHelper::logPath() 162 | { 163 | return QDir::temp().absoluteFilePath(QStringLiteral("MasterTest.log")); 164 | } 165 | 166 | bool ProcessHelper::testLog(const QByteArrayList &log, const QByteArrayList &device, bool fullEqual) 167 | { 168 | auto index = 0; 169 | 170 | foreach(auto line, device) { 171 | auto logStr = line.trimmed(); 172 | if(logStr.isEmpty()) 173 | continue; 174 | //filter out unrelated logs 175 | if(logStr.contains("QCtrlSignals") || 176 | logStr.contains("XDG_RUNTIME_DIR")) 177 | continue; 178 | 179 | bool fullOk; 180 | do { 181 | fullOk = true; 182 | if(index >= log.size()) 183 | return false; 184 | auto testStr = log[index++]; 185 | 186 | auto testSplit = testStr.split(Stamp); 187 | if(testSplit.size() == 2) { 188 | if(logStr.startsWith(testSplit[0]) && 189 | logStr.endsWith(testSplit[1])) 190 | break; 191 | } 192 | 193 | if(logStr == testStr) 194 | break; 195 | 196 | fullOk = false; 197 | } while(!fullEqual); 198 | 199 | if(!fullOk) 200 | return false; 201 | } 202 | 203 | return !fullEqual || index == log.size(); 204 | } 205 | -------------------------------------------------------------------------------- /src/backgroundprocess/app.h: -------------------------------------------------------------------------------- 1 | #ifndef QTBACKGROUNDPROCESS_APP_H 2 | #define QTBACKGROUNDPROCESS_APP_H 3 | 4 | #include "QtBackgroundProcess/qtbackgroundprocess_global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | //! The Namespace containing all classes of the QtBackgroundProcess module 13 | namespace QtBackgroundProcess { 14 | 15 | class Terminal; 16 | 17 | //! Will be thrown, if you perform an operation, that is not allowed in running state 18 | class Q_BACKGROUNDPROCESS_EXPORT NotAllowedInRunningStateException : public QException 19 | { 20 | public: 21 | NotAllowedInRunningStateException(); 22 | 23 | //! @inherit{std::exception::what} 24 | const char *what() const noexcept override; 25 | 26 | //! @inherit{QException::raise} 27 | void raise() const override; 28 | //! @inherit{QException::clone} 29 | QException *clone() const override; 30 | }; 31 | 32 | class AppPrivate; 33 | //! The background process application. The main class of QtBackgroundProcess 34 | class Q_BACKGROUNDPROCESS_EXPORT App : public QCoreApplication 35 | { 36 | Q_OBJECT 37 | friend class AppPrivate; 38 | 39 | //! The current id of the singleton instance of the master process 40 | Q_PROPERTY(QString instanceID READ instanceID WRITE setInstanceID RESET createDefaultInstanceID) 41 | //! Specify whether the app should be a systemwide or userwide single instance 42 | Q_PROPERTY(bool globalInstance READ globalInstance WRITE setGlobalInstance) 43 | //! Specifies, whether the master should forward debug output to all terminals 44 | Q_PROPERTY(bool forwardMasterLog READ forwardMasterLog WRITE setForwardMasterLog) 45 | //! If true, the master process will always be started, not only with "start" 46 | Q_PROPERTY(bool autoStartMaster READ autoStartMaster WRITE setAutoStartMaster) 47 | //! If true, "start" commands will be ignored, if the master is already running 48 | Q_PROPERTY(bool ignoreMultiStarts READ ignoreMultiStarts WRITE setIgnoreMultiStarts) 49 | //! If true, the master process will automatically delete terminals that have been disconnected 50 | Q_PROPERTY(bool autoDeleteTerminals READ autoDeleteTerminals WRITE setAutoDeleteTerminals) 51 | //! If true, the master process will automatically close terminals after it received the parameters 52 | Q_PROPERTY(bool autoKillTerminals READ autoKillTerminals WRITE setAutoKillTerminals) 53 | //! Holds a list of all currently connected terminals 54 | Q_PROPERTY(QList connectedTerminals READ connectedTerminals NOTIFY connectedTerminalsChanged) 55 | 56 | public: 57 | //! Creates a new app with it's arguments 58 | App(int &argc, char **argv, int flags = ApplicationFlags); 59 | //! Destructor 60 | ~App(); 61 | 62 | //! @readAcFn{App::instanceID} 63 | QString instanceID() const; 64 | //! @readAcFn{App::globalInstance} 65 | bool globalInstance() const; 66 | //! @readAcFn{App::forwardMasterLog} 67 | bool forwardMasterLog() const; 68 | //! @readAcFn{App::autoStartMaster} 69 | bool autoStartMaster() const; 70 | //! @readAcFn{App::ignoreMultiStarts} 71 | bool ignoreMultiStarts() const; 72 | //! @readAcFn{App::autoDeleteTerminals} 73 | bool autoDeleteTerminals() const; 74 | //! @readAcFn{App::autoKillTerminals} 75 | bool autoKillTerminals() const; 76 | 77 | //! Sets the function to be called for the creation of the parser (Instead of overriding) 78 | void setParserSetupFunction(const std::function &function); 79 | //! Sets the function to be called to startup the application (Instead of overriding) 80 | void setStartupFunction(const std::function &function); 81 | //! Sets the function to be called to handle shutdown requests (Instead of overriding) 82 | void setShutdownRequestFunction(const std::function &function); 83 | //! Sets the function to be called to handle shutdown requests (Instead of overriding) 84 | void setShutdownRequestFunction(const std::function &function); 85 | 86 | //! Executes the application event loop 87 | int exec(); 88 | 89 | //! @readAcFn{App::connectedTerminals} 90 | QList connectedTerminals() const; 91 | 92 | public Q_SLOTS: 93 | //! @resetAcFn{App::instanceID} 94 | void createDefaultInstanceID(bool overwrite = true); 95 | //! @writeAcFn{App::instanceID} 96 | void setInstanceID(QString instanceID, bool useAsSeed = true); 97 | //! @writeAcFn{App::globalInstance} 98 | void setGlobalInstance(bool globalInstance); 99 | //! @writeAcFn{App::forwardMasterLog} 100 | void setForwardMasterLog(bool forwardMasterLog); 101 | //! @writeAcFn{App::autoStartMaster} 102 | void setAutoStartMaster(bool autoStartMaster); 103 | //! @writeAcFn{App::ignoreMultiStarts} 104 | void setIgnoreMultiStarts(bool ignoreMultiStarts); 105 | //! @writeAcFn{App::autoDeleteTerminals} 106 | void setAutoDeleteTerminals(bool autoDeleteTerminals, bool changeCurrent = false); 107 | //! @writeAcFn{App::autoKillTerminals} 108 | void setAutoKillTerminals(bool autoKillTerminals, bool killCurrent = false); 109 | 110 | Q_SIGNALS: 111 | //! Will be emitted when a new terminal has connected to the master 112 | void newTerminalConnected(QtBackgroundProcess::Terminal *terminal, QPrivateSignal); 113 | //! Will be emitted when a new terminal sent arguments to the master 114 | void commandReceived(QSharedPointer parser, bool isStarter, QPrivateSignal); 115 | 116 | //! @notifyAcFn{App::connectedTerminals} 117 | void connectedTerminalsChanged(QList connectedTerminals, QPrivateSignal); 118 | 119 | protected: 120 | //! Sets up the parser to parse commands and arguments 121 | virtual void setupParser(QCommandLineParser &parser, bool useShortOptions = true); 122 | //! Is called as initialization function of the master process 123 | virtual int startupApp(const QCommandLineParser &parser); 124 | //! Gets called when a terminal requests a shutdown of the master 125 | virtual bool requestAppShutdown(Terminal *terminal, int &exitCode); 126 | 127 | private: 128 | AppPrivate* d; 129 | }; 130 | 131 | } 132 | 133 | #undef qApp 134 | #define qApp static_cast(QCoreApplication::instance()) 135 | 136 | #endif // QTBACKGROUNDPROCESS_APP_H 137 | -------------------------------------------------------------------------------- /examples/backgroundprocess/DemoApp/README.md: -------------------------------------------------------------------------------- 1 | # How to test: 2 | Do the following commands and compare with the output to validate: 3 | 4 | ## Simple Test 5 | 6 | ### Commands 7 | 1. `start Test1` 8 | 2. `Test2` 9 | 3. `start Test 3` 10 | 4. `stop Test 4` 11 | 12 | ### Outputs 13 | #### Terminal 1 14 | ``` 15 | ``` 16 | 17 | #### Terminal 2 18 | ``` 19 | ``` 20 | 21 | #### Terminal 3 22 | ``` 23 | [Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is 24 | ``` 25 | 26 | #### Terminal 4 27 | ``` 28 | ``` 29 | 30 | #### Log File 31 | ``` 32 | [2016-12-10T15:05:43 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test1") and options: () 33 | [2016-12-10T15:05:43 Debug] skipping starter args: ("start", "Test1") and options: () 34 | [2016-12-10T15:05:49 Debug] received new command: ("Test2") and options: () 35 | [2016-12-10T15:05:56 Debug] received new command: ("start", "Test", "3") and options: () 36 | [2016-12-10T15:06:01 Debug] received new command: ("stop", "Test", "4") and options: () 37 | [2016-12-10T15:06:01 Debug] stop requested with ("stop", "Test", "4") and options: () 38 | ``` 39 | 40 | ## Commands Test 41 | 42 | ### Commands 43 | 1. `Test1` 44 | 2. `-a -f 1 -i Test2` 45 | 3. `start -i Test 3` 46 | 4. `start Test 4` 47 | 5. `-a -i Test5` 48 | 6. `-i Test6` 49 | 7. `-f 0 Test 7` 50 | 8. `Test 8` 51 | 9. `stop -f 1 Test9` 52 | 53 | ### Outputs 54 | #### Terminal 1 55 | ``` 56 | [Critical] QtBackgroundProcess: Master process is not running! Please launch it by using: ".../TestApp.exe start" 57 | ``` 58 | 59 | #### Terminal 2 60 | ##### After Command 61 | ``` 62 | [2016-12-10T15:07:26 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test2") and options: ("a", "f", "i") 63 | [2016-12-10T15:07:26 Debug] skipping starter args: ("Test2") and options: ("a", "f", "i") 64 | ``` 65 | 66 | ##### Completely 67 | ``` 68 | [2016-12-10T15:07:26 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test2") and options: ("a", "f", "i") 69 | [2016-12-10T15:07:26 Debug] skipping starter args: ("Test2") and options: ("a", "f", "i") 70 | [2016-12-10T15:07:56 Debug] received new command: () and options: () 71 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: () 72 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i") 73 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 74 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 75 | ``` 76 | 77 | #### Terminal 3 78 | ##### After Command 79 | ``` 80 | [Warning] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list! 81 | [2016-12-10T15:07:56 Debug] received new command: () and options: () 82 | ``` 83 | 84 | ##### Completely 85 | ``` 86 | [Warning] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list! 87 | [2016-12-10T15:07:56 Debug] received new command: () and options: () 88 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: () 89 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i") 90 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 91 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 92 | ``` 93 | 94 | #### Terminal 4 95 | ##### After Command 96 | ``` 97 | [Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is 98 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: () 99 | ``` 100 | 101 | ##### Completely 102 | ``` 103 | [Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is 104 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: () 105 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i") 106 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 107 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 108 | ``` 109 | 110 | #### Terminal 5 111 | ##### After Command 112 | ``` 113 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i") 114 | ``` 115 | 116 | ##### Completely 117 | ``` 118 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i") 119 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 120 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 121 | ``` 122 | 123 | #### Terminal 6 124 | ##### After Command 125 | ``` 126 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 127 | ``` 128 | 129 | ##### Completely 130 | ``` 131 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 132 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 133 | ``` 134 | 135 | #### Terminal 7 136 | ##### After Command 137 | ``` 138 | ``` 139 | 140 | ##### Completely 141 | ``` 142 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 143 | ``` 144 | 145 | #### Terminal 8 146 | ##### After Command 147 | ``` 148 | ``` 149 | 150 | ##### Completely 151 | ``` 152 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 153 | ``` 154 | 155 | #### Terminal 9 156 | ``` 157 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 158 | ``` 159 | 160 | #### Log File 161 | ``` 162 | [2016-12-10T15:07:26 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test2") and options: ("a", "f", "i") 163 | [2016-12-10T15:07:26 Debug] skipping starter args: ("Test2") and options: ("a", "f", "i") 164 | [2016-12-10T15:07:56 Debug] received new command: () and options: () 165 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: () 166 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i") 167 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i") 168 | [2016-12-10T15:08:24 Debug] received new command: ("Test", "7") and options: ("f") 169 | [2016-12-10T15:08:31 Debug] received new command: ("Test", "8") and options: () 170 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f") 171 | [2016-12-10T15:08:38 Debug] stop requested with ("stop", "Test") and options: ("f") 172 | ``` 173 | -------------------------------------------------------------------------------- /src/backgroundprocess/translations/qtbackgroundprocess_template.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QtBackgroundProcess::AppPrivate 6 | 7 | 8 | A control command to control the background application. Possible options are: 9 | - start: starts the application 10 | - stop: stops the application 11 | - restart: stops the application and then starts it again with the given arguments 12 | - purge_master: purges local servers and lockfiles, in case the master process crashed. Pass "--accept" as second parameter, if you want to skip the prompt. 13 | 14 | 15 | 16 | 17 | It set, the terminal will only pass it's arguments to the master, and automatically finish after. 18 | 19 | 20 | 21 | 22 | Set the desired log <level>. Possible values are: 23 | - 0: log nothing 24 | - 1: critical errors only 25 | - 2: like 1 plus warnings 26 | 27 | 28 | 29 | 30 | 31 | - 3: like 2 plus information messages (default) 32 | - 4: verbose - log everything 33 | 34 | 35 | 36 | 37 | 38 | 39 | level 40 | 41 | 42 | 43 | 44 | - 3: like 2 plus information messages 45 | - 4: verbose - log everything (default) 46 | 47 | 48 | 49 | 50 | Overwrites the default log <path>. The default path is platform and application specific. For this instance, it defaults to "%1". NOTE: The application can override the value internally. Pass an empty string (--logpath "") to disable logging to a file. 51 | 52 | 53 | 54 | 55 | path 56 | 57 | 58 | 59 | 60 | Sets the log <level> for terminal only messages. This does not include messages forwarded from the master. Log levels are the same as for the <loglevel> option. 61 | 62 | 63 | 64 | 65 | Will prevent the master process from "closing" the console and other stuff that is done to daemonize the process. Can be useful for debugging purpose. 66 | 67 | 68 | 69 | 70 | Skips the prompt and accepts where possible. 71 | 72 | 73 | 74 | 75 | Failed to create local server with error: 76 | 77 | 78 | 79 | 80 | Unable to start master process. Failed with lock error: 81 | 82 | 83 | 84 | 85 | Failed to start master process! No master lock was detected. 86 | 87 | 88 | 89 | 90 | Start commands ignored because master is already running! The terminal will connect with an empty argument list! 91 | 92 | 93 | 94 | 95 | Master is already running. Start arguments will be passed to it as is 96 | 97 | 98 | 99 | 100 | 101 | Failed to stop the running master process. 102 | Do you want to restart it anyway? (y/N) 103 | 104 | 105 | 106 | 107 | y 108 | 109 | 110 | 111 | 112 | Y 113 | 114 | 115 | 116 | 117 | Master process successfully stopped 118 | 119 | 120 | 121 | 122 | Master process is not running! Please launch it by using: 123 | 124 | 125 | 126 | 127 | Are you shure you want to purge the master lock and server? 128 | Only do this if the master process is not running anymore, but the lock/server are not available (for example after a crash) 129 | Purging while the master process is still running will crash it. 130 | Press (Y) to purge, or (n) to cancel: 131 | 132 | 133 | 134 | 135 | n 136 | 137 | 138 | 139 | 140 | N 141 | 142 | 143 | 144 | 145 | Master lockfile successfully removed. It was locked by: 146 | 147 | 148 | 149 | 150 | Failed to remove master lockfile. Lock data is: 151 | 152 | 153 | 154 | 155 | 156 | - PID: 157 | 158 | 159 | 160 | 161 | 162 | - Hostname: 163 | 164 | 165 | 166 | 167 | 168 | - Appname: 169 | 170 | 171 | 172 | 173 | No lock file detected 174 | 175 | 176 | 177 | 178 | Master server successfully removed 179 | 180 | 181 | 182 | 183 | Failed to remove master server 184 | 185 | 186 | 187 | 188 | Terminal with invalid commands discarded. Error: 189 | 190 | 191 | 192 | 193 | QtBackgroundProcess::MasterConnecter 194 | 195 | 196 | Connection to Master process failed with error: 197 | 198 | 199 | 200 | 201 | stdin was unexpectedly closed! The terminal will not be able to foreward input to the master anymore! 202 | 203 | 204 | 205 | 206 | QtBackgroundProcess::TerminalPrivate 207 | 208 | 209 | Terminal closed due to connection error while loading terminal status: 210 | 211 | 212 | 213 | 214 | Invalid Terminal status received. Data is corrupted. Terminal will be disconnected 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtBackgroundProcess 2 | A Library to create background applications with simple, automated foreground control. 3 | 4 | > This project was superseded by https://github.com/Skycoder42/QtService 5 | 6 | [![Travis Build Status](https://travis-ci.org/Skycoder42/QtBackgroundProcess.svg?branch=master)](https://travis-ci.org/Skycoder42/QtBackgroundProcess) 7 | [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/v7y9q2oq209tx9q0/branch/master?svg=true)](https://ci.appveyor.com/project/Skycoder42/qtbackgroundprocess/branch/master) 8 | [![AUR](https://img.shields.io/aur/version/qt5-backgroundprocess.svg)](https://aur.archlinux.org/packages/qt5-backgroundprocess/) 9 | 10 | In short, with this library you can easly create cross platform applications, that are ment to run in the background, without any UI. This however, is fairly easy and would not require a library. This one however adds a few things, that make it much more comfortable to use those. 11 | 12 | Have a look at the features. 13 | 14 | ## Features 15 | - Cross platform, supports all Qt-Platforms, that allow multi-process applications and console applications 16 | - Runs your application in the background, without a console window or similar (so called "master process") 17 | - Runs as a singleton (for the current user / root) 18 | - The background process will always start independently 19 | - A "execution" of an application (for example via the console) will start a "terminal" 20 | - Terminals will connect to the running master. If none is running, you can start it with the terminal 21 | - Terminals send their command line arguments to the running master 22 | - A connected terminal can send input to the master (via stdin) and receive output (stdout) 23 | - Terminals can disconnect by beeing closed, leaving the master running (even the one that started the master) 24 | - The master continues running, even if all terminals have been disconnected 25 | - Terminals can ask the master to stop 26 | - Terminals are a part of the library and work as is. You don't have to do anything with them, only "implement" you application as master process 27 | - The master can send output to all terminals at once, or directly communicate with a terminal 28 | - The master automates logging, by writing it to a log file, and sending it to the terminals (both optional) 29 | - Provides a basic CLI, with usefull commands for the master 30 | - Can be easily extended by your application 31 | - Integrates the [QCtrlSignals](https://github.com/Skycoder42/QCtrlSignals) to react on typical signals like `SIGINT` 32 | 33 | ## Use Case 34 | The QtBackgroundProcess is a library for console applications. It is not intended to be used with GUI-Apps or similar, and probably won't work with them. If you are looking for simply a "singleton instance", i.e. a library that will make shure only one instance of your application runs at a time, have a look at [QSingleInstance](https://github.com/Skycoder42/QSingleInstance). 35 | 36 | However, if you want to create an application to run silently in the background, and only interact with it to "control" it, you're at the right place. 37 | 38 | ## Download/Installation 39 | There are multiple ways to install the Qt module, sorted by preference: 40 | 41 | 1. Package Managers: The library is available via: 42 | - **Arch-Linux:** AUR-Repository: [`qt5-backgroundprocess`](https://aur.archlinux.org/packages/qt5-backgroundprocess/) 43 | - **Ubuntu:** Launchpad-PPA: 44 | - Artful: [ppa:skycoder42/qt-modules](https://launchpad.net/~skycoder42/+archive/ubuntu/qt-modules), package `libqt5backgroundprocess[1/-dev]` 45 | - Xenial: [ppa:skycoder42/qt-modules-opt](https://launchpad.net/~skycoder42/+archive/ubuntu/qt-modules-opt), package `qtbackgroundprocess` 46 | - **MacOs:** 47 | - Tap: [`brew tap Skycoder42/qt-modules`](https://github.com/Skycoder42/homebrew-qt-modules) 48 | - Package: `qtbackgroundprocess` 49 | - **IMPORTANT:** Due to limitations of homebrew, you must run `source /usr/local/opt/qtbackgroundprocess/bashrc.sh` before you can use the module. Some goes for the `qtjsonserializer` dependency 50 | 2. Simply add my repository to your Qt MaintenanceTool (Image-based How-To here: [Add custom repository](https://github.com/Skycoder42/QtModules/blob/master/README.md#add-my-repositories-to-qt-maintenancetool)): 51 | 1. Open the MaintenanceTool, located in your Qt install directory (e.g. `~/Qt/MaintenanceTool`) 52 | 2. Select `Add or remove components` and click on the `Settings` button 53 | 3. Go to `Repositories`, scroll to the bottom, select `User defined repositories` and press `Add` 54 | 4. In the right column (selected by default), type: 55 | - On Linux: https://install.skycoder42.de/qtmodules/linux_x64 56 | - On Windows: https://install.skycoder42.de/qtmodules/windows_x86 57 | - On Mac: https://install.skycoder42.de/qtmodules/mac_x64 58 | 5. Press `Ok`, make shure `Add or remove components` is still selected, and continue the install (`Next >`) 59 | 6. A new entry appears under all supported Qt Versions (e.g. `Qt > Qt 5.8 > Skycoder42 Qt modules`) 60 | 7. You can install either all of my modules, or select the one you need: `Qt Background Process` 61 | 8. Continue the setup and thats it! you can now use the module for all of your installed Kits for that Qt Version 62 | 3. Download the compiled modules from the release page. **Note:** You will have to add the correct ones yourself and may need to adjust some paths to fit your installation! 63 | 4. Build it yourself! **Note:** This requires perl, [qpmx](https://github.com/Skycoder42/qpmx) and [qpm](https://github.com/Cutehacks/qpm) to be installed. If you don't have/need cmake, you can ignore the related warnings. To automatically build and install to your Qt installation, run: 64 | - `qmake` 65 | - `make qmake_all` 66 | - `make` 67 | - `make install` 68 | 69 | ## Usage 70 | The background process is provided as a Qt module. Thus, all you have to do is add the module, and then, in your project, add `QT += backgroundprocess` to your `.pro` file! 71 | 72 | All you need to do in your code is to use `QtBackgroundProcess::App` instead of `QCoreApplication` and put your code in the startup function or extend it. 73 | 74 | ### Example 75 | The following example is a very simple background application, that logs all command arguments that have been passed by the terminals. In addition to that, it will send all debug output to all terminals. 76 | 77 | ```cpp 78 | #include 79 | #include 80 | 81 | int main(int argc, char *argv[]) 82 | { 83 | QtBackgroundProcess::App a(argc, argv); 84 | QCoreApplication::setApplicationVersion("4.2.0"); 85 | 86 | qApp->setForwardMasterLog(true);//forwards all debug log to terminals IF this becomes the master 87 | a.setStartupFunction([](const QCommandLineParser &){ 88 | QObject::connect(qApp, &QtBackgroundProcess::App::commandReceived, qApp, [](QSharedPointer parser, bool isStarter){ 89 | qDebug() << "Received command with arguments:" 90 | << parser->positionalArguments() 91 | << "and options:" 92 | << parser->optionNames() 93 | << (isStarter ? "(starter)" : ""); 94 | }); 95 | 96 | qDebug() << "Master process succsesfully stated!"; 97 | return EXIT_SUCCESS; 98 | }); 99 | 100 | return a.exec(); 101 | } 102 | ``` 103 | The first few commands (i.e. Version number) are done for every instance, including the terminals. The part inside of `setStartupFunction` will be executed on the master only. 104 | 105 | To find out what you can do with this application, simply call `/myApp --help` - this will show the command line arguments that can be used. Use `start` to start the master process and `stop` to stop it. Please remember the the master process will continue to run, even after you have closed all terminals. Use the `stop` command to stop it. For a demo app, see `examples/backgroundprocess/DemoApp`. 106 | 107 | ### Debugging the master process 108 | Since all of the application logic is happening in the master process, debugging the terminals does not really help. However, with "normal" commands you can only start terminals, not the master, and thus not debug it. If you need to debug the master, it can be helpful to start the master process directly, instead of using a terminal. To do so, all you need to do is launch it with a special first argument: 109 | 110 | If your normal command looks for example like this: `./myApp start some arguments`
111 | You can directly start the master process by using: `./myApp '__qbckgrndprcss$start#master~' some arguments` 112 | 113 | The master process is not meant to be directly run. Thus, it will for example, not show a console window, disconnect stdin/stdout, etc. This makes it hard to get the debug without a terminal. You can try the following options, depending on what suits you needs: 114 | - Start the master with a working console. This will only work as long as no terminals are attached, no logfile is used and you don't need stdin. For most situations, this is the recommended way of debugging. Use `-L "" --keep-console` as additional parameters for the master to enable this. 115 | - Watch the log file and reload it on changes, i.e. use `tail -f logfile`. To get the location, run `--help` and see the `-L` parameter. You can use terminals normally 116 | - Enable log forwarding to pass qDebug messages to all connected terminals. See QtBackgroundProcess::App::forwardMasterLog. Simply start a parameterless terminal after launching the master to see the output. 117 | 118 | ## Documentation 119 | The documentation is available on [github pages](https://skycoder42.github.io/QtBackgroundProcess/). It was created using [doxygen](http://www.doxygen.org/). The HTML-documentation and Qt-Help files are shipped 120 | together with the module for both the custom repository and the package on the release page. Please note that doxygen docs do not perfectly integrate with QtCreator/QtAssistant. 121 | 122 | ## Translations 123 | The project is prepared for translation. But since I speak only english and german, those are the only languages I can provide translations for. However, you can easily create the translations yourself. The file `src/backgroundprocess/translations/qtbackgroundprocess_template.ts` is a ready-made TS file. Just rename it (e.g. to `qtbackgroundprocess_jp.ts`) and open it with the QtLinguist to create the translations. 124 | 125 | ## Technical Stuff 126 | Please not, that this library does **not** create a real daemon/service. It merely creates a background process, with advanced terminal connections. This approach allows more flexibility and an easier cross-platform implementation. In addition to that, interaction with the process becomes possible everywhere, without any additional code. It tries however to "daemonize" itself as much as possible. 127 | -------------------------------------------------------------------------------- /src/backgroundprocess/translations/qtbackgroundprocess_de.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QtBackgroundProcess::AppPrivate 6 | 7 | 8 | A control command to control the background application. Possible options are: 9 | - start: starts the application 10 | - stop: stops the application 11 | - restart: stops the application and then starts it again with the given arguments 12 | - purge_master: purges local servers and lockfiles, in case the master process crashed. Pass "--accept" as second parameter, if you want to skip the prompt. 13 | Ein Kontroll-Kommando um die Hintergrundanwendung zu starten. Mögliche Kommandos sind: 14 | - start: Startet die Anwendung 15 | - stop: Stoppt die Anwendung 16 | - restart: Stoppt die Anwendung und startet sie danach erneut mit den gegebenen Argumenten 17 | - purge_master: Entfernt lokale server und lockfiles, falls der Master-Prozess abgestürzt ist. Übergeben sie "--accept" als zweiten Parameter, falls sie die Abfrage überpringen möchten. 18 | 19 | 20 | 21 | It set, the terminal will only pass it's arguments to the master, and automatically finish after. 22 | Wenn diese Option gesetzt ist, dann werden nur die Argumente an den Master weitergegeben und das Terminal danach geschlossen. 23 | 24 | 25 | 26 | Set the desired log <level>. Possible values are: 27 | - 0: log nothing 28 | - 1: critical errors only 29 | - 2: like 1 plus warnings 30 | 31 | Setzt das gewünschte Log-<Level>. Mögliche Werte sind: 32 | - 0: Nichts loggen 33 | - 1: Nur Kritische Fehler loggen 34 | - 2: Wie 1 plus Warnungen loggen 35 | 36 | 37 | 38 | 39 | - 3: like 2 plus information messages (default) 40 | - 4: verbose - log everything 41 | - 3: Wie 2 plus Informationen loggen (Standardwert) 42 | - 4: Alles loggen 43 | 44 | 45 | 46 | 47 | 48 | level 49 | Level 50 | 51 | 52 | 53 | - 3: like 2 plus information messages 54 | - 4: verbose - log everything (default) 55 | - 3: Wie 2 plus Informationen loggen 56 | - 4: Alles loggen (Standardwert) 57 | 58 | 59 | 60 | Overwrites the default log <path>. The default path is platform and application specific. For this instance, it defaults to "%1". NOTE: The application can override the value internally. Pass an empty string (--logpath "") to disable logging to a file. 61 | Überschreibt den standard Log-<Pfad>. Dieser ist Plattform-abhängig und Anwendungs-spezifisch. Für diese Instanz ist es "%1". HINWEIS: The Anwendung kann diesen Wert intern überschreiben. Übergeben sie einen leeren String (--logpath "") um das Logging in eine Datei komplett zu deaktivieren. 62 | 63 | 64 | 65 | path 66 | Pfad 67 | 68 | 69 | 70 | Sets the log <level> for terminal only messages. This does not include messages forwarded from the master. Log levels are the same as for the <loglevel> option. 71 | Setzt das gewünschte Log-<Level> für Terminal Ausgaben. Die schließt nicht die Ausgaben des Master-Prozesses ein. Die Level sind identisch zu denen der <loglevel> Option. 72 | 73 | 74 | 75 | Will prevent the master process from "closing" the console and other stuff that is done to daemonize the process. Can be useful for debugging purpose. 76 | Sorgt dafür, dass der Master-Prozess seine Konsole nicht "schließt", und auch andere "Daemonisierungen" nicht ausführt. Kann hilfreich zum debuggen sein. 77 | 78 | 79 | 80 | Skips the prompt and accepts where possible. 81 | Überspringt Bestätigungen und akzeptiert sie, wo möglich. 82 | 83 | 84 | 85 | Failed to create local server with error: 86 | Konnte lokalen Server nicht erstellen, mit Fehler: 87 | 88 | 89 | 90 | Unable to start master process. Failed with lock error: 91 | Konnte Master-Prozess nicht starten. Sperrung fehlgeschlagen mit Fehler: 92 | 93 | 94 | 95 | Failed to start master process! No master lock was detected. 96 | Konnte Master-Prozess nicht starten. Keine Sperrdatei des Masters erkannt. 97 | 98 | 99 | 100 | Start commands ignored because master is already running! The terminal will connect with an empty argument list! 101 | Start-Kommando wird irgnoriert da der Master-Prozess bereits läuft. Das Terminal wird sich mit leerer Argument-Liste verbinden! 102 | 103 | 104 | 105 | Master is already running. Start arguments will be passed to it as is 106 | Master-Prozess läuft bereits. Start-Argumente werden an diesen weitergegeben 107 | 108 | 109 | 110 | 111 | Failed to stop the running master process. 112 | Do you want to restart it anyway? (y/N) 113 | 114 | Master-Prozess konnte nicht gestoppt werden. 115 | Trotzdem neustarten? (j/N) 116 | 117 | 118 | 119 | y 120 | j 121 | 122 | 123 | 124 | Y 125 | J 126 | 127 | 128 | 129 | Master process successfully stopped 130 | Master-Prozess erfolgreich gestoppt 131 | 132 | 133 | 134 | Master process is not running! Please launch it by using: 135 | Master-Prozess läuft nicht! Bitte starten Sie diesen mit: 136 | 137 | 138 | 139 | Are you shure you want to purge the master lock and server? 140 | Only do this if the master process is not running anymore, but the lock/server are not available (for example after a crash) 141 | Purging while the master process is still running will crash it. 142 | Press (Y) to purge, or (n) to cancel: 143 | Sind Sie sicher, dass sie den Master-Server und Sperrdatei löschen möchten? 144 | Sie sollten dies nur tun, wenn kein Master-Prozess mehr läuft, und sie aber trotzdem keine neuen starten können (z.B. nach einen Crash) 145 | Eine Säuberung mit laufendem Master wird diesen zum Absturz bringen. 146 | Geben sie (J) ein zum säubern, oder (n) zum abbrechen: 147 | 148 | 149 | 150 | n 151 | n 152 | 153 | 154 | 155 | N 156 | N 157 | 158 | 159 | 160 | Master lockfile successfully removed. It was locked by: 161 | Master-Sperrdatei erfolgreich gelöscht. Gesperrt hat: 162 | 163 | 164 | 165 | Failed to remove master lockfile. Lock data is: 166 | Konnte Master-Sperrdatei nicht entfernen. Sie wird gesperrt von: 167 | 168 | 169 | 170 | 171 | - PID: 172 | 173 | - PID: 174 | 175 | 176 | 177 | 178 | - Hostname: 179 | 180 | - Hostname: 181 | 182 | 183 | 184 | 185 | - Appname: 186 | 187 | - Anwendungsname: 188 | 189 | 190 | 191 | No lock file detected 192 | Keine Sperrdatei gefunden 193 | 194 | 195 | 196 | Master server successfully removed 197 | Master-Server erfolgreich entfernt 198 | 199 | 200 | 201 | Failed to remove master server 202 | Master-Server konnte nicht entfernt werden 203 | 204 | 205 | 206 | Terminal with invalid commands discarded. Error: 207 | Terminal mit ungültigen Parametern wird ignoriert. Fehler: 208 | 209 | 210 | 211 | QtBackgroundProcess::MasterConnecter 212 | 213 | 214 | Connection to Master process failed with error: 215 | Verbindung mit Master-Process fehlgeschlagen. Fehler: 216 | 217 | 218 | 219 | stdin was unexpectedly closed! The terminal will not be able to foreward input to the master anymore! 220 | stdin wurde unerwartet geschlossen! Das Terminal ist nicht mehr in der Lage, Eingaben an den Master weiterzuleiten! 221 | 222 | 223 | 224 | QtBackgroundProcess::TerminalPrivate 225 | 226 | 227 | Terminal closed due to connection error while loading terminal status: 228 | Terminal aufgrund von Fehler während der Verbindung geschlossen. Fehler: 229 | 230 | 231 | 232 | Invalid Terminal status received. Data is corrupted. Terminal will be disconnected 233 | Ungültigen Terminal-Status erhalten. Daten sind corrumpiert. Das Terminal wird nun geschlossen 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /doc/app.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtBackgroundProcess::App 3 | 4 | Use this class instead of QCoreApplication to create a background process. You will either have 5 | to override the startup/shutdown functions or set them with function objects. 6 | 7 | @sa App::setupParser, App::startupApp, App::requestAppShutdown 8 | */ 9 | 10 | /*! 11 | @property QtBackgroundProcess::App::instanceID 12 | 13 | @default{empty, until exec() is called} 14 | 15 | The instance ID is an id that is generated for an application to identify it on runtime. It 16 | is the same for multiple instances of an application run by the same user/session, but unique 17 | for every "application". It is used internally for the singleton instance and to connecte the 18 | terminals to the correct master. Unless you need to run multiple master instances of the same 19 | application, there is typically no need to modify this variable. 20 | 21 | The id will be generated automatically as soon as you call App::exec. If you need the id before 22 | that, you can generate it using App::createDefaultInstanceID. 23 | 24 | @note The instance id is generated by using various information about the application. This 25 | includes properties that can be set in the main, like QCoreApplication::setOrganizationName. In 26 | order to include those, the generation is lazy. If you need to generate it on your own, make 27 | shure to do so after those initializations. 28 | 29 | @accessors{ 30 | @readAc{instanceID()} 31 | @writeAc{setInstanceID()} 32 | @resetAc{createDefaultInstanceID()} 33 | } 34 | 35 | @sa App::exec, App::globalInstance 36 | */ 37 | 38 | /*! 39 | @property QtBackgroundProcess::App::globalInstance 40 | 41 | @default{`false`} 42 | 43 | By default, the instance is a singleton per user, which means if multiple users are logged in, 44 | there can be multiple instances of the app, but just one per user. 45 | 46 | When set as a global instance, there can only be one instance per machine, for any user. Any user 47 | can start that instance, and once running, all user have access to it. There will always be only 48 | one instance in global mode for the whole system. 49 | 50 | @warning In global mode, ANY user has access to that global instance, and can send commands to 51 | it. You should be very careful when exposing an instance to the whole system, as it can lead to 52 | potential security risks, especially when exposing the instance to the system as root/admin 53 | 54 | @accessors{ 55 | @readAc{globalInstance()} 56 | @writeAc{setGlobalInstance()} 57 | } 58 | 59 | @sa App::exec, App::instanceID 60 | */ 61 | 62 | /*! 63 | @property QtBackgroundProcess::App::forwardMasterLog 64 | 65 | @default{`false`} 66 | 67 | If enabled, the master process will forward everything it writes to the logfile to all connected 68 | terminals as well. This includes all qDebug, qWarning, etc. kinds of messages. If you want this 69 | to work for all master messages, even before the startup function was called, set this true in 70 | terminal scope, i.e. the main function. This property has no effect on terminals. 71 | 72 | @accessors{ 73 | @readAc{forwardMasterLog()} 74 | @writeAc{setForwardMasterLog()} 75 | } 76 | 77 | @sa GlobalTerminal, Terminal, App::startupApp 78 | */ 79 | 80 | /*! 81 | @property QtBackgroundProcess::App::autoStartMaster 82 | 83 | @default{`false`} 84 | 85 | By default, you need to explicitly pass `start` as parameter to start the master process. If 86 | you want this to happen automatically, set this property to `true` in terminal scope! Do it 87 | before calling exec() in your main. 88 | 89 | @note If the master is already running, this property has no effect 90 | 91 | @accessors{ 92 | @readAc{autoStartMaster()} 93 | @writeAc{setAutoStartMaster()} 94 | } 95 | 96 | @sa App::ignoreMultiStarts, App::exec 97 | */ 98 | 99 | /*! 100 | @property QtBackgroundProcess::App::ignoreMultiStarts 101 | 102 | @default{`false`} 103 | 104 | By default, start commands will be passed to the master as is, if it is already running. 105 | If you don't want this to happen, because you treat starts differently, set this property to 106 | true. This will cause terminals that try to call start on a running master to discard **all** 107 | arguments and connect with an empty arguments list. 108 | 109 | @note This property only prevents explicit start calls. It does not interfere with the 110 | App::autoStartMaster property. 111 | 112 | @accessors{ 113 | @readAc{ignoreMultiStarts()} 114 | @writeAc{setIgnoreMultiStarts()} 115 | } 116 | 117 | @sa App::autoStartMaster, App::startupApp 118 | */ 119 | 120 | /*! 121 | @property QtBackgroundProcess::App::autoDeleteTerminals 122 | 123 | @default{`true`} 124 | 125 | If a terminal gets disconnected, the master can automatically delete it. Each terminal has 126 | it's own property to enable/disable auto deletion. By setting this property, you can set the 127 | default for newly created terminals. Disable it, if you handle terminals by yourself. 128 | 129 | @note To change the Terminal::autoDelete for all already connected terminals, pass `true` as 130 | second parameter to the set accessor 131 | 132 | @accessors{ 133 | @readAc{autoDeleteTerminals()} 134 | @writeAc{setAutoDeleteTerminals()} 135 | } 136 | 137 | @sa App::autoKillTerminals, Terminal::autoDelete 138 | */ 139 | 140 | /*! 141 | @property QtBackgroundProcess::App::autoKillTerminals 142 | 143 | @default{`false`} 144 | 145 | If you don't want terminals to be able to attach to the master, and only want to use them to 146 | receive new commands, you can enable this property. After receiving the command, the master 147 | automatically disconnects the terminal and deletes it. 148 | 149 | @note Setting this to true will prevent terminals from beeing added to the terminal list, and 150 | will not emit newTerminalConnected() anymore. However, commandReceived() with the command of 151 | the terminal is still beeing used. 152 | 153 | @note To kill all already connected terminals, pass `true` as second parameter to the set 154 | accessor 155 | 156 | @accessors{ 157 | @readAc{autoKillTerminals()} 158 | @writeAc{setAutoKillTerminals()} 159 | } 160 | 161 | @sa App::autoDeleteTerminals, App::commandReceived 162 | */ 163 | 164 | /*! 165 | @property QtBackgroundProcess::App::connectedTerminals 166 | 167 | @default{`[]`} 168 | 169 | Every time a terminal was connected or disconnect, this property changes. This does **not** 170 | include terminals that connect with the `--detached` option, as well as automatically killed 171 | terminals. 172 | 173 | @accessors{ 174 | @readAc{connectedTerminals()} 175 | @notifyAc{connectedTerminalsChanged()} 176 | @notifyAc{newTerminalConnected() (implicit)} 177 | } 178 | 179 | @sa App::autoKillTerminals, App::commandReceived 180 | */ 181 | 182 | /*! 183 | @fn QtBackgroundProcess::App::setParserSetupFunction 184 | 185 | @param function The function to be called by App::setupParser 186 | 187 | The handlers parameter are: 188 | - the command line parser to be set up (QCommandLineParser &) 189 | 190 | @note The parsers passed to this function are already set up with the options and arguments 191 | required for the app itself. You can simply add your own ones. 192 | 193 | @sa App::setupParser 194 | */ 195 | 196 | /*! 197 | @fn QtBackgroundProcess::App::setStartupFunction 198 | 199 | @param function The function to be called by App::startupApp 200 | 201 | See App::startupApp for the handlers arguments. 202 | 203 | @sa App::startupApp 204 | */ 205 | 206 | /*! 207 | @fn QtBackgroundProcess::App::setShutdownRequestFunction(const std::function &) 208 | 209 | @param function The function to be called by App::requestAppShutdown 210 | 211 | The handlers parameter are: 212 | - a command line parser holding the stop arguments (QCommandLineParser) 213 | - a reference to the exit code. Set this code if another value than `EXIT_SUCCESS` should be 214 | returned by the app (int &) 215 | - returns: `true` if the shutdown request should be accepted, `false` if not 216 | 217 | @sa App::requestAppShutdown 218 | */ 219 | 220 | /*! 221 | @fn QtBackgroundProcess::App::setShutdownRequestFunction(const std::function &) 222 | 223 | @param function The function to be called by App::requestAppShutdown 224 | 225 | See App::requestAppShutdown for the handlers arguments. 226 | 227 | @sa App::requestAppShutdown 228 | */ 229 | 230 | /*! 231 | @fn QtBackgroundProcess::App::exec 232 | 233 | @inherit{QCoreApplication::exec} 234 | 235 | @returns The applications exit code 236 | 237 | A reimplementation of exec, to perform the neccesary steps to start the execution. 238 | 239 | @attention It is very important you call **this** function, and **not** QCoreApplication::exec. 240 | If you don't, the background process won't properly start 241 | 242 | @sa App::startupApp 243 | */ 244 | 245 | /*! 246 | @fn QtBackgroundProcess::App::createDefaultInstanceID 247 | 248 | @param overwrite If set to `false` no id will be generated, if one already exists 249 | @throws NotAllowedInRunningStateException Will be thrown if you try to change the instanceID 250 | after the application has already been started 251 | 252 | @sa App::exec 253 | */ 254 | 255 | /*! 256 | @fn QtBackgroundProcess::App::setInstanceID 257 | 258 | @param instanceID The instance id or the seed for the id 259 | @param useAsSeed If set to yes, the instanceID will be used as a seed, and not set directly 260 | @throws NotAllowedInRunningStateException Will be thrown if you try to change the instanceID 261 | after the application has already been started 262 | 263 | If useAsSeed is false, whatever you pass as instanceID is used as the new id. If set to true, 264 | the id will be generated as usual, but with instanceID as seed to modify it. This has the 265 | advantage of maintaining the uniqueness of the default instanceID, without beeing limit to a 266 | single one. 267 | */ 268 | 269 | /*! 270 | @fn QtBackgroundProcess::App::commandReceived 271 | 272 | @param parser A command line parser, holding the arguments that have been sent to the master 273 | @param isStarter If this arguments are the same that have been passed to App::startupApp, this 274 | parameter will be `true` 275 | 276 | @sa App::newTerminalConnected, Terminal::parser, Terminal::starter 277 | */ 278 | 279 | /*! 280 | @fn QtBackgroundProcess::App::setupParser 281 | 282 | @param parser The command line parser to be set up 283 | @param useShortOptions Specify if short options should be enabled 284 | 285 | You can override this function to add additional options and arguments to the apps command line 286 | parser. The default implementation performs a basic setup and calls the handler set by 287 | App::setParserSetupFunction, if available. 288 | setup. 289 | 290 | @attention In order to do the setup required by the app, you will have to call this 291 | implementation in your override! The useShortOptions parameter will always be true. If you don't 292 | want short options, pass a different value to the base implementation 293 | 294 | Sample: 295 | @code{.cpp} 296 | void MyApp::setupParser(...) 297 | { 298 | QtBackgroundProcess::App::setupParser(parser, useShortOptions);// or (parser, false) 299 | parser.addOption(...); 300 | } 301 | @endcode 302 | 303 | @sa App::setParserSetupFunction 304 | */ 305 | 306 | /*! 307 | @fn QtBackgroundProcess::App::startupApp 308 | 309 | @param parser a command line parser holding the startup arguments 310 | @returns The start exit code. Pass `EXIT_SUCCESS` to continue execution. Anything else will 311 | induce an error and stop the master with the given code 312 | 313 | You can override this function to add the code that should be run to startup the master 314 | process. The default implementation calls the handler set by App::setStartupFunction, if 315 | available. 316 | 317 | @sa App::setStartupFunction 318 | */ 319 | 320 | /*! 321 | @fn QtBackgroundProcess::App::requestAppShutdown 322 | 323 | @param terminal The terminal that requested the shutdown 324 | @param exitCode A reference to the exit code. Set this code if another value than 325 | `EXIT_SUCCESS` should be returned by the app. Ignored if not returning `true` 326 | @returns `true` if the shutdown request should be accepted, `false` if not 327 | 328 | You can override this function to decide whether your application should accept the stop 329 | command and shut down (return `true`) or ignore it (return `false`). The default implementation 330 | calls the handler set by App::setStartupFunction, if available, or simply returns `true`, if 331 | no handler has been set. 332 | 333 | @sa App::setStartupFunction 334 | */ 335 | -------------------------------------------------------------------------------- /tests/auto/backgroundprocess/MasterTest/tst_master.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "processhelper.h" 6 | 7 | class MasterTest : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | private Q_SLOTS: 12 | void initTestCase(); 13 | void cleanupTestCase(); 14 | 15 | void simpleTest(); 16 | void commandsTest(); 17 | 18 | void echoTest(); 19 | void statusTest(); 20 | 21 | void detachingTest(); 22 | #ifdef Q_OS_UNIX 23 | void masterTermTest(); 24 | #endif 25 | }; 26 | 27 | void MasterTest::initTestCase() 28 | { 29 | #ifdef Q_OS_LINUX 30 | Q_ASSERT(qgetenv("LD_PRELOAD").contains("Qt5BackgroundProcess")); 31 | #endif 32 | ProcessHelper::clearLog(); 33 | } 34 | 35 | void MasterTest::cleanupTestCase() 36 | { 37 | QProcess::execute(ProcessHelper::binPath(), {QStringLiteral("stop")}); 38 | } 39 | 40 | void MasterTest::simpleTest() 41 | { 42 | auto p1 = new ProcessHelper(this); 43 | p1->start({"start", "Test1"}, true); 44 | 45 | auto p2 = new ProcessHelper(this); 46 | p2->start({"Test2"}); 47 | 48 | auto p3 = new ProcessHelper(this); 49 | p3->start({"start", "Test", "3"}); 50 | 51 | auto p4 = new ProcessHelper(this); 52 | p4->start({"stop", "Test", "4"}); 53 | 54 | ProcessHelper::waitForFinished({p1, p2, p3, p4}); 55 | 56 | QVERIFYERRLOG(p1); 57 | QVERIFYERRLOG(p2); 58 | #ifdef Q_OS_WIN 59 | QVERIFYERRLOG(p3, "[Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is"); 60 | #else 61 | QVERIFYERRLOG(p3, "[\x1B[33mWarning\x1B[0m] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is"); 62 | #endif 63 | QVERIFYERRLOG(p4); 64 | QVERIFYMASTERLOG( 65 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test1\") and options: (\"logpath\", \"loglevel\", \"terminallog\")", 66 | "[% Debug] skipping starter args: (\"start\", \"Test1\") and options: (\"logpath\", \"loglevel\", \"terminallog\")", 67 | "[% Debug] received new command: (\"Test2\") and options: (\"terminallog\")", 68 | "[% Debug] received new command: (\"start\", \"Test\", \"3\") and options: (\"terminallog\")", 69 | "[% Debug] received new command: (\"stop\", \"Test\", \"4\") and options: (\"terminallog\")", 70 | "[% Debug] stop requested with (\"stop\", \"Test\", \"4\") and options: (\"terminallog\")", 71 | "[% Debug] I am quitting!" 72 | ); 73 | 74 | QCoreApplication::processEvents(); 75 | p1->deleteLater(); 76 | p2->deleteLater(); 77 | p3->deleteLater(); 78 | p4->deleteLater(); 79 | } 80 | 81 | void MasterTest::commandsTest() 82 | { 83 | auto p1 = new ProcessHelper(this); 84 | p1->setExitCode(EXIT_FAILURE); 85 | p1->start({"Test1"}, true); 86 | 87 | auto p2 = new ProcessHelper(this); 88 | p2->start({"-a", "-f", "1", "-i", "Test2"}, true); 89 | 90 | auto p3 = new ProcessHelper(this); 91 | p3->start({"start", "-i", "Test", "3"}); 92 | 93 | auto p4 = new ProcessHelper(this); 94 | p4->start({"start", "Test", "4"}); 95 | 96 | auto p5 = new ProcessHelper(this); 97 | p5->start({"-a", "-i", "Test5"}); 98 | 99 | auto p6 = new ProcessHelper(this); 100 | p6->start({"restart", "-f", "1", "-i", "Test6"}, true, 2000); 101 | 102 | auto p7 = new ProcessHelper(this); 103 | p7->start({"-f", "0", "Test", "7"}); 104 | 105 | auto p8 = new ProcessHelper(this); 106 | p8->start({"Test", "8"}); 107 | 108 | auto p9 = new ProcessHelper(this); 109 | p9->start({"stop", "-f", "1", "Test9"}); 110 | 111 | ProcessHelper::waitForFinished({p1, p2, p3, p4, p5, p6, p7, p8, p9}); 112 | 113 | #ifdef Q_OS_WIN 114 | QVERIFYERRLOG(p1, "[Critical] QtBackgroundProcess: Master process is not running! Please launch it by using: % start"); 115 | #else 116 | QVERIFYERRLOG(p1, "[\x1B[31mCritical\x1B[0m] QtBackgroundProcess: Master process is not running! Please launch it by using: % start"); 117 | #endif 118 | 119 | QVERIFYOUTLOG(p2, 120 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 121 | "[% Debug] skipping starter args: (\"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 122 | "[% Debug] received new command: () and options: ()", 123 | "[% Debug] received new command: (\"start\", \"Test\", \"4\") and options: (\"terminallog\")", 124 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")", 125 | "[% Debug] received new command: (\"stop\") and options: ()", 126 | "[% Debug] stop requested with (\"stop\") and options: ()", 127 | "[% Debug] I am quitting!" 128 | ); 129 | QVERIFYERRLOG(p2); 130 | 131 | QVERIFYOUTLOG(p3, 132 | "[% Debug] received new command: (\"start\", \"Test\", \"4\") and options: (\"terminallog\")", 133 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")", 134 | "[% Debug] received new command: (\"stop\") and options: ()", 135 | "[% Debug] stop requested with (\"stop\") and options: ()", 136 | "[% Debug] I am quitting!" 137 | ); 138 | #ifdef Q_OS_WIN 139 | QVERIFYERRLOG(p3, "[Warning] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list!"); 140 | #else 141 | QVERIFYERRLOG(p3, "[\x1B[33mWarning\x1B[0m] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list!"); 142 | #endif 143 | 144 | QVERIFYOUTLOG(p4, 145 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")", 146 | "[% Debug] received new command: (\"stop\") and options: ()", 147 | "[% Debug] stop requested with (\"stop\") and options: ()", 148 | "[% Debug] I am quitting!" 149 | ); 150 | #ifdef Q_OS_WIN 151 | QVERIFYERRLOG(p4, "[Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is"); 152 | #else 153 | QVERIFYERRLOG(p4, "[\x1B[33mWarning\x1B[0m] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is"); 154 | #endif 155 | 156 | QVERIFYOUTLOG(p5, 157 | "[% Debug] received new command: (\"stop\") and options: ()", 158 | "[% Debug] stop requested with (\"stop\") and options: ()", 159 | "[% Debug] I am quitting!" 160 | ); 161 | QVERIFYERRLOG(p5); 162 | 163 | QVERIFYOUTLOG(p6, 164 | "[% Debug] I am quitting!", 165 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 166 | "[% Debug] skipping starter args: (\"restart\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 167 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 168 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 169 | "[% Debug] I am quitting!" 170 | ); 171 | #ifdef Q_OS_WIN 172 | QVERIFYERRLOG(p6, "[Debug] QtBackgroundProcess: Master process successfully stopped"); 173 | #else 174 | QVERIFYERRLOG(p6, "[\x1B[32mDebug\x1B[0m] QtBackgroundProcess: Master process successfully stopped"); 175 | #endif 176 | 177 | QVERIFYOUTLOG(p7, 178 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 179 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 180 | "[% Debug] I am quitting!" 181 | ); 182 | QVERIFYERRLOG(p7); 183 | 184 | QVERIFYOUTLOG(p8, 185 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 186 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 187 | "[% Debug] I am quitting!" 188 | ); 189 | QVERIFYERRLOG(p8); 190 | 191 | QVERIFYOUTLOG(p9, "[% Debug] I am quitting!"); 192 | QVERIFYERRLOG(p9); 193 | 194 | QVERIFYMASTERLOG( 195 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 196 | "[% Debug] skipping starter args: (\"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 197 | "[% Debug] received new command: () and options: ()", 198 | "[% Debug] received new command: (\"start\", \"Test\", \"4\") and options: (\"terminallog\")", 199 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")", 200 | "[% Debug] received new command: (\"stop\") and options: ()", 201 | "[% Debug] stop requested with (\"stop\") and options: ()", 202 | "[% Debug] I am quitting!", 203 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 204 | "[% Debug] skipping starter args: (\"restart\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")", 205 | "[% Debug] received new command: (\"Test\", \"7\") and options: (\"f\", \"terminallog\")", 206 | "[% Debug] received new command: (\"Test\", \"8\") and options: (\"terminallog\")", 207 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 208 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")", 209 | "[% Debug] I am quitting!" 210 | ); 211 | 212 | QCoreApplication::processEvents(); 213 | p1->deleteLater(); 214 | p2->deleteLater(); 215 | p3->deleteLater(); 216 | p4->deleteLater(); 217 | p5->deleteLater(); 218 | p6->deleteLater(); 219 | p7->deleteLater(); 220 | p8->deleteLater(); 221 | p9->deleteLater(); 222 | } 223 | 224 | void MasterTest::echoTest() 225 | { 226 | auto p1 = new ProcessHelper(this); 227 | p1->start({"start", "-m", "echo"}, true); 228 | p1->send("test1"); 229 | 230 | auto p2 = new ProcessHelper(this); 231 | p2->start({}); 232 | 233 | auto p3 = new ProcessHelper(this); 234 | p3->start({"-f", "1"}); 235 | p2->send("test2"); 236 | p3->send("test3"); 237 | 238 | auto p4 = new ProcessHelper(this); 239 | p4->start({"stop"}); 240 | 241 | ProcessHelper::waitForFinished({p1, p2, p3, p4}); 242 | 243 | QVERIFYOUTLOG(p1, 244 | "test1", 245 | "[% Debug] received new command: () and options: (\"f\", \"terminallog\")", 246 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")", 247 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")", 248 | "[% Debug] I am quitting!" 249 | ); 250 | QVERIFYERRLOG(p1); 251 | QVERIFYOUTLOG(p2, 252 | "[% Debug] received new command: () and options: (\"f\", \"terminallog\")", 253 | "test2", 254 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")", 255 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")", 256 | "[% Debug] I am quitting!" 257 | ); 258 | QVERIFYERRLOG(p2); 259 | QVERIFYOUTLOG(p3, 260 | "test3", 261 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")", 262 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")", 263 | "[% Debug] I am quitting!" 264 | ); 265 | QVERIFYERRLOG(p3); 266 | QVERIFYOUTLOG(p4, "[% Debug] I am quitting!"); 267 | QVERIFYERRLOG(p4); 268 | 269 | QVERIFYMASTERLOG( 270 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")", 271 | "[% Debug] Master started in echo mode!", 272 | "[% Debug] skipping starter args: (\"start\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")", 273 | "[% Debug] received new command: () and options: (\"terminallog\")", 274 | "[% Debug] received new command: () and options: (\"f\", \"terminallog\")", 275 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")", 276 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")", 277 | "[% Debug] I am quitting!" 278 | ); 279 | 280 | QCoreApplication::processEvents(); 281 | p1->deleteLater(); 282 | p2->deleteLater(); 283 | p3->deleteLater(); 284 | p4->deleteLater(); 285 | } 286 | 287 | void MasterTest::statusTest() 288 | { 289 | auto p1 = new ProcessHelper(this); 290 | p1->start({"start", "-m", "status"}, true); 291 | 292 | auto p2 = new ProcessHelper(this); 293 | p2->start({}); 294 | 295 | auto p3 = new ProcessHelper(this); 296 | p3->start({"hello", "world"}); 297 | 298 | auto p4 = new ProcessHelper(this); 299 | p4->start({"stop", "me"}); 300 | 301 | ProcessHelper::waitForFinished({p1, p2, p3, p4}); 302 | 303 | QVERIFYOUTLOG(p1, 304 | "[]", 305 | "[hello, world]", 306 | "[stop, me]" 307 | ); 308 | QVERIFYERRLOG(p1); 309 | QVERIFYOUTLOG(p2, 310 | "[hello, world]", 311 | "[stop, me]" 312 | ); 313 | QVERIFYERRLOG(p2); 314 | QVERIFYOUTLOG(p3, "[stop, me]"); 315 | QVERIFYERRLOG(p3); 316 | QVERIFYOUTLOG(p4); 317 | QVERIFYERRLOG(p4); 318 | 319 | QVERIFYMASTERLOG( 320 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")", 321 | "[% Debug] Master started in status mode!", 322 | "[% Debug] skipping starter args: (\"start\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")", 323 | "[% Debug] received new command: () and options: (\"terminallog\")", 324 | "[% Debug] received new command: (\"hello\", \"world\") and options: (\"terminallog\")", 325 | "[% Debug] received new command: (\"stop\", \"me\") and options: (\"terminallog\")", 326 | "[% Debug] stop requested with (\"stop\", \"me\") and options: (\"terminallog\")", 327 | "[% Debug] I am quitting!" 328 | ); 329 | 330 | QCoreApplication::processEvents(); 331 | p1->deleteLater(); 332 | p2->deleteLater(); 333 | p3->deleteLater(); 334 | p4->deleteLater(); 335 | } 336 | 337 | void MasterTest::detachingTest() 338 | { 339 | auto p1 = new ProcessHelper(this); 340 | p1->start({"start", "-f", "1", "--detached"}, true); 341 | p1->waitForFinished(); 342 | 343 | auto p2 = new ProcessHelper(this); 344 | p2->start({}); 345 | QEXPECT_FAIL("", "Undetached process does not finish", Continue); 346 | p2->waitForFinished(false); 347 | 348 | auto p3 = new ProcessHelper(this); 349 | p3->start({"--detached"}); 350 | p3->waitForFinished(); 351 | 352 | auto p4 = new ProcessHelper(this); 353 | p4->start({"stop"}); 354 | 355 | ProcessHelper::waitForFinished({p2, p4}); 356 | 357 | QVERIFYOUTLOG(p1); 358 | QVERIFYERRLOG(p1); 359 | QVERIFYOUTLOG(p2, 360 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")", 361 | "[% Debug] skipping starter args: (\"start\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")", 362 | "[% Debug] received new command: () and options: (\"terminallog\")", 363 | "[% Debug] received new command: () and options: (\"detached\", \"terminallog\")", 364 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")", 365 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")", 366 | "[% Debug] I am quitting!" 367 | ); 368 | QVERIFYERRLOG(p2); 369 | QVERIFYOUTLOG(p3); 370 | QVERIFYERRLOG(p3); 371 | QVERIFYOUTLOG(p4, "[% Debug] I am quitting!"); 372 | QVERIFYERRLOG(p4); 373 | 374 | QVERIFYMASTERLOG( 375 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")", 376 | "[% Debug] skipping starter args: (\"start\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")", 377 | "[% Debug] received new command: () and options: (\"terminallog\")", 378 | "[% Debug] received new command: () and options: (\"detached\", \"terminallog\")", 379 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")", 380 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")", 381 | "[% Debug] I am quitting!" 382 | ); 383 | 384 | QCoreApplication::processEvents(); 385 | p1->deleteLater(); 386 | p2->deleteLater(); 387 | p3->deleteLater(); 388 | p4->deleteLater(); 389 | } 390 | 391 | #ifdef Q_OS_UNIX 392 | void MasterTest::masterTermTest() 393 | { 394 | auto p1 = new ProcessHelper(this); 395 | p1->start({"start", "-m", "pid"}, true); 396 | p1->waitForFinished(); 397 | 398 | auto p2 = new ProcessHelper(this); 399 | p2->start({}); 400 | 401 | p1->termMaster(); 402 | p2->waitForFinished(); 403 | 404 | QVERIFYMASTERLOG( 405 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")", 406 | "[% Debug] Master started in pid mode!", 407 | "[% Debug] skipping starter args: (\"start\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")", 408 | "[% Debug] pid written", 409 | "[% Debug] received new command: () and options: (\"terminallog\")", 410 | "[% Debug] I am quitting!" 411 | ); 412 | 413 | QCoreApplication::processEvents(); 414 | p1->deleteLater(); 415 | p2->deleteLater(); 416 | } 417 | #endif 418 | 419 | QTEST_MAIN(MasterTest) 420 | 421 | #include "tst_master.moc" 422 | -------------------------------------------------------------------------------- /src/backgroundprocess/app_p.cpp: -------------------------------------------------------------------------------- 1 | #include "app_p.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #ifdef Q_OS_WIN 12 | #include 13 | #else 14 | #include 15 | #include 16 | #endif 17 | 18 | using namespace QtBackgroundProcess; 19 | 20 | //logging category 21 | Q_LOGGING_CATEGORY(QtBackgroundProcess::loggingCategory, "QtBackgroundProcess") 22 | 23 | bool AppPrivate::p_valid = false; 24 | 25 | const QString AppPrivate::masterArgument(QStringLiteral("__qbckgrndprcss$start#master~")); 26 | const QString AppPrivate::purgeArgument(QStringLiteral("purge_master")); 27 | const QString AppPrivate::startArgument(QStringLiteral("start")); 28 | const QString AppPrivate::restartArgument(QStringLiteral("restart")); 29 | #ifdef Q_OS_WIN 30 | const QString AppPrivate::terminalMessageFormat(QStringLiteral("%{if-debug}[Debug] %{endif}" 31 | "%{if-info}[Info] %{endif}" 32 | "%{if-warning}[Warning] %{endif}" 33 | "%{if-critical}[Critical] %{endif}" 34 | "%{if-fatal}[Fatal] %{endif}" 35 | "%{if-category}%{category}: %{endif}" 36 | "%{message}")); 37 | #else 38 | const QString AppPrivate::terminalMessageFormat(QStringLiteral("%{if-debug}[\033[32mDebug\033[0m] %{endif}" 39 | "%{if-info}[\033[36mInfo\033[0m] %{endif}" 40 | "%{if-warning}[\033[33mWarning\033[0m] %{endif}" 41 | "%{if-critical}[\033[31mCritical\033[0m] %{endif}" 42 | "%{if-fatal}[\033[35mFatal\033[0m] %{endif}" 43 | "%{if-category}%{category}: %{endif}" 44 | "%{message}")); 45 | #endif 46 | const QString AppPrivate::masterMessageFormat(QStringLiteral("[%{time} " 47 | "%{if-debug}Debug] %{endif}" 48 | "%{if-info}Info] %{endif}" 49 | "%{if-warning}Warning] %{endif}" 50 | "%{if-critical}Critical] %{endif}" 51 | "%{if-fatal}Fatal] %{endif}" 52 | "%{if-category}%{category}: %{endif}" 53 | "%{message}")); 54 | 55 | AppPrivate *AppPrivate::p_ptr() 56 | { 57 | return qApp->d; 58 | } 59 | 60 | void AppPrivate::qbackProcMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) 61 | { 62 | auto fMsg = qFormatLogMessage(type, context, msg); 63 | QByteArray message = fMsg.toUtf8() + "\n"; 64 | auto any = false; 65 | 66 | if(p_valid) { 67 | auto self = p_ptr(); 68 | 69 | if(self->debugTerm) { 70 | self->debugTerm->write(message); 71 | self->debugTerm->flush(); 72 | any = true; 73 | } 74 | 75 | if(self->logFile && self->logFile->isWritable()) { 76 | self->logFile->write(message); 77 | self->logFile->flush(); 78 | any = true; 79 | } 80 | } 81 | 82 | if(!any) 83 | std::cerr << fMsg.toStdString() << std::endl; 84 | 85 | if(type == QtMsgType::QtFatalMsg) 86 | std::abort(); 87 | } 88 | 89 | AppPrivate::AppPrivate(App *q_ptr) : 90 | QObject(q_ptr), 91 | running(false), 92 | masterLogging(false), 93 | autoStart(false), 94 | ignoreExtraStart(false), 95 | autoDelete(true), 96 | autoKill(false), 97 | instanceId(), 98 | globalInstance(false), 99 | masterLock(nullptr), 100 | masterServer(nullptr), 101 | parserFunc(), 102 | startupFunc(), 103 | shutdownFunc(), 104 | master(nullptr), 105 | debugTerm(nullptr), 106 | logFile(nullptr), 107 | q(q_ptr) 108 | {} 109 | 110 | QString AppPrivate::generateSingleId(const QString &seed) 111 | { 112 | auto fullId = QCoreApplication::applicationName().toLower(); 113 | fullId.remove(QRegularExpression(QStringLiteral("[^a-zA-Z0-9_]"))); 114 | fullId.truncate(8); 115 | fullId.prepend(QStringLiteral("qtbackproc-")); 116 | QByteArray hashBase = (QCoreApplication::organizationName() + 117 | QCoreApplication::organizationDomain() + 118 | seed).toUtf8(); 119 | fullId += QLatin1Char('-') + 120 | QString::number(qChecksum(hashBase.data(), hashBase.size()), 16); 121 | 122 | if(!globalInstance) { 123 | fullId += QLatin1Char('-'); 124 | #ifdef Q_OS_WIN 125 | DWORD sessID; 126 | if(::ProcessIdToSessionId(::GetCurrentProcessId(), &sessID)) 127 | fullId += QString::number(sessID, 16); 128 | #else 129 | fullId += QString::number(::getuid(), 16); 130 | #endif 131 | } 132 | 133 | return fullId; 134 | } 135 | 136 | void AppPrivate::setInstanceId(const QString &id) 137 | { 138 | if(running) 139 | throw NotAllowedInRunningStateException(); 140 | 141 | auto lockDir = QDir::temp(); 142 | if(!globalInstance) { 143 | auto dirName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); 144 | QDir dir(dirName); 145 | if(!dirName.isEmpty() && dir.exists()) 146 | lockDir = dir; 147 | } 148 | 149 | instanceId = id; 150 | auto lockPath = lockDir.absoluteFilePath(id + QStringLiteral(".lock")); 151 | masterLock.reset(new QLockFile(lockPath)); 152 | masterLock->setStaleLockTime(0); 153 | } 154 | 155 | QString AppPrivate::socketName() const 156 | { 157 | #ifdef Q_OS_UNIX 158 | QString socket = instanceId + QStringLiteral(".socket"); 159 | if(!globalInstance) { 160 | auto dirName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); 161 | QDir dir(dirName); 162 | if(!dirName.isEmpty() && dir.exists()) 163 | socket = dir.absoluteFilePath(socket); 164 | } 165 | return socket; 166 | #else 167 | return instanceId; 168 | #endif 169 | } 170 | 171 | void AppPrivate::setupDefaultParser(QCommandLineParser &parser, bool useShortOptions) 172 | { 173 | parser.addHelpOption(); 174 | if(!QCoreApplication::applicationVersion().isEmpty()) 175 | parser.addVersionOption(); 176 | 177 | QStringList DParams(QStringLiteral("detached")); 178 | QStringList lParams({QStringLiteral("log"), QStringLiteral("loglevel")}); 179 | QStringList LParams(QStringLiteral("logpath")); 180 | if(useShortOptions) { 181 | DParams.prepend(QStringLiteral("D")); 182 | lParams.prepend(QStringLiteral("l")); 183 | LParams.prepend(QStringLiteral("L")); 184 | } 185 | 186 | parser.addPositionalArgument(QStringLiteral(""), 187 | tr("A control command to control the background application. " 188 | "Possible options are:\n" 189 | " - start: starts the application\n" 190 | " - stop: stops the application\n" 191 | " - restart: stops the application and then starts it again with the given arguments\n" 192 | " - purge_master: purges local servers and lockfiles, in case the master process crashed. " 193 | "Pass \"--accept\" as second parameter, if you want to skip the prompt."), 194 | QStringLiteral("[start|stop|purge_master]")); 195 | 196 | parser.addOption({ 197 | DParams, 198 | tr("It set, the terminal will only pass it's arguments to the master, and automatically finish after.") 199 | }); 200 | parser.addOption({ 201 | lParams, 202 | tr("Set the desired log . Possible values are:\n" 203 | " - 0: log nothing\n" 204 | " - 1: critical errors only\n" 205 | " - 2: like 1 plus warnings\n") + 206 | #ifdef QT_NO_DEBUG 207 | tr(" - 3: like 2 plus information messages (default)\n" 208 | " - 4: verbose - log everything"), 209 | tr("level"), 210 | QStringLiteral("3") 211 | #else 212 | tr(" - 3: like 2 plus information messages\n" 213 | " - 4: verbose - log everything (default)"), 214 | tr("level"), 215 | QStringLiteral("4") 216 | #endif 217 | }); 218 | 219 | QString defaultPath; 220 | #ifdef Q_OS_UNIX 221 | if(QFileInfo(QStringLiteral("/var/log")).isWritable()) 222 | defaultPath = QStringLiteral("/var/log/%1.log").arg(QCoreApplication::applicationName()); 223 | else 224 | #endif 225 | { 226 | auto basePath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); 227 | QDir(basePath).mkpath(QStringLiteral(".")); 228 | defaultPath = QStringLiteral("%1/%2.log") 229 | .arg(basePath) 230 | .arg(QCoreApplication::applicationName()); 231 | } 232 | 233 | parser.addOption({ 234 | LParams, 235 | tr("Overwrites the default log . The default path is platform and application specific. " 236 | "For this instance, it defaults to \"%1\". NOTE: The application can override the value internally. " 237 | "Pass an empty string (--logpath \"\") to disable logging to a file.") 238 | .arg(defaultPath), 239 | tr("path"), 240 | defaultPath 241 | }); 242 | parser.addOption({ 243 | QStringLiteral("terminallog"), 244 | tr("Sets the log for terminal only messages. This does not include messages forwarded from the master. " 245 | "Log levels are the same as for the option."), 246 | tr("level"), 247 | #ifdef QT_NO_DEBUG 248 | QStringLiteral("3") 249 | #else 250 | QStringLiteral("4") 251 | #endif 252 | }); 253 | parser.addOption({ 254 | {QStringLiteral("no-daemon"), QStringLiteral("keep-console")}, 255 | tr("Will prevent the master process from \"closing\" the console and other stuff that is done to daemonize the process. " 256 | "Can be useful for debugging purpose.") 257 | }); 258 | 259 | parser.addOption({ 260 | QStringLiteral("accept"), 261 | tr("Skips the prompt and accepts where possible.") 262 | }); 263 | } 264 | 265 | void AppPrivate::updateLoggingMode(int level) 266 | { 267 | QString logStr; 268 | QT_WARNING_PUSH 269 | QT_WARNING_DISABLE_GCC("-Wimplicit-fallthrough=") 270 | switch (level) { 271 | case 0: 272 | logStr.prepend(QStringLiteral("\n*.critical=false")); 273 | case 1: 274 | logStr.prepend(QStringLiteral("\n*.warning=false")); 275 | case 2: 276 | logStr.prepend(QStringLiteral("\n*.info=false")); 277 | case 3: 278 | logStr.prepend(QStringLiteral("*.debug=false")); 279 | case 4: 280 | break; 281 | default: 282 | return; 283 | } 284 | QT_WARNING_POP 285 | QLoggingCategory::setFilterRules(logStr); 286 | } 287 | 288 | void AppPrivate::updateLoggingPath(const QString &path) 289 | { 290 | if(logFile) { 291 | logFile->close(); 292 | logFile->deleteLater(); 293 | logFile.clear(); 294 | } 295 | if(!path.isEmpty()) { 296 | logFile = new QFile(path, this); 297 | if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { 298 | auto error = logFile->errorString(); 299 | logFile->deleteLater(); 300 | logFile.clear(); 301 | qWarning() << "Failed to open logfile with error:" << error; 302 | } 303 | } 304 | } 305 | 306 | int AppPrivate::initControlFlow(const QCommandLineParser &parser) 307 | { 308 | //start/make master 309 | auto args = parser.positionalArguments(); 310 | if(args.size() > 0) { 311 | if(args[0] == masterArgument) 312 | return makeMaster(parser); 313 | else if(args[0] == purgeArgument) 314 | return purgeMaster(parser); 315 | else if(args[0] == startArgument) 316 | return startMaster(); 317 | else if(args[0] == restartArgument) 318 | return restartMaster(parser); 319 | } 320 | 321 | //neither start nor make master --> "normal" client or autostart 322 | if(autoStart) 323 | return startMaster(true); 324 | else 325 | return commandMaster(); 326 | } 327 | 328 | int AppPrivate::makeMaster(const QCommandLineParser &parser) 329 | { 330 | //create local server --> do first to make shure clients only see a "valid" master lock if the master started successfully 331 | masterServer = new QLocalServer(this); 332 | masterServer->setSocketOptions(globalInstance ? QLocalServer::WorldAccessOption : QLocalServer::UserAccessOption); 333 | connect(masterServer, &QLocalServer::newConnection, 334 | this, &AppPrivate::newTerminalConnected, 335 | Qt::QueuedConnection); 336 | if(!masterServer->listen(socketName())) { 337 | qCritical() << tr("Failed to create local server with error:") 338 | << masterServer->errorString(); 339 | return EXIT_FAILURE; 340 | } 341 | 342 | //get the lock 343 | if(!masterLock->tryLock(5000)) {//wait at most 5 sec 344 | masterServer->close(); 345 | qCritical() << tr("Unable to start master process. Failed with lock error:") 346 | << masterLock->error(); 347 | return EXIT_FAILURE; 348 | } else { 349 | //setup master logging stuff 350 | qSetMessagePattern(AppPrivate::masterMessageFormat); 351 | if(masterLogging) 352 | debugTerm = new GlobalTerminal(this, true); 353 | updateLoggingMode(parser.value(QStringLiteral("loglevel")).toInt()); 354 | updateLoggingPath(parser.value(QStringLiteral("logpath"))); 355 | 356 | //detache from any console, if wished 357 | if(!parser.isSet(QStringLiteral("no-daemon"))) { 358 | #ifdef Q_OS_WIN //detach the console window 359 | if(!FreeConsole()) { 360 | auto console = GetConsoleWindow(); 361 | if(console) 362 | ShowWindow(GetConsoleWindow(), SW_HIDE); 363 | } 364 | 365 | auto sigHandler = QCtrlSignalHandler::instance(); 366 | sigHandler->setAutoQuitActive(true); 367 | 368 | //set current directory 369 | QDir::setCurrent(QDir::rootPath()); 370 | #else 371 | daemon(false, false); 372 | 373 | auto sigHandler = QCtrlSignalHandler::instance(); 374 | sigHandler->setAutoQuitActive(true); 375 | sigHandler->registerForSignal(SIGINT); 376 | sigHandler->registerForSignal(SIGHUP); 377 | sigHandler->registerForSignal(SIGWINCH); 378 | #endif 379 | } else 380 | QDir::setCurrent(QDir::rootPath()); 381 | 382 | auto res = q->startupApp(parser); 383 | if(res != EXIT_SUCCESS) { 384 | //cleanup 385 | masterServer->close(); 386 | masterLock->unlock(); 387 | } 388 | 389 | return res; 390 | } 391 | } 392 | 393 | int AppPrivate::startMaster(bool isAutoStart, bool isRestart) 394 | { 395 | auto arguments = QCoreApplication::arguments(); 396 | arguments.removeFirst();//remove app name 397 | //check if master already lives 398 | if(masterLock->tryLock()) {//no master alive 399 | auto ok = false; 400 | 401 | auto args = arguments; 402 | if(isRestart) 403 | args.removeOne(restartArgument); 404 | else if(!isAutoStart) 405 | args.removeOne(startArgument); 406 | args.prepend(masterArgument); 407 | if(QProcess::startDetached(QCoreApplication::applicationFilePath(), args)) {//start MASTER with additional start params 408 | //wait for the master to start 409 | masterLock->unlock(); 410 | for(auto i = 0; i < 50; i++) {//wait at most 5 sec 411 | qint64 pid; 412 | QString hostname; 413 | QString appname; 414 | if(masterLock->getLockInfo(&pid, &hostname, &appname)) { 415 | ok = true; 416 | break; 417 | } else 418 | QThread::msleep(100); 419 | } 420 | } 421 | 422 | if(ok) {//master started --> start to connect (after safety delay) 423 | QThread::msleep(250); 424 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection, 425 | Q_ARG(QStringList, arguments),//send original arguments 426 | Q_ARG(bool, true)); 427 | return EXIT_SUCCESS; 428 | } else { 429 | qCritical() << tr("Failed to start master process! No master lock was detected."); 430 | return EXIT_FAILURE; 431 | } 432 | } else {//master is running --> ok 433 | if(!isAutoStart && ignoreExtraStart) {// ignore only on normal starts, not on auto start 434 | qWarning() << tr("Start commands ignored because master is already running! " 435 | "The terminal will connect with an empty argument list!"); 436 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection, 437 | Q_ARG(QStringList, QStringList()), 438 | Q_ARG(bool, false)); 439 | return EXIT_SUCCESS; 440 | } else { 441 | if(!isAutoStart) 442 | qWarning() << tr("Master is already running. Start arguments will be passed to it as is"); 443 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection, 444 | Q_ARG(QStringList, arguments),//send original arguments 445 | Q_ARG(bool, false)); 446 | return EXIT_SUCCESS; 447 | } 448 | } 449 | } 450 | 451 | int AppPrivate::restartMaster(const QCommandLineParser &parser) 452 | { 453 | //step 1 -> stop master, by running another terminal 454 | auto res = QProcess::execute(QCoreApplication::applicationFilePath(), {QStringLiteral("stop")}); 455 | if(res != EXIT_SUCCESS && !parser.isSet(QStringLiteral("accept"))) { 456 | std::cout << tr("\nFailed to stop the running master process.\n" 457 | "Do you want to restart it anyway? (y/N)").toStdString(); 458 | std::cout.flush(); 459 | char res = (char)std::cin.get(); 460 | if(res != tr("y") && res != tr("Y")) 461 | return EXIT_FAILURE; 462 | } else 463 | qDebug() << tr("Master process successfully stopped"); 464 | 465 | //step 2 -> start master 466 | return startMaster(false, true); 467 | } 468 | 469 | int AppPrivate::commandMaster() 470 | { 471 | auto arguments = QCoreApplication::arguments(); 472 | arguments.removeFirst();//remove app name 473 | if(masterLock->tryLock()) { 474 | masterLock->unlock(); 475 | qCritical() << tr("Master process is not running! Please launch it by using:") 476 | << QCoreApplication::applicationFilePath() + QStringLiteral(" start"); 477 | return EXIT_FAILURE; 478 | } else { 479 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection, 480 | Q_ARG(QStringList, arguments), 481 | Q_ARG(bool, false)); 482 | return EXIT_SUCCESS; 483 | } 484 | } 485 | 486 | int AppPrivate::purgeMaster(const QCommandLineParser &parser) 487 | { 488 | if(!parser.isSet(QStringLiteral("accept"))) { 489 | std::cout << tr("Are you shure you want to purge the master lock and server?\n" 490 | "Only do this if the master process is not running anymore, but the lock/server " 491 | "are not available (for example after a crash)\n" 492 | "Purging while the master process is still running will crash it.\n" 493 | "Press (Y) to purge, or (n) to cancel:").toStdString(); 494 | std::cout.flush(); 495 | char res = (char)std::cin.get(); 496 | if(res == tr("n") || res == tr("N")) 497 | return EXIT_FAILURE; 498 | } 499 | 500 | auto res = 0; 501 | 502 | qint64 pid; 503 | QString hostname; 504 | QString appname; 505 | if(masterLock->getLockInfo(&pid, &hostname, &appname)) { 506 | if(masterLock->removeStaleLockFile()) 507 | std::cout << tr("Master lockfile successfully removed. It was locked by:").toStdString(); 508 | else { 509 | std::cout << tr("Failed to remove master lockfile. Lock data is:").toStdString(); 510 | res |= 0x02; 511 | } 512 | std::cout << tr("\n - PID: ").toStdString() 513 | << pid 514 | << tr("\n - Hostname: ").toStdString() 515 | << hostname.toStdString() 516 | << tr("\n - Appname: ").toStdString() 517 | << appname.toStdString() 518 | << std::endl; 519 | } else 520 | std::cout << tr("No lock file detected").toStdString() << std::endl; 521 | 522 | if(QLocalServer::removeServer(socketName())) 523 | std::cout << tr("Master server successfully removed").toStdString() << std::endl; 524 | else { 525 | std::cout << tr("Failed to remove master server").toStdString() << std::endl; 526 | res |= 0x04; 527 | } 528 | 529 | return res == 0 ? -1 : res; 530 | } 531 | 532 | void AppPrivate::newTerminalConnected() 533 | { 534 | while(masterServer->hasPendingConnections()) { 535 | auto termp = new TerminalPrivate(masterServer->nextPendingConnection(), this); 536 | connect(termp, &TerminalPrivate::statusLoadComplete, 537 | this, &AppPrivate::terminalLoaded); 538 | } 539 | } 540 | 541 | void AppPrivate::terminalLoaded(TerminalPrivate *terminal, bool success) 542 | { 543 | if(success) { 544 | //create terminal parser and validate it 545 | terminal->parser.reset(new QCommandLineParser()); 546 | qApp->setupParser(*terminal->parser.data()); 547 | if(!terminal->loadParser()) { 548 | qWarning() << tr("Terminal with invalid commands discarded. Error:") 549 | << terminal->parser->errorText(); 550 | terminal->deleteLater(); 551 | return; 552 | } 553 | 554 | //handle own arguments (logging) 555 | if(terminal->parser->isSet(QStringLiteral("loglevel"))) 556 | updateLoggingMode(terminal->parser->value(QStringLiteral("loglevel")).toInt()); 557 | if(terminal->parser->isSet(QStringLiteral("logpath"))) 558 | updateLoggingPath(terminal->parser->value(QStringLiteral("logpath"))); 559 | 560 | //create real terminal 561 | auto rTerm = new Terminal(terminal, this); 562 | rTerm->setAutoDelete(autoDelete); 563 | //emit the command 564 | emit q->commandReceived(rTerm->parser(), rTerm->isStarter(), App::QPrivateSignal()); 565 | 566 | //test if stop command 567 | auto args = rTerm->parser()->positionalArguments(); 568 | if(!args.isEmpty() && args.first() == QStringLiteral("stop")) 569 | stopMaster(rTerm); 570 | 571 | if(autoKill || rTerm->parser()->isSet(QStringLiteral("detached"))) { 572 | rTerm->setAutoDelete(true); 573 | rTerm->disconnectTerminal(); 574 | } else { 575 | //add terminal to terminal list 576 | connect(rTerm, &Terminal::destroyed, this, [=](){ 577 | activeTerminals.removeOne(rTerm); 578 | emit q->connectedTerminalsChanged(activeTerminals, App::QPrivateSignal()); 579 | }); 580 | activeTerminals.append(rTerm); 581 | emit q->connectedTerminalsChanged(activeTerminals, App::QPrivateSignal()); 582 | //new terminal signal 583 | emit q->newTerminalConnected(rTerm, App::QPrivateSignal()); 584 | } 585 | } else 586 | terminal->deleteLater(); 587 | } 588 | 589 | void AppPrivate::stopMaster(Terminal *term) 590 | { 591 | int eCode = EXIT_SUCCESS; 592 | if(q->requestAppShutdown(term, eCode)) { 593 | foreach(auto termin, activeTerminals) 594 | termin->flush(); 595 | QMetaObject::invokeMethod(this, "doExit", Qt::QueuedConnection, 596 | Q_ARG(int, eCode)); 597 | } 598 | } 599 | 600 | void AppPrivate::doExit(int code) 601 | { 602 | QCoreApplication::exit(code); 603 | } 604 | 605 | void AppPrivate::beginMasterConnect(const QStringList &arguments, bool isStarter) 606 | { 607 | master = new MasterConnecter(socketName(), arguments, isStarter, this); 608 | } 609 | --------------------------------------------------------------------------------