Add Single Application.

Add command parser and output modes (--verbose --qt-logs-only).
Move static Logger to core as it is used from Qt and dispatch logs to the SDK thread.
Update SDK to allow builds from MSVC 2022.
This commit is contained in:
Julien Wadel 2023-10-09 17:17:34 +02:00
parent 21f002105f
commit b6ff625370
20 changed files with 1400 additions and 54 deletions

View file

@ -34,10 +34,12 @@ set(APP_TARGETS ${LinphoneCxx_TARGET})
set(QT_DEFAULT_MAJOR_VERSION 6)
set(QT_PACKAGES Core Quick Qml Widgets)# Search Core at first for initialize Qt scripts for next find_packages.
if (UNIX AND NOT APPLE)
list(APPEND QT_PACKAGES DBus)
endif()
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS ${QT_PACKAGES})
if(${Qt6_VERSION} VERSION_LESS "6.3.0")
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

View file

@ -22,13 +22,16 @@
#include <QCoreApplication>
#include "core/logger/QtLogger.hpp"
#include "core/singleapplication/singleapplication.h"
#include "tool/Constants.hpp"
#include "view/Page/LoginPage.hpp"
App::App(QObject *parent) : QObject(parent) {
App::App(int &argc, char *argv[])
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
mLinphoneThread = new Thread(this);
init();
qDebug() << "Starting Thread";
qDebug() << "[App] Starting Thread";
mLinphoneThread->start();
}
@ -37,9 +40,20 @@ App::App(QObject *parent) : QObject(parent) {
//-----------------------------------------------------------
void App::init() {
// Core
// Core. Manage the logger so it must be instantiate at first.
mCoreModel = QSharedPointer<CoreModel>::create("", mLinphoneThread);
// Console Commands
createCommandParser();
mParser->parse(this->arguments());
// TODO : Update languages for command translations.
createCommandParser(); // Recreate parser in order to use translations from config.
mParser->process(*this);
if (mParser->isSet("verbose")) QtLogger::enableVerbose(true);
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
connect(mLinphoneThread, &QThread::started, mCoreModel.get(), &CoreModel::start);
// QML
mEngine = new QQmlApplicationEngine(this);
@ -62,3 +76,27 @@ void App::initCppInterfaces() {
Constants::MainQmlUri, 1, 0, "LoginPageCpp",
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new LoginPage(engine); });
}
//------------------------------------------------------------
void App::createCommandParser() {
if (!mParser) delete mParser;
mParser = new QCommandLineParser();
mParser->setApplicationDescription(tr("applicationDescription"));
mParser->addPositionalArgument("command", tr("commandLineDescription").replace("%1", APPLICATION_NAME),
"[command]");
mParser->addOptions({
{{"h", "help"}, tr("commandLineOptionHelp")},
{"cli-help", tr("commandLineOptionCliHelp").replace("%1", APPLICATION_NAME)},
{{"v", "version"}, tr("commandLineOptionVersion")},
{"config", tr("commandLineOptionConfig").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionConfigArg")},
{"fetch-config", tr("commandLineOptionFetchConfig").replace("%1", EXECUTABLE_NAME),
tr("commandLineOptionFetchConfigArg")},
{{"c", "call"}, tr("commandLineOptionCall").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionCallArg")},
#ifndef Q_OS_MACOS
{"iconified", tr("commandLineOptionIconified")},
#endif // ifndef Q_OS_MACOS
{{"V", "verbose"}, tr("commandLineOptionVerbose")},
{"qt-logs-only", tr("commandLineOptionQtLogsOnly")},
});
}

View file

@ -19,14 +19,16 @@
*/
#include <QQmlApplicationEngine>
#include <QCommandLineParser>
#include <QSharedPointer>
#include "core/thread/Thread.hpp"
#include "core/singleapplication/singleapplication.h"
#include "model/core/CoreModel.hpp"
class App : public QObject {
class App : public SingleApplication {
public:
App(QObject *parent = nullptr);
App(int &argc, char *argv[]);
void init();
void initCppInterfaces();
@ -36,4 +38,9 @@ public:
QQmlApplicationEngine *mEngine = nullptr;
Thread *mLinphoneThread = nullptr;
QSharedPointer<CoreModel> mCoreModel;
};
private:
void createCommandParser();
QCommandLineParser *mParser = nullptr;
};

View file

@ -1,9 +1,20 @@
list(APPEND _LINPHONEAPP_SOURCES
core/App.cpp
core/logger/QtLogger.cpp
core/path/Paths.cpp
core/setting/Settings.cpp
core/thread/Thread.cpp
)
## Single Application
if(APPLE OR WIN32)
list(APPEND _LINPHONEAPP_SOURCES core/singleapplication/singleapplication.cpp
core/singleapplication/singleapplication_p.cpp)
else() # Use QDBus for Linux
list(APPEND _LINPHONEAPP_SOURCES
core/singleapplication/singleapplication.h #Added for Moc
core/singleapplication/SingleApplicationDBusPrivate.hpp
core/singleapplication/SingleApplicationDBus.cpp)
endif()
set(_LINPHONEAPP_SOURCES ${_LINPHONEAPP_SOURCES} PARENT_SCOPE)

View file

@ -18,20 +18,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LoggerStaticModel.hpp"
#include "QtLogger.hpp"
#include <QMetaMethod>
// -----------------------------------------------------------------------------
static LoggerStaticModel gLogger;
static QtLogger gLogger;
LoggerStaticModel::LoggerStaticModel(QObject *parent) : QObject(parent) {
qInstallMessageHandler(LoggerStaticModel::onQtLog);
QtLogger::QtLogger(QObject *parent) : QObject(parent) {
qInstallMessageHandler(QtLogger::onQtLog);
}
LoggerStaticModel *LoggerStaticModel::getInstance() {
QtLogger *QtLogger::getInstance() {
return &gLogger;
}
void LoggerStaticModel::onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
void QtLogger::onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
emit gLogger.logReceived(type, context.file, context.line, msg);
}
void QtLogger::enableVerbose(bool verbose) {
emit gLogger.requestVerboseEnabled(verbose);
}
void QtLogger::enableQtOnly(bool qtOnly) {
emit gLogger.requestQtOnlyEnabled(qtOnly);
}

View file

@ -27,16 +27,21 @@
// =============================================================================
// Only one instance. Use getInstance() and logReceived() to bind logs coming from Qt.
class LoggerStaticModel : public QObject {
class QtLogger : public QObject {
Q_OBJECT
public:
LoggerStaticModel(QObject *parent = nullptr);
QtLogger(QObject *parent = nullptr);
static LoggerStaticModel *getInstance();
static QtLogger *getInstance();
static void onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
static void enableVerbose(bool verbose);
static void enableQtOnly(bool qtOnly);
signals:
void logReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
void requestVerboseEnabled(bool verbose);
void requestQtOnlyEnabled(bool qtOnly);
};
#endif

View file

@ -0,0 +1,10 @@
Coming from https://github.com/itay-grudev/SingleApplication
File format are kept from initial repository :
- singleapplication.cpp
- singleapplication.h
- singleapplication_p.cpp
- singleapplication_p.h
Support for Linux has been added with DBus.

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <QDBusInterface>
#include "config.h"
#include "SingleApplicationDBusPrivate.hpp"
#include "singleapplication.h"
// =============================================================================
namespace {
constexpr char ServiceName[] = "org." EXECUTABLE_NAME ".SingleApplication";
}
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *q_ptr)
: QDBusAbstractAdaptor(q_ptr), q_ptr(q_ptr) {
}
QDBusConnection SingleApplicationPrivate::getBus() const {
if (options & SingleApplication::Mode::User) return QDBusConnection::sessionBus();
return QDBusConnection::systemBus();
}
void SingleApplicationPrivate::startPrimary() {
signal(SIGINT, SingleApplicationPrivate::terminate);
if (!getBus().registerObject("/", this, QDBusConnection::ExportAllSlots))
qWarning() << QStringLiteral("Failed to register single application object on DBus.");
instanceNumber = 0;
}
void SingleApplicationPrivate::startSecondary() {
signal(SIGINT, SingleApplicationPrivate::terminate);
instanceNumber = 1;
}
void SingleApplicationPrivate::terminate(int signum) {
SingleApplication::instance()->exit(signum);
}
SingleApplication::SingleApplication(
int &argc, char *argv[], bool allowSecondary, Options options, int, const QString &userData)
: QApplication(argc, argv), d_ptr(new SingleApplicationPrivate(this)) {
Q_D(SingleApplication);
// Store the current mode of the program.
d->options = options;
if (!d->getBus().isConnected()) {
qWarning() << QStringLiteral("Cannot connect to the D-Bus session bus.");
delete d;
::exit(EXIT_FAILURE);
}
if (d->getBus().registerService(ServiceName)) {
d->startPrimary();
return;
}
if (allowSecondary) {
d->startSecondary();
return;
}
delete d;
::exit(EXIT_SUCCESS);
}
SingleApplication::~SingleApplication() {
Q_D(SingleApplication);
delete d;
}
bool SingleApplication::isPrimary() const {
auto d = d_func();
return d->instanceNumber == 0;
}
bool SingleApplication::isSecondary() const {
auto d = d_func();
return d->instanceNumber != 0;
}
quint32 SingleApplication::instanceId() const {
auto d = d_func();
return d->instanceNumber;
}
bool SingleApplication::sendMessage(const QByteArray &message, int timeout, SendMode sendMode) {
auto d = d_func();
if (isPrimary()) return false;
QDBusInterface iface(ServiceName, "/", "", d->getBus());
if (iface.isValid()) {
iface.setTimeout(timeout);
iface.call(QDBus::Block, "handleMessageReceived", instanceId(), message);
return true;
}
return false;
}
void SingleApplicationPrivate::handleMessageReceived(quint32 instanceId, QByteArray message) {
Q_Q(SingleApplication);
emit q->receivedMessage(instanceId, message);
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SINGLE_APPLICATION_DBUS_PRIVATE_H_
#define SINGLE_APPLICATION_DBUS_PRIVATE_H_
#include <QDBusAbstractAdaptor>
#include <QDBusConnection>
#include "singleapplication.h"
// =============================================================================
struct InstancesInfo {
bool primary;
quint32 secondary;
};
class SingleApplicationPrivate : public QDBusAbstractAdaptor {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.linphone.DBus.SingleApplication")
public:
SingleApplicationPrivate(SingleApplication *q_ptr);
QDBusConnection getBus() const;
void startPrimary();
void startSecondary();
static void terminate(int signum);
SingleApplication *q_ptr;
SingleApplication::Options options;
quint32 instanceNumber;
// Explicit public slot. Cannot be private, must be exported as a method via D-Bus.
public slots:
void handleMessageReceived(quint32 instanceId, QByteArray message);
private:
Q_DECLARE_PUBLIC(SingleApplication)
};
#endif // SINGLE_APPLICATION_DBUS_PRIVATE_H_

View file

@ -0,0 +1,262 @@
// Copyright (c) Itay Grudev 2015 - 2023
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// Permission is not granted to use this software or any of the associated files
// as sample data for the purposes of building machine learning models.
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <QtCore/QByteArray>
#include <QtCore/QElapsedTimer>
#include <QtCore/QSharedMemory>
#include "singleapplication.h"
#include "singleapplication_p.h"
/**
* @brief Constructor. Checks and fires up LocalServer or closes the program
* if another instance already exists
* @param argc
* @param argv
* @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleApplication::SingleApplication(
int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData)
: app_t(argc, argv), d_ptr(new SingleApplicationPrivate(this)) {
Q_D(SingleApplication);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
// On Android and iOS since the library is not supported fallback to
// standard QApplication behaviour by simply returning at this point.
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
return;
#endif
// Store the current mode of the program
d->options = options;
// Add any unique user data
if (!userData.isEmpty()) d->addAppData(userData);
// Generating an application ID used for identifying the shared memory
// block and QLocalServer
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes
// attempting to attach at the same time
SingleApplicationPrivate::randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix.
d->memory = new QSharedMemory(d->blockServerName);
d->memory->attach();
delete d->memory;
#endif
// Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory(d->blockServerName);
// Create a shared memory block
if (d->memory->create(sizeof(InstancesInfo))) {
// Initialize the shared memory block
if (!d->memory->lock()) {
qCritical() << "SingleApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock();
} else {
if (d->memory->error() == QSharedMemory::AlreadyExists) {
// Attempt to attach to the memory segment
if (!d->memory->attach()) {
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
abortSafely();
}
if (!d->memory->lock()) {
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
} else {
qCritical() << "SingleApplication: Unable to create block.";
abortSafely();
}
}
auto *inst = static_cast<InstancesInfo *>(d->memory->data());
QElapsedTimer time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
while (true) {
// If the shared memory block's checksum is valid continue
if (d->blockChecksum() == inst->checksum) break;
// If more than 5s have elapsed, assume the primary instance crashed and
// assume it's position
if (time.elapsed() > 5000) {
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. "
"Assuming primary instance failure.";
d->initializeMemoryBlock();
}
// Otherwise wait for a random period and try again. The random sleep here
// limits the probability of a collision between two racing apps and
// allows the app to initialise faster
if (!d->memory->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory->errorString();
}
SingleApplicationPrivate::randomSleep();
if (!d->memory->lock()) {
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
if (inst->primary == false) {
d->startPrimary();
if (!d->memory->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory->errorString();
}
return;
}
// Check if another instance can be started
if (allowSecondary) {
d->startSecondary();
if (d->options & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
}
if (!d->memory->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory->errorString();
}
return;
}
if (!d->memory->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory->errorString();
}
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
delete d;
::exit(EXIT_SUCCESS);
}
SingleApplication::~SingleApplication() {
Q_D(SingleApplication);
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleApplication::isPrimary() const {
Q_D(const SingleApplication);
return d->server != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() const {
Q_D(const SingleApplication);
return d->server == nullptr;
}
/**
* Allows you to identify an instance by returning unique consecutive instance
* ids. It is reset when the first (primary) instance of your app starts and
* only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() const {
Q_D(const SingleApplication);
return d->instanceNumber;
}
/**
* Returns the OS PID (Process Identifier) of the process running the primary
* instance. Especially useful when SingleApplication is coupled with OS.
* specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() const {
Q_D(const SingleApplication);
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() const {
Q_D(const SingleApplication);
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() const {
return SingleApplicationPrivate::getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @param sendMode mode of operation
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleApplication::sendMessage(const QByteArray &message, int timeout, SendMode sendMode) {
Q_D(SingleApplication);
// Nobody to connect to
if (isPrimary()) return false;
// Make sure the socket is connected
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) return false;
return d->writeConfirmedMessage(timeout, message, sendMode);
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely() {
Q_D(SingleApplication);
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
delete d;
::exit(EXIT_FAILURE);
}
QStringList SingleApplication::userData() const {
Q_D(const SingleApplication);
return d->appData();
}

View file

@ -0,0 +1,192 @@
// Copyright (c) Itay Grudev 2015 - 2023
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// Permission is not granted to use this software or any of the associated files
// as sample data for the purposes of building machine learning models.
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// V3.4.0
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H
#include <QObject>
#include <QtCore/QtGlobal>
#include <QtNetwork/QLocalSocket>
#ifndef QAPPLICATION_CLASS
#define QAPPLICATION_CLASS QApplication
#endif
#include QT_STRINGIFY(QAPPLICATION_CLASS)
class SingleApplicationPrivate;
/**
* @brief Handles multiple instances of the same
* Application
* @see QCoreApplication
*/
class SingleApplication : public QAPPLICATION_CLASS {
Q_OBJECT
using app_t = QAPPLICATION_CLASS;
public:
/**
* @brief Mode of operation of `SingleApplication`.
* Whether the block should be user-wide or system-wide and whether the
* primary instance should be notified when a secondary instance had been
* started.
* @note Operating system can restrict the shared memory blocks to the same
* user, in which case the User/System modes will have no effect and the
* block will be user wide.
*/
enum Mode {
/** The `SingleApplication` block should apply user wide
* (this adds user specific data to the key used for the shared memory and server name)
* */
User = 1 << 0,
/**
* The `SingleApplication` block applies system-wide.
*/
System = 1 << 1,
/**
* Whether to trigger `instanceStarted()` even whenever secondary instances are started
*/
SecondaryNotification = 1 << 2,
/**
* Excludes the application version from the server name (and memory block) hash
*/
ExcludeAppVersion = 1 << 3,
/**
* Excludes the application path from the server name (and memory block) hash
*/
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
/**
* @brief Intitializes a `SingleApplication` instance with argc command line
* arguments in argv
* @arg argc - Number of arguments in argv
* @arg argv - Supplied command line arguments
* @arg allowSecondary - Whether to start the instance as secondary
* if there is already a primary instance.
* @arg mode - Whether for the `SingleApplication` block to be applied
* User wide or System wide.
* @arg timeout - Timeout to wait in milliseconds.
* @note argc and argv may be changed as Qt removes arguments that it
* recognizes
* @note `Mode::SecondaryNotification` only works if set on both the primary
* instance and the secondary instance.
* @note The timeout is just a hint for the maximum time of blocking
* operations. It does not guarantee that the `SingleApplication`
* initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding `QAPPLICATION_CLASS` constructor for reference
*/
explicit SingleApplication(int &argc,
char *argv[],
bool allowSecondary = false,
Options options = Mode::User,
int timeout = 1000,
const QString &userData = {});
~SingleApplication() override;
/**
* @brief Checks if the instance is primary instance
* @returns `true` if the instance is primary
*/
bool isPrimary() const;
/**
* @brief Checks if the instance is a secondary instance
* @returns `true` if the instance is secondary
*/
bool isSecondary() const;
/**
* @brief Returns a unique identifier for the current instance
* @returns instance id
*/
quint32 instanceId() const;
/**
* @brief Returns the process ID (PID) of the primary instance
* @returns pid
*/
qint64 primaryPid() const;
/**
* @brief Returns the username of the user running the primary instance
* @returns user name
*/
QString primaryUser() const;
/**
* @brief Returns the username of the current user
* @returns user name
*/
QString currentUser() const;
/**
* @brief Mode of operation of sendMessage.
*/
enum SendMode {
NonBlocking, /** Do not wait for the primary instance termination and return immediately */
BlockUntilPrimaryExit, /** Wait until the primary instance is terminated */
};
/**
* @brief Sends a message to the primary instance
* @param message data to send
* @param timeout timeout for connecting
* @param sendMode - Mode of operation
* @returns `true` on success
* @note sendMessage() will return false if invoked from the primary instance
*/
bool sendMessage(const QByteArray &message, int timeout = 100, SendMode sendMode = NonBlocking);
/**
* @brief Get the set user data.
* @returns user data
*/
QStringList userData() const;
signals:
/**
* @brief Triggered whenever a new instance had been started,
* except for secondary instances if the `Mode::SecondaryNotification` flag is not specified
*/
void instanceStarted();
/**
* @brief Triggered whenever there is a message received from a secondary instance
*/
void receivedMessage(quint32 instanceId, QByteArray message);
private:
SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
#endif // SINGLE_APPLICATION_H

View file

@ -0,0 +1,510 @@
// Copyright (c) Itay Grudev 2015 - 2023
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// Permission is not granted to use this software or any of the associated files
// as sample data for the purposes of building machine learning models.
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <cstddef>
#include <cstdlib>
#include <QtCore/QByteArray>
#include <QtCore/QCryptographicHash>
#include <QtCore/QDataStream>
#include <QtCore/QDir>
#include <QtCore/QElapsedTimer>
#include <QtCore/QThread>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#include <QtCore/QRandomGenerator>
#else
#include <QtCore/QDateTime>
#endif
#include "singleapplication.h"
#include "singleapplication_p.h"
#ifdef Q_OS_UNIX
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef Q_OS_WIN
#ifndef NOMINMAX
#define NOMINMAX 1
#endif
#include <lmcons.h>
#include <windows.h>
#endif
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *q_ptr) : q_ptr(q_ptr) {
server = nullptr;
socket = nullptr;
memory = nullptr;
instanceNumber = 0;
}
SingleApplicationPrivate::~SingleApplicationPrivate() {
if (socket != nullptr) {
socket->close();
socket->deleteLater();
}
if (memory != nullptr) {
memory->lock();
auto *inst = static_cast<InstancesInfo *>(memory->data());
if (server != nullptr) {
server->close();
server->deleteLater();
inst->primary = false;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
memory->unlock();
memory->deleteLater();
}
}
QString SingleApplicationPrivate::getUsername() {
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) return QString::fromWCharArray(username);
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return QString::fromLocal8Bit(qgetenv("USERNAME"));
#else
return qEnvironmentVariable("USERNAME");
#endif
#endif
#ifdef Q_OS_UNIX
QString username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if (pw) username = QString::fromLocal8Bit(pw->pw_name);
if (username.isEmpty()) {
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit(qgetenv("USER"));
#else
username = qEnvironmentVariable("USER");
#endif
}
return username;
#endif
}
void SingleApplicationPrivate::genBlockServerName() {
QCryptographicHash appData(QCryptographicHash::Sha256);
#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
appData.addData("SingleApplication", 17);
#else
appData.addData(QByteArrayView{"SingleApplication"});
#endif
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
if (!appDataList.isEmpty()) appData.addData(appDataList.join(QString()).toUtf8());
if (!(options & SingleApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
}
if (!(options & SingleApplication::Mode::ExcludeAppPath)) {
#if defined(Q_OS_WIN)
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
#elif defined(Q_OS_LINUX)
// If the application is running as an AppImage then the APPIMAGE env var should be used
// instead of applicationPath() as each instance is launched with its own executable path
const QByteArray appImagePath = qgetenv("APPIMAGE");
if (appImagePath.isEmpty()) { // Not running as AppImage: use path to executable file
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
} else { // Running as AppImage: Use absolute path to AppImage file
appData.addData(appImagePath);
};
#else
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
#endif
}
// User level block requires a user specific data in the hash
if (options & SingleApplication::Mode::User) {
appData.addData(getUsername().toUtf8());
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
// server naming requirements.
blockServerName = QString::fromUtf8(appData.result().toBase64().replace("/", "_"));
}
void SingleApplicationPrivate::initializeMemoryBlock() const {
auto *inst = static_cast<InstancesInfo *>(memory->data());
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
void SingleApplicationPrivate::startPrimary() {
// Reset the number of connections
auto *inst = static_cast<InstancesInfo *>(memory->data());
inst->primary = true;
inst->primaryPid = QCoreApplication::applicationPid();
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
inst->checksum = blockChecksum();
instanceNumber = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName);
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions
if (options & SingleApplication::Mode::User) {
server->setSocketOptions(QLocalServer::UserAccessOption);
} else {
server->setSocketOptions(QLocalServer::WorldAccessOption);
}
server->listen(blockServerName);
QObject::connect(server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
}
void SingleApplicationPrivate::startSecondary() {
auto *inst = static_cast<InstancesInfo *>(memory->data());
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber = inst->secondary;
}
bool SingleApplicationPrivate::connectToPrimary(int msecs, ConnectionType connectionType) {
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already
// connected.
if (socket == nullptr) {
socket = new QLocalSocket();
}
if (socket->state() == QLocalSocket::ConnectedState) return true;
if (socket->state() != QLocalSocket::ConnectedState) {
while (true) {
randomSleep();
if (socket->state() != QLocalSocket::ConnectingState) socket->connectToServer(blockServerName);
if (socket->state() == QLocalSocket::ConnectingState) {
socket->waitForConnected(static_cast<int>(msecs - time.elapsed()));
}
// If connected break out of the loop
if (socket->state() == QLocalSocket::ConnectedState) break;
// If elapsed time since start is longer than the method timeout return
if (time.elapsed() >= msecs) return false;
}
}
// Initialisation message according to the SingleApplication protocol
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
writeStream.setVersion(QDataStream::Qt_5_6);
#endif
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(initMsg.constData(), static_cast<quint32>(initMsg.length())));
#else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif
writeStream << checksum;
return writeConfirmedMessage(static_cast<int>(msecs - time.elapsed()), initMsg);
}
void SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
sock->putChar('\n');
}
bool SingleApplicationPrivate::writeConfirmedMessage(int msecs,
const QByteArray &msg,
SingleApplication::SendMode sendMode) {
QElapsedTimer time;
time.start();
// Frame 1: The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion(QDataStream::Qt_5_6);
#endif
headerStream << static_cast<quint64>(msg.length());
if (!writeConfirmedFrame(static_cast<int>(msecs - time.elapsed()), header)) return false;
// Frame 2: The message
const bool result = writeConfirmedFrame(static_cast<int>(msecs - time.elapsed()), msg);
// Block if needed
if (socket && sendMode == SingleApplication::BlockUntilPrimaryExit) socket->waitForDisconnected(-1);
return result;
}
bool SingleApplicationPrivate::writeConfirmedFrame(int msecs, const QByteArray &msg) {
socket->write(msg);
socket->flush();
bool result = socket->waitForReadyRead(msecs); // await ack byte
if (result) {
socket->read(1);
return true;
}
return false;
}
quint16 SingleApplicationPrivate::blockChecksum() const {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum =
qChecksum(QByteArray(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum)));
#else
quint16 checksum = qChecksum(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum));
#endif
return checksum;
}
qint64 SingleApplicationPrivate::primaryPid() const {
qint64 pid;
memory->lock();
auto *inst = static_cast<InstancesInfo *>(memory->data());
pid = inst->primaryPid;
memory->unlock();
return pid;
}
QString SingleApplicationPrivate::primaryUser() const {
QByteArray username;
memory->lock();
auto *inst = static_cast<InstancesInfo *>(memory->data());
username = inst->primaryUser;
memory->unlock();
return QString::fromUtf8(username);
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleApplicationPrivate::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
});
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this,
[nextConnSocket, this]() { connectionMap.remove(nextConnSocket); });
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
switch (info.stage) {
case StageInitHeader:
readMessageHeader(nextConnSocket, StageInitBody);
break;
case StageInitBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnectedHeader:
readMessageHeader(nextConnSocket, StageConnectedBody);
break;
case StageConnectedBody:
this->slotDataAvailable(nextConnSocket, info.instanceId);
break;
default:
break;
};
});
}
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock,
SingleApplicationPrivate::ConnectionStage nextStage) {
if (!connectionMap.contains(sock)) {
return;
}
if (sock->bytesAvailable() < (qint64)sizeof(quint64)) {
return;
}
QDataStream headerStream(sock);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion(QDataStream::Qt_5_6);
#endif
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock];
info.stage = nextStage;
info.msgLen = msgLen;
writeAck(sock);
}
bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
if (!connectionMap.contains(sock)) {
return false;
}
ConnectionInfo &info = connectionMap[sock];
if (sock->bytesAvailable() < (qint64)info.msgLen) {
return false;
}
return true;
}
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleApplication);
if (!isFrameComplete(sock)) return;
// Read the message body
QByteArray msgBytes = sock->readAll();
QDataStream readStream(msgBytes);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
readStream.setVersion(QDataStream::Qt_5_6);
#endif
// server name
QByteArray latin1Name;
readStream >> latin1Name;
// connection type
ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
connectionType = static_cast<ConnectionType>(connTypeVal);
// instance id
quint32 instanceId = 0;
readStream >> instanceId;
// checksum
quint16 msgChecksum = 0;
readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum =
qChecksum(QByteArray(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else
const quint16 actualChecksum =
qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName &&
msgChecksum == actualChecksum;
if (!isValid) {
sock->close();
return;
}
ConnectionInfo &info = connectionMap[sock];
info.instanceId = instanceId;
info.stage = StageConnectedHeader;
if (connectionType == NewInstance ||
(connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification)) {
Q_EMIT q->instanceStarted();
}
writeAck(sock);
}
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, quint32 instanceId) {
Q_Q(SingleApplication);
if (!isFrameComplete(dataSocket)) return;
const QByteArray message = dataSocket->readAll();
writeAck(dataSocket);
ConnectionInfo &info = connectionMap[dataSocket];
info.stage = StageConnectedHeader;
Q_EMIT q->receivedMessage(instanceId, message);
}
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0) slotDataAvailable(closedSocket, instanceId);
}
void SingleApplicationPrivate::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::msleep(qrand() % 11 + 8);
#endif
}
void SingleApplicationPrivate::addAppData(const QString &data) {
appDataList.push_back(data);
}
QStringList SingleApplicationPrivate::appData() const {
return appDataList;
}

View file

@ -0,0 +1,107 @@
// Copyright (c) Itay Grudev 2015 - 2023
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// Permission is not granted to use this software or any of the associated files
// as sample data for the purposes of building machine learning models.
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#ifndef SINGLEAPPLICATION_P_H
#define SINGLEAPPLICATION_P_H
#include "singleapplication.h"
#include <QtCore/QSharedMemory>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
char primaryUser[128];
quint16 checksum; // Must be the last field
};
struct ConnectionInfo {
qint64 msgLen = 0;
quint32 instanceId = 0;
quint8 stage = 0;
};
class SingleApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 { InvalidConnection = 0, NewInstance = 1, SecondaryInstance = 2, Reconnect = 3 };
enum ConnectionStage : quint8 {
StageInitHeader = 0,
StageInitBody = 1,
StageConnectedHeader = 2,
StageConnectedBody = 3,
};
Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate(SingleApplication *q_ptr);
~SingleApplicationPrivate() override;
static QString getUsername();
void genBlockServerName();
void initializeMemoryBlock() const;
void startPrimary();
void startSecondary();
bool connectToPrimary(int msecs, ConnectionType connectionType);
quint16 blockChecksum() const;
qint64 primaryPid() const;
QString primaryUser() const;
bool isFrameComplete(QLocalSocket *sock);
void readMessageHeader(QLocalSocket *socket, ConnectionStage nextStage);
void readInitMessageBody(QLocalSocket *socket);
void writeAck(QLocalSocket *sock);
bool writeConfirmedFrame(int msecs, const QByteArray &msg);
bool writeConfirmedMessage(int msecs,
const QByteArray &msg,
SingleApplication::SendMode sendMode = SingleApplication::NonBlocking);
static void randomSleep();
void addAppData(const QString &data);
QStringList appData() const;
SingleApplication *q_ptr;
QSharedMemory *memory;
QLocalSocket *socket;
QLocalServer *server;
quint32 instanceNumber;
QString blockServerName;
SingleApplication::Options options;
QMap<QLocalSocket *, ConnectionInfo> connectionMap;
QStringList appDataList;
public Q_SLOTS:
void slotConnectionEstablished();
void slotDataAvailable(QLocalSocket *, quint32);
void slotClientConnectionClosed(QLocalSocket *, quint32);
};
#endif // SINGLEAPPLICATION_P_H

View file

@ -7,7 +7,7 @@
#include "core/App.hpp"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
App app(argc, argv);
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
@ -18,7 +18,7 @@ int main(int argc, char *argv[]) {
break;
}
}
App a;
int result = app.exec();
return result;
}

View file

@ -4,7 +4,6 @@ list(APPEND _LINPHONEAPP_SOURCES
model/logger/LoggerModel.cpp
model/logger/LoggerListener.cpp
model/logger/LoggerStaticModel.cpp
model/setting/SettingsModel.cpp
)

View file

@ -62,7 +62,7 @@ void LoggerListener::onLogMessageWritten(const std::shared_ptr<linphone::Logging
linphone::LogLevel level,
const std::string &message) {
bool isAppLog = domain == Constants::AppDomain;
if (!mIsVerbose || (!isAppLog && mQtOnlyEnabled)) return;
if (!mVerboseEnabled || (!isAppLog && mQtOnlyEnabled)) return;
FILE *out = stdout;
// TypeColor Date SourceColor [Domain] TypeColor Type Reset Message
QString format = "%1 %2 %3[%4]%1 %5" RESET " %6\n";

View file

@ -30,15 +30,15 @@
class LoggerListener : public linphone::LoggingServiceListener {
public:
LoggerListener();
bool mVerboseEnabled = false;
bool mQtOnlyEnabled = false;
private:
virtual void onLogMessageWritten(const std::shared_ptr<linphone::LoggingService> &logService,
const std::string &domain,
linphone::LogLevel level,
const std::string &message) override;
bool mIsVerbose = true;
bool mQtOnlyEnabled = false;
};
#endif

View file

@ -31,11 +31,14 @@
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
#include "LoggerStaticModel.hpp"
#include "core/logger/QtLogger.hpp"
// -----------------------------------------------------------------------------
LoggerModel::LoggerModel(QObject *parent) : QObject(parent) {
connect(LoggerStaticModel::getInstance(), &LoggerStaticModel::logReceived, this, &LoggerModel::onLog,
connect(QtLogger::getInstance(), &QtLogger::logReceived, this, &LoggerModel::onLog, Qt::QueuedConnection);
connect(QtLogger::getInstance(), &QtLogger::requestVerboseEnabled, this, &LoggerModel::enableVerbose,
Qt::QueuedConnection);
connect(QtLogger::getInstance(), &QtLogger::requestQtOnlyEnabled, this, &LoggerModel::enableQtOnly,
Qt::QueuedConnection);
}
@ -43,11 +46,34 @@ LoggerModel::~LoggerModel() {
}
bool LoggerModel::isVerbose() const {
return mVerbose;
return mListener ? mListener->mVerboseEnabled : false;
}
void LoggerModel::setVerbose(bool verbose) {
mVerbose = verbose;
void LoggerModel::enableVerbose(bool verbose) {
if (mListener && mListener->mVerboseEnabled != verbose) {
mListener->mVerboseEnabled = verbose;
emit verboseEnabledChanged();
}
}
void LoggerModel::enableFullLogs(const bool &full) {
auto service = linphone::LoggingService::get();
if (service) service->setLogLevel(full ? linphone::LogLevel::Debug : linphone::LogLevel::Message);
}
bool LoggerModel::qtOnlyEnabled() const {
return mListener ? mListener->mQtOnlyEnabled : false;
}
void LoggerModel::enableQtOnly(const bool &enable) {
if (mListener) {
if (mListener->mQtOnlyEnabled != enable) {
mListener->mQtOnlyEnabled = enable;
auto service = linphone::LoggingService::get();
if (service) service->setDomain(enable ? Constants::AppDomain : "");
emit qtOnlyEnabledChanged();
}
}
}
// -----------------------------------------------------------------------------
@ -124,25 +150,6 @@ void LoggerModel::init() {
enable(true);
}
void LoggerModel::enableFullLogs(const bool &full) {
auto service = linphone::LoggingService::get();
if (service) {
service->setLogLevel(full ? linphone::LogLevel::Debug : linphone::LogLevel::Message);
}
}
bool LoggerModel::qtOnlyEnabled() const {
return mQtOnly;
}
void LoggerModel::enableQtOnly(const bool &enable) {
mQtOnly = enable;
auto service = linphone::LoggingService::get();
if (service) {
service->setDomain(enable ? Constants::AppDomain : "");
}
}
QString LoggerModel::getLogText() const {
QDir path = QString::fromStdString(linphone::Core::getLogCollectionPath());
QString prefix = QString::fromStdString(linphone::Core::getLogCollectionPrefix());

View file

@ -35,7 +35,7 @@ public:
~LoggerModel();
bool isVerbose() const;
void setVerbose(bool verbose);
void enableVerbose(bool verbose);
void enable(bool status);
QString getLogText() const;
void enableFullLogs(const bool &full);
@ -51,13 +51,11 @@ public slots:
signals:
void logReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
void verboseEnabledChanged();
void qtOnlyEnabledChanged();
private:
static void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);
bool mVerbose = false;
bool mQtOnly = false;
std::shared_ptr<LoggerListener> mListener;
};

@ -1 +1 @@
Subproject commit 521a001ab21f1127682333efce8d4a292099203c
Subproject commit 949394d2158c44ddad4725327dc65e4488d36581