/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #ifndef __TNW_DB_EXCHANGE_GUARD_H__ #define __TNW_DB_EXCHANGE_GUARD_H__ #include "NwDBExternalGeometryPE.h" #include #include #include #include /** \details Polling policy configuration for job monitoring. Manages exponential backoff and timeout behavior. */ struct NwPollPolicy { std::chrono::milliseconds first_sleep{20}; ///< Initial sleep interval between polls. std::chrono::milliseconds max_sleep{250}; ///< Maximum sleep interval. double backoff = 1.5; ///< Exponential backoff multiplier. std::chrono::milliseconds heartbeat{0}; ///< Heartbeat interval for periodic on_tick() calls (0 = disabled). std::chrono::milliseconds timeout{0}; ///< Operation timeout (0 = no timeout). }; /** \details Callback functions for job lifecycle events. */ struct NwGuardCallbacks { std::function on_tick; ///< Called on each poll if heartbeat > 0. std::function on_progress; ///< Plugin progress callback (optional). std::function on_finish; ///< Called on job completion. }; /** \details Status codes for job guard execution. */ enum class NwGuardStatus { kOk, ///< Job completed successfully. kFailed, ///< Job failed. kCanceled, ///< Job was canceled. kTimeout ///< Job timed out. }; /** \details Result of job guard execution. */ struct NwGuardResult { NwGuardStatus status; ///< Execution status. NwExchangeResult result; ///< Result details (valid for ok/failed status, contains error code if failed). }; /** \details RAII guard class for managing job lifetime. Automatically cancels job on destruction if not completed. */ class NwJobGuard { public: /** \details Constructor with protocol extension and job ID. \param pe Protocol extension instance. \param id Job ID. \param cancel Optional cancel token for external cancellation support. */ NwJobGuard(OdNwExchangeJobManagerBase* pMgr, NwJobId id, NwCancelToken* cancel = nullptr) : m_pMgr(pMgr), m_id(id), m_cancel(cancel) {} NwJobGuard(const NwJobGuard&) = delete; NwJobGuard& operator=(const NwJobGuard&) = delete; /** \details Move constructor. */ NwJobGuard(NwJobGuard&& other) noexcept : m_pMgr(other.m_pMgr) , m_id(other.m_id) , m_cancel(other.m_cancel) , m_finished(other.m_finished) { other.m_finished = true; } NwJobGuard& operator=(NwJobGuard&&) = delete; /** \details Destructor. Performs soft cancellation if job not finished. */ ~NwJobGuard() { if (!m_finished && m_pMgr) { try { m_pMgr->cancel(m_id); } catch (...) {} } } /** \details Run job with polling and callbacks. \param policy Polling policy configuration. \param cb Callback handlers (optional). \returns Job execution result with status. */ NwGuardResult run(const NwPollPolicy& policy, NwGuardCallbacks cb = {}) { using namespace std::chrono; auto sleep = policy.first_sleep; auto next_heartbeat = policy.heartbeat; const auto start = steady_clock::now(); int last_percent = -1; int percent = 0; NwExchangeResult out{}; for (;;) { if (!m_pMgr) return { NwGuardStatus::kFailed, {OdString(L"Manager is null")} }; if (m_cancel && m_cancel->m_flag.load(std::memory_order_relaxed)) { m_pMgr->cancel(m_id); } NwExchangeJobStatus st = m_pMgr->query(m_id, &out); if (cb.on_progress) { // (st == NwExchangeJobStatus::kDone) -> 100% int current_percent = (st == NwExchangeJobStatus::kDone) ? 100 : percent; if (current_percent > 0 && current_percent != last_percent) { cb.on_progress(current_percent); last_percent = current_percent; } } if (st == NwExchangeJobStatus::kDone) { m_finished = true; if (cb.on_finish) cb.on_finish(st); return {NwGuardStatus::kOk, std::move(out)}; } if (st == NwExchangeJobStatus::kFailed) { m_finished = true; if (cb.on_finish) cb.on_finish(st); return {NwGuardStatus::kFailed, std::move(out)}; } if (st == NwExchangeJobStatus::kCanceled) { m_finished = true; if (cb.on_finish) cb.on_finish(st); return {NwGuardStatus::kCanceled, std::move(out)}; } if (policy.timeout.count() > 0) { if (steady_clock::now() - start >= policy.timeout) { m_pMgr->cancel(m_id); m_finished = true; if (cb.on_finish) cb.on_finish(NwExchangeJobStatus::kCanceled); return {NwGuardStatus::kTimeout, {}}; } } if (policy.heartbeat.count() > 0) { if (next_heartbeat <= milliseconds::zero()) { if (cb.on_tick) cb.on_tick(); next_heartbeat = policy.heartbeat; } else { if (cb.on_tick) next_heartbeat -= sleep; } } std::this_thread::sleep_for(sleep); auto next_ms = std::chrono::milliseconds(static_cast(sleep.count() * policy.backoff)); sleep = std::min(next_ms, policy.max_sleep); } } private: OdNwExchangeJobManagerBase* m_pMgr; NwJobId m_id; NwCancelToken* m_cancel = nullptr; bool m_finished = false; }; /** \details Convenience helpers for convert/import operations with job polling. */ struct NwConvertGuard { /** \details Convert OdNwDatabase with job polling. \param pe Protocol extension instance. \param pObj Database object to convert. \param params Conversion parameters. \param policy Polling policy configuration. \param cb Callback handlers (optional). \returns Job execution result with status. */ static NwGuardResult convertTo(OdNwExchangeJobManagerBase* pMgr, const NwExchangeConvertParams& params, const NwPollPolicy& policy, NwGuardCallbacks cb = {}) { if (!pMgr) return { NwGuardStatus::kFailed, {OdString(L"Manager is null")} }; NwJobId id = pMgr->startConvert2(params); if (id == 0) { NwExchangeResult res; res.m_res = OdString(L"Failed to start job"); return { NwGuardStatus::kFailed, std::move(res) }; } NwJobGuard guard{ pMgr, id, params.m_pCancel }; return guard.run(policy, std::move(cb)); } /** \details Import from external file with job polling. \param pe Protocol extension instance. \param path Path to external file. \param params Conversion parameters. \param policy Polling policy configuration. \param cb Callback handlers (optional). \returns Job execution result with status. */ static NwGuardResult importFrom(OdNwExchangeJobManagerBase* pMgr, const OdString& path, const NwExchangeConvertParams& params, const NwPollPolicy& policy, NwGuardCallbacks cb = {}) { if (!pMgr) return { NwGuardStatus::kFailed, {OdString(L"Manager is null")} }; NwJobId id = pMgr->startImportFrom(path, params); if (id == 0) { NwExchangeResult res; res.m_res = OdString(L"Failed to start job"); return { NwGuardStatus::kFailed, std::move(res) }; } NwJobGuard guard{ pMgr, id, params.m_pCancel }; return guard.run(policy, std::move(cb)); } }; #endif //__TNW_DB_EXCHANGE_GUARD_H__