22#include <QtConcurrent/qtconcurrentrun.h>
25#include <condition_variable>
34QString buildExportSuccessStatusMessage() {
38QString currentTimestamp() {
39 return QDateTime::currentDateTime().toString(
40 QStringLiteral(
"dd.MM.yyyy HH:mm:ss"));
43QString generateLogId() {
44 return QUuid::createUuid().toString(QUuid::WithoutBraces);
56 std::lock_guard lock(
mutex);
61 std::unique_lock lock(
mutex);
69 std::lock_guard lock(
mutex);
77 std::lock_guard lock(
mutex);
87 std::shared_ptr<ui::adapters::ExportAdapter> exportAdapter,
88 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter,
90 : QObject(parent), stateSnapshotProvider_(std::move(stateSnapshotProvider)),
91 exportAdapter_(std::move(exportAdapter)),
92 errorReporter_(std::move(errorReporter)) {
93 if (!errorReporter_) {
94 throw std::invalid_argument(
"ExportWorkflow requires an error reporter");
97 connect(&exportWatcher_,
98 &QFutureWatcher<core::ports::exporting::ExportResult>::finished,
this,
99 &ExportWorkflow::onExportFinished);
101 exportControl_ = std::make_shared<ExportControlState>();
105 exportLogSink_ = std::move(sink);
115ExportWorkflow::stateSnapshot()
const {
116 return stateSnapshotProvider_ ? stateSnapshotProvider_()
117 :
core::
ports::workspace::WorkspaceSnapshot{};
121ExportWorkflow::buildRequest(
int format,
const QString& path,
122 bool includeFormulas,
const QString& locale,
123 const QVariantMap& payload)
const {
124 if (!exportAdapter_) {
127 return exportAdapter_->buildExportRequest(format, path, includeFormulas,
131core::ports::workspace::ExportLogSnapshot
132ExportWorkflow::publishExportLogById(
const QString& logId,
const QString& path,
133 const QString& status,
134 const QString& message) {
135 core::ports::workspace::ExportLogSnapshot row;
136 const QString resolvedLogId = logId.isEmpty() ? generateLogId() : logId;
137 const auto snapshot = stateSnapshot();
138 for (
const auto& candidate : snapshot.exportLogs) {
139 if (QString::fromStdString(candidate.id) == resolvedLogId) {
145 row.
id = resolvedLogId.toStdString();
146 row.
time = currentTimestamp().toStdString();
148 row.
status = status.toStdString();
149 row.
message = message.toStdString();
150 publishExportLog(row);
154void ExportWorkflow::publishExportLog(
155 const core::ports::workspace::ExportLogSnapshot& row) {
156 if (!exportLogSink_) {
166 std::current_exception());
171 activeExportLogId_.clear();
175void ExportWorkflow::finishExport(
bool success,
const QString& outputPath) {
176 completedSteps_ = totalSteps_;
179 :
ui::
text::exporting::phaseFailed();
182 :
ui::
text::exporting::statusFailed();
183 const QString path = outputPath.isEmpty() ? activeExportPath_ : outputPath;
184 QString message = lastError_;
185 if (success && message.isEmpty()) {
186 message = buildExportSuccessStatusMessage();
188 publishExportLogById(activeExportLogId_, path, status, message);
192 cancelRequested_ =
false;
200void ExportWorkflow::finishCanceled() {
201 completedSteps_ = totalSteps_;
205 publishExportLogById(activeExportLogId_, activeExportPath_,
211 cancelRequested_ =
false;
212 if (exportControl_) {
213 exportControl_->cancel();
220 bool includeFormulas,
221 const QString& locale,
226 errorReporter_.get(),
230 "Export ignored: already running");
236 exportControl_ = std::make_shared<ExportControlState>();
237 totalSteps_ = std::max(1, totalSteps);
241 activeExportPath_ = path;
242 activeExportLogId_ = generateLogId();
243 publishExportLogById(activeExportLogId_, activeExportPath_,
248 auto request = buildRequest(format, path, includeFormulas, locale,
payload);
249 auto control = exportControl_;
250 request.shouldCancel = [control]() {
251 return control && control->shouldCancel();
253 request.waitIfPaused = [control]() {
255 control->waitIfPaused();
258 QPointer<ExportWorkflow> self(
this);
259 request.progressCallback = [self](
double progressValue,
260 const std::string& phaseText) {
263 QMetaObject::invokeMethod(
265 [self, progressValue, phaseText]() {
266 if (!self || !self->isRunning_)
268 const double clamped = std::max(0.0, std::min(1.0, progressValue));
269 self->progress_ = clamped;
270 if (!phaseText.empty()) {
271 self->phase_ = QString::fromStdString(phaseText);
273 emit self->stateChanged();
275 Qt::QueuedConnection);
278 if (!stateSnapshotProvider_) {
281 errorReporter_.get(),
285 "Export rejected: state snapshot unavailable",
286 {{observability::context::kPath, strings::toStdString(path)}});
287 publishExportLogById(activeExportLogId_, activeExportPath_,
295 if (!exportAdapter_) {
298 errorReporter_.get(),
302 "Export rejected: export runner unavailable",
303 {{observability::context::kPath, strings::toStdString(path)}});
304 publishExportLogById(activeExportLogId_, activeExportPath_,
322 includeFormulas ?
"true" :
"false"},
325 errorReporter_.get(),
329 {{observability::context::kPath, strings::toStdString(path)},
330 {observability::context::kFormat, std::to_string(format)},
331 {observability::context::kIncludeFormulas,
332 includeFormulas ?
"true" :
"false"},
333 {observability::context::kLocale, strings::toStdString(locale)}});
335 const auto snapshot = stateSnapshot();
336 exportFuture_ = QtConcurrent::run([exportAdapter = exportAdapter_, snapshot,
337 request = std::move(request)]()
mutable {
338 return exportAdapter->runExport(snapshot, std::move(request));
340 exportWatcher_.setFuture(exportFuture_);
341 }
catch (
const std::exception& ex) {
345 observability::origins::workflow::exportFlow::kStart, ex.what());
347 observability::reportFlow(
348 errorReporter_.get(),
350 observability::codes::FlowExportFailed,
351 observability::origins::workflow::exportFlow::kStart,
352 "Export failed with exception",
353 {{observability::context::kException, ex.what()},
354 {observability::context::kPath, strings::toStdString(path)}});
360 observability::origins::workflow::exportFlow::kStart,
361 std::current_exception());
363 observability::reportFlow(
364 errorReporter_.get(),
366 observability::codes::FlowExportFailed,
367 observability::origins::workflow::exportFlow::kStart,
368 "Export failed with non-std exception",
369 {{observability::context::kPath, strings::toStdString(path)}});
377 cancelRequested_ =
true;
378 if (exportControl_) {
379 exportControl_->cancel();
386 if (!isRunning_ || isPaused_)
389 if (exportControl_) {
390 exportControl_->setPaused(isPaused_);
397 if (!isRunning_ || !isPaused_)
400 if (exportControl_) {
401 exportControl_->setPaused(isPaused_);
407void ExportWorkflow::onExportFinished() {
408 if (cancelRequested_) {
413 bool success =
false;
414 QString resolvedOutputPath;
416 const auto result = exportFuture_.result();
417 success = result.success;
419 resolvedOutputPath = QString::fromStdString(result.resolvedOutputPath);
426 lastError_ = result.message.empty()
428 : QString::fromStdString(result.message);
432 : result.errorCode.c_str(),
433 observability::origins::workflow::exportFlow::kFinish,
434 strings::toStdString(lastError_));
435 observability::reportFlow(
436 errorReporter_.get(),
438 observability::codes::FlowExportFailed,
439 observability::origins::workflow::exportFlow::kFinish,
440 "Export finished with failure",
441 {{observability::context::kError, strings::toStdString(lastError_)}});
444 observability::reportFlow(
445 errorReporter_.get(),
447 observability::codes::FlowExportFinished,
448 observability::origins::workflow::exportFlow::kFinish,
449 "Export finished successfully");
451 }
catch (
const std::exception& ex) {
455 observability::origins::workflow::exportFlow::kFinish, ex.what());
457 observability::reportFlow(
458 errorReporter_.get(),
460 observability::codes::FlowExportFailed,
461 observability::origins::workflow::exportFlow::kFinish,
462 "Export finished with std exception",
463 {{observability::context::kException, ex.what()}});
469 observability::origins::workflow::exportFlow::kFinish,
470 std::current_exception());
472 observability::reportFlow(
473 errorReporter_.get(),
475 observability::codes::FlowExportFailed,
476 observability::origins::workflow::exportFlow::kFinish,
477 "Export finished with non-std exception");
481 finishExport(success, resolvedOutputPath);
Provides shared helpers for reporting structured errors and exceptions.
Declares the export adapter used by UI workflows.
Declares export use-case request DTOs and related port types.
Declares the asynchronous export workflow used by ExportViewModel.
Declarations for the UI Origins component.
Declares lightweight conversions between common Qt string types and standard strings.
Provides translated C++ UI strings for shell, workflow, adapter and view model code.
Declarations for the UI Trace component.
void exportDataWithPayload(int format, const QString &path, bool includeFormulas, const QString &locale, const QVariantMap &payload, int totalSteps=1)
Starts an export using a QML selection payload.
void exportFailed(const QString &error)
Emitted when an export failed.
int currentMode() const noexcept
Returns the current export workflow mode.
void setExportLogSink(ExportLogSink sink)
Sets the sink used to save export logs through workspace commands.
void pauseExport()
Pauses the running export.
std::function< void(const core::ports::workspace::ExportLogSnapshot &)> ExportLogSink
void clearActiveExportLog()
Clears the active export log selection.
void cancelExport()
Requests cancellation of the running export.
ExportWorkflow(StateSnapshotProvider stateSnapshotProvider, std::shared_ptr< ui::adapters::ExportAdapter > exportAdapter, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter, QObject *parent=nullptr)
Creates an export workflow backed by a snapshot provider and runner.
void stateChanged()
Emitted when export workflow state changed.
std::function< core::ports::workspace::WorkspaceSnapshot()> StateSnapshotProvider
void exportFinished(bool success)
Emitted when an export finished or stopped.
void resumeExport()
Resumes the paused export.
Defines shared structured error codes.
constexpr const char * ExceptionError
Fallback code for exception-backed errors.
constexpr const char * GenericError
Fallback code for generic structured errors.
constexpr const char * ExceptionStd
Code used when a standard exception is captured.
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.
constexpr const char * FlowExportStarted
constexpr const char * FlowExportFailed
constexpr auto kIncludeFormulas
void reportFlow(core::ports::diagnostics::IErrorReporter *reporter, core::errors::ErrorSeverity severity, const char *code, const char *origin, std::string message, core::errors::ErrorContext context={})
Reports a UI flow error event.
void traceWorkflow(const char *origin, std::string message, core::errors::ErrorContext context={})
Emits a workflow trace event.
std::string toStdString(const QString &value)
Converts a QString to a UTF-8 encoded std::string.
QString phaseStarting()
Phase text when an export has just been submitted.
QString phaseCanceled()
Phase text after an export was canceled.
QString runnerUnavailable()
Error text when no export runner has been wired.
QString phaseRunning()
Phase text while an export is running.
QString phaseFinished()
Phase text after an export completed successfully.
QString statusFailed()
Export log status for failed runs.
QString phaseCancelRequested()
Phase text after the user requests export cancellation.
QString statusSuccess()
Export log status for successful runs.
QString statusCanceled()
Export log status for canceled runs.
QString startingDetail()
Export log detail for newly started runs.
QString successDetail()
Export log detail for successful runs.
QString statusRunning()
Export log status for active runs.
QString phasePaused()
Phase text while an export is paused.
QString exportStateUnavailable()
Error text when export state cannot be read.
QString exportFailed()
Generic export failure text.
Describes one export run against a workspace snapshot.
bool shouldCancel() const
void setPaused(bool value)
std::condition_variable changed