Add Widget module for MessageBox (in case of fatal error) and display critical popup just before abort.

Instantiate SDK thread.
Add logger.
Format logs to match SDK syntax.
Change log domain to 'app'.
This commit is contained in:
Julien Wadel 2023-10-05 10:12:03 +02:00
parent 52b1ce5ecf
commit e795f0ea51
20 changed files with 544 additions and 53 deletions

View file

@ -33,7 +33,7 @@ set(APP_TARGETS ${LinphoneCxx_TARGET})
set(QT_DEFAULT_MAJOR_VERSION 6)
set(QT_PACKAGES Core Quick Qml)# Search Core at first for initialize Qt scripts for next find_packages.
set(QT_PACKAGES Core Quick Qml Widgets)# Search Core at first for initialize Qt scripts for next find_packages.
#find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS ${QT_PACKAGES})
@ -42,12 +42,11 @@ set(_LINPHONEAPP_SOURCES main.cpp)
set(_LINPHONEAPP_QML_FILES)
add_subdirectory(data)
add_subdirectory(tool)
add_subdirectory(model)
add_subdirectory(view)
add_subdirectory(core)
qt6_add_executable(Linphone
${_LINPHONEAPP_SOURCES}
)
@ -59,7 +58,6 @@ qt6_add_qml_module(Linphone
QML_FILES ${_LINPHONEAPP_QML_FILES}
)
set_target_properties(${TARGET_NAME} PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER org.linphone
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}

View file

@ -6,7 +6,10 @@
#include "view/Page/LoginPage.hpp"
App::App(QObject *parent) : QObject(parent) {
mLinphoneThread = new Thread(this);
init();
qDebug() << "Starting Thread";
mLinphoneThread->start();
}
//-----------------------------------------------------------
@ -15,8 +18,9 @@ App::App(QObject *parent) : QObject(parent) {
void App::init() {
// Core
mCoreModel = QSharedPointer<CoreModel>::create("", this);
mCoreModel->start();
mCoreModel = QSharedPointer<CoreModel>::create("", mLinphoneThread);
connect(mLinphoneThread, &QThread::started, mCoreModel.get(), &CoreModel::start);
// QML
mEngine = new QQmlApplicationEngine(this);
mEngine->addImportPath(":/");

View file

@ -2,6 +2,7 @@
#include <QQmlApplicationEngine>
#include <QSharedPointer>
#include "core/thread/Thread.hpp"
#include "model/core/CoreModel.hpp"
class App : public QObject {
@ -11,6 +12,9 @@ public:
void init();
void initCppInterfaces();
void onLoggerInitialized();
QQmlApplicationEngine *mEngine = nullptr;
Thread *mLinphoneThread = nullptr;
QSharedPointer<CoreModel> mCoreModel;
};

View file

@ -2,6 +2,7 @@ list(APPEND _LINPHONEAPP_SOURCES
core/App.cpp
core/path/Paths.cpp
core/setting/Settings.cpp
core/thread/Thread.cpp
)

View file

@ -0,0 +1,12 @@
#include "Thread.hpp"
Thread::Thread(QObject *parent) : QThread(parent) {
}
void Thread::run() {
int toExit = false;
while (!toExit) {
int result = exec();
if (result < 0) toExit = true;
}
}

View file

@ -0,0 +1,9 @@
#include <QThread>
class Thread : public QThread {
public:
Thread(QObject *parent = nullptr);
virtual void run();
};

View file

@ -1,4 +1,4 @@
#include <QGuiApplication>
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QLocale>
@ -7,7 +7,7 @@
#include "core/App.hpp"
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QApplication app(argc, argv);
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();

View file

@ -2,6 +2,10 @@ list(APPEND _LINPHONEAPP_SOURCES
model/core/CoreModel.cpp
model/core/CoreListener.cpp
model/logger/LoggerModel.cpp
model/logger/LoggerListener.cpp
model/logger/LoggerStaticModel.cpp
model/setting/SettingsModel.cpp
)

View file

@ -20,6 +20,7 @@
#include "CoreModel.hpp"
#include <QApplication>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
@ -30,26 +31,27 @@
// =============================================================================
CoreModel::CoreModel(const QString &configPath, QObject *parent) : QThread(parent) {
CoreModel::CoreModel(const QString &configPath, QObject *parent) : QObject(parent) {
mConfigPath = configPath;
mLogger = std::make_shared<LoggerModel>(this);
mLogger->init();
}
CoreModel::~CoreModel() {
}
void CoreModel::run() {
mCore = linphone::Factory::get()->createCore(Utils::appStringToCoreString(mConfigPath), "", nullptr);
void CoreModel::start() {
auto configPath = Utils::appStringToCoreString(mConfigPath);
mCore = linphone::Factory::get()->createCore(configPath, "", nullptr);
mCore->enableAutoIterate(true);
mCore->start();
while (!mEnd) {
mCore->iterate();
}
mCore->stop();
mCore = nullptr;
}
// -----------------------------------------------------------------------------
CoreModel *CoreModel::getInstance() {
return nullptr;
}
std::shared_ptr<linphone::Core> CoreModel::getCore() {
return mCore;
}

View file

@ -27,9 +27,11 @@
#include <QThread>
#include <linphone++/linphone.hh>
#include "model/logger/LoggerModel.hpp"
// =============================================================================
class CoreModel : public QThread {
class CoreModel : public QObject {
Q_OBJECT
public:
CoreModel(const QString &configPath, QObject *parent);
@ -37,7 +39,7 @@ public:
std::shared_ptr<linphone::Core> getCore();
virtual void run();
void start();
static CoreModel *getInstance();
@ -45,6 +47,10 @@ public:
QString mConfigPath;
std::shared_ptr<linphone::Core> mCore;
std::shared_ptr<LoggerModel> mLogger;
signals:
void loggerInitialized();
};
#endif

View file

@ -0,0 +1,115 @@
/*
* 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 "LoggerListener.hpp"
#include <QCoreApplication>
#include <QDateTime>
#include <QMessageBox>
#include <QString>
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
// =============================================================================
#if defined(__linux__) || defined(__APPLE__)
#define BLUE "\x1B[1;34m"
#define YELLOW "\x1B[1;33m"
#define GREEN "\x1B[1;32m"
#define PURPLE "\x1B[1;35m"
#define RED "\x1B[1;31m"
#define RESET "\x1B[0m"
#else
#define BLUE ""
#define YELLOW ""
#define GREEN ""
#define PURPLE ""
#define RED ""
#define RESET ""
#endif // if defined(__linux__) || defined(__APPLE__)
// -----------------------------------------------------------------------------
static inline QByteArray getFormattedCurrentTime() {
return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz").toLocal8Bit();
}
// -----------------------------------------------------------------------------
LoggerListener::LoggerListener() {
}
void LoggerListener::onLogMessageWritten(const std::shared_ptr<linphone::LoggingService> &,
const std::string &domain,
linphone::LogLevel level,
const std::string &message) {
bool isAppLog = domain == Constants::AppDomain;
if (!mIsVerbose || (!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";
QString colorType;
QString type;
QString qMessage = Utils::coreStringToAppString(message);
switch (level) {
case linphone::LogLevel::Debug:
colorType = GREEN;
type = "DEBUG";
break;
case linphone::LogLevel::Trace:
colorType = BLUE;
type = "TRACE";
break;
case linphone::LogLevel::Message:
colorType = BLUE;
type = "MESSAGE";
break;
case linphone::LogLevel::Warning:
colorType = RED;
type = "WARNING";
out = stderr;
break;
case linphone::LogLevel::Error:
colorType = RED;
type = "ERROR";
out = stderr;
break;
case linphone::LogLevel::Fatal:
colorType = RED;
type = "FATAL";
out = stderr;
break;
}
QString messageToDisplay = format.arg(colorType)
.arg(getFormattedCurrentTime())
.arg(isAppLog ? PURPLE : YELLOW)
.arg(Utils::coreStringToAppString(domain))
.arg(type)
.arg(qMessage);
fprintf(out, "%s", qPrintable(messageToDisplay));
fflush(out);
if (level == linphone::LogLevel::Fatal) {
QMetaObject::invokeMethod(qApp, [qMessage]() {
QMessageBox::critical(nullptr, EXECUTABLE_NAME " will crash", qMessage);
std::terminate();
});
}
}

View file

@ -0,0 +1,44 @@
/*
* 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 LOGGER_LISTENER_H_
#define LOGGER_LISTENER_H_
#include <QMutex>
#include <memory>
#include <linphone++/linphone.hh>
// =============================================================================
class LoggerListener : public linphone::LoggingServiceListener {
public:
LoggerListener();
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

@ -0,0 +1,161 @@
/*
* 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 <QDateTime>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QThread>
#include <linphone++/linphone.hh>
#include "config.h"
//#include "components/settings/SettingsModel.hpp"
#include "LoggerListener.hpp"
#include "LoggerModel.hpp"
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
#include "LoggerStaticModel.hpp"
// -----------------------------------------------------------------------------
LoggerModel::LoggerModel(QObject *parent) : QObject(parent) {
connect(LoggerStaticModel::getInstance(), &LoggerStaticModel::logReceived, this, &LoggerModel::onLog,
Qt::QueuedConnection);
}
LoggerModel::~LoggerModel() {
}
bool LoggerModel::isVerbose() const {
return mVerbose;
}
void LoggerModel::setVerbose(bool verbose) {
mVerbose = verbose;
}
// -----------------------------------------------------------------------------
// Called from Qt
void LoggerModel::onLog(QtMsgType type, QString contextFile, int contextLine, QString msg) {
connect(this, &LoggerModel::logReceived, this, &LoggerModel::onLog, Qt::QueuedConnection);
auto service = linphone::LoggingService::get();
QString contextStr = "";
#ifdef QT_MESSAGELOGCONTEXT
{
QStringList cleanFiles = contextFile.split(Constants::SrcPattern);
QString fileToDisplay = cleanFiles.back();
contextStr = QStringLiteral("%1:%2: ").arg(fileToDisplay).arg(contextLine);
}
#else
Q_UNUSED(context);
#endif
auto serviceMsg = Utils::appStringToCoreString(contextStr + msg);
if (service) {
switch (type) {
case QtDebugMsg:
service->debug(serviceMsg);
break;
case QtInfoMsg:
service->message(serviceMsg);
break;
case QtWarningMsg:
service->warning(serviceMsg);
break;
case QtCriticalMsg:
service->error(serviceMsg);
break;
case QtFatalMsg:
service->fatal(serviceMsg);
break;
}
}
}
// -----------------------------------------------------------------------------
void LoggerModel::enable(bool status) {
linphone::Core::enableLogCollection(status ? linphone::LogCollectionState::Enabled
: linphone::LogCollectionState::Disabled);
}
void LoggerModel::init(const std::shared_ptr<linphone::Config> &config) {
// TODO update from config
// const QString folder = SettingsModel::getLogsFolder(config);
// linphone::Core::setLogCollectionPath(Utils::appStringToCoreString(folder));
// enableFullLogs(SettingsModel::getFullLogsEnabled(config));
// enable(SettingsModel::getLogsEnabled(config));
}
void LoggerModel::init() {
QLoggingCategory::setFilterRules("qt.qml.connections.warning=false");
mListener = std::make_shared<LoggerListener>();
{
std::shared_ptr<linphone::LoggingService> loggingService = linphone::LoggingService::get();
loggingService->setDomain(Constants::AppDomain);
loggingService->setLogLevel(linphone::LogLevel::Debug);
loggingService->addListener(mListener);
#ifdef _WIN32
loggingService->enableStackTraceDumps(true);
#endif
}
linphone::Core::setLogCollectionPrefix(EXECUTABLE_NAME);
linphone::Core::setLogCollectionMaxFileSize(Constants::MaxLogsCollectionSize);
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());
auto files = path.entryInfoList(QStringList(prefix + "*.log"), QDir::Files | QDir::NoSymLinks | QDir::Readable,
QDir::Time | QDir::Reversed);
QString result;
for (auto fileInfo : files) {
QFile file(fileInfo.filePath());
if (file.open(QIODevice::ReadOnly)) {
QByteArray arr = file.readAll();
result += QString::fromLatin1(arr);
file.close();
}
}
return result;
}

View file

@ -0,0 +1,64 @@
/*
* 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 LOGGER_MODEL_H_
#define LOGGER_MODEL_H_
#include <QObject>
#include <QString>
#include <linphone++/linphone.hh>
#include "LoggerListener.hpp"
// =============================================================================
class LoggerModel : public QObject {
Q_OBJECT
public:
LoggerModel(QObject *parent = nullptr);
~LoggerModel();
bool isVerbose() const;
void setVerbose(bool verbose);
void enable(bool status);
QString getLogText() const;
void enableFullLogs(const bool &full);
bool qtOnlyEnabled() const;
void enableQtOnly(const bool &enable);
void init();
void init(const std::shared_ptr<linphone::Config> &config);
static void onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
public slots:
void onLog(QtMsgType type, QString file, int contextLine, QString msg);
signals:
void logReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
private:
static void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);
bool mVerbose = false;
bool mQtOnly = false;
std::shared_ptr<LoggerListener> mListener;
};
#endif

View file

@ -0,0 +1,37 @@
/*
* 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 "LoggerStaticModel.hpp"
#include <QMetaMethod>
// -----------------------------------------------------------------------------
static LoggerStaticModel gLogger;
LoggerStaticModel::LoggerStaticModel(QObject *parent) : QObject(parent) {
qInstallMessageHandler(LoggerStaticModel::onQtLog);
}
LoggerStaticModel *LoggerStaticModel::getInstance() {
return &gLogger;
}
void LoggerStaticModel::onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
emit gLogger.logReceived(type, context.file, context.line, msg);
}

View file

@ -0,0 +1,42 @@
/*
* 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 LOGGER_STATIC_MODEL_H_
#define LOGGER_STATIC_MODEL_H_
#include <QMetaMethod>
#include <QObject>
// =============================================================================
// Only one instance. Use getInstance() and logReceived() to bind logs coming from Qt.
class LoggerStaticModel : public QObject {
Q_OBJECT
public:
LoggerStaticModel(QObject *parent = nullptr);
static LoggerStaticModel *getInstance();
static void onQtLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
signals:
void logReceived(QtMsgType type, QString contextFile, int contextLine, QString msg);
};
#endif

View file

@ -29,7 +29,7 @@ constexpr int Constants::DefaultEmojiFontPointSize;
QStringList Constants::getReactionsList() {
return {"❤️", "👍", "😂", "😮", "😢"};
}
constexpr char Constants::QtDomain[];
constexpr char Constants::AppDomain[];
constexpr size_t Constants::MaxLogsCollectionSize;
constexpr char Constants::SrcPattern[];
constexpr char Constants::LinphoneLocaleEncoding[];

View file

@ -163,8 +163,8 @@ public:
static constexpr char AssistantViewName[] = "Assistant";
static constexpr char QtDomain[] = "qt";
static constexpr char SrcPattern[] = "/src/";
static constexpr char AppDomain[] = "app";
static constexpr char SrcPattern[] = "/Linphone/";
static constexpr char LinphoneLocaleEncoding[] = "UTF-8"; // Alternative is to use "locale"
static constexpr char VcardScheme[] = EXECUTABLE_NAME "-desktop:/";
static constexpr int CbsCallInterval = 20;

View file

@ -40,27 +40,28 @@
#include <QtPdfWidgets/QPdfView>
#endif
#include "UriTools.hpp"
//#include "UriTools.hpp"
#include "Utils.hpp"
#include "app/App.hpp"
#include "app/paths/Paths.hpp"
#include "app/providers/ImageProvider.hpp"
#include "components/contact/ContactModel.hpp"
#include "components/contact/VcardModel.hpp"
#include "components/contacts/ContactsListModel.hpp"
#include "components/core/CoreManager.hpp"
#include "components/other/colors/ColorListModel.hpp"
#include "components/other/colors/ColorModel.hpp"
#include "components/other/date/DateModel.hpp"
#include "components/settings/AccountSettingsModel.hpp"
#include "components/settings/SettingsModel.hpp"
#include "components/sip-addresses/SipAddressesModel.hpp"
#ifdef _WIN32
#include <time.h>
#endif
// =============================================================================
char *Utils::rstrstr(const char *a, const char *b) {
size_t a_len = strlen(a);
size_t b_len = strlen(b);
if (b_len > a_len) return nullptr;
for (const char *s = a + a_len - b_len; s >= a; --s) {
if (!strncmp(s, b, b_len)) return const_cast<char *>(s);
}
return nullptr;
}
/*
namespace {
constexpr int SafeFilePathLimit = 100;
@ -79,20 +80,6 @@ CoreManager::getInstance()->getCore()->interpretUrl(Utils::appStringToCoreString
}
return interpretedAddress;
}
char *Utils::rstrstr (const char *a, const char *b) {
size_t a_len = strlen(a);
size_t b_len = strlen(b);
if (b_len > a_len)
return nullptr;
for (const char *s = a + a_len - b_len; s >= a; --s) {
if (!strncmp(s, b, b_len))
return const_cast<char *>(s);
}
return nullptr;
}
// -----------------------------------------------------------------------------

View file

@ -128,9 +128,10 @@ public:
if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return str.toStdString();
else return qPrintable(str);
}
/*
// Reverse function of strstr.
static char *rstrstr (const char *a, const char *b);
static char *rstrstr(const char *a, const char *b);
/*
// Return the path if it is an image else an empty path.
static QImage getImage(const QString &pUri);
// Returns the same path given in parameter if `filePath` exists.