23#include <QApplication>
38#include "diagnostics/DiagnosticsDefaults.h"
39#include "diagnostics/ErrorReporter.h"
40#include "diagnostics/FileDiagnostics.h"
45#include <QStandardPaths>
53std::shared_ptr<core::ports::pdf_rendering::IPdfRenderer>
55std::shared_ptr<core::ports::document_image_processing::IDocumentImageProcessor>
57std::shared_ptr<core::ports::text_recognition::ITextRecognizer>
62inline constexpr std::string_view kAppDataDirectoryName =
".fossredder";
63inline constexpr std::string_view kDatabaseFileName =
"workspace.fossredder";
64inline constexpr std::string_view kRegistryFileName =
"registry.db";
66std::weak_ptr<core::ports::diagnostics::IErrorReporter> g_qtMessageReporter;
67std::mutex g_qtMessageReporterMutex;
69void setQtMessageReporter(
70 const std::shared_ptr<core::ports::diagnostics::IErrorReporter>& reporter) {
71 std::lock_guard<std::mutex> lock(g_qtMessageReporterMutex);
72 g_qtMessageReporter = reporter;
75std::shared_ptr<core::ports::diagnostics::IErrorReporter> qtMessageReporter() {
76 std::lock_guard<std::mutex> lock(g_qtMessageReporterMutex);
77 return g_qtMessageReporter.lock();
80void ensureParentDirectoryExists(
const std::filesystem::path& path,
81 const std::shared_ptr<
86 if (path.has_parent_path())
87 std::filesystem::create_directories(path.parent_path());
91 std::current_exception());
95bool startBannerEnabled() {
96 const QByteArray value =
97 qgetenv(
"FOSSREDDER_START_BANNER").trimmed().toLower();
98 return value !=
"0" && value !=
"false" && value !=
"off";
101bool consoleColorEnabled() {
102 if (!qgetenv(
"NO_COLOR").isEmpty()) {
105 const QByteArray value =
106 qgetenv(
"FOSSREDDER_CONSOLE_COLOR").trimmed().toLower();
107 return value !=
"0" && value !=
"false" && value !=
"off";
110const char* buildType() {
118const char* appVersion() {
119#if defined(FOSSREDDER_VERSION)
120 return FOSSREDDER_VERSION;
126void printStartBanner(
const QApplication& app,
127 const std::filesystem::path& workspacePath,
128 const std::filesystem::path& registryPath) {
129 if (!startBannerEnabled()) {
133 const bool color = consoleColorEnabled();
134 const std::string_view cyan = color ?
"\033[36m" :
"";
135 const std::string_view dim = color ?
"\033[2m" :
"";
136 const std::string_view gold = color ?
"\033[33m" :
"";
137 const std::string_view reset = color ?
"\033[0m" :
"";
140 << cyan <<
" .----------------. " << reset <<
'\n'
141 << cyan <<
" .' '. " << reset <<
'\n'
142 << cyan <<
" / ________ \\ " << reset <<
'\n'
143 << cyan <<
" | / ____/_, o | " << reset <<
'\n'
144 << cyan <<
" | / /___ o--' | " << reset <<
'\n'
145 << cyan <<
" | / ____/-----" << gold <<
"o" << cyan
146 <<
" | " << reset <<
'\n'
147 << cyan <<
" | /__/ | " << reset <<
'\n'
148 << cyan <<
" \\ / " << reset <<
'\n'
149 << cyan <<
" '. .' " << reset <<
'\n'
150 << cyan <<
" '----------------' " << reset <<
'\n'
152 <<
" FossRedder" << dim <<
" | desktop workspace runtime"
154 << dim <<
" version: " << appVersion()
155 <<
" | build: " << buildType() <<
" | Qt: " << QT_VERSION_STR
156 <<
" | app: " << app.applicationName().toStdString() << reset
158 << dim <<
" workspace: " << workspacePath.string() << reset <<
'\n'
159 << dim <<
" registry: " << registryPath.string() << reset <<
'\n'
164void printShutdownMessage(
int exitCode) {
165 if (!startBannerEnabled()) {
169 const bool color = consoleColorEnabled();
170 const std::string_view green = color ?
"\033[32m" :
"";
171 const std::string_view dim = color ?
"\033[2m" :
"";
172 const std::string_view reset = color ?
"\033[0m" :
"";
174 std::cout << green <<
"[shutdown] FossRedder stopped cleanly." << reset << dim
175 <<
" exitCode=" << exitCode << reset <<
'\n';
179struct UseCaseRunners {
180 std::shared_ptr<core::ports::analysis::IAnalysisRunner> analysisRunner;
181 std::shared_ptr<core::ports::annual::IAnnualRunner> annualRunner;
182 std::shared_ptr<core::ports::exporting::IExportRunner> exportRunner;
183 std::shared_ptr<core::ports::importing::IImportRunner> importRunner;
186class AppComposition {
188 explicit AppComposition(
189 std::shared_ptr<core::ports::diagnostics::IErrorReporter> reporter)
190 : errorReporter_(std::move(reporter)) {}
192 UseCaseRunners createUseCaseRunners()
const {
194 createAnalysisRunner(),
195 createAnnualRunner(),
196 createExportRunner(),
197 createImportRunner(),
202 std::shared_ptr<core::ports::analysis::IAnalysisRunner>
203 createAnalysisRunner()
const {
204 return std::make_shared<core::application::analysis::AnalysisService>(
205 createAnalysisRenderer());
208 std::shared_ptr<core::ports::annual::IAnnualRunner>
209 createAnnualRunner()
const {
210 return std::make_shared<core::application::annual::AnnualService>();
213 std::shared_ptr<core::ports::exporting::IExportRunner>
214 createExportRunner()
const {
215 return std::make_shared<core::application::exporting::ExportService>(
216 createArchiveAdapter(), createTableWriterAdapter(),
217 createAnalysisRenderer());
220 std::shared_ptr<core::ports::importing::IImportRunner>
221 createImportRunner()
const {
222 const auto importDiagnostics = createImportDiagnostics();
227 const auto importRunBasePath =
228 QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
230 return std::make_shared<core::application::importing::StatementImportRunner>(
231 importService, importRunBasePath, errorReporter_);
234 std::shared_ptr<infra::analysis_rendering::
235 OpenCvAnalysisRendererAdapter>
236 createAnalysisRenderer()
const {
237 return std::make_shared<
238 infra::analysis_rendering::OpenCvAnalysisRendererAdapter>(
242 std::shared_ptr<infra::archive::ZipArchiveAdapter>
243 createArchiveAdapter()
const {
244 return std::make_shared<infra::archive::ZipArchiveAdapter>(errorReporter_);
247 std::shared_ptr<infra::xlsx_writer::XlntTableWriterAdapter>
248 createTableWriterAdapter()
const {
249 return std::make_shared<infra::xlsx_writer::XlntTableWriterAdapter>(
253 std::shared_ptr<core::ports::diagnostics::IDiagnostics>
254 createImportDiagnostics()
const {
255 return std::make_shared<diagnostics::FileDiagnostics>(
256 "", std::string(diagnostics::defaults::kImportProcessName));
259 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter_;
262UseCaseRunners createUseCaseRunners(
263 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter) {
264 return AppComposition(std::move(errorReporter)).createUseCaseRunners();
271static void qtMessageHandler(QtMsgType type,
const QMessageLogContext& context,
272 const QString& msg) {
273 QByteArray localMsg = msg.toLocal8Bit();
274 const char* file = context.file ? context.file :
"";
275 const std::string text = localMsg.constData();
277 {
"file", file}, {
"line", std::to_string(context.line)}};
278 const auto reporter = qtMessageReporter();
283 "app::qtMessageHandler", text, ctx);
293 "app::qtMessageHandler", text, ctx);
298 "app::qtMessageHandler", text, ctx);
303 "app::qtMessageHandler", text, ctx);
319 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter,
320 std::shared_ptr<core::ports::analysis::IAnalysisRunner> analysisRunner,
321 std::shared_ptr<core::ports::annual::IAnnualRunner> annualRunner,
322 std::shared_ptr<core::ports::exporting::IExportRunner> exportRunner,
323 std::shared_ptr<core::ports::importing::IImportRunner> importRunner);
325int main(
int argc,
char* argv[]) {
326 auto errorReporter = diagnostics::createDefaultErrorReporter();
327 setQtMessageReporter(errorReporter);
330 if (qEnvironmentVariableIsEmpty(
"QT_ACCESSIBILITY")) {
331 qputenv(
"QT_ACCESSIBILITY", QByteArrayLiteral(
"0"));
335 const auto previousQtMessageHandler =
336 qInstallMessageHandler(qtMessageHandler);
340 QApplication app(argc, argv);
346 const QString appDataLocation =
347 QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
348 const std::filesystem::path appDataRoot =
349 appDataLocation.isEmpty()
350 ? std::filesystem::path(QDir::homePath().toStdString()) /
351 std::string(kAppDataDirectoryName)
352 : std::filesystem::path(appDataLocation.toStdString());
354 const std::filesystem::path defaultDbPath =
355 appDataRoot / std::string(kDatabaseFileName);
356 const std::filesystem::path registryDbPath =
357 appDataRoot / std::string(kRegistryFileName);
358 ensureParentDirectoryExists(defaultDbPath, errorReporter,
359 "app::main::createConfigDirectory");
360 ensureParentDirectoryExists(registryDbPath, errorReporter,
361 "app::main::createRegistryDirectory");
362 printStartBanner(app, defaultDbPath, registryDbPath);
364 std::shared_ptr<core::ports::storage::IRegistry> registry;
367 }
catch (
const std::exception& ex) {
370 "app::main::openRegistryDb",
371 std::string(
"failed to open registry DB '") +
372 registryDbPath.string() +
"': " + ex.what(),
373 {{
"path", registryDbPath.string()}});
378 auto smPtr = std::make_unique<core::storage::StorageManager>(std::move(sm));
381 appStateFacade.setErrorReporter(errorReporter);
382 appStateFacade.setAtomicStoreLoad([](
const std::string& dbPath) {
387 appStateFacade.setAtomicStoreSave(
388 [](
const std::string& dbPath,
392 return store.save(document);
396 appStateFacade.openLatest();
397 }
catch (
const std::exception& ex) {
401 "app::main::openLatest",
402 std::current_exception());
406 if (appStateFacade.currentPath().empty() &&
407 appStateFacade.workspaceSnapshot().empty()) {
408 appStateFacade.newFile(defaultDbPath.string());
411 QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
414 auto runners = createUseCaseRunners(errorReporter);
416 app, appStateFacade, appStateFacade, errorReporter,
417 std::move(runners.analysisRunner), std::move(runners.annualRunner),
418 std::move(runners.exportRunner), std::move(runners.importRunner));
419 printShutdownMessage(exitCode);
420 qInstallMessageHandler(previousQtMessageHandler);
421 appStateFacade.setErrorReporter({});
422 setQtMessageReporter({});
424 }
catch (
const std::exception& ex) {
425 qInstallMessageHandler(previousQtMessageHandler);
428 "app::main::startQmlApp",
429 std::current_exception());
430 QMessageBox::critical(
431 nullptr, QObject::tr(
"Fatal error"),
432 QObject::tr(
"Startup failed: %1").arg(QString::fromUtf8(ex.what())));
433 appStateFacade.setErrorReporter({});
434 setQtMessageReporter({});
437 qInstallMessageHandler(previousQtMessageHandler);
440 "app::main::startQmlAppUnknown",
441 std::current_exception());
442 QMessageBox::critical(
nullptr, QObject::tr(
"Fatal error"),
443 QObject::tr(
"Startup failed: unknown exception"));
444 appStateFacade.setErrorReporter({});
445 setQtMessageReporter({});
Declares the application service that resolves and executes analyses.
Builds annual derived snapshots from workspace snapshots.
Defines UI-specific runtime constants that are not shared with core.
Provides shared helpers for reporting structured errors and exceptions.
Declares the export entry point used by the application shell.
Declares factories for SQLite storage dependencies and repository bundles.
std::shared_ptr< core::ports::storage::IRegistry > createSqliteRegistry(const std::string &dbPath)
Create the SQLite-backed registry for the given path.
std::shared_ptr< SqliteDb > createSqliteDb(const std::string &dbPath)
Create a SQLite database wrapper for the given path.
Analysis use-case port consumed by UI workflows.
Annual use-case port consumed by application boundaries.
Declares the diagnostics artifact writing port.
Document image processing port for import workflows.
Export use-case port consumed by UI workflows.
Import runner port consumed by UI workflows.
Declares the public statement import service interface and factory.
PDF rendering port for import workflows.
Read-side workspace boundary that exposes immutable snapshots.
Write-side workspace boundary that accepts typed command models.
Declares the OpenCV-based analysis image renderer adapter.
Declares the application-level asynchronous statement import runner.
Storage manager: repository factory, atomic save/load and registry handling.
std::shared_ptr< core::ports::text_recognition::ITextRecognizer > createTextRecognizerAdapter()
Declares the workspace facade implementing snapshot reader and command writer ports.
Internal mutable workspace session state for application services.
Declares SQLite-backed loading and saving of the full application state.
Declares the XLSX writer adapter used by export features.
Declares the archive adapter used by export packaging.
Loads and saves the aggregate application state through SQLite repositories.
Exposes state mutation and workspace persistence as a single application facade.
Port for reporting structured diagnostic error events.
Write-side workspace boundary for persistence, CRUD, drafts, and logs.
Persists workspace session state through atomic store callbacks and latest-path registry access.
Defines shared structured error codes.
int main(int argc, char *argv[])
int startQmlApp(QApplication &app, core::ports::workspace::IWorkspaceReader &workspaceReader, core::ports::workspace::IWorkspaceWriter &workspaceWriter, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter, std::shared_ptr< core::ports::analysis::IAnalysisRunner > analysisRunner, std::shared_ptr< core::ports::annual::IAnnualRunner > annualRunner, std::shared_ptr< core::ports::exporting::IExportRunner > exportRunner, std::shared_ptr< core::ports::importing::IImportRunner > importRunner)
Start the QML application UI.
std::shared_ptr< core::ports::pdf_rendering::IPdfRenderer > createPdfRendererAdapter(std::shared_ptr< core::ports::diagnostics::IDiagnostics > dbg)
std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > createDocumentImageProcessorAdapter(std::shared_ptr< core::ports::diagnostics::IDiagnostics > dbg)
std::shared_ptr< IImportStatement > createImportStatement(std::shared_ptr< core::ports::pdf_rendering::IPdfRenderer > poppler, std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > documentImageProcessor, std::shared_ptr< core::ports::text_recognition::ITextRecognizer > tesseract, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter)
Creates the default statement import service over the configured adapters.
constexpr const char * QtCritical
Code used for Qt critical messages.
constexpr const char * QtWarning
Code used for Qt warning messages.
constexpr const char * ConfigDbOpenFailed
Code used when opening the configuration database fails.
constexpr const char * QtInfo
Code used for Qt informational messages.
constexpr const char * QtDebug
Code used for Qt debug messages.
constexpr const char * QtFatal
Code used for Qt fatal messages.
std::vector< std::pair< std::string, std::string > > ErrorContext
Key/value context attached to an error event.
void reportException(core::ports::diagnostics::IErrorReporter *reporter, ErrorSeverity severity, const char *origin, std::exception_ptr exception)
Report an exception through the reporter's default exception handling.
void report(core::ports::diagnostics::IErrorReporter *reporter, const ErrorEvent &event)
Report an already constructed error event when a reporter exists.
const auto kAppIconResource
Resource URL of the application icon embedded into the executable.
const auto kSettingsApplicationName
Application name used for the UI's QSettings store.
const auto kSettingsOrganizationName
Organization name used for the UI's QSettings store.
const auto kQtStyle
Qt Quick Controls style used by the desktop/QML test runtime.