]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
updated location of test scripts
authormbs <martin.bernhard@opencascade.com>
Fri, 15 Dec 2023 11:32:20 +0000 (11:32 +0000)
committermbs <martin.bernhard@opencascade.com>
Fri, 19 Jan 2024 23:11:08 +0000 (23:11 +0000)
CMakeLists.txt
check_validity.py [new file with mode: 0755]
src/SHAPERGUI/SHAPERGUI.cpp
src/SHAPERGUI/SHAPERGUI.h
src/SHAPERGUI/SHAPERGUI_CheckBackup.cpp
src/XGUI/XGUI_Workshop.h
test_backup.py [new file with mode: 0755]

index 61d60a42c5776a4dc5ed1c0d576172860162ba9c..e39ef09c398e41bb78856df9eb796c43747a4aa3 100644 (file)
@@ -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 (executable)
index 0000000..f51d08f
--- /dev/null
@@ -0,0 +1,66 @@
+import salome
+import sys
+import os
+import SalomePyQt
+import ModelAPI
+#import PartSetAPI
+#from salome.shaper import model
+
+
+if len(sys.argv) < 2:
+  raise Exception("no study given to validate")
+
+outFolder,filename = os.path.split(sys.argv[1])
+base = os.path.splitext(filename)[0]
+
+with open(os.path.join(outFolder, base+"_valid.log"), "w") as f:
+  f.write("#args = {}\n".format(len(sys.argv)))
+  lastIdx = len(sys.argv)-1
+  f.write("#arg[{}] = {}\n".format(lastIdx, sys.argv[lastIdx]))
+
+  salome.standalone()
+  f.write("initializing SALOME\n")
+  salome.salome_init(sys.argv[1], embedded=True, forced=True)
+  f.write("StudyName = {}\n".format(salome.myStudyName))
+  f.write("Study = {}\n".format(salome.myStudy))
+
+  session = salome.naming_service.Resolve('/Kernel/Session')
+  f.write("session = {}\n".format(session))
+  session.emitMessage("connect_to_study")
+  f.write("session = {}\n".format(session))
+
+  sg = SalomePyQt.SalomePyQt()
+  sg.activateModule("Shaper")
+  session = ModelAPI.ModelAPI_Session.get()
+  aFactory = session.validators()
+  aPartSet = session.moduleDocument()
+  numParts = aPartSet.size("Parts")
+  # f.write("NumParts = {}\n".format(numParts))
+  # numFeats = aPartSet.size("Features")
+  # f.write("NumFeats = {}\n".format(numFeats))
+  # numBodies = aPartSet.size("Bodies")
+  # f.write("NumBodies = {}\n".format(numBodies))
+  # numGroups = aPartSet.size("Groups")
+  # f.write("NumGroups = {}\n".format(numGroups))
+  # numConsts = aPartSet.size("Construction")
+  # f.write("NumConstr = {}\n".format(numConsts))
+  # numFolders = aPartSet.size("Folders")
+  # f.write("NumFolder = {}\n".format(numFolders))
+  for partIdx in range(numParts+1):
+    aPart = session.document(partIdx)
+    if aPart is None:
+      continue
+    aPart.setActive(True)
+    if partIdx == 0:
+      f.write("---PartSet:------------------\n")
+    else:
+      f.write(f"---Part_{partIdx}:------------------\n")
+    for aFeat in aPart.allFeatures():
+      if aFeat.isInHistory():
+        f.write("  * {} --> [{}]\n".format(aFeat.getKind(), aFeat.data().name()))
+      else:
+        f.write("    - {}\n".format(aFeat.data().name()))
+
+  f.write("done!\n")
+
+exit()
index 4b61fa21d6233d24cf9a1dabd50b8b2fdb15b5be..8d97ebf82a96c2e788157757bee35e2a568dfd9c 100644 (file)
@@ -34,6 +34,7 @@
 #include <XGUI_FacesPanel.h>
 #include <XGUI_SelectionActivate.h>
 #include <XGUI_InspectionPanel.h>
+#include <XGUI_Tools.h>
 #include <XGUI_ViewerProxy.h>
 
 #include <ModuleBase_Operation.h>
@@ -94,6 +95,7 @@
 #define MBCLASSNAME "SHAPERGUI"
 #include "MBDebug.h"
 // <-- insert includes for addtional debug headers here!
+//#define DBG_BACKUP_INTERVAL   1 /*Use a short 1 min interval for auto backup for debugging*/
 //---------------------------------------------------------
 
 #if OCC_VERSION_HEX < 0x070400
@@ -152,14 +154,6 @@ private:
 };
 
 
-// static void DumpOperations(XGUI_Workshop *wshop)
-// {
-//   XGUI_OperationMgr *aOpMgr = wshop->operationMgr();
-//   if (!aOpMgr) return;
-//   QStringList operations = aOpMgr->operationList();
-//   SHOW(operations);
-// }
-
 
 //******************************************************
 SHAPERGUI::SHAPERGUI()
@@ -424,7 +418,9 @@ bool SHAPERGUI::activateModule(SUIT_Study* theStudy)
     if (useBackup) {
       int backupInterval = aResMgr->integerValue( "General", "backup_interval", 1 );
       if ( backupInterval > 0 ){
-        backupInterval = 1;
+#ifdef DBG_BACKUP_INTERVAL
+        backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging
+#endif
         MSGEL("....starting BackupTimer: interval=" << backupInterval);
         myBackupTimer->start( backupInterval*60000 );
       }
@@ -610,20 +606,7 @@ void SHAPERGUI::onOperationCommitted(ModuleBase_Operation* theOperation)
 
   onOperationGeneric(theOperation, moduleName(), "committed");
 
-  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();
-  }
+  checkForWaitingBackup();
 }
 
 //******************************************************
@@ -634,6 +617,12 @@ void SHAPERGUI::onOperationAborted(ModuleBase_Operation* theOperation)
 
   onOperationGeneric(theOperation, moduleName(), "aborted");
 
+  checkForWaitingBackup();
+}
+
+//******************************************************
+void SHAPERGUI::checkForWaitingBackup()
+{
   if (myWorkshop && myWorkshop->waitForBackup()) 
   {
     XGUI_OperationMgr *operMgr = myWorkshop->operationMgr();
@@ -764,6 +753,8 @@ void SHAPERGUI::onBackupDoc()
   DBG_FUN();
   MSGEL("  ...backing up current study");
 
+  // 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())
@@ -797,32 +788,46 @@ bool SHAPERGUI::backupDoc()
 {
   DBG_FUN();
 
+  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 false;
+  }
+
   bool isOk = false;
-#if 0
-  MSGEL("...sleep for 5 seconds");
-  std::this_thread::sleep_for(std::chrono::seconds(5));
-  isOk = true;
-  QString aName("/some/file/name.hdf");
-#else
   SUIT_Study* study = application()->activeStudy();
-  if ( !study )
+  if ( !study ) {
+    myBackupError = tr("There is no active study");
     return false;
-
-  // if ( !abortAllOperations() )
-  //   return false;
+  }
 
   LockBackupState lockBackup(myWorkshop);
 
-  QString aName = study->studyName();
+  QString aFolder{};
   try
   {
+    QString aName = study->studyName();
     SHOW(aName);
-    if ( aName.isNull() )
+    if ( aName.isEmpty() ) {
+      myBackupError = tr("Study name is empty");
       return false;
+    }
 
     const QChar aSep = QDir::separator();
-
-    QString aFolder("/home/bernhard/backups");
+    //MBS:
+    SUIT_ResourceMgr* aResMgr = application()->resourceMgr();
+    if ( aResMgr && application()->activeStudy() ) {
+      aFolder = aResMgr->path( "General", "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");
@@ -831,6 +836,10 @@ bool SHAPERGUI::backupDoc()
     if (!aDir.exists()) {
       aDir.mkpath(aFolder);
       aDir.mkdir(aFolder);
+      if (!aDir.exists()) {
+        myBackupError = tr("Cannot create backup folder");
+        return false;
+      }
     }
 
     if (study->isSaved()) {
@@ -844,58 +853,109 @@ bool SHAPERGUI::backupDoc()
     // Save the study into a single HDF file
     isOk = study->saveDocumentAs( aFullName, true );
     if (!isOk){
-      MSGEL("ERR: failed to backup study document");
-      return isOk;
+      myBackupError = tr("Cannot backup study document");
+      return false;
     }
 
     // Now, dump the python script
     LightApp_Study *lightStudy = dynamic_cast<LightApp_Study*>(study);
-    if (!lightStudy)
+    if (!lightStudy) {
+      myBackupError = tr("Study is not dumpable");
       return false;
+    }
 
     aFullName = aFolder + aSep + aName + QString(".py");
     isOk = lightStudy->dump(aFullName, true, false, false);
     if (!isOk){
-      MSGEL("ERR: failed to backup python script");
-      return isOk;
+      myBackupError = tr("Cannot backup python script");
+      return false;
     }
 
     // Finally start another salome process and reload the saved document & script for verification
+    SHOW(aFolder);
     SHAPERGUI_CheckBackup checkBackup(aFolder, aName);
-    QString testScript("/home/bernhard/backups/checkValidity.py");
+    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() );
     int aResult = checkBackup.run(testScript);
     isOk = (aResult == 0);
   }
   catch (std::exception &ex)
   {
-    std::cout << "ERROR: std::exception caught" << std::endl;
+    myBackupError = tr("std::exception caught");
     isOk = false;
   }
   catch (...)
   {
-    std::cout << "ERROR: unknown exception caught" << std::endl;
+    myBackupError = tr("unknown exception caught");
     isOk = false;
   }
-  #endif
+
 
   MSGEL("...emit backupDone signal");
-  emit backupDone(aName, isOk);
+  SHOW(aFolder);
+  SHOW(isOk);
+  emit backupDone(aFolder, isOk);
   return isOk;
 }
 
 //******************************************************
-void SHAPERGUI::onBackupDone(QString aName, bool aResult)
+void SHAPERGUI::onBackupDone(QString aFolder, bool aResult)
 {
   DBG_FUN();
-  ARG(aName);
+  ARG(aFolder);
   ARG(aResult);
 
   bool isOk = myBackupResult.get();
   SHOW(isOk);
-  QFileInfo fi(aName);
-  aName = fi.canonicalPath();
-  SHOW(aName);
-  putInfo( isOk ? tr("Backup done in folder: %1").arg(aName) : tr("Failed to backup active study!"), 5000 );
+  putInfo( isOk ? tr("Backup done in folder: %1").arg(aFolder) : tr("Failed to backup active study!"), 5000 );
+
+  int aBackupStorage{-1};
+  SUIT_ResourceMgr* aResMgr = application()->resourceMgr();
+  if ( aResMgr ) {
+    aBackupStorage = aResMgr->integerValue( "General", "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:
+      //  * <FileName>.hdf       - the study itself
+      //  * <FileName>.log       - the output from the additional SALOME instance
+      //  * <FileName>.py        - the python dump
+      //  * <FileName>_test.log  - the output of the "test_backup.py" script
+      //  * <FileName>_valid.log - the output of the "check_validity.py" script
+      QDir dir(myLastBackupFolder);
+      QStringList files = dir.entryList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot);
+      if (!files.isEmpty() && files.length() <= 3) {
+        QString baseName = files.constFirst();
+        baseName = baseName.left(baseName.lastIndexOf('.'));
+        if (!baseName.isEmpty() && files.filter(baseName).length() == files.length())
+          dir.removeRecursively();
+      }
+    }
+    myLastBackupFolder = aFolder;
+  }
+
+  // Start the timer again
+  if ( aResMgr && application()->activeStudy() ) {
+    bool useBackup = aResMgr->booleanValue( "General", "use_auto_backup", true );
+    if (useBackup) {
+      int backupInterval = aResMgr->integerValue( "General", "backup_interval", 1 );
+      if ( backupInterval > 0 ){
+#ifdef DBG_BACKUP_INTERVAL
+        backupInterval = DBG_BACKUP_INTERVAL; // MBS: use shorter interval for debugging
+#endif
+        MSGEL("....starting BackupTimer: interval=" << backupInterval);
+        myBackupTimer->start( backupInterval*60000 );
+      }
+    }
+  }
 }
 
 //******************************************************
index 173426f822defedfd9425530133a85e6eda9010b..e3d6e76bdeaf24ac862320a95da27e51305b9d2e 100644 (file)
@@ -238,6 +238,7 @@ Q_OBJECT
   /// Abort all operations
   virtual bool abortAllOperations();
 
+  /// The automatic backup thread function
   bool backupDoc();
 
 private slots:
@@ -277,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;
 
@@ -319,8 +323,11 @@ private slots:
 
   double myAxisArrowRate;
 
-  QTimer*   myBackupTimer;
-  std::future<bool> myBackupResult;
+  /// Automatic backup
+  QTimer*           myBackupTimer;      // The timer which triggers the automatic backup
+  std::future<bool> 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
index b5e643e49b74c961d2c001a431b275c52f94a5a3..6af1d0a5ae5c090a5ffb4f598b5e84f30715a685 100644 (file)
@@ -61,17 +61,10 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript)
 
   QString testBackup("test_backup.py");
   QStringList dirs;
-#if 0
   dirs << QString(std::getenv("SHAPER_ROOT_DIR"))
        << QString("bin")
        << QString("salome")
        << testBackup;
-#else
-  dirs << QString(std::getenv("HOME"))
-       << QString("S2")
-       << QString("SALOME-MBS-DB11")
-       << testBackup;
-#endif
   QString scriptName = dirs.join( QDir::separator() );
 
   QStringList args;
@@ -81,12 +74,15 @@ int SHAPERGUI_CheckBackup::run(const QString &theTestScript)
   if (!myProcess)
     return 2;
 
-  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)));
+  // 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
   SHOW(aProgName);
   SHOW(args);
   myProcess->setStandardOutputFile((QStringList() << myFolder << myBaseName+".log").join(aSep));
index 676a44d463099829c421a4047f899d91da43a5bd..1853feb8e9cfd8e9a50a987716de633d6190a24c 100644 (file)
@@ -356,6 +356,8 @@ Q_OBJECT
 
   void setBackupState(bool theDoBackup=true);
 
+  bool backupState() const    { return myDoBackup; }
+
   void setWaitForBackup(bool theDoWait=true);
 
   bool waitForBackup() const  { return myWaitForBackup; }
diff --git a/test_backup.py b/test_backup.py
new file mode 100755 (executable)
index 0000000..7923901
--- /dev/null
@@ -0,0 +1,57 @@
+#!/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
+
+testTimeout = 600
+
+# if len(sys.argv) != 4:
+#   raise Exception("check_validity.py could not be found. Check your environment.")
+
+backupFolder = sys.argv[1]
+baseName = sys.argv[2]
+checkScript = sys.argv[3]
+
+errCode = 0
+with open(os.path.join(backupFolder, baseName+"_test.log"), "w") as f:
+  f.write("script started\n")
+  f.write("  backupFolder = {}\n".format(backupFolder))
+  f.write("  baseName     = {}\n".format(baseName))
+  f.write("  checkScript     = {}\n".format(checkScript))
+  hdffile = os.path.join(backupFolder, baseName+".hdf")
+  pyfile = os.path.join(backupFolder, baseName+".py")
+
+  f.write("starting SALOME process:\n")
+  proc = subprocess.Popen(["runSalome.py", "--modules", "SHAPER,SHAPERSTUDY", "--batch", "--splash", "0", checkScript, "args:" + hdffile])
+  try:
+    f.write("start communication with SALOME\n")
+    proc.communicate(timeout = testTimeout)
+    f.write("SALOME terminated\n")
+  except subprocess.TimeoutExpired:
+    errCode = 99
+    f.write("SALOME timed out\n")
+
+  f.write("\ndone.\n")
+
+exit(errCode)
\ No newline at end of file