// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * Copyright (c) 2019 Zheng Lei (realthunder) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ****************************************************************************/ #include #include "AutoTransaction.h" #include "Application.h" #include "Document.h" #include "Transactions.h" FC_LOG_LEVEL_INIT("App", true, true) using namespace App; static int _TransactionLock; static int _TransactionClosed; AutoTransaction::AutoTransaction(const char* name, bool tmpName) { auto& app = GetApplication(); if (name && app._activeTransactionGuard >= 0) { if (!app.getActiveTransaction() || (!tmpName && app._activeTransactionTmpName)) { FC_LOG("auto transaction '" << name << "', " << tmpName); tid = app.setActiveTransaction(name); app._activeTransactionTmpName = tmpName; } } // We use negative transaction guard to disable auto transaction from here // and any stack below. This is to support user setting active transaction // before having any existing AutoTransaction on stack, or 'persist' // transaction that can out live AutoTransaction. if (app._activeTransactionGuard < 0) { --app._activeTransactionGuard; } else if (tid || app._activeTransactionGuard > 0) { ++app._activeTransactionGuard; } else if (app.getActiveTransaction()) { FC_LOG("auto transaction disabled because of '" << app._activeTransactionName << "'"); --app._activeTransactionGuard; } else { ++app._activeTransactionGuard; } FC_TRACE("construct auto Transaction " << app._activeTransactionGuard); } AutoTransaction::~AutoTransaction() { auto& app = GetApplication(); FC_TRACE("before destruct auto Transaction " << app._activeTransactionGuard); if (app._activeTransactionGuard < 0) { ++app._activeTransactionGuard; } else if (!app._activeTransactionGuard) { #ifdef FC_DEBUG FC_ERR("Transaction guard error"); #endif } else if (--app._activeTransactionGuard == 0) { try { // We don't call close() here, because close() only closes // transaction that we opened during construction time. However, // when _activeTransactionGuard reaches zero here, we are supposed // to close any transaction opened. app.closeActiveTransaction(); } catch (Base::Exception& e) { e.reportException(); } catch (...) { } } FC_TRACE("destruct auto Transaction " << app._activeTransactionGuard); } void AutoTransaction::close(bool abort) { if (tid || abort) { GetApplication().closeActiveTransaction(abort, abort ? 0 : tid); tid = 0; } } void AutoTransaction::setEnable(bool enable) { auto& app = GetApplication(); if (!app._activeTransactionGuard) { return; } if ((enable && app._activeTransactionGuard > 0) || (!enable && app._activeTransactionGuard < 0)) { return; } app._activeTransactionGuard = -app._activeTransactionGuard; FC_TRACE("toggle auto Transaction " << app._activeTransactionGuard); if (!enable && app._activeTransactionTmpName) { bool close = true; for (auto& v : app.DocMap) { if (v.second->hasPendingTransaction()) { close = false; break; } } if (close) { app.closeActiveTransaction(); } } } int Application::setActiveTransaction(const char* name, bool persist) { if (!name || !name[0]) { name = "Command"; } this->signalBeforeOpenTransaction(name); if (_activeTransactionGuard > 0 && getActiveTransaction()) { if (_activeTransactionTmpName) { FC_LOG("transaction rename to '" << name << "'"); for (auto& v : DocMap) { v.second->renameTransaction(name, _activeTransactionID); } } else { if (persist) { AutoTransaction::setEnable(false); } return 0; } } else if (_TransactionLock) { if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { FC_WARN("Transaction locked, ignore new transaction '" << name << "'"); } return 0; } else { FC_LOG("set active transaction '" << name << "'"); _activeTransactionID = 0; for (auto& v : DocMap) { v.second->_commitTransaction(); } _activeTransactionID = Transaction::getNewID(); } _activeTransactionTmpName = false; _activeTransactionName = name; if (persist) { AutoTransaction::setEnable(false); } return _activeTransactionID; } const char* Application::getActiveTransaction(int* id) const { int tid = 0; if (Transaction::getLastID() == _activeTransactionID) { tid = _activeTransactionID; } if (id) { *id = tid; } return tid ? _activeTransactionName.c_str() : nullptr; } void Application::closeActiveTransaction(bool abort, int id) { if (!id) { id = _activeTransactionID; } if (!id) { return; } if (_activeTransactionGuard > 0 && !abort) { FC_LOG("ignore close transaction"); return; } if (_TransactionLock) { if (_TransactionClosed >= 0) { _TransactionLock = abort ? -1 : 1; } FC_LOG("pending " << (abort ? "abort" : "close") << " transaction"); return; } FC_LOG("close transaction '" << _activeTransactionName << "' " << abort); _activeTransactionID = 0; TransactionSignaller signaller(abort, false); for (auto& v : DocMap) { if (v.second->getTransactionID(true) != id) { continue; } if (abort) { v.second->_abortTransaction(); } else { v.second->_commitTransaction(); } } } //////////////////////////////////////////////////////////////////////// TransactionLocker::TransactionLocker(bool lock) : active(lock) { if (lock) { ++_TransactionLock; } } TransactionLocker::~TransactionLocker() { if (active) { try { activate(false); return; } catch (Base::Exception& e) { e.reportException(); } catch (Py::Exception&) { Base::PyException e; e.reportException(); } catch (std::exception& e) { FC_ERR(e.what()); } catch (...) { } FC_ERR("Exception when unlocking transaction"); } } void TransactionLocker::activate(bool enable) { if (active == enable) { return; } active = enable; if (active) { ++_TransactionLock; return; } if (--_TransactionLock != 0) { return; } if (_TransactionClosed) { bool abort = (_TransactionClosed < 0); _TransactionClosed = 0; GetApplication().closeActiveTransaction(abort); } } bool TransactionLocker::isLocked() { return _TransactionLock > 0; }