From: mbs Date: Sat, 9 Dec 2023 22:09:53 +0000 (+0000) Subject: [bos#35161][EDF](2023-T1) Automatic backup - initial commit X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=df11907a94bd87d4ee1b1faaf05ceeb1ef2ce877;p=modules%2Fshaper.git [bos#35161][EDF](2023-T1) Automatic backup - initial commit Added temporary debug files. To be removed again when task is finished. updated location of test scripts ensure the old backup folder gets removed fixed creation of backup folder preference check the output of the check_validity.py script code refactoring and bug fixing * backupDoc returns now an error code instead of bool * added check of dumped python script * show warning on error * refactored test and validation scripts removed DEBUG macros --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 0804d5a4e..d1c324798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,8 @@ IF(${HAVE_SALOME}) # without SALOME there is another kind of documentation, separated one SALOME_INSTALL_SCRIPTS("shaper_test.py" ${SHAPER_INSTALL_PYTHON_FILES}) + SALOME_INSTALL_SCRIPTS("test_backup.py" ${SHAPER_INSTALL_PYTHON_FILES}) + SALOME_INSTALL_SCRIPTS("check_validity.py" ${SHAPER_INSTALL_PYTHON_FILES}) ELSE(${HAVE_SALOME}) SET(SHAPER_INSTALL_EXE bin CACHE INTERNAL "" FORCE) SET(SHAPER_INSTALL_ADDONS addons CACHE INTERNAL "" FORCE) diff --git a/check_validity.py b/check_validity.py new file mode 100755 index 000000000..8dc111092 --- /dev/null +++ b/check_validity.py @@ -0,0 +1,146 @@ +import salome +import sys +import os +import SalomePyQt +import ModelAPI +from salome.shaper import model + + +#------------------------------------------------------------------------ +def validateSession(logFile): + """ + Iterate through and write all feature names of each part in the active study + to the logFile. + """ + errCode = 0 + try: + logFile.write("getting SHAPER session\n") + session = ModelAPI.ModelAPI_Session.get() + # aFactory = session.validators() + logFile.write("getting PartSet\n") + aPartSet = session.moduleDocument() + numParts = aPartSet.size("Parts") + for partIdx in range(numParts+1): + aPart = session.document(partIdx) + if aPart is None: + continue + aPart.setActive(True) + if partIdx == 0: + logFile.write("---PartSet:------------------\n") + else: + logFile.write(f"---Part_{partIdx}:------------------\n") + + for aFeat in aPart.allFeatures(): + if aFeat.isInHistory(): + logFile.write(" * {} --> [{}]\n".format(aFeat.getKind(), aFeat.data().name())) + else: + logFile.write(" - {}\n".format(aFeat.data().name())) + except: + errCode = 128 + + return errCode + + +#------------------------------------------------------------------------ +def checkHDF(logFile, hdfFile): + """ + Open and validate a HDF document + """ + errCode = 0 + try: + logFile.write("-----CHECKING HDF DOCUMENT-------\n") + salome.standalone() + logFile.write("initializing SALOME\n") + salome.salome_init(hdfFile, embedded=True, forced=True) + logFile.write("StudyName = {}\n".format(salome.myStudyName)) + + logFile.write("getting KERNEL session\n") + session = salome.naming_service.Resolve('/Kernel/Session') + logFile.write("connecting to study\n") + session.emitMessage("connect_to_study") + + logFile.write("activating SHAPER module\n") + sg = SalomePyQt.SalomePyQt() + sg.activateModule("Shaper") + + errCode = validateSession(logFile) + except: + errCode = 129 + + return errCode + + +#------------------------------------------------------------------------ +def checkPyScript(logFile, pyFile): + """ + Load and validate a Python script + """ + errCode = 0 + try: + logFile.write("-----CHECKING PYTHON SCRIPT-------\n") + logFile.write("executing dumped script\n") + exec(compile(open(pyFile, 'rb').read(), pyFile, 'exec')) + + errCode = validateSession(logFile) + except: + errCode = 130 + + return errCode + + +#------------------------------------------------------------------------ +def validateBackup(fullName): + """ + Open the backed up file and validate its content. + This test script will be called once for the stored HDF file + end once for the dumped Python script. + """ + errCode = 0 + try: + outFolder,filename = os.path.split(fullName) + base,ext = os.path.splitext(filename) + ext = ext[1:].lower() # remove the dot from the extension + + # Create a log file in the backup folder starting with the same base name + logFile = os.path.join(outFolder, base+"_valid.log") + if os.path.exists(logFile): + append_write = 'a' # append if already exists + else: + append_write = 'w' # make a new file if not + + with open(logFile, append_write) as f: + try: + f.write(f"fullName = {fullName}\n") + if ext == "hdf": + errCode = checkHDF(f, fullName) + elif ext == "py": + errCode = checkPyScript(f, fullName) + else: + f.write(f"-----UNSUPPORTED FILE TYPE [{ext}]-------\n") + errCode = 131 + f.write(f"errCode = {errCode}\n") + if errCode == 0: + f.write("{} Test - PASSED\n\n".format(ext.upper())) + else: + f.write("{} Test - FAILED\n\n".format(ext.upper())) + except: + errCode = 132 + f.write("Exception caught\n") + except: + errCode = 133 + + return errCode + + +#------------------------------------------------------------------------ +errCode = 0 +try: + if (len(sys.argv) != 2): + errCode = 134 + else: + fullName = sys.argv[1] + errCode = validateBackup(fullName) +except: + errCode = 135 + +exit(errCode) diff --git a/src/Model/Model_Document.cpp b/src/Model/Model_Document.cpp index 1927466b4..badd2ae19 100644 --- a/src/Model/Model_Document.cpp +++ b/src/Model/Model_Document.cpp @@ -87,6 +87,7 @@ # define _separator_ '/' #endif + static const int UNDO_LIMIT = 1000; // number of possible undo operations (big for sketcher) static const int TAG_GENERAL = 1; // general properties tag @@ -485,7 +486,9 @@ static bool saveDocument(Handle(Model_Application) theApp, } bool Model_Document::save( - const char* theDirName, const char* theFileName, std::list& theResults) + const char* theDirName, const char* theFileName, + std::list& theResults, + bool doBackup/*=false*/) { // if the history line is not in the end, move it to the end before save, otherwise // problems with results restore and (the most important) naming problems will appear diff --git a/src/Model/Model_Document.h b/src/Model/Model_Document.h index 001758919..f8f0d74d9 100644 --- a/src/Model/Model_Document.h +++ b/src/Model/Model_Document.h @@ -73,7 +73,7 @@ class Model_Document : public ModelAPI_Document //! \param theResults the result full file names that were stored by "save" //! \returns true if file was stored successfully MODEL_EXPORT virtual bool save( - const char* theDirName, const char* theFileName, std::list& theResults); + const char* theDirName, const char* theFileName, std::list& theResults, bool doBackup=false); //! Export the list of features to the file //! \param theFilename path to save the file diff --git a/src/Model/Model_Session.cpp b/src/Model/Model_Session.cpp index 270fa1e36..da1fb36ad 100644 --- a/src/Model/Model_Session.cpp +++ b/src/Model/Model_Session.cpp @@ -52,6 +52,7 @@ #include + static Model_Session* myImpl = new Model_Session(); // to redirect all calls to the root document @@ -65,9 +66,9 @@ bool Model_Session::load(const char* theFileName) return aRes; } -bool Model_Session::save(const char* theFileName, std::list& theResults) +bool Model_Session::save(const char* theFileName, std::list& theResults, bool doBackup=false) { - return ROOT_DOC->save(theFileName, "root", theResults); + return ROOT_DOC->save(theFileName, "root", theResults, doBackup); } void Model_Session::closeAll() diff --git a/src/Model/Model_Session.h b/src/Model/Model_Session.h index a93901a20..e355bb52a 100644 --- a/src/Model/Model_Session.h +++ b/src/Model/Model_Session.h @@ -66,7 +66,7 @@ class Model_Session : public ModelAPI_Session, public Events_Listener //! \param theFileName full name of the file to store //! \param theResults the result full file names that were stored by "save" //! \returns true if file was stored successfully - MODEL_EXPORT virtual bool save(const char* theFileName, std::list& theResults); + MODEL_EXPORT virtual bool save(const char* theFileName, std::list& theResults, bool dobackup=false); //! Closes all documents MODEL_EXPORT virtual void closeAll(); diff --git a/src/ModelAPI/ModelAPI_Session.h b/src/ModelAPI/ModelAPI_Session.h index 733e3b6ff..44befaa24 100644 --- a/src/ModelAPI/ModelAPI_Session.h +++ b/src/ModelAPI/ModelAPI_Session.h @@ -59,7 +59,7 @@ protected: //! \param theFileName full name of the file to store //! \param theResults the result full file names that were stored by "save" //! \returns true if file was stored successfully - virtual bool save(const char* theFileName, std::list& theResults) = 0; + virtual bool save(const char* theFileName, std::list& theResults, bool isBackup=false) = 0; //! Closes all documents virtual void closeAll() = 0; diff --git a/src/ModuleBase/ModuleBase_Preferences.cpp b/src/ModuleBase/ModuleBase_Preferences.cpp index 6cb464b7b..af734166f 100644 --- a/src/ModuleBase/ModuleBase_Preferences.cpp +++ b/src/ModuleBase/ModuleBase_Preferences.cpp @@ -220,6 +220,36 @@ void ModuleBase_Preferences::createGeneralTab(ModuleBase_IPrefMgr* thePref, int "part_visualization_study"); thePref->setItemProperty("strings", visuItemList, visuId); thePref->setItemProperty("indexes", visuIdList, visuId); + +#if 1 + // Group related to automatic backup + group = thePref->addPreference(QObject::tr("Automatically backup a study"), generalTab, + SUIT_PreferenceMgr::Auto, QString(), QString()); + + thePref->addPreference(QObject::tr("Enable automatic backup"), group, SUIT_PreferenceMgr::Bool, + ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup"); + + int delay = thePref->addPreference(QObject::tr("Backup Interval" ), group, SUIT_PreferenceMgr::IntSpin, + ModuleBase_Preferences::GENERAL_SECTION, "backup_interval" ); + thePref->setItemProperty( "min", 5, delay ); + thePref->setItemProperty( "max", 1440, delay ); + thePref->setItemProperty( "suffix", " min", delay ); + + int folderPref = thePref->addPreference(QObject::tr("Backup Folder"), group, SUIT_PreferenceMgr::File, + ModuleBase_Preferences::GENERAL_SECTION, "backup_folder"); + thePref->setItemProperty( "mode", Qtx::PT_Directory, folderPref ); + + visuItemList.clear(); + visuItemList << QObject::tr("Store last backup only") + << QObject::tr("Store full backup history"); + visuIdList.clear(); + visuIdList << 0 << 1; + visuId = thePref->addPreference(QObject::tr("Backup Storage"), group, SUIT_PreferenceMgr::Selector, + ModuleBase_Preferences::GENERAL_SECTION, + "auto_backup_storage"); + thePref->setItemProperty("strings", visuItemList, visuId); + thePref->setItemProperty("indexes", visuIdList, visuId); +#endif } void ModuleBase_Preferences::updateSketchTab(ModuleBase_IPrefMgr* thePref, int thePageId) diff --git a/src/SHAPERGUI/CMakeLists.txt b/src/SHAPERGUI/CMakeLists.txt index cba839757..986f0ab20 100644 --- a/src/SHAPERGUI/CMakeLists.txt +++ b/src/SHAPERGUI/CMakeLists.txt @@ -30,6 +30,7 @@ SET(UPDATE_TRANSLATION OFF) SET(PROJECT_HEADERS SHAPER_SHAPERGUI.h SHAPERGUI.h + SHAPERGUI_CheckBackup.h SHAPERGUI_DataModel.h SHAPERGUI_OCCSelector.h SHAPERGUI_SalomeViewer.h @@ -39,6 +40,7 @@ SET(PROJECT_HEADERS SET(PROJECT_MOC_HEADERS SHAPERGUI.h + SHAPERGUI_CheckBackup.h SHAPERGUI_DataModel.h SHAPERGUI_NestedButton.h SHAPERGUI_SalomeViewer.h @@ -50,6 +52,7 @@ QT_WRAP_MOC(PROJECT_AUTOMOC ${PROJECT_MOC_HEADERS}) SET(PROJECT_SOURCES SHAPERGUI.cpp + SHAPERGUI_CheckBackup.cpp SHAPERGUI_DataModel.cpp SHAPERGUI_OCCSelector.cpp SHAPERGUI_SalomeViewer.cpp diff --git a/src/SHAPERGUI/SHAPERGUI.cpp b/src/SHAPERGUI/SHAPERGUI.cpp index 93883f604..2e6ffda44 100644 --- a/src/SHAPERGUI/SHAPERGUI.cpp +++ b/src/SHAPERGUI/SHAPERGUI.cpp @@ -18,6 +18,7 @@ // #include "SHAPERGUI.h" +#include "SHAPERGUI_CheckBackup.h" #include "SHAPERGUI_DataModel.h" #include "SHAPERGUI_OCCSelector.h" #include "SHAPERGUI_NestedButton.h" @@ -33,6 +34,7 @@ #include #include #include +#include #include #include @@ -76,10 +78,23 @@ #include #include #include +#include +#include +#include + +#include +#include +#include #include #include +//--------------------------------------------------------- +// Use a short 1 min interval for auto backup for debugging +// Uncomment the following line for debugging. +//#define DBG_BACKUP_INTERVAL 1 +//--------------------------------------------------------- + #if OCC_VERSION_HEX < 0x070400 #define SALOME_PATCH_FOR_CTRL_WHEEL #endif @@ -137,7 +152,6 @@ private: - //****************************************************** SHAPERGUI::SHAPERGUI() : LightApp_Module("SHAPER"), @@ -153,6 +167,12 @@ SHAPERGUI::SHAPERGUI() ModuleBase_Preferences::setResourceMgr(application()->resourceMgr()); + // initialize backup timer + myBackupTimer = new QTimer( this ); + myBackupTimer->setSingleShot( true ); + connect( myBackupTimer, SIGNAL( timeout() ), this, SLOT( onBackupDoc() ) ); + connect( this, SIGNAL( backupDone(QString,int) ), this, SLOT( onBackupDone(QString,int) )); + // It will be called in XGUI_Workshop::startApplication // ModuleBase_Preferences::loadCustomProps(); } @@ -383,6 +403,22 @@ bool SHAPERGUI::activateModule(SUIT_Study* theStudy) this, SLOT(onSaveDocByShaper())); connect(getApp()->action(LightApp_Application::FileSaveAsId), SIGNAL(triggered(bool)), this, SLOT(onSaveAsDocByShaper())); + + SUIT_ResourceMgr* aResMgr = application()->resourceMgr(); + if ( aResMgr && application()->activeStudy() ) { + bool useBackup = aResMgr->booleanValue( ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup", true ); + if (useBackup) { + int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 5 ); + if ( backupInterval > 0 ){ +#ifdef DBG_BACKUP_INTERVAL + backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging +#endif + myBackupTimer->start( backupInterval*60000 ); + } + } + } + //connect(); + updateInfoPanel(); //connect(myWorkshop->operationMgr(), SIGNAL(operationResumed(ModuleBase_Operation*)), @@ -431,11 +467,12 @@ bool SHAPERGUI::deactivateModule(SUIT_Study* theStudy) saveToolbarsConfig(); myWorkshop->deactivateModule(); + myBackupTimer->stop(); + myIsInspectionVisible = myInspectionPanel->isVisible(); myIsFacesPanelVisible = myWorkshop->facesPanel()->isVisible(); hideInternalWindows(); - // the active operation should be stopped for the next activation. // There should not be active operation and visualized preview. // Abort operation should be performed before the selection's remove @@ -530,16 +567,58 @@ static void onOperationGeneric( ModuleBase_Operation* theOperation, event ); } +class WaitBackupResetter { +public: + WaitBackupResetter(XGUI_Workshop *theWshop) + :myWorkshop(theWshop) + { + } + WaitBackupResetter() = delete; + ~WaitBackupResetter() + { + if (myWorkshop) + { + myWorkshop->setWaitForBackup(false); + myWorkshop = nullptr; + } + } +private: + XGUI_Workshop *myWorkshop; +}; + //****************************************************** void SHAPERGUI::onOperationCommitted(ModuleBase_Operation* theOperation) { onOperationGeneric(theOperation, moduleName(), "committed"); + + checkForWaitingBackup(); } //****************************************************** void SHAPERGUI::onOperationAborted(ModuleBase_Operation* theOperation) { onOperationGeneric(theOperation, moduleName(), "aborted"); + + checkForWaitingBackup(); +} + +//****************************************************** +void SHAPERGUI::checkForWaitingBackup() +{ + if (myWorkshop && myWorkshop->waitForBackup()) + { + XGUI_OperationMgr *operMgr = myWorkshop->operationMgr(); + if (operMgr && operMgr->hasOperation()) + { + // If the user is still running an operation, e.g. we left the line creation + // during sketch creation, do not yet create the backup + std::cout << "---> There are still active operations!" << std::endl; + return; + } + // There are no more active operations => we can now do the backup + WaitBackupResetter _resetter(myWorkshop); + onBackupDoc(); + } } //****************************************************** @@ -644,6 +723,227 @@ void SHAPERGUI::onSaveAsDocByShaper() getApp()->onSaveAsDoc(); } +//****************************************************** +void SHAPERGUI::onBackupDoc() +{ + // We cannot save the study while we are still in an ongoing operation + // => so test for this case first and delay the backup to the time when operation finishes + if (myWorkshop && myWorkshop->operationMgr()) + { + if (myWorkshop->operationMgr()->hasOperation()) + { + // If the user is running an operation, only do the backup + // after the operation has finished (committed or cancelled). + myWorkshop->setWaitForBackup(); + return; + } + // Run the backup in a separate thread!! + myBackupResult = std::async(std::launch::async, [this]{return this->backupDoc();}); + } +} + +class LockBackupState { +public: + LockBackupState(XGUI_Workshop *wshop) : myWorkshop(wshop) + { + if (myWorkshop) myWorkshop->setBackupState(); + } + ~LockBackupState() + { + if (myWorkshop) myWorkshop->setBackupState(false); + myWorkshop = nullptr; + } + XGUI_Workshop *myWorkshop; +}; + +//****************************************************** +int SHAPERGUI::backupDoc() +{ + if (myWorkshop->backupState()) { + // This should never happen as I restart the backup timer only when a backup has finished + myBackupError = tr("Another backup is still running"); + return 32; + } + + int aResult = 0; + bool isOk = false; + SUIT_Study* study = application()->activeStudy(); + if ( !study ) { + myBackupError = tr("There is no active study"); + return 33; + } + + LockBackupState lockBackup(myWorkshop); + + QString aFolder{}; + try + { + QString aName = study->studyName(); + if ( aName.isEmpty() ) { + myBackupError = tr("Study name is empty"); + return 34; + } + + const QChar aSep = QDir::separator(); + SUIT_ResourceMgr* aResMgr = application()->resourceMgr(); + if ( aResMgr && application()->activeStudy() ) { + aFolder = aResMgr->path( ModuleBase_Preferences::GENERAL_SECTION, "backup_folder", "" ); + } + if (aFolder.isEmpty()) { +#ifdef HAVE_SALOME + aFolder = XGUI_Tools::getTmpDirByEnv("SALOME_TMP_DIR").c_str(); +#else + aFolder = XGUI_Tools::getTmpDirByEnv("").c_str(); +#endif + } + if (aFolder.endsWith(aSep)) + aFolder = aFolder.remove(aFolder.length()-1,1); + + QDateTime now = QDateTime::currentDateTime(); + aFolder += aSep + now.toString("yyyyMMdd_hhmmss"); + + QDir aDir(aFolder); + if (!aDir.exists()) { + aDir.mkpath(aFolder); + aDir.mkdir(aFolder); + if (!aDir.exists()) { + myBackupError = tr("Cannot create backup folder"); + return 35; + } + } + + if (study->isSaved()) { + // Retrieve the filename only from the fullpath + QFileInfo fi(aName); + aName = fi.completeBaseName(); + } + QString aFullName = aFolder + aSep + aName + QString(".hdf"); + + // Save the study into a single HDF file + isOk = study->saveDocumentAs( aFullName, true ); + if (!isOk){ + myBackupError = tr("Cannot backup study document"); + return 36; + } + + // Now, dump the python script + LightApp_Study *lightStudy = dynamic_cast(study); + if (!lightStudy) { + myBackupError = tr("Study is not dumpable"); + return 37; + } + + aFullName = aFolder + aSep + aName + QString(".py"); + isOk = lightStudy->dump(aFullName, true, false, false); + if (!isOk){ + myBackupError = tr("Cannot backup python script"); + return 38; + } + + // Finally start another salome process and reload the saved document & script for verification + SHAPERGUI_CheckBackup checkBackup(aFolder, aName); + QString testBackup("check_validity.py"); + QStringList dirs; + dirs << QString(std::getenv("SHAPER_ROOT_DIR")) + << QString("bin") + << QString("salome") + << testBackup; + QString testScript = dirs.join( QDir::separator() ); + aResult = checkBackup.run(testScript); + } + catch (std::exception &ex) + { + myBackupError = tr("std::exception caught"); + aResult = 39; + } + catch (...) + { + myBackupError = tr("unknown exception caught"); + aResult = 40; + } + + emit backupDone(aFolder, aResult); + return aResult; +} + +//****************************************************** +void SHAPERGUI::onBackupDone(QString aFolder, int aResult) +{ + int aErrCode = myBackupResult.get(); + bool isOk = (aResult == 0); + if (isOk) + { + putInfo(tr("Backup done in folder: %1").arg(aFolder), 5000 ); + } + else + { + QString aMsg = tr("Failed to backup active study!\nError Code: %1").arg(aResult); + QMessageBox::warning(application()->desktop(), tr("Automatic Backup"), aMsg); + } + + int aBackupStorage{-1}; + SUIT_ResourceMgr* aResMgr = application()->resourceMgr(); + if ( aResMgr ) + { + aBackupStorage = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "auto_backup_storage", -1); + } + if (aBackupStorage == 0/*StoreLastBackupOnly*/) + { + // Only keep the latest successful backup => delete the previous one, if it exists + if (isOk && !myLastBackupFolder.isEmpty()) + { + // Delete the previous backup folder + // To avoid deleting accidently an incorrect folder, check for + // the correct content. A backup folder should have 3-5 files: + // * .hdf - the study itself + // * .py - the python dump + // * _test.log - the output of the "test_backup.py" script + // * _valid.log - the output of the "check_validity.py" script + QDir dir(myLastBackupFolder); + QStringList files = dir.entryList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); + // I am afraid of accidently removing an entire folder tree, therefore check + // if "dir" contains really the latest backups and nothing more + if (!files.isEmpty() && files.length() <= 4) + { + QString baseName = files.constFirst(); + baseName = baseName.left(baseName.lastIndexOf('.')); + if (!baseName.isEmpty() && files.filter(baseName).length() == files.length()) + { + const bool success = dir.removeRecursively(); + if (!success) + { + QString aMsg = tr("The previous backup folder could not be removed!"); + QMessageBox::warning(application()->desktop(), tr("Automatic Backup"), aMsg); + } + } + } + else + { + QString aMsg = tr("The previous backup folder was not deleted,\nas there are more files in it than it is expected!"); + QMessageBox::warning(application()->desktop(), tr("Automatic Backup"), aMsg); + } + } + myLastBackupFolder = aFolder; + } + + // Start the timer again + if ( aResMgr && application()->activeStudy() ) + { + bool useBackup = aResMgr->booleanValue( ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup", true ); + if (useBackup) + { + int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 5 ); + if ( backupInterval > 0 ) + { +#ifdef DBG_BACKUP_INTERVAL + backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging +#endif + myBackupTimer->start( backupInterval*60000 ); + } + } + } +} + //****************************************************** void SHAPERGUI::onUpdateCommandStatus() { @@ -1081,11 +1381,32 @@ void SHAPERGUI::preferencesChanged(const QString& theSection, const QString& the } } } - else if (theSection == ModuleBase_Preferences::GENERAL_SECTION && theParam == "create_init_part") { - bool aCreate = ModuleBase_Preferences::resourceMgr()->booleanValue( - ModuleBase_Preferences::GENERAL_SECTION, "create_init_part", true); - Events_MessageBool aCreateMsg(Events_Loop::eventByName(EVENT_CREATE_PART_ON_START), aCreate); - aCreateMsg.send(); + else if (theSection == ModuleBase_Preferences::GENERAL_SECTION) { + if (theParam == "create_init_part") { + bool aCreate = ModuleBase_Preferences::resourceMgr()->booleanValue( + ModuleBase_Preferences::GENERAL_SECTION, "create_init_part", true); + Events_MessageBool aCreateMsg(Events_Loop::eventByName(EVENT_CREATE_PART_ON_START), aCreate); + aCreateMsg.send(); + } + else if (theParam == "use_auto_backup") { + bool useBackup = ModuleBase_Preferences::resourceMgr()->booleanValue( + ModuleBase_Preferences::GENERAL_SECTION, "use_auto_backup", true); + if (useBackup) { + int backupInterval = aResMgr->integerValue( ModuleBase_Preferences::GENERAL_SECTION, "backup_interval", 5 ); + if ( backupInterval > 0 ){ +#ifdef DBG_BACKUP_INTERVAL + backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging +#endif + myBackupTimer->start( backupInterval*60000 ); + } + else { + myBackupTimer->stop(); + } + } + else { + myBackupTimer->stop(); + } + } } else if (theSection == ModuleBase_Preferences::VIEWER_SECTION && theParam.startsWith("group_names_")) diff --git a/src/SHAPERGUI/SHAPERGUI.h b/src/SHAPERGUI/SHAPERGUI.h index 631ac080e..0ba515d8b 100644 --- a/src/SHAPERGUI/SHAPERGUI.h +++ b/src/SHAPERGUI/SHAPERGUI.h @@ -30,6 +30,7 @@ #include #include +#include class XGUI_Workshop; class SHAPERGUI_OCCSelector; @@ -177,6 +178,9 @@ Q_OBJECT virtual void updateInfoPanel(); + signals: + void backupDone(QString aName, int aResult); + public slots: /// \brief The method is redefined to connect to the study viewer before the data /// model is filled by opened file. This file open will flush redisplay signals for, @@ -212,6 +216,12 @@ Q_OBJECT /// Save application functionality with additional processing of aborting the current operation void onSaveAsDocByShaper(); + /// Backup document functionality with additional file validation + void onBackupDoc(); + + /// Document has been backed-up + void onBackupDone(QString aName, int aResult); + /// Obtains the current application and updates its actions void onUpdateCommandStatus(); @@ -228,6 +238,9 @@ Q_OBJECT /// Abort all operations virtual bool abortAllOperations(); + /// The automatic backup thread function + int backupDoc(); + private slots: void onWhatIs(bool isToggled); @@ -265,6 +278,9 @@ private slots: void hideInternalWindows(); + // Check, whether we are waiting for a backup => if yes, do it now + void checkForWaitingBackup(); + /// List of registered nested actions QStringList myNestedActionsList; @@ -306,6 +322,12 @@ private slots: Handle(Graphic3d_AspectMarker3d) myHighlightPointAspect; double myAxisArrowRate; + + /// Automatic backup + QTimer* myBackupTimer; // The timer which triggers the automatic backup + std::future myBackupResult; // The result (succes/failure) of the backup + QString myBackupError; // The backup error message in case of a failure + QString myLastBackupFolder; // The folder of the last backup }; #endif diff --git a/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp b/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp new file mode 100644 index 000000000..fca627954 --- /dev/null +++ b/src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp @@ -0,0 +1,166 @@ +// Copyright (C) 2024 CEA, EDF, OPEN CASCADE SAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "SHAPERGUI_CheckBackup.h" +#include +#include +#include +#include + + + +SHAPERGUI_CheckBackup::SHAPERGUI_CheckBackup(const QString &theFolder, const QString &theBaseName) + : myProcess(nullptr) + , myFolder(theFolder) + , myBaseName(theBaseName) +{ +} + + +int SHAPERGUI_CheckBackup::run(const QString &theTestScript) +{ + int aResult = 0; + if (myProcess) + return 64; + + QString aProgName = std::getenv("PYTHONBIN"); + if (aProgName.isEmpty()) + aProgName = "python3"; + + const QChar aSep = QDir::separator(); + + QString testBackup("test_backup.py"); + QStringList dirs; + dirs << QString(std::getenv("SHAPER_ROOT_DIR")) + << QString("bin") + << QString("salome") + << testBackup; + QString scriptName = dirs.join( QDir::separator() ); + + QStringList args; + args << scriptName << myFolder << myBaseName << theTestScript; + + myProcess = new QProcess(this); + if (!myProcess) + return 65; + + // connect(myProcess, SIGNAL(started()), this, SLOT(procStarted())); + // connect(myProcess, SIGNAL(error(QProcess::ProcessError)), this, + // SLOT(procError(QProcess::ProcessError))); + // connect(myProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, + // SLOT(procFinished(int, QProcess::ExitStatus))); + + // Start a python script (test_backup.py), which itself + // * launches a SALOME process + // * opens the previously backed up HDF study + myProcess->start(aProgName, args); + myProcess->waitForFinished(300000); + QProcess::ExitStatus exitStat = myProcess->exitStatus(); // 0=NormalExit, 1=CrashExit + int exitCode = myProcess->exitCode(); + if (exitStat == QProcess::NormalExit && exitCode != 0) + aResult = exitCode; + else if (exitStat == QProcess::CrashExit) + aResult = 66; + myProcess->deleteLater(); + + // Check the output of the log file + if (aResult == 0) + { + std::ifstream log((QStringList() << myFolder << myBaseName+"_valid.log").join(aSep).toStdString().c_str()); + if (log) + { + uint8_t testFlag = 0; + std::string line; + while (std::getline(log, line)) + { + if (line.find("HDF Test - ") == 0) + { + testFlag |= 0x1; // 0x01 = HDF Test performed + std::string strResult = line.substr(11); + if (strResult.find("PASSED") == 0) + { + if (testFlag == 0x03) + break; + } + else if (strResult.find("FAILED") == 0) + { + aResult = 67; + break; + } + else + { + aResult = 68; + break; + } + } + else if (line.find("PY Test - ") == 0) + { + testFlag |= 0x2; // 0x02 = PY Test performed + std::string strResult = line.substr(10); + if (strResult.find("PASSED") == 0) + { + if (testFlag == 0x03) + break; + } + else if (strResult.find("FAILED") == 0) + { + aResult = 69; + break; + } + else + { + aResult = 70; + break; + } + } + } + if (aResult == 0 && testFlag != 0x03) + { + // Not all tests were performed or they were interrupted + switch (testFlag) + { + case 0x00: aResult = 71; break; + case 0x01: aResult = 72; break; + case 0x02: aResult = 73; break; + } + } + } + else + { + aResult = 74; // log file not found + } + } + + return aResult; +} + + +void SHAPERGUI_CheckBackup::procStarted() +{ +} + + +void SHAPERGUI_CheckBackup::procFinished(int code, QProcess::ExitStatus stat) +{ +} + + +void SHAPERGUI_CheckBackup::procError(QProcess::ProcessError err) +{ +} diff --git a/src/SHAPERGUI/SHAPERGUI_CheckBackup.h b/src/SHAPERGUI/SHAPERGUI_CheckBackup.h new file mode 100644 index 000000000..b491912f0 --- /dev/null +++ b/src/SHAPERGUI/SHAPERGUI_CheckBackup.h @@ -0,0 +1,45 @@ +// Copyright (C) 2023 CEA, EDF, OPEN CASCADE SAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef SHAPERGUI_CHECKBACKUP_H +#define SHAPERGUI_CHECKBACKUP_H + +#include "SHAPER_SHAPERGUI.h" +#include + + +class SHAPERGUI_EXPORT SHAPERGUI_CheckBackup : public QObject +{ + Q_OBJECT +public: + SHAPERGUI_CheckBackup(const QString &theFolder, const QString &theBaseName); + int run(const QString &theTestScript); + +private slots: + void procStarted(); + void procFinished(int, QProcess::ExitStatus); + void procError(QProcess::ProcessError); + +private: + QProcess* myProcess; + QString myFolder; + QString myBaseName; +}; + +#endif diff --git a/src/SHAPERGUI/SHAPERGUI_DataModel.cpp b/src/SHAPERGUI/SHAPERGUI_DataModel.cpp index 0d976bd9e..e9ec48d01 100644 --- a/src/SHAPERGUI/SHAPERGUI_DataModel.cpp +++ b/src/SHAPERGUI/SHAPERGUI_DataModel.cpp @@ -37,6 +37,7 @@ #include #include + #define DUMP_NAME "shaper_dump.py" @@ -83,7 +84,7 @@ bool SHAPERGUI_DataModel::open(const QString& thePath, CAM_Study* theStudy, QStr return true; } -bool SHAPERGUI_DataModel::save(QStringList& theFiles) +bool SHAPERGUI_DataModel::save(QStringList& theFiles, bool isBackup/*=false*/) { // Publish to study before saving of the data model myModule->publishToStudy(); @@ -120,10 +121,10 @@ bool SHAPERGUI_DataModel::save(QStringList& theFiles) return true; } -bool SHAPERGUI_DataModel::saveAs(const QString& thePath, CAM_Study* theStudy, QStringList& theFiles) +bool SHAPERGUI_DataModel::saveAs(const QString& thePath, CAM_Study* theStudy, QStringList& theFiles, bool isBackup/*=false*/) { myStudyPath = thePath; - return save(theFiles); + return save(theFiles, isBackup); } bool SHAPERGUI_DataModel::close() diff --git a/src/SHAPERGUI/SHAPERGUI_DataModel.h b/src/SHAPERGUI/SHAPERGUI_DataModel.h index aacc4483b..18d2970b8 100644 --- a/src/SHAPERGUI/SHAPERGUI_DataModel.h +++ b/src/SHAPERGUI/SHAPERGUI_DataModel.h @@ -46,13 +46,13 @@ class SHAPERGUI_EXPORT SHAPERGUI_DataModel : public LightApp_DataModel /// Save module data to file /// \param theFiles list of created files - virtual bool save(QStringList& theFiles); + virtual bool save(QStringList& theFiles, bool isBackup=false); /// Save module data to a file /// \param thePath a path to the directory /// \param theStudy a current study /// \param theFiles a list of files to open - virtual bool saveAs(const QString& thePath, CAM_Study* theStudy, QStringList& theFiles); + virtual bool saveAs(const QString& thePath, CAM_Study* theStudy, QStringList& theFiles, bool isBackup=false); /// Close data structure virtual bool close(); diff --git a/src/SHAPERGUI/SHAPERGUI_msg_fr.ts b/src/SHAPERGUI/SHAPERGUI_msg_fr.ts index 740d35f89..2bf82b282 100644 --- a/src/SHAPERGUI/SHAPERGUI_msg_fr.ts +++ b/src/SHAPERGUI/SHAPERGUI_msg_fr.ts @@ -182,6 +182,14 @@ Text color Couleur du texte + + Backup done in folder: %1 + Sauvegarde effectuée dans le dossier: %1 + + + Failed to backup active study! + Échec de la sauvegarde de l'étude active! + SHAPERGUI_ToolbarItemsDlg diff --git a/src/XGUI/MBDebug.h b/src/XGUI/MBDebug.h new file mode 100644 index 000000000..9b4a83e29 --- /dev/null +++ b/src/XGUI/MBDebug.h @@ -0,0 +1,313 @@ +#ifndef MBDebug_HeaderFile +#define MBDebug_HeaderFile + +//--------------------------------------------------------------- +// Usage of the logging facilities: +// +// (1) At the beginning of each class file to be debugged, there +// should be a static string variable defined with the name +// of the class. Then, include the "MBDebug.h" header file. +// +// //--------------------------------------------------------- +// #define USE_DEBUG +// //#define MB_IGNORE_QT +// //#define MB_FULL_DUMP +// #define MBCLASSNAME "ClassName" +// #include "MBDebug.h" +// // <-- insert includes for addtional debug headers here! +// //--------------------------------------------------------- +// +// (2) At the beginning of each class method, call the DBG_FUN +// macro. +// +// int ClassName::MyMethod(int x) +// { +// DBG_FUN(); +// ... +// } +// +// NOTE: For static methods, call the DBG_FUNC() macro!! +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// This debugging/logging class is a "header-only" solution and +// does NOT require any additional implementation (.cpp) file! +//--------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef MB_IGNORE_QT +# include +# include +#endif + +static std::mutex mtx; + +//--------------------------------------------------------------- +// Set the debug flags dependent on the preprocessor definitions +//--------------------------------------------------------------- +#ifdef USE_DEBUG +# define MBS_DEBUG_FLAG MBDebug::DF_DEBUG +#else +# define MBS_DEBUG_FLAG 0 +#endif /*DEBUG*/ + +#define MBS_DBG_FLAGS (MBS_DEBUG_FLAG) + + +//--------------------------------------------------------------- +// Define the global debug macros +//--------------------------------------------------------------- +#define DLOG MBDebug::LogPrint() +#define RETURN(var) { RET(var); return (var); } + +#ifdef USE_DEBUG + +# define DBG_FUN() MBDebug _dbg(MBCLASSNAME, __FUNCTION__, MBS_DBG_FLAGS, (void*)this) +# define DBG_FUNC() MBDebug _dbg(MBCLASSNAME, __FUNCTION__, MBS_DBG_FLAGS) +# define DBG_FUNB(blk) MBDebug _dbg(MBCLASSNAME, blk, MBS_DBG_FLAGS) +# define MSGEL(txt) MBDebug::LogPrint() << ":" << txt << std::endl +# define PRINT(txt) MBDebug::LogPrint() << txt +# define SHOW2(var,typ) do { PRINT(std::this_thread::get_id()); DumpVar(#var,(typ)(var)); } while (0) +# define SHOW(var) do { PRINT(std::this_thread::get_id()); DumpVar(#var,var); } while (0) +# define ARG(var) do { PRINT(std::this_thread::get_id() << ":in:"); DumpVar(#var,var); } while (0) +# define ARG2(var,typ) do { PRINT(std::this_thread::get_id() << ":in:"); DumpVar(#var,(typ)(var)); } while (0) +# define RET(var) do { PRINT(std::this_thread::get_id() << ":out:"); DumpVar(#var,var); } while (0) +# define MSG(txt) MBDebug::LogPrint() << std::this_thread::get_id() << ":" << txt + +#else /*!USE_DEBUG*/ + +# define DBG_FUN() +# define DBG_FUNC() +# define DBG_FUNB(blk) +# define MSGEL(txt) +# define PRINT(txt) +# define SHOW2(var,typ) +# define SHOW(var) +# define ARG(var) +# define ARG2(var,typ) +# define RET(var) +# define MSG(txt) + +#endif /*USE_DEBUG*/ + + +//--------------------------------------------------------------- +// Declare the debugging and profiling class +//--------------------------------------------------------------- +class MBDebug +{ +public: + enum { + DF_NONE = 0x00, // no debug + DF_DEBUG = 0x01 // debug a function + }; + + MBDebug(const char* aClassName, const char* aFuncName, const short aFlag, void* aThis=NULL) + :mClassName(aClassName),mFuncName(aFuncName),mThis(aThis),mFlags((unsigned char)aFlag) + { + if (mFlags & (DF_DEBUG)) + { + std::lock_guard lck(mtx); + std::cout << std::this_thread::get_id() << ":{ENTER: " << mClassName + "::" + mFuncName; + if (mThis) std::cout << "(this=" << mThis << ")"; + std::cout << std::endl; + } + } + virtual ~MBDebug() + { + if (mFlags & (DF_DEBUG)) + { + std::lock_guard lck(mtx); + std::cout << std::this_thread::get_id() << ":}LEAVE: " << mClassName << "::" << mFuncName << std::endl; + } + } + + // Log file output management + static std::ostream& LogPrint() { return std::cout; } + +private: + std::string mClassName; // Name of class to be debugged + std::string mFuncName; // Name of function to be debugged + void* mThis; // The "this" pointer to the class being debugged + unsigned char mFlags; // Debug mode flags +}; + + + +#define YesNo(b) (b ? "Yes" : "No") + + + +inline std::string w2s(std::wstring ws) +{ + using convert_typeX = std::codecvt_utf8; + std::wstring_convert converterX; + return(converterX.to_bytes(ws)); +} + +// Primitive types +inline void DumpVar(const char *szName, char value) +{ + std::lock_guard lck(mtx); + DLOG << "[chr]: " << szName << "='" << value << "'" << std::endl; +} + +inline void DumpVar(const char *szName, bool value) +{ + std::lock_guard lck(mtx); + DLOG << "[bool]: " << szName << "=" << (value ? "true" : "false") << std::endl; +} + +inline void DumpVar(const char *szName, short value) +{ + std::lock_guard lck(mtx); + DLOG << "[shrt]: " << szName << "=" << value << std::endl; +} + +inline void DumpVar(const char *szName, int value) +{ + std::lock_guard lck(mtx); + DLOG << "[int]: " << szName << "=" << value << std::endl; +} + +inline void DumpVar(const char *szName, long value) +{ + std::lock_guard lck(mtx); + DLOG << "[long]: " << szName << "=" << value << std::endl; +} + +inline void DumpVar(const char *szName, double value) +{ + std::lock_guard lck(mtx); + DLOG << "[dbl]: " << szName << "=" << value << std::endl; +} + +inline void DumpVar(const char *szName, unsigned char value) +{ + std::lock_guard lck(mtx); + DLOG << "[byte]: " << szName << "=0x" << std::hex << value << std::dec << std::endl; +} + +inline void DumpVar(const char *szName, unsigned short value) +{ + std::lock_guard lck(mtx); + DLOG << "[word]: " << szName << "=0x" << std::hex << value << std::dec << std::endl; +} + +inline void DumpVar(const char *szName, unsigned int value) +{ + std::lock_guard lck(mtx); + DLOG << "[uint]: " << szName << "=0x" << std::hex << value << std::dec << std::endl; +} + +inline void DumpVar(const char *szName, unsigned long value) +{ + std::lock_guard lck(mtx); + DLOG << "[dword]: " << szName << "=0x" << std::hex << value << std::dec << std::endl; +} + +inline void DumpVar(const char *szName, const char* value) +{ + std::lock_guard lck(mtx); + DLOG << "[str]: " << szName << "=\"" << (value ? value : "") << "\"" << std::endl; +} + +inline void DumpVar(const char *szName, const std::string &value) +{ + std::lock_guard lck(mtx); + DLOG << "[Str]: " << szName << "=\"" << value << "\"" << std::endl; +} + +inline void DumpVar(const char *szName, const std::wstring &value) +{ + std::lock_guard lck(mtx); + DLOG << "[WStr]: " << szName << "=\"" << w2s(value) << "\"" << std::endl; +} + +#ifndef MB_IGNORE_QT +inline void DumpVar(const char *szName, const QString &value) +{ + std::lock_guard lck(mtx); + DLOG << "[QStr]: " << szName << "=\"" << value.toStdString() << "\"" << std::endl; +} + +inline void DumpVar(const char *szName, const QStringList &value) +{ + std::lock_guard lck(mtx); + DLOG << "[QStrLst]: " << szName << "=[len=" << value.length() << "] {"; + bool first = true; + QStringList::const_iterator it = value.constBegin(); + for ( ; it != value.constEnd(); ++it) + { + DLOG << (first ? "" : ",") << "\"" << (*it).toStdString() << "\""; + first = false; + } + DLOG << "}" << std::endl; +} +#endif + +inline void DumpVar(const char *szName, const void* value) +{ + std::lock_guard lck(mtx); + DLOG << "[ptr]: " << szName << "=" << value << std::endl; +} + + +// Collection of primitive types +inline void DumpVar(const char *szName, const std::set &values) +{ + std::lock_guard lck(mtx); + DLOG << "[intSet]: " << szName << "={" << values.size() << "}["; + bool bFirst = true; + for (auto it=values.cbegin(); it!=values.cend(); ++it) { + DLOG << (bFirst ? "" : ",") << *it; + bFirst = false; + } + DLOG << "]" << std::endl; +} + +inline void DumpVar(const char *szName, const std::vector &values) +{ + std::lock_guard lck(mtx); + DLOG << "[intVect]: " << szName << "={" << values.size() << "}["; + bool bFirst = true; + for (auto it=values.cbegin(); it!=values.cend(); ++it) { + DLOG << (bFirst ? "" : ",") << *it; + bFirst = false; + } + DLOG << "]" << std::endl; +} + +inline void DumpVar(const char *szName, const std::list& values) +{ + std::lock_guard lck(mtx); + DLOG << "[boolList]: " << szName << "={" << values.size() << "}["; + bool bFirst = true; + for (auto it=values.cbegin(); it!=values.cend(); ++it) { + DLOG << (bFirst ? "" : ",") << (*it ? "Y" : "N"); + bFirst = false; + } + DLOG << "]" << std::endl; +} + +inline void DumpVar(const char *szName, const std::list &values) +{ + std::lock_guard lck(mtx); + DLOG << "[strLst]: " << szName << "={" << values.size() << "}["; + bool bFirst = true; + for (auto it=values.cbegin(); it!=values.cend(); ++it) { + DLOG << (bFirst ? "\"" : ", \"") << *it << "\""; + bFirst = false; + } + DLOG << "]" << std::endl; +} + +#endif // MBDebug_HeaderFile + diff --git a/src/XGUI/XGUI_Workshop.cpp b/src/XGUI/XGUI_Workshop.cpp index 0df895c85..78146b8c6 100644 --- a/src/XGUI/XGUI_Workshop.cpp +++ b/src/XGUI/XGUI_Workshop.cpp @@ -180,6 +180,15 @@ static Handle(VInspector_CallBack) MyVCallBack; #include #endif +//--------------------------------------------------------- +#define USE_DEBUG +//#define MB_IGNORE_QT +//#define MB_FULL_DUMP +#define MBCLASSNAME "XGUI_Workshop" +#include "MBDebug.h" +// <-- insert includes for addtional debug headers here! +//--------------------------------------------------------- + //#define DEBUG_WITH_MESSAGE_REPORT QString XGUI_Workshop::MOVE_TO_END_COMMAND = QObject::tr("Move to the end"); @@ -212,8 +221,10 @@ XGUI_Workshop::XGUI_Workshop(XGUI_SalomeConnector* theConnector) myDisplayer(0), mySalomeConnector(theConnector), //myViewerSelMode(TopAbs_FACE), - myInspectionPanel(0) + myInspectionPanel(0), + myDoBackup(false) { + DBG_FUN(); mySelector = new XGUI_SelectionMgr(this); myModuleConnector = new XGUI_ModuleConnector(this); myOperationMgr = new XGUI_OperationMgr(this, myModuleConnector); @@ -315,6 +326,7 @@ XGUI_Workshop::XGUI_Workshop(XGUI_SalomeConnector* theConnector) //****************************************************** XGUI_Workshop::~XGUI_Workshop(void) { + DBG_FUN(); #ifdef _DEBUG #ifdef MISSED_TRANSLATION // Save Missed translations @@ -332,6 +344,7 @@ XGUI_Workshop::~XGUI_Workshop(void) //****************************************************** void XGUI_Workshop::startApplication() { + DBG_FUN(); //Initialize event listening myEventsListener->initializeEventListening(); @@ -700,6 +713,28 @@ void XGUI_Workshop::showHelpPage(const QString& thePage) const } +//****************************************************** +void XGUI_Workshop::setBackupState(bool doBackup/*=true*/) +{ + DBG_FUN(); + ARG(doBackup); + myDoBackup = doBackup; +} + + +//****************************************************** +void XGUI_Workshop::setWaitForBackup(bool theDoWait/*=true*/) +{ + DBG_FUN(); + ARG(theDoWait); + myWaitForBackup = theDoWait; + if (theDoWait) + { + std::cout << "..... waiting for operation to finish to start backup." << std::endl; + } +} + + //****************************************************** void XGUI_Workshop::deactivateActiveObject(const ObjectPtr& theObject, const bool theUpdateViewer) { @@ -856,6 +891,9 @@ void XGUI_Workshop::connectToPropertyPanel(const bool isToConnect) //****************************************************** void XGUI_Workshop::onOperationResumed(ModuleBase_Operation* theOperation) { + DBG_FUN(); + ARG(theOperation->getDescription()->operationId()); + setGrantedFeatures(theOperation); if (theOperation->getDescription()->hasXmlRepresentation()) { //!< No need for property panel @@ -876,6 +914,9 @@ void XGUI_Workshop::onOperationResumed(ModuleBase_Operation* theOperation) //****************************************************** void XGUI_Workshop::onOperationStopped(ModuleBase_Operation* theOperation) { + DBG_FUN(); + ARG(theOperation->getDescription()->operationId()); + updateCommandStatus(); ModuleBase_OperationFeature* aFOperation = dynamic_cast @@ -958,6 +999,10 @@ void XGUI_Workshop::setGrantedFeatures(ModuleBase_Operation* theOperation) //****************************************************** void XGUI_Workshop::saveDocument(const QString& theName, std::list& theFileNames) { + DBG_FUN(); + ARG(theName); + ARG(theFileNames); + SHOW(myDoBackup); QApplication::restoreOverrideCursor(); SessionPtr aMgr = ModelAPI_Session::get(); @@ -976,12 +1021,15 @@ void XGUI_Workshop::saveDocument(const QString& theName, std::list& //****************************************************** bool XGUI_Workshop::abortAllOperations() { + DBG_FUN(); return myOperationMgr->abortAllOperations(); } //****************************************************** void XGUI_Workshop::operationStarted(ModuleBase_Operation* theOperation) { + DBG_FUN(); + ARG(theOperation->getDescription()->operationId()); setGrantedFeatures(theOperation); if (!theOperation->getDescription()->hasXmlRepresentation()) { //!< No need for property panel updateCommandStatus(); @@ -994,6 +1042,7 @@ void XGUI_Workshop::operationStarted(ModuleBase_Operation* theOperation) //****************************************************** void XGUI_Workshop::onOpen() { + DBG_FUN(); if(!abortAllOperations()) return; //save current file before close if modified @@ -1024,6 +1073,8 @@ void XGUI_Workshop::onOpen() //****************************************************** void XGUI_Workshop::openFile(const QString& theDirectory) { + DBG_FUN(); + ARG(theDirectory); myCurrentFile = theDirectory; if (myCurrentFile.isEmpty()) return; @@ -1196,7 +1247,10 @@ void XGUI_Workshop::onTrihedronVisibilityChanged(bool theState) //****************************************************** bool XGUI_Workshop::onSave() { - if(!myOperationMgr->abortAllOperations(XGUI_OperationMgr::XGUI_InformationMessage)) + DBG_FUN(); + SHOW(myDoBackup); + + if(!myDoBackup && !myOperationMgr->abortAllOperations(XGUI_OperationMgr::XGUI_InformationMessage)) return false; if (myCurrentFile.isEmpty()) { return onSaveAs(); @@ -1228,6 +1282,8 @@ bool XGUI_Workshop::onSave() //****************************************************** bool XGUI_Workshop::onSaveAs() { + DBG_FUN(); + SHOW(myDoBackup); if(!myOperationMgr->abortAllOperations(XGUI_OperationMgr::XGUI_InformationMessage)) return false; qreal aRatio = ModuleBase_Tools::currentPixelRatio(); @@ -1446,6 +1502,8 @@ void XGUI_Workshop::onExportPart() //****************************************************** ModuleBase_IModule* XGUI_Workshop::loadModule(const QString& theModule) { + DBG_FUN(); + ARG(theModule); QString libName = QString::fromStdString(library(theModule.toStdString())); if (libName.isEmpty()) { qWarning(qPrintable(tr("Information about module \"%1\" doesn't exist.").arg(theModule))); @@ -2355,10 +2413,12 @@ bool hasResults(QObjectPtrList theObjects, const std::set& theTypes // all nested parts. std::list allFeatures(const DocumentPtr& theDocument) { + DBG_FUNC(); std::list aResultList; std::list anAllFeatures = theDocument->allFeatures(); foreach (const FeaturePtr& aFeature, anAllFeatures) { // The order of appending features of the part and the part itself is important + MSGEL("* aFeature = [" << aFeature->getKind() << "] action=" << aFeature->isAction()); // Append features from a part feature std::list aResults; diff --git a/src/XGUI/XGUI_Workshop.h b/src/XGUI/XGUI_Workshop.h index e0d97cbae..d84457249 100644 --- a/src/XGUI/XGUI_Workshop.h +++ b/src/XGUI/XGUI_Workshop.h @@ -354,6 +354,14 @@ Q_OBJECT void showHelpPage(const QString& thePage) const; + void setBackupState(bool theDoBackup=true); + + bool backupState() const { return myDoBackup; } + + void setWaitForBackup(bool theDoWait=true); + + bool waitForBackup() const { return myWaitForBackup; } + signals: /// Emitted when selection happens in Salome viewer void salomeViewerSelection(); @@ -592,6 +600,8 @@ private: Config_DataModelReader* myDataModelXMLReader; ///< XML reader of data model XGUI_InspectionPanel* myInspectionPanel; ///< container of feature attributes widgets QTemporaryDir myTmpDir; ///< a direcory for uncompressed files + bool myDoBackup; ///< whether the current save was initiated by automatic backup + bool myWaitForBackup; ///< whether to do a backup when current operation finishes }; #endif diff --git a/test.hdfs/testme.py b/test.hdfs/testme.py index 01f8c1f7c..747451f91 100644 --- a/test.hdfs/testme.py +++ b/test.hdfs/testme.py @@ -64,7 +64,7 @@ if __name__ == '__main__': with open(testlogfile, 'r') as inputFile: s = inputFile.read() - #print("logfile: ", s) + print("logfile: ", s) if s.find("FAIL") > 0: isOk = False error = s diff --git a/test_backup.py b/test_backup.py new file mode 100755 index 000000000..f8cc2918c --- /dev/null +++ b/test_backup.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 OPEN CASCADE SAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +import subprocess +import sys, os +import tempfile + + +#------------------------------------------------------------------------ +def checkFileInSubprocess(logFile, title, args): + errCode = 0 + try: + logFile.write(f"starting {title} process:\n") + logFile.write(" cmd_line = '{}'\n".format(" ".join(args))) + proc = subprocess.Popen(args) + try: + logFile.write(f" start communication with {title}\n") + proc.communicate(timeout = 500) + logFile.write(f"{title} terminated\n") + errCode = proc.returncode + logFile.write(f"{title} returned: {errCode}\n") + except subprocess.TimeoutExpired: + errCode = 96 + logFile.write(f"{title} timed out\n") + except Exception as ex: + errCode = 97 + logFile.write(f"Exception caught: {ex}\n") + except: + errCode = 98 + logFile.write("Unknown Exception caught\n") + + logFile.write(f"errCode = {errCode}\n") + except: + errCode = 99 + + return errCode + + +#------------------------------------------------------------------------ +def checkBackups(backupFolder, baseName, checkScript): + errCode = 0 + try: + # Create a log file in the backup folder starting with the same base name + with open(os.path.join(backupFolder, baseName+"_test.log"), "w") as logFile: + logFile.write("test_backup script started\n") + logFile.write(f" backupFolder = {backupFolder}\n") + logFile.write(f" baseName = {baseName}\n") + logFile.write(f" checkScript = {checkScript}\n") + hdfFile = os.path.join(backupFolder, baseName+".hdf") + logFile.write(f" hdfFile = {hdfFile}\n") + + args = ["runSalome.py", "--modules", "SHAPER,SHAPERSTUDY", "--batch", "--splash", "0", checkScript, "args:" + hdfFile] + errCode = checkFileInSubprocess(logFile, "SALOME", args) + logFile.write(f"errCode = {errCode}\n") + + if errCode == 0: + pyFile = os.path.join(backupFolder, baseName+".py") + logFile.write(f" pyfile = {pyFile}\n") + + args = ["python", checkScript, pyFile] + errCode = checkFileInSubprocess(logFile, "PYTHON", args) + logFile.write(f"errCode = {errCode}\n") + + except: + errCode = 100 + + return errCode + + +#------------------------------------------------------------------------ +errCode = 0 +try: + if (len(sys.argv) != 4): + errCode = 101 + else: + backupFolder = sys.argv[1] + baseName = sys.argv[2] + checkScript = sys.argv[3] + errCode = checkBackups(backupFolder, baseName, checkScript) +except: + errCode = 102 + +exit(errCode)