From 43551584e1831b2e55c59918e0c741a94614487b Mon Sep 17 00:00:00 2001 From: aiekick Date: Tue, 11 Jun 2024 01:47:49 +0200 Subject: [PATCH] [ADD] : add a threaded import dialog --- 3rdparty/ImGuiPack | 2 +- plugins/LCLBroker/src/Abstract/Base.cpp | 55 +++++ plugins/LCLBroker/src/Abstract/Base.h | 15 +- .../LCLBroker/src/Headers/LCLBrokerBuild.h | 4 +- .../src/Modules/OfcAccountStatementModule.cpp | 19 +- .../src/Modules/PdfAccountStatementModule.cpp | 18 +- src/Backend/MainBackend.cpp | 4 + src/Frontend/Dialogs/AccountDialog.cpp | 4 +- src/Frontend/Dialogs/AccountDialog.h | 2 +- src/Frontend/Dialogs/BankDialog.h | 2 +- src/Frontend/Dialogs/CategoryDialog.h | 2 +- src/Frontend/Dialogs/EntityDialog.h | 2 +- src/Frontend/Dialogs/OperationDialog.h | 2 +- src/Frontend/Dialogs/TransactionDialog.cpp | 8 +- src/Frontend/Dialogs/TransactionDialog.h | 2 +- src/Frontend/Dialogs/abstract/ADataDialog.cpp | 16 +- .../{ADataDialog.hpp => ADataDialog.h} | 1 - src/Headers/CashMeBuild.h | 4 +- src/Models/DataBase.cpp | 14 +- src/Panes/AccountPane.cpp | 53 ++-- src/Panes/AccountPane.h | 10 +- src/Threads/ImportWorkerThread.cpp | 233 ++++++++++++++++++ src/Threads/ImportWorkerThread.h | 64 +++++ 23 files changed, 416 insertions(+), 120 deletions(-) create mode 100644 plugins/LCLBroker/src/Abstract/Base.cpp rename src/Frontend/Dialogs/abstract/{ADataDialog.hpp => ADataDialog.h} (88%) create mode 100644 src/Threads/ImportWorkerThread.cpp create mode 100644 src/Threads/ImportWorkerThread.h diff --git a/3rdparty/ImGuiPack b/3rdparty/ImGuiPack index c0218c0..76eecad 160000 --- a/3rdparty/ImGuiPack +++ b/3rdparty/ImGuiPack @@ -1 +1 @@ -Subproject commit c0218c005a58858317cff4994e75278cdce08681 +Subproject commit 76eecadcc2212295d4a2e7b88a6e729a9108c230 diff --git a/plugins/LCLBroker/src/Abstract/Base.cpp b/plugins/LCLBroker/src/Abstract/Base.cpp new file mode 100644 index 0000000..80286e8 --- /dev/null +++ b/plugins/LCLBroker/src/Abstract/Base.cpp @@ -0,0 +1,55 @@ +#include +#include + +bool parseDescription(const std::string& vDesc, // + std::string& vOutEntity, + std::string& vOutOperation, + std::string& vOutDescription) { + bool ret = false; + if (!vDesc.empty()) { + vOutDescription = vDesc; + const auto first_not_space_pos = vOutDescription.find_first_not_of(' '); + if (first_not_space_pos != std::string::npos) { + auto space_pos = vOutDescription.find(' ', first_not_space_pos); + if (space_pos != std::string::npos) { + vOutOperation = vOutDescription.substr(first_not_space_pos, space_pos - first_not_space_pos); + ++space_pos; // inc from ' ' + const auto first_slash_pos = vOutDescription.find('/', space_pos); + if (first_slash_pos != std::string::npos) { + const auto end_space_pos = vOutDescription.rfind(' ', first_slash_pos); + if (end_space_pos != std::string::npos) { + vOutEntity = vOutDescription.substr(space_pos, end_space_pos - space_pos); + } + } + if (vOutEntity.empty()) { + vOutEntity = vOutDescription.substr(space_pos); + } + } + } + while (ct::replaceString(vOutEntity, " ", " ")) { + } + while (ct::replaceString(vOutOperation, " ", " ")) { + } + while (ct::replaceString(vOutDescription, " ", " ")) { + } + if (vOutDescription.front() == ' ') { + vOutDescription = vOutDescription.substr(1); + } + if (vOutDescription.back() == ' ') { + vOutDescription = vOutDescription.substr(0, vOutDescription.size() - 1U); + } + ret = true; + } + return ret; +} + +bool Base::init(Cash::PluginBridge* vBridgePtr) { + return true; +}; + +void Base::unit() { +}; + +std::string Base::getFileExt() const { + return ""; +} diff --git a/plugins/LCLBroker/src/Abstract/Base.h b/plugins/LCLBroker/src/Abstract/Base.h index c76741e..09831f2 100644 --- a/plugins/LCLBroker/src/Abstract/Base.h +++ b/plugins/LCLBroker/src/Abstract/Base.h @@ -2,14 +2,15 @@ #include +bool parseDescription(const std::string& vDesc, // + std::string& vOutEntity, + std::string& vOutOperation, + std::string& vOutDescription); + class Base : public Cash::BankStatementImportModule { public: virtual ~Base() = default; - bool init(Cash::PluginBridge* vBridgePtr) final { - return true; - }; - void unit() final{}; - std::string getFileExt() const override { - return ""; - } + bool init(Cash::PluginBridge* vBridgePtr) final; + void unit() final; + std::string getFileExt() const override; }; \ No newline at end of file diff --git a/plugins/LCLBroker/src/Headers/LCLBrokerBuild.h b/plugins/LCLBroker/src/Headers/LCLBrokerBuild.h index 981aff3..ec6247b 100644 --- a/plugins/LCLBroker/src/Headers/LCLBrokerBuild.h +++ b/plugins/LCLBroker/src/Headers/LCLBrokerBuild.h @@ -1,7 +1,7 @@ #pragma once #define LCLBroker_Prefix "LCLBroker" -#define LCLBroker_BuildNumber 249 +#define LCLBroker_BuildNumber 260 #define LCLBroker_MinorNumber 0 #define LCLBroker_MajorNumber 0 -#define LCLBroker_BuildId "0.0.249" +#define LCLBroker_BuildId "0.0.260" diff --git a/plugins/LCLBroker/src/Modules/OfcAccountStatementModule.cpp b/plugins/LCLBroker/src/Modules/OfcAccountStatementModule.cpp index 0d56fa5..3b7906c 100644 --- a/plugins/LCLBroker/src/Modules/OfcAccountStatementModule.cpp +++ b/plugins/LCLBroker/src/Modules/OfcAccountStatementModule.cpp @@ -91,24 +91,7 @@ Cash::AccountStatements OfcAccountStatementModule::importBankStatement(const std trans.trans.amount = ct::dvariant(line).GetD(); } else if (line.find("") != std::string::npos) { ct::replaceString(line, "", ""); - trans.trans.description = line; - const auto& first_not_space = trans.trans.description.find_first_not_of(' '); - if (first_not_space != std::string::npos) { - const auto& space_pos = trans.trans.description.find(' ', first_not_space); - if (space_pos != std::string::npos) { - trans.trans.operation = trans.trans.description.substr(first_not_space, space_pos - first_not_space); - } - } - trans.trans.entity = ""; // todo - while (ct::replaceString(trans.trans.description, " ", " ")) { - // empty - } - if (trans.trans.description.front() == ' ') { - trans.trans.description = trans.trans.description.substr(1); - } - if (trans.trans.description.back() == ' ') { - trans.trans.description = trans.trans.description.substr(0, trans.trans.description.size() - 1U); - } + parseDescription(line, trans.trans.entity, trans.trans.operation, trans.trans.description); } else if (line.find("") != std::string::npos) { ct::replaceString(line, "", ""); trans.trans.comment = line; diff --git a/plugins/LCLBroker/src/Modules/PdfAccountStatementModule.cpp b/plugins/LCLBroker/src/Modules/PdfAccountStatementModule.cpp index d9eeef3..e9b8409 100644 --- a/plugins/LCLBroker/src/Modules/PdfAccountStatementModule.cpp +++ b/plugins/LCLBroker/src/Modules/PdfAccountStatementModule.cpp @@ -492,23 +492,7 @@ class TableSolver { } } else if (idx == 1) { if (is_new_line) { - trans.trans.description = tk.token; - const auto &first_not_space = trans.trans.description.find_first_not_of(' '); - if (first_not_space != std::string::npos) { - const auto &space_pos = trans.trans.description.find(' ', first_not_space); - if (space_pos != std::string::npos) { - trans.trans.operation = trans.trans.description.substr(first_not_space, space_pos - first_not_space); - } - } - trans.trans.entity = "";//todo - while (ct::replaceString(trans.trans.description, " ", " ")) { - } - if (trans.trans.description.front() == ' ') { - trans.trans.description = trans.trans.description.substr(1); - } - if (trans.trans.description.back() == ' ') { - trans.trans.description = trans.trans.description.substr(0, trans.trans.description.size() - 1U); - } + parseDescription(tk.token, trans.trans.entity, trans.trans.operation, trans.trans.description); } else { trans.trans.comment += tk.token; } diff --git a/src/Backend/MainBackend.cpp b/src/Backend/MainBackend.cpp index f631c79..f357931 100644 --- a/src/Backend/MainBackend.cpp +++ b/src/Backend/MainBackend.cpp @@ -33,6 +33,7 @@ #include #include +#include #include @@ -154,6 +155,9 @@ void MainBackend::PostRenderingActions() { ProjectFile::Instance()->Clear(); m_NeedToCloseProject = false; } + + // Backend operation, cant be blocked if a imgui item is not displayed + AccountPane::Instance()->DoBackend(); } bool MainBackend::IsNeedToCloseApp() { diff --git a/src/Frontend/Dialogs/AccountDialog.cpp b/src/Frontend/Dialogs/AccountDialog.cpp index 8fa3cb7..ac644e8 100644 --- a/src/Frontend/Dialogs/AccountDialog.cpp +++ b/src/Frontend/Dialogs/AccountDialog.cpp @@ -128,7 +128,7 @@ void AccountDialog::m_drawContentCreation(const ImVec2& vPos) { m_AccountNameInputText.DisplayInputText(width, "Account Name", "", false, align, true); m_AccountTypeInputText.DisplayInputText(width, "Account Type", "", false, align, true); m_AccountNumberInputText.DisplayInputText(width, "Account Number", "", false, align, true); - m_DisplayAlignedWidget(width, "Base Solde", align, [this]() { ImGui::InputDouble("##BaseSolde", &m_AccountBaseSoldeInputDouble); }); + ImGui::DisplayAlignedWidget(width, "Base Solde", align, [this]() { ImGui::InputDouble("##BaseSolde", &m_AccountBaseSoldeInputDouble); }); } void AccountDialog::m_confirmDialogUpdate() { @@ -152,7 +152,7 @@ void AccountDialog::m_drawContentUpdate(const ImVec2& vPos) { m_AccountNameInputText.DisplayInputText(width, "Account Name", "", false, align, true); m_AccountTypeInputText.DisplayInputText(width, "Account Type", "", false, align, true); m_AccountNumberInputText.DisplayInputText(width, "Account Number", "", false, align, true); - m_DisplayAlignedWidget(width, "Base Solde", align, [this]() { ImGui::InputDouble("##BaseSolde", &m_AccountBaseSoldeInputDouble); }); + ImGui::DisplayAlignedWidget(width, "Base Solde", align, [this]() { ImGui::InputDouble("##BaseSolde", &m_AccountBaseSoldeInputDouble); }); } void AccountDialog::m_confirmDialogDeletion() { diff --git a/src/Frontend/Dialogs/AccountDialog.h b/src/Frontend/Dialogs/AccountDialog.h index d811b09..128e132 100644 --- a/src/Frontend/Dialogs/AccountDialog.h +++ b/src/Frontend/Dialogs/AccountDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include class AccountDialog : public ADataDialog { private: diff --git a/src/Frontend/Dialogs/BankDialog.h b/src/Frontend/Dialogs/BankDialog.h index c6262cc..0918517 100644 --- a/src/Frontend/Dialogs/BankDialog.h +++ b/src/Frontend/Dialogs/BankDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include class BankDialog : public ADataDialog { private: diff --git a/src/Frontend/Dialogs/CategoryDialog.h b/src/Frontend/Dialogs/CategoryDialog.h index 5da535c..da1ce7b 100644 --- a/src/Frontend/Dialogs/CategoryDialog.h +++ b/src/Frontend/Dialogs/CategoryDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include class CategoryDialog : public ADataDialog { private: diff --git a/src/Frontend/Dialogs/EntityDialog.h b/src/Frontend/Dialogs/EntityDialog.h index ff6f9a7..233f77f 100644 --- a/src/Frontend/Dialogs/EntityDialog.h +++ b/src/Frontend/Dialogs/EntityDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include class EntityDialog : public ADataDialog { private: diff --git a/src/Frontend/Dialogs/OperationDialog.h b/src/Frontend/Dialogs/OperationDialog.h index a6afecc..5eddd45 100644 --- a/src/Frontend/Dialogs/OperationDialog.h +++ b/src/Frontend/Dialogs/OperationDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include class OperationDialog : public ADataDialog { private: diff --git a/src/Frontend/Dialogs/TransactionDialog.cpp b/src/Frontend/Dialogs/TransactionDialog.cpp index 21759b2..4d97d46 100644 --- a/src/Frontend/Dialogs/TransactionDialog.cpp +++ b/src/Frontend/Dialogs/TransactionDialog.cpp @@ -56,8 +56,8 @@ void TransactionDialog::m_drawContentCreation(const ImVec2& vPos) { m_TransactionDateInputText.DisplayInputText(width, "Date", "", false, align); m_TransactionDescriptionInputText.DisplayInputText(width, "Description", "", false, align); m_TransactionCommentInputText.DisplayInputText(width, "Comment", "", false, align); - m_DisplayAlignedWidget(width, "Amount", align, [this]() { ImGui::InputDouble("##Amount", &m_TransactionAmountInputDouble); }); - m_DisplayAlignedWidget(width, "Confirmed", align, [this]() { ImGui::CheckBoxBoolDefault("##Confirmed", &m_TransactionConfirmed, false); }); + ImGui::DisplayAlignedWidget(width, "Amount", align, [this]() { ImGui::InputDouble("##Amount", &m_TransactionAmountInputDouble); }); + ImGui::DisplayAlignedWidget(width, "Confirmed", align, [this]() { ImGui::CheckBoxBoolDefault("##Confirmed", &m_TransactionConfirmed, false); }); } void TransactionDialog::m_drawContentUpdate(const ImVec2& vPos) { @@ -72,9 +72,9 @@ void TransactionDialog::m_drawContentUpdate(const ImVec2& vPos) { m_TransactionCommentInputText.DisplayInputText(width, "Comment", "", false, align); // the update all if for descriptive items buit not for amounrt if (getCurrentMode() != DataDialogMode::MODE_UPDATE_ALL) { - m_DisplayAlignedWidget(width, "Amount", align, [this]() { ImGui::InputDouble("##Amount", &m_TransactionAmountInputDouble); }); + ImGui::DisplayAlignedWidget(width, "Amount", align, [this]() { ImGui::InputDouble("##Amount", &m_TransactionAmountInputDouble); }); } - m_DisplayAlignedWidget(width, "Confirmed", align, [this]() { + ImGui::DisplayAlignedWidget(width, "Confirmed", align, [this]() { if (!m_TransactionConfirmedManyValues) { ImGui::CheckBoxBoolDefault("##Confirmed", &m_TransactionConfirmed, false); } else { diff --git a/src/Frontend/Dialogs/TransactionDialog.h b/src/Frontend/Dialogs/TransactionDialog.h index 6ae2100..3a51a41 100644 --- a/src/Frontend/Dialogs/TransactionDialog.h +++ b/src/Frontend/Dialogs/TransactionDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include class TransactionDialog : public ADataDialog { private: diff --git a/src/Frontend/Dialogs/abstract/ADataDialog.cpp b/src/Frontend/Dialogs/abstract/ADataDialog.cpp index 406ea81..df3b997 100644 --- a/src/Frontend/Dialogs/abstract/ADataDialog.cpp +++ b/src/Frontend/Dialogs/abstract/ADataDialog.cpp @@ -1,4 +1,4 @@ -#include +#include ADataDialog::ADataDialog(const char* vPopupLabel) : m_PopupLabel(vPopupLabel) { } @@ -54,17 +54,3 @@ bool ADataDialog::draw(const ImVec2& vPos) { } return ret; } - -void ADataDialog::m_DisplayAlignedWidget(const float& vWidth, const std::string& vLabel, const float& vOffsetFromStart, std::function vWidget) { - float px = ImGui::GetCursorPosX(); - ImGui::Text("%s", vLabel.c_str()); - ImGui::SameLine(vOffsetFromStart); - const float w = vWidth - (ImGui::GetCursorPosX() - px); - ImGui::PushID(++ImGui::CustomStyle::pushId); - ImGui::PushItemWidth(w); - if (vWidget != nullptr) { - vWidget(); - } - ImGui::PopItemWidth(); - ImGui::PopID(); -} \ No newline at end of file diff --git a/src/Frontend/Dialogs/abstract/ADataDialog.hpp b/src/Frontend/Dialogs/abstract/ADataDialog.h similarity index 88% rename from src/Frontend/Dialogs/abstract/ADataDialog.hpp rename to src/Frontend/Dialogs/abstract/ADataDialog.h index a0f0688..2b93ae1 100644 --- a/src/Frontend/Dialogs/abstract/ADataDialog.hpp +++ b/src/Frontend/Dialogs/abstract/ADataDialog.h @@ -38,5 +38,4 @@ class ADataDialog { virtual bool m_canConfirm() = 0; virtual void m_confirmDialog() = 0; virtual void m_cancelDialog() = 0; - void m_DisplayAlignedWidget(const float& vWidth, const std::string& vLabel, const float& vOffsetFromStart, std::function vWidget); }; diff --git a/src/Headers/CashMeBuild.h b/src/Headers/CashMeBuild.h index 2fa8b3c..3102128 100644 --- a/src/Headers/CashMeBuild.h +++ b/src/Headers/CashMeBuild.h @@ -1,7 +1,7 @@ #pragma once #define CashMe_Prefix "CashMe" -#define CashMe_BuildNumber 533 +#define CashMe_BuildNumber 544 #define CashMe_MinorNumber 0 #define CashMe_MajorNumber 0 -#define CashMe_BuildId "0.0.533" +#define CashMe_BuildId "0.0.544" diff --git a/src/Models/DataBase.cpp b/src/Models/DataBase.cpp index 0c752bb..f3aed93 100644 --- a/src/Models/DataBase.cpp +++ b/src/Models/DataBase.cpp @@ -1357,12 +1357,14 @@ CREATE TABLE settings ( } void DataBase::m_CloseDB() { - if (m_SqliteDB) { - if (sqlite3_close(m_SqliteDB) == SQLITE_BUSY) { - // try to force closing - sqlite3_close_v2(m_SqliteDB); + if (!m_TransactionStarted) { + if (m_SqliteDB) { + if (sqlite3_close(m_SqliteDB) == SQLITE_BUSY) { + // try to force closing + sqlite3_close_v2(m_SqliteDB); + } } + m_SqliteDB = nullptr; + // there is also sqlite3LeaveMutexAndCloseZombie when sqlite is stucked } - m_SqliteDB = nullptr; - // there is also sqlite3LeaveMutexAndCloseZombie when sqlite is stucked } diff --git a/src/Panes/AccountPane.cpp b/src/Panes/AccountPane.cpp index 1db6fe5..a852119 100644 --- a/src/Panes/AccountPane.cpp +++ b/src/Panes/AccountPane.cpp @@ -75,6 +75,7 @@ bool AccountPane::DrawDialogsAndPopups(const uint32_t& /*vCurrentFrame*/, const const ImVec2 center = vRect.GetCenter(); bool ret = false; + ret |= m_BankDialog.draw(center); if (m_AccountDialog.draw(center)) { DebitCreditPane::Instance()->Load(); @@ -83,6 +84,8 @@ bool AccountPane::DrawDialogsAndPopups(const uint32_t& /*vCurrentFrame*/, const ret |= m_CategoryDialog.draw(center); ret |= m_OperationDialog.draw(center); ret |= m_TransactionDialog.draw(center); + + m_ImportThread.drawDialog(center); if (ret) { m_refreshDatas(); @@ -114,6 +117,10 @@ bool AccountPane::DrawWidgets(const uint32_t& /*vCurrentFrame*/, ImGuiContext* v return false; } +void AccountPane::DoBackend() { + m_ImportThread.finishIfNeeded(); +} + void AccountPane::Load() { m_refreshDatas(); } @@ -549,42 +556,16 @@ void AccountPane::m_GetAvailableDataBrokers() { } } -void AccountPane::m_ImportFromFiles(const std::vector vFiles) { - auto ptr = m_SelectedBroker.lock(); - if (ptr != nullptr) { - for (const auto& file : vFiles) { - const auto& stmt = ptr->importBankStatement(file); - if (!stmt.statements.empty()) { - RowID account_id = 0U; - if (DataBase::Instance()->GetAccount(stmt.account.number, account_id)) { - if (DataBase::Instance()->BeginTransaction()) { - for (const auto& s : stmt.statements) { - DataBase::Instance()->AddTransaction( // - account_id, - s.entity, - s.operation, - s.category, - s.source, - s.source_type, - s.source_sha1, - s.date, - s.description, - s.comment, - s.amount, - s.confirmed, - s.hash); - } - DataBase::Instance()->CommitTransaction(); - m_refreshDatas(); - m_refreshFiltering(); - } - } else { - LogVarError("Import interrupted, no account found for %s", stmt.account.number.c_str()); - break; - } - } - } - } +void AccountPane::m_ImportFromFiles(const std::vector& vFiles) { + m_ImportThread.start( // + "Import Datas", + m_SelectedBroker, + vFiles, + [this]() { + m_refreshDatas(); + m_refreshFiltering(); + }, + nullptr); } void AccountPane::m_ResetFiltering() { diff --git a/src/Panes/AccountPane.h b/src/Panes/AccountPane.h index ce6a50c..cda5556 100644 --- a/src/Panes/AccountPane.h +++ b/src/Panes/AccountPane.h @@ -8,7 +8,7 @@ #include #include - +#include #include #include #include @@ -45,6 +45,8 @@ class AccountPane : public AbstractPane, public conf::ConfigAbstract { ImGuiListClipper m_TransactionsListClipper; size_t m_SelectedAccountIdx = 0U; + ImportWorkerThread m_ImportThread; + BankDialog m_BankDialog; AccountDialog m_AccountDialog; EntityDialog m_EntityDialog; @@ -73,6 +75,8 @@ class AccountPane : public AbstractPane, public conf::ConfigAbstract { bool DrawPanes(const uint32_t& vCurrentFrame, bool* vOpened = nullptr, ImGuiContext* vContextPtr = nullptr, void* vUserDatas = nullptr) override; bool DrawDialogsAndPopups(const uint32_t& vCurrentFrame, const ImRect& vRect, ImGuiContext* vContextPtr = nullptr, void* vUserDatas = nullptr) override; + void DoBackend(); + void Load(); std::string getXml(const std::string& vOffset, const std::string& vUserDatas) override; @@ -90,7 +94,6 @@ class AccountPane : public AbstractPane, public conf::ConfigAbstract { void m_drawDebugMenu(FrameActionSystem& vFrameActionSystem); void m_Clear(); void m_GetAvailableDataBrokers(); - void m_ImportFromFiles(const std::vector vFiles); void m_ResetFiltering(); void m_refreshFiltering(); void m_SelectOrDeselectRow(const Transaction& vTransaction); @@ -109,10 +112,11 @@ class AccountPane : public AbstractPane, public conf::ConfigAbstract { void m_drawAccountMenu(const Account& vAccount); void m_drawTransactionMenu(const Transaction& vTransaction); - void m_drawSearchRow(); void m_drawAmount(const double& vAmount); + void m_ImportFromFiles(const std::vector& vFiles); + public: // singleton static std::shared_ptr Instance() { static std::shared_ptr _instance = std::make_shared(); diff --git a/src/Threads/ImportWorkerThread.cpp b/src/Threads/ImportWorkerThread.cpp new file mode 100644 index 0000000..5e37de7 --- /dev/null +++ b/src/Threads/ImportWorkerThread.cpp @@ -0,0 +1,233 @@ +#include +#include +#include + +#include + +#define DIALOG_WIDTH_ALIGN 80.0f +#define DIALOG_WIDTH_ITEM 300.0f + +std::mutex ImportWorkerThread::s_Mutex; +std::atomic ImportWorkerThread::a_Progress(0.0f); +std::atomic ImportWorkerThread::a_Working(false); +std::atomic ImportWorkerThread::a_GenerationTime(0.0f); +std::atomic ImportWorkerThread::a_ErrorsCount(0U); + +void ImportWorkerThread::start( // + const char* vTitle, // + Cash::BankStatementModuleWeak vBrocker, + std::vector vFiles, + std::function vFinishFunc, + std::function vCancelFunc) { + if (!stop()) { + m_CountFiles = static_cast(vFiles.size()); + m_Title = vTitle; + m_FinishFunc = vFinishFunc; + m_CancelFunc = vCancelFunc; + m_WorkerThread = std::thread( // + &ImportWorkerThread::m_worker, + this, + std::ref(a_Progress), + std::ref(a_Working), + std::ref(a_GenerationTime), + std::ref(a_ErrorsCount), + vBrocker, + vFiles); + } +} + +bool ImportWorkerThread::stop() { + bool res = false; + res = isJoinable(); + if (res) { + a_Working = false; // must be done before join + join(); + if (m_FinishFunc != nullptr) { + m_FinishFunc(); + } + } + return res; +} + +bool ImportWorkerThread::cancel() { + bool res = false; + res = isJoinable(); + if (res) { + a_Working = false; // must be done before join + join(); + if (m_CancelFunc != nullptr) { + m_CancelFunc(); + } + } + return res; +} + +bool ImportWorkerThread::isJoinable() { + return m_WorkerThread.joinable(); +} + +void ImportWorkerThread::join() { + m_WorkerThread.join(); +} + +void ImportWorkerThread::finishIfNeeded() { + if (isJoinable() && a_Working == false) { + join(); + if (m_FinishFunc != nullptr) { + m_FinishFunc(); + } + } +} + +void ImportWorkerThread::drawDialog(const ImVec2& vPos) { + if (a_Working == true) { + ImGui::OpenPopup(m_Title); + ImGui::SetNextWindowPos(vPos, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal( // + m_Title, // + (bool*)nullptr, // + ImGuiWindowFlags_NoTitleBar | // + ImGuiWindowFlags_NoResize | // + ImGuiWindowFlags_AlwaysAutoResize | // + ImGuiWindowFlags_NoDocking)) { + ImGui::Header(m_Title); + ImGui::Separator(); + m_drawPhase(); + m_drawProgressBar(); + ImGui::Separator(); + if (ImGui::ContrastedButton("Stop")) { + cancel(); + } + ImGui::EndPopup(); + } + } +} + +void ImportWorkerThread::m_setCurrentParsedFile(const std::string& vFile) { + s_Mutex.lock(); + m_CurrentParsedFile = vFile; + s_Mutex.unlock(); +} + +void ImportWorkerThread::m_setCurrentPhase(const std::string& vPhase) { + s_Mutex.lock(); + m_CurrentPhase = vPhase; + s_Mutex.unlock(); +} + +void ImportWorkerThread::m_drawPhase() { + s_Mutex.lock(); + if (!m_CurrentParsedFile.empty()) { + ImGui::DisplayAlignedWidget(DIALOG_WIDTH_ITEM, "Current file", DIALOG_WIDTH_ALIGN, [this]() { // + ImGui::TextWrapped("%s", m_CurrentParsedFile.c_str()); + }); + } + ImGui::DisplayAlignedWidget(DIALOG_WIDTH_ITEM, "Phase", DIALOG_WIDTH_ALIGN, [this]() { // + ImGui::Text("%s", m_CurrentPhase.c_str()); + }); + s_Mutex.unlock(); + if (a_ErrorsCount > 0) { + ImGui::DisplayAlignedWidget(DIALOG_WIDTH_ITEM, "Errors", DIALOG_WIDTH_ALIGN, [this]() { // + const uint32_t errors = a_ErrorsCount; + ImGui::Text("%u/%u", errors, m_CountFiles); + }); + } +} + +void ImportWorkerThread::m_drawProgressBar() { + ImGui::DisplayAlignedWidget(DIALOG_WIDTH_ITEM, "Progres", DIALOG_WIDTH_ALIGN, [this]() { // + float progress = a_Progress; + float elapsed_time = a_GenerationTime; + const char* text; + ImFormatStringToTempBuffer(&text, nullptr, "%.3f s", elapsed_time); + ImGui::ProgressBar(progress, ImVec2(300.0f, 0.0f), text); + }); +} + +void ImportWorkerThread::m_worker( // + std::atomic& vProgress, + std::atomic& vWorking, + std::atomic& vGenerationTime, + std::atomic& vErrorsCount, + Cash::BankStatementModuleWeak vBrocker, + std::vector vFiles) { + vProgress = 0.0f; + vGenerationTime = 0.0f; + vWorking = true; + const auto mt0 = ct::GetTicks(); + auto ptr = vBrocker.lock(); + if (ptr != nullptr) { + if (!vFiles.empty()) { + // N Files for Parsing + // + N Files for DB writing + // + 1 Transaction Commit + // => N Files * 2U + 1 + const float progress_steps = 1.0f / static_cast(vFiles.size() * 2U + 1U); + std::vector stmts; + // 1) parsing + if (vWorking) { + m_setCurrentPhase("Parsing"); + for (const auto& file : vFiles) { + m_setCurrentParsedFile(file); + const auto& stmt = ptr->importBankStatement(file); + if (!stmt.statements.empty()) { + stmts.push_back(stmt); + } + vGenerationTime = (ct::GetTicks() - mt0) / 1000.0f; + vProgress = vProgress + progress_steps; + if (!vWorking) { + break; + } + } + } + m_setCurrentParsedFile({}); // clear current file + // 2) database writing + if (vWorking && !stmts.empty()) { + m_setCurrentPhase("DataBase Transaction Insertion"); + if (DataBase::Instance()->BeginTransaction()) { + for (const auto& stmt : stmts) { + RowID account_id = 0U; + if (DataBase::Instance()->GetAccount(stmt.account.number, account_id)) { + for (const auto& stm : stmt.statements) { + DataBase::Instance()->AddTransaction( // + account_id, + stm.entity, + stm.operation, + stm.category, + stm.source, + stm.source_type, + stm.source_sha1, + stm.date, + stm.description, + stm.comment, + stm.amount, + stm.confirmed, + stm.hash); + vGenerationTime = (ct::GetTicks() - mt0) / 1000.0f; + if (!vWorking) { + break; + } + } + } else { + LogVarError( // + "Import interrupted for the file %s, no account found for %s", // + stmt.source.c_str(), // + stmt.account.number.c_str()); + ++vErrorsCount; + } + vProgress = vProgress + progress_steps; + if (!vWorking) { + break; + } + } + m_setCurrentPhase("DataBase Transaction Commit"); + DataBase::Instance()->CommitTransaction(); + } + } + } + } + // For the thread can be joined, the flow must quit the function + // The vWorking atomic control boolean is the way for quit during the run + // in a normal case (no suer stop), the join will be called only if the atomic vWorking is false + vWorking = false; +} diff --git a/src/Threads/ImportWorkerThread.h b/src/Threads/ImportWorkerThread.h new file mode 100644 index 0000000..1d491fd --- /dev/null +++ b/src/Threads/ImportWorkerThread.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +struct ImportWorkerThreadDatas { + +}; + +class ImportWorkerThread { +private: + // static ImportWorkerThreadDatas *s_ThreadDatas; + static std::mutex s_Mutex; + static std::atomic a_Progress; // [0.0:1.0] + static std::atomic a_Working; + static std::atomic a_GenerationTime; // secondes + static std::atomic a_ErrorsCount; // count + +private: + std::thread m_WorkerThread; + float m_GenerationTime = 0.0f; + std::function m_FinishFunc; + std::function m_CancelFunc; + const char* m_Title = nullptr; + uint32_t m_CountFiles = 0U; + + // thread shared data (need a mutex lock) + std::string m_CurrentParsedFile; + std::string m_CurrentPhase; + +public: + void start( // + const char* vTitle, // + Cash::BankStatementModuleWeak vBrocker, + std::vector vFiles, + std::function vFinishFunc = nullptr, + std::function vCancelFunc = nullptr); + bool stop(); + bool cancel(); + bool isJoinable(); + void join(); + void finishIfNeeded(); + void drawDialog(const ImVec2& vPos); + +private: + void m_setCurrentParsedFile(const std::string& vFile); + void m_setCurrentPhase(const std::string& vPhase); + void m_drawPhase(); + void m_drawProgressBar(); + void m_worker( // + std::atomic& vProgress, + std::atomic& vWorking, + std::atomic& vGenerationTime, + std::atomic& vErrorsCount, + Cash::BankStatementModuleWeak vBrocker, + std::vector vFiles); +};