mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-07-30 16:04:12 +02:00
9a13fef959
qInstallMessageHandler() ran inside the startup worker thread, which is scheduled after QETApp construction - but QETApp's constructor performs the entire startup (collections, editor, opening projects passed on the command line). Everything logged during that window went to the default handler, i.e. stderr, which is invisible in a Windows GUI session: the daily log file ended right after the machine-info block, and exactly the interesting lines - the elements-collection timer and the project load timer from #560 - never reached it. Install the handler synchronously before SingleApplication instead; the worker keeps the old-log cleanup and machine-info dump. The CLI path returns earlier and intentionally keeps plain stderr logging. Verified: a GUI session now logs the full timeline, e.g. 12:37:03.623 Elements collection reload 12:37:03.776 ... finished to be loaded in 0.151 seconds 12:37:04.744 Project "..." (726 KiB) opened in 1.583 seconds (cherry picked from commit 72b1a1d9ec2e3fd3787224759797978d710880bd)
310 lines
8.8 KiB
C++
310 lines
8.8 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "cli_export.h"
|
|
#include "machine_info.h"
|
|
#include "qet.h"
|
|
#include "qetapp.h"
|
|
#include "qetproject.h"
|
|
#include "singleapplication.h"
|
|
#include "utils/qetsettings.h"
|
|
|
|
#include <QApplication>
|
|
|
|
#include <QStyleFactory>
|
|
#include <QtConcurrentRun>
|
|
|
|
#ifdef Q_OS_MACOS
|
|
#include <QFileOpenEvent>
|
|
|
|
/**
|
|
@brief EarlyFileOpenCatcher
|
|
On macOS, a cold launch via Finder double-click can deliver the
|
|
QFileOpenEvent to QApplication before QETApp exists and before its
|
|
real eventFilter is installed (the event loop can start servicing
|
|
native/Cocoa events before our own code in main() reaches that
|
|
point). This tiny filter is installed immediately on `app` so no
|
|
QFileOpenEvent can slip through unseen; it just buffers the path.
|
|
Once QETApp is constructed, main() drains the buffer and installs
|
|
the real QETApp::eventFilter for any subsequent event.
|
|
*/
|
|
class EarlyFileOpenCatcher : public QObject
|
|
{
|
|
public:
|
|
using QObject::QObject;
|
|
QStringList bufferedFiles;
|
|
|
|
protected:
|
|
bool eventFilter(QObject *object, QEvent *e) override
|
|
{
|
|
if (e->type() == QEvent::FileOpen) {
|
|
bufferedFiles << static_cast<QFileOpenEvent *>(e)->file();
|
|
return true;
|
|
}
|
|
return QObject::eventFilter(object, e);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
/**
|
|
@brief myMessageOutput
|
|
for debugging
|
|
@param type : the messages that can be sent to a message handler
|
|
@param context : were? wat?
|
|
@param msg : Message
|
|
*/
|
|
void myMessageOutput(QtMsgType type,
|
|
const QMessageLogContext &context,
|
|
const QString &msg)
|
|
{
|
|
|
|
QString txt=QTime::currentTime().toString("hh:mm:ss.zzz");
|
|
QByteArray dbs =txt.toLocal8Bit();
|
|
QByteArray localMsg = msg.toLocal8Bit();
|
|
const char *file = context.file ? context.file : "";
|
|
const char *function = context.function ? context.function : "";
|
|
|
|
switch (type) {
|
|
case QtDebugMsg:
|
|
fprintf(stderr,
|
|
"%s Debug: %s (%s:%u, %s)\n",
|
|
dbs.constData(),
|
|
localMsg.constData(),
|
|
file,
|
|
context.line,
|
|
function);
|
|
txt+=" Debug: ";
|
|
break;
|
|
case QtInfoMsg:
|
|
fprintf(stderr,
|
|
"%s Info: %s \n",
|
|
dbs.constData(),
|
|
localMsg.constData());
|
|
txt+=" Info: ";
|
|
break;
|
|
case QtWarningMsg:
|
|
fprintf(stderr,
|
|
"%s Warning: %s (%s:%u, %s)\n",
|
|
dbs.constData(),
|
|
localMsg.constData(),
|
|
file, context.line,
|
|
function);
|
|
txt+=" Warning: ";
|
|
break;
|
|
case QtCriticalMsg:
|
|
fprintf(stderr,
|
|
"%s Critical: %s (%s:%u, %s)\n",
|
|
dbs.constData(),
|
|
localMsg.constData(),
|
|
file,
|
|
context.line,
|
|
function);
|
|
txt+=" Critical: ";
|
|
break;
|
|
case QtFatalMsg:
|
|
fprintf(stderr,
|
|
"%s Fatal: %s (%s:%u, %s)\n",
|
|
dbs.constData(),
|
|
localMsg.constData(),
|
|
file,
|
|
context.line,
|
|
function);
|
|
txt+=" Fatal: ";
|
|
break;
|
|
default:
|
|
fprintf(stderr,
|
|
"%s Unknown: %s (%s:%u, %s)\n",
|
|
dbs.constData(),
|
|
localMsg.constData(),
|
|
file,
|
|
context.line,
|
|
function);
|
|
txt+=" Unknown: ";
|
|
}
|
|
txt+= msg;
|
|
if(type==QtInfoMsg){
|
|
txt+=" \n";
|
|
} else {
|
|
txt+= " (";
|
|
txt+= context.file ? context.file : "";
|
|
txt+= ":";
|
|
txt+=QString::number(context.line ? context.line :0);
|
|
txt+= ", ";
|
|
txt+= context.function ? context.function : "";
|
|
txt+=")\n";
|
|
}
|
|
QFile outFile(QETApp::dataDir()
|
|
+"/"
|
|
+QDate::currentDate().toString("yyyyMMdd")
|
|
+".log");
|
|
if(outFile.open(QIODevice::WriteOnly | QIODevice::Append))
|
|
{
|
|
QTextStream ts(&outFile);
|
|
ts << txt;
|
|
}
|
|
outFile.close();
|
|
}
|
|
|
|
/**
|
|
@brief delete_old_log_files
|
|
delete old log files
|
|
@param days : max days old
|
|
*/
|
|
void delete_old_log_files(int days)
|
|
{
|
|
const QDate today = QDate::currentDate();
|
|
const QString path = QETApp::dataDir() % "/";
|
|
|
|
QString filter("%1%1%1%1%1%1%1%1.log"); // pattern
|
|
filter = filter.arg("[0123456789]"); // valid characters
|
|
|
|
Q_FOREACH (auto fileInfo,
|
|
QDir(path).entryInfoList(
|
|
QStringList(filter),
|
|
QDir::Files))
|
|
{
|
|
if (fileInfo.lastRead().date().daysTo(today) > days)
|
|
{
|
|
QString filepath = fileInfo.absoluteFilePath();
|
|
QDir deletefile;
|
|
deletefile.setPath(filepath);
|
|
deletefile.remove(filepath);
|
|
qDebug() << "File " % filepath % " is deleted!";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief main
|
|
Main function of QElectroTech
|
|
@param argc : number of parameters
|
|
\~French number of paramètres
|
|
\~ @param argv : parameters
|
|
\~French paramètres
|
|
\~ @return exit code
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
// before creating Application:
|
|
// export environment-variable "QT_HASH_SEED" with value "0" to
|
|
// disable radomisation for hashes in order to obtain "clean" XML-diffs:
|
|
qputenv("QT_HASH_SEED", "0");
|
|
//Some setup, notably to use with QSetting.
|
|
QCoreApplication::setOrganizationName("QElectroTech");
|
|
QCoreApplication::setOrganizationDomain("qelectrotech.org");
|
|
QCoreApplication::setApplicationName("QElectroTech");
|
|
//Creation and execution of the application
|
|
//HighDPI
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
|
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
|
#else
|
|
#if TODO_LIST
|
|
#pragma message("@TODO remove code for QT 6 or later")
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#if QT_VERSION > QT_VERSION_CHECK(5, 7, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
|
|
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
|
#endif
|
|
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
|
|
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFactorRoundingPolicy());
|
|
#endif
|
|
|
|
|
|
// Headless command-line export: render a project to PDF/PNG/SVG without
|
|
// opening the GUI, then exit. Must be handled before SingleApplication
|
|
// (which would forward the args to an already-running instance).
|
|
{
|
|
QStringList raw_args;
|
|
for (int i = 0; i < argc; ++i)
|
|
raw_args << QString::fromLocal8Bit(argv[i]);
|
|
if (CLIExport::isExportRequest(raw_args)) {
|
|
QApplication export_app(argc, argv);
|
|
// No crash-recovery backups in one-shot CLI mode: the backup write
|
|
// runs on a background thread referencing the project and races the
|
|
// process exit (intermittent segfault in QET::writeToFile).
|
|
QETProject::setBackupEnabled(false);
|
|
return CLIExport::run(export_app.arguments());
|
|
}
|
|
}
|
|
|
|
// Install the log-file message handler BEFORE the application starts:
|
|
// QETApp's constructor does the whole startup (collections, editor,
|
|
// opening the projects given on the command line), so installing the
|
|
// handler afterwards - as was done in the startup worker below - meant
|
|
// exactly the interesting lines (collection and project load timers)
|
|
// went to stderr, which is invisible in a Windows GUI session.
|
|
qInstallMessageHandler(myMessageOutput);
|
|
|
|
SingleApplication app(argc, argv, true);
|
|
#ifdef Q_OS_MACOS
|
|
app.setStyle(QStyleFactory::create("Fusion"));
|
|
// Installed as early as possible, before anything else can run an
|
|
// event loop, to catch a QFileOpenEvent that might be delivered
|
|
// during a cold launch before QETApp exists.
|
|
EarlyFileOpenCatcher early_catcher;
|
|
app.installEventFilter(&early_catcher);
|
|
#endif
|
|
|
|
if (app.isSecondary())
|
|
{
|
|
QStringList arg_list = app.arguments();
|
|
//Remove the first argument, it's the binary file
|
|
arg_list.takeFirst();
|
|
QETArguments qetarg(arg_list);
|
|
QString message = "launched-with-args: " + QET::joinWithSpaces(
|
|
QStringList(qetarg.arguments()));
|
|
app.sendMessage(message.toUtf8());
|
|
return 0;
|
|
}
|
|
|
|
QETApp qetapp;
|
|
QETApp::instance()->installEventFilter(&qetapp);
|
|
#ifdef Q_OS_MACOS
|
|
//Handle the opening of QET when user double click on a .qet .elmt .tbt file
|
|
//or drop these same files to the QET icon of the dock.
|
|
//Swap the early catcher (installed right after `app` was constructed,
|
|
//see above) for the real filter, then drain anything it buffered
|
|
//during the cold-launch window before QETApp existed.
|
|
app.removeEventFilter(&early_catcher);
|
|
app.installEventFilter(&qetapp);
|
|
if (!early_catcher.bufferedFiles.isEmpty())
|
|
qetapp.openFiles(QETArguments(early_catcher.bufferedFiles));
|
|
#endif
|
|
QObject::connect(&app, &SingleApplication::receivedMessage,
|
|
&qetapp, &QETApp::receiveMessage);
|
|
|
|
// Pre-initialise on the main (GUI) thread: the constructor calls
|
|
// qApp->screens() which is not thread-safe in Qt5 — calling instance()
|
|
// here guarantees the singleton is fully built before the worker runs.
|
|
MachineInfo::instance();
|
|
|
|
[[maybe_unused]] auto startup_future = QtConcurrent::run([=]()
|
|
{
|
|
qInfo("Start-up");
|
|
// delete old log files of max 7 days old.
|
|
delete_old_log_files(7);
|
|
MachineInfo::instance()->send_info_to_debug();
|
|
});
|
|
return app.exec();
|
|
}
|
|
|