From c4a421dcaaf41d3c61f8722f565709eaa18a3ee8 Mon Sep 17 00:00:00 2001
From: "Kilic, Deniz" <d.kilic@fz-juelich.de>
Date: Thu, 18 Nov 2021 15:21:47 +0100
Subject: [PATCH] Add simple AutoSave capability for TRC files

---
 include/personStorage.h                 |   5 +-
 include/petrack.h                       |   9 +-
 src/autosave.cpp                        | 108 ++++++++++++++++++++++++
 src/personStorage.cpp                   |  24 ++++++
 src/petrack.cpp                         |  12 +++
 tests/unit_test/tst_moCapController.cpp |   3 +-
 6 files changed, 154 insertions(+), 7 deletions(-)
 create mode 100644 src/autosave.cpp

diff --git a/include/personStorage.h b/include/personStorage.h
index 352489a23..f7ff02a20 100644
--- a/include/personStorage.h
+++ b/include/personStorage.h
@@ -26,7 +26,7 @@
 #include <vector>
 
 class Petrack;
-
+class Autosave;
 
 class PersonStorage
 {
@@ -38,7 +38,7 @@ public:
         Whole
     };
 
-    explicit PersonStorage(Petrack &mainWindow) : mMainWindow(mainWindow) {}
+    explicit PersonStorage(Petrack &mainWindow, Autosave &autosave) : mMainWindow(mainWindow), mAutosave(autosave) {}
 
     void splitPerson(size_t pers, int frame);
     bool splitPersonAt(const Vec2F &p, int frame, const QSet<int> &onlyVisible);
@@ -121,6 +121,7 @@ public:
 private:
     std::vector<TrackPerson> mPersons;
     Petrack &                mMainWindow;
+    Autosave &               mAutosave;
 };
 
 #endif // PERSONSTORAGE_H
diff --git a/include/petrack.h b/include/petrack.h
index 36bdcef31..2add0cb9c 100644
--- a/include/petrack.h
+++ b/include/petrack.h
@@ -32,6 +32,7 @@
 #include "stereoContext.h"
 #endif
 #include "autoCalib.h"
+#include "autosave.h"
 #include "backgroundFilter.h"
 #include "borderFilter.h"
 #include "brightContrastFilter.h"
@@ -458,14 +459,15 @@ private:
 
     reco::Recognizer mReco;
 
-    PersonStorage mPersonStorage{*this};
+    QDomDocument mDefaultSettings;
+    Autosave     mAutosave{*this};
+
+    PersonStorage mPersonStorage{*this, mAutosave};
     Tracker *     mTracker;
     TrackerReal * mTrackerReal;
     double        mHeadSize;
     double        mCmPerPixel;
 
-    QDomDocument mDefaultSettings;
-
     double mShowFPS;
 
     bool mAutoBackTrack;
@@ -475,7 +477,6 @@ private:
     MoCapStorage    mStorage;
     MoCapController mMoCapController{mStorage, mExtrCalibration};
 
-
     QString mPetrackVersion{"Unknown"};  ///< Version of PeTrack used to compile
     QString mGitCommitID{"Unknown"};     ///< Commit hash used to compile
     QString mGitCommitDate{"Unknown"};   ///< Commit date used to compile
diff --git a/src/autosave.cpp b/src/autosave.cpp
new file mode 100644
index 000000000..c3c1f5ff4
--- /dev/null
+++ b/src/autosave.cpp
@@ -0,0 +1,108 @@
+#include "autosave.h"
+
+#include "petrack.h"
+
+
+void Autosave::trackPersonModified()
+{
+    mChangeCounter++;
+    constexpr int changesTillAutosave = 10;
+    if(mChangeCounter >= changesTillAutosave)
+    {
+        mChangeCounter = 0;
+        saveTrc();
+    }
+}
+
+bool Autosave::checkAutosave()
+{
+    return !getAutosave().empty();
+}
+
+void Autosave::deleteAutosave()
+{
+    auto autosaves = getAutosave();
+    if(!autosaves.empty())
+    {
+        for(auto &save : autosaves)
+        {
+            QFile saveFile{save};
+            saveFile.remove();
+        }
+    }
+}
+
+void Autosave::loadAutosave()
+{
+    auto autosaveFiles = getAutosave();
+    if(autosaveFiles.empty())
+    {
+        return;
+    }
+
+    QString autosaveFilename = autosaveFiles[0];
+    auto    trcFile          = mPetrack.getTrackFileName();
+    mPetrack.importTracker(autosaveFilename);
+    mPetrack.setTrackFileName(trcFile);
+}
+
+QString Autosave::buildAutosaveName(const QString &projectFileName, bool temp)
+{
+    QFileInfo projectFile{projectFileName};
+    if(temp)
+    {
+        return projectFile.dir().filePath("." + projectFile.baseName() + "_autosave_running.trc");
+    }
+
+    return projectFile.dir().filePath("." + projectFile.baseName() + "_autosave.trc");
+}
+
+void Autosave::savePet() {}
+
+void Autosave::saveTrc()
+{
+    auto projectName = mPetrack.getProFileName();
+    QString autosaveName  = buildAutosaveName(projectName, true);
+    auto    trackFileName = mPetrack.getTrackFileName();
+    mPetrack.exportTracker(autosaveName);
+    mPetrack.setTrackFileName(trackFileName);
+
+    QString finalAutosaveName = buildAutosaveName(projectName);
+    QFile   tempAutosave{autosaveName};
+    QFile   autosave{finalAutosaveName};
+    if(tempAutosave.exists())
+    {
+        if(autosave.exists())
+        {
+            autosave.remove();
+        }
+        if(tempAutosave.copy(finalAutosaveName))
+        {
+            // we don't currently use it for loading, so we could remove it even if the copying fails...
+            tempAutosave.remove();
+        }
+    }
+}
+
+QStringList Autosave::getAutosave()
+{
+    auto projectPath = QFileInfo(mPetrack.getProFileName());
+    if(projectPath.isDir())
+    {
+        auto children      = projectPath.dir().entryList();
+        auto autosaveFiles = children.filter(QRegularExpression(R"(\..*_autosave.trc)"));
+        return autosaveFiles;
+    }
+
+    if(projectPath.isFile())
+    {
+        auto      autosaveName = buildAutosaveName(projectPath.absoluteFilePath());
+        QFileInfo autosave{autosaveName};
+        if(autosave.exists())
+        {
+            return QStringList{autosaveName};
+        }
+    }
+
+    return QStringList{};
+}
diff --git a/src/personStorage.cpp b/src/personStorage.cpp
index 1bc6d97ea..b428e2b4b 100644
--- a/src/personStorage.cpp
+++ b/src/personStorage.cpp
@@ -21,6 +21,7 @@
 #include "personStorage.h"
 
 #include "animation.h"
+#include "autosave.h"
 #include "control.h"
 #include "multiColorMarkerWidget.h"
 #include "pMessageBox.h"
@@ -34,6 +35,7 @@
  */
 void PersonStorage::splitPerson(size_t pers, int frame)
 {
+    mAutosave.trackPersonModified();
     int j;
 
     if(mPersons.at(pers).firstFrame() < frame)
@@ -66,6 +68,7 @@ void PersonStorage::splitPerson(size_t pers, int frame)
  */
 bool PersonStorage::splitPersonAt(const Vec2F &point, int frame, const QSet<int> &onlyVisible)
 {
+    mAutosave.trackPersonModified();
     for(size_t i = 0; i < mPersons.size(); ++i)
     { // ueber TrackPerson
         if(((onlyVisible.empty()) || (onlyVisible.contains(i))) &&
@@ -90,6 +93,7 @@ bool PersonStorage::splitPersonAt(const Vec2F &point, int frame, const QSet<int>
  */
 bool PersonStorage::delPointOf(int pers, int direction, int frame)
 {
+    mAutosave.trackPersonModified();
     if(direction == -1)
     {
         for(int j = 0; j < frame - mPersons.at(pers).firstFrame(); ++j)
@@ -125,6 +129,7 @@ bool PersonStorage::delPointOf(int pers, int direction, int frame)
  */
 bool PersonStorage::delPoint(const Vec2F &point, int direction, int frame, const QSet<int> &onlyVisible)
 {
+    mAutosave.trackPersonModified();
     for(int i = 0; i < static_cast<int>(mPersons.size()); ++i)
     { // ueber TrackPerson
         if(((onlyVisible.empty()) || (onlyVisible.contains(i))) &&
@@ -146,6 +151,7 @@ bool PersonStorage::delPoint(const Vec2F &point, int direction, int frame, const
  */
 void PersonStorage::delPointAll(Direction direction, int frame)
 {
+    mAutosave.trackPersonModified();
     for(size_t i = 0; i < mPersons.size(); ++i) // ueber TrackPerson
     {
         if(mPersons.at(i).trackPointExist(frame))
@@ -190,6 +196,7 @@ void PersonStorage::delPointAll(Direction direction, int frame)
  */
 void PersonStorage::delPointInsideROI()
 {
+    mAutosave.trackPersonModified();
     QRectF rect = mMainWindow.getRecoRoiItem()->rect();
     bool   inside;
 
@@ -225,6 +232,7 @@ void PersonStorage::delPointInsideROI()
  */
 void PersonStorage::delPointROI()
 {
+    mAutosave.trackPersonModified();
     int    anz  = 0;
     QRectF rect = mMainWindow.getRecoRoiItem()->rect();
 
@@ -258,6 +266,7 @@ void PersonStorage::delPointROI()
  */
 bool PersonStorage::editTrackPersonComment(const Vec2F &point, int frame, const QSet<int> &onlyVisible)
 {
+    mAutosave.trackPersonModified();
     for(int i = 0; i < static_cast<int>(mPersons.size()); ++i) // ueber TrackPerson
     {
         if(((onlyVisible.empty()) || (onlyVisible.contains(i))) &&
@@ -312,6 +321,7 @@ bool PersonStorage::editTrackPersonComment(const Vec2F &point, int frame, const
  */
 bool PersonStorage::setTrackPersonHeight(const Vec2F &point, int frame, const QSet<int> &onlyVisible)
 {
+    mAutosave.trackPersonModified();
     for(int i = 0; i < static_cast<int>(mPersons.size()); ++i) // ueber TrackPerson
     {
         if(((onlyVisible.empty()) || (onlyVisible.contains(i))) &&
@@ -379,6 +389,7 @@ bool PersonStorage::setTrackPersonHeight(const Vec2F &point, int frame, const QS
  */
 bool PersonStorage::resetTrackPersonHeight(const Vec2F &point, int frame, QSet<int> onlyVisible)
 {
+    mAutosave.trackPersonModified();
     for(int i = 0; i < static_cast<int>(mPersons.size()); ++i) // ueber TrackPerson
     {
         if(((onlyVisible.empty()) || (onlyVisible.contains(i))) &&
@@ -472,6 +483,11 @@ bool PersonStorage::addPoint(
     reco::RecognitionMethod method,
     int *                   pers)
 {
+    if(point.qual() > 100)
+    {
+        // manually added point
+        mAutosave.trackPersonModified();
+    }
     bool  found = false;
     int   i, iNearest = 0.;
     float scaleHead;
@@ -682,6 +698,7 @@ int PersonStorage::smallestFirstFrame() const
  */
 void PersonStorage::recalcHeight(float altitude)
 {
+    mAutosave.trackPersonModified();
     for(auto &person : mPersons)
     {
         person.recalcHeight(altitude);
@@ -894,6 +911,7 @@ void PersonStorage::checkPlausibility(
 /// optimize color for all persons
 void PersonStorage::optimizeColor()
 {
+    mAutosave.trackPersonModified();
     for(auto &person : mPersons)
     {
         if(person.color().isValid())
@@ -906,6 +924,7 @@ void PersonStorage::optimizeColor()
 /// reset the height of all persons, but not the pos of the trackpoints
 void PersonStorage::resetHeight()
 {
+    mAutosave.trackPersonModified();
     for(auto &person : mPersons)
     {
         person.resetHeight();
@@ -915,6 +934,7 @@ void PersonStorage::resetHeight()
 /// reset the pos of the tzrackpoints, but not the heights
 void PersonStorage::resetPos()
 {
+    mAutosave.trackPersonModified();
     for(auto &person : mPersons)
     {
         for(auto &point : person)
@@ -985,6 +1005,7 @@ bool PersonStorage::printHeightDistribution()
  */
 void PersonStorage::setMarkerHeights(const std::unordered_map<int, float> &heights)
 {
+    mAutosave.trackPersonModified();
     for(auto &person : mPersons) // over TrackPerson
     {
         for(auto &point : person) // over TrackPoints
@@ -1017,6 +1038,7 @@ void PersonStorage::setMarkerHeights(const std::unordered_map<int, float> &heigh
  */
 void PersonStorage::setMarkerIDs(const std::unordered_map<int, int> &markerIDs)
 {
+    mAutosave.trackPersonModified();
     for(int i = 0; i < static_cast<int>(mPersons.size()); ++i) // over TrackPerson
     {
         // personID of current person
@@ -1050,6 +1072,7 @@ void PersonStorage::setMarkerIDs(const std::unordered_map<int, int> &markerIDs)
  */
 void PersonStorage::purge(int frame)
 {
+    mAutosave.trackPersonModified();
     int   i, j;
     float count; ///< number of trackpoints without recognition
 
@@ -1177,6 +1200,7 @@ void PersonStorage::insertFeaturePoint(
  */
 int PersonStorage::merge(int pers1, int pers2)
 {
+    mAutosave.trackPersonModified();
     auto &     person      = mPersons.at(pers1);
     auto &     other       = mPersons.at(pers2);
     const bool extrapolate = mMainWindow.getControlWidget()->trackExtrapolation->checkState() == Qt::Checked;
diff --git a/src/petrack.cpp b/src/petrack.cpp
index ed3ae0850..d6fb82e92 100644
--- a/src/petrack.cpp
+++ b/src/petrack.cpp
@@ -593,6 +593,7 @@ void Petrack::openProject(QString fileName, bool openSeq) // default fileName=""
         {
             return;
         }
+        mAutosave.deleteAutosave();
     }
     // if no destination file or folder is given
     if(fileName.isEmpty())
@@ -649,6 +650,16 @@ void Petrack::openProject(QString fileName, bool openSeq) // default fileName=""
             mControlWidget->setNewModelChecked(false);
         }
         updateWindowTitle();
+
+        if(mAutosave.checkAutosave())
+        {
+            auto ret =
+                PQuestion(this, "Autosave detected", "An autosave was detected.\nDo you want to load the Autosave?");
+            if(ret == PMessageBox::StandardButton::Yes)
+            {
+                mAutosave.loadAutosave();
+            }
+        }
     }
 }
 
@@ -2526,6 +2537,7 @@ void Petrack::closeEvent(QCloseEvent *event)
     if(maybeSave())
     {
         writeSettings();
+        mAutosave.deleteAutosave();
         event->accept();
     }
     else
diff --git a/tests/unit_test/tst_moCapController.cpp b/tests/unit_test/tst_moCapController.cpp
index 1e8591e6f..c26580c51 100644
--- a/tests/unit_test/tst_moCapController.cpp
+++ b/tests/unit_test/tst_moCapController.cpp
@@ -60,7 +60,8 @@ SCENARIO("I want to get the render data with one person loaded", "[ui]")
     // default time offset 0
 
     Petrack       pet;
-    PersonStorage st{pet};
+    Autosave      save{pet};
+    PersonStorage st{pet, save};
     ExtrCalibMock extrCalib{st};
 
     /*
-- 
GitLab