diff --git a/Source/StudyFrameworkPlugin/Private/SFCondition.cpp b/Source/StudyFrameworkPlugin/Private/SFCondition.cpp index 4ff81d9b5d7496b8bd2a91e9e8e3beba22326ab3..ed178fa7ef9967a05e14871f1f8ba06a672017eb 100644 --- a/Source/StudyFrameworkPlugin/Private/SFCondition.cpp +++ b/Source/StudyFrameworkPlugin/Private/SFCondition.cpp @@ -26,7 +26,7 @@ void USFCondition::Generate(const FString& InPhaseName, const TArray<int>& Condi if (Factor->IsA(USFMapFactor::StaticClass())) { Map = FactorLevel; - //for better readybility strip path! + //for better readability strip path! FactorLevel = FPaths::GetBaseFilename(FactorLevel); } FactorLevels.Add(Factor->FactorName, FactorLevel); @@ -232,6 +232,11 @@ bool USFCondition::HasRequiredVariables() const return false; } +void USFCondition::SetbStarted(bool WasStarted) +{ + bStarted = WasStarted; +} + bool USFCondition::WasStarted() const { return bStarted; diff --git a/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp b/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp index 5ecf2b0d12b9f4a22d8745951b9d0383e701692f..04fc39807fdc3e705666affdc3ffbd7913e46bfc 100644 --- a/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp +++ b/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp @@ -529,6 +529,8 @@ void USFGameInstance::HandleGoToConditionSynced(FString ConditionName, bool bFor { LogComment("Restarting Condition " + NextCondition->UniqueName + ", so we delete all gathered data for that condition."); + FString BackUpFolderName = "Restart_" + NextCondition->UniqueName + "_BackUp-" + FDateTime::Now().ToString(); + Participant->SetCurrentBackUpFolderName(BackUpFolderName); //so remove already stored data! Participant->DeleteStoredDataForConditionFromLongTable(NextCondition); for (USFDependentVariable* DV : NextCondition->DependentVariables) diff --git a/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp b/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp index d783a320663a06fe080dec7ec9f59523735806ca..35cfceedb71f68693297ccee9fce29d5121f4600 100644 --- a/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp +++ b/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp @@ -10,6 +10,7 @@ #include "Help/SFUtils.h" #include "Logging/SFLoggingBPLibrary.h" #include "Logging/SFLoggingUtils.h" +#include "JsonUtilities.h" USFParticipant::USFParticipant() { @@ -335,10 +336,11 @@ void USFParticipant::RemoveLinesOfConditionAndWriteToFile(USFCondition* Conditio } CleanedLines.Add(Lines[0]); + bool bHasRemovedLines = false; for (int i = 1; i < Lines.Num(); ++i) { TArray<FString> Entries; - Lines[i].ParseIntoArray(Entries, TEXT(","), false); + Lines[i].ParseIntoArray(Entries, TEXT(","), false); if (Entries.Num() > 0 && Entries[0] == ParticipantID) { @@ -356,6 +358,11 @@ void USFParticipant::RemoveLinesOfConditionAndWriteToFile(USFCondition* Conditio { CleanedLines.Add(Lines[i]); } + //At least this line will be removed + else if(!bHasRemovedLines) + { + bHasRemovedLines = true; + } } else { @@ -363,6 +370,12 @@ void USFParticipant::RemoveLinesOfConditionAndWriteToFile(USFCondition* Conditio CleanedLines.Add(Lines[i]); } } + + if(bHasRemovedLines) + { + CreateLongTableBackUp(Filename); + } + FFileHelper::SaveStringArrayToFile(CleanedLines, *Filename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM, &IFileManager::Get(), EFileWrite::FILEWRITE_None); } @@ -451,6 +464,15 @@ TArray<USFCondition*> USFParticipant::GetLastParticipantsConditions() TArray<USFCondition*> LoadedConditions; TMap<USFIndependentVariable*, FString> LoadedIndependentVariablesValues; ReadExecutionJsonFile(GetLastParticipantID(), LoadedConditions, LoadedIndependentVariablesValues); + //Load WasStarted-Values from JSON, to ensure data is not overwritten without backups, if conditions are restarted + TMap<FString, bool> LastParticipantHasStartedConditionValues = GetLastParticipantHasStartedConditionValues(); + for(int i = 0; i<LoadedConditions.Num(); i++) + { + if(LastParticipantHasStartedConditionValues.Contains(LoadedConditions[i]->CreateIdentifiableName())) + { + LoadedConditions[i]->SetbStarted(LastParticipantHasStartedConditionValues[LoadedConditions[i]->CreateIdentifiableName()]); + } + } return LoadedConditions; } @@ -487,6 +509,27 @@ int USFParticipant::GetLastParticipantLastConditionStarted() return ParticipantJson->GetNumberField("CurrentConditionIdx"); } +TMap<FString, bool> USFParticipant::GetLastParticipantHasStartedConditionValues() +{ + TMap<FString, bool> ConditionsStarted; + TSharedPtr<FJsonObject> ParticipantJson = FSFUtils::ReadJsonFromFile("StudyRuns/LastParticipant.txt"); + if (ParticipantJson != nullptr) + { + TArray<TSharedPtr<FJsonValue>> StartedConditionsArray = ParticipantJson->GetArrayField("StartedConditions"); + for(auto& JsonValue : StartedConditionsArray) + { + TSharedPtr<FJsonObject> Entry = JsonValue->AsObject(); + if(Entry.IsValid()) + { + ConditionsStarted.Add(Entry->GetStringField("Condition"), Entry->GetBoolField("WasStarted")); + } + } + + } + return ConditionsStarted; + +} + bool USFParticipant::GetLastParticipantFinished() { TSharedPtr<FJsonObject> ParticipantJson = FSFUtils::ReadJsonFromFile("StudyRuns/LastParticipant.txt"); @@ -649,6 +692,16 @@ void USFParticipant::RecoverStudyResultsOfFinishedConditions() } } +void USFParticipant::CreateLongTableBackUp(const FString PathToSrcFile) const +{ + IFileManager& FileManager = IFileManager::Get(); + const FString ReplaceWith = "StudyFramework/StudyLogs/RecyclingBin/" + CurrentBackUpFolderName + "/"; + const FString PathToDestFile = PathToSrcFile.Replace(TEXT("StudyFramework/StudyLogs/"), *ReplaceWith); + FileManager.Copy( *PathToDestFile, *PathToSrcFile); + FSFLoggingUtils::Log("Created Backup: " + PathToDestFile); + +} + void USFParticipant::ClearPhaseLongtables(ASFStudySetup* StudySetup) { const FString LongTableFolder = FPaths::ProjectDir() + "StudyFramework/StudyLogs/"; @@ -667,6 +720,12 @@ void USFParticipant::ClearPhaseLongtables(ASFStudySetup* StudySetup) FSFLoggingUtils::Log("Moved .csv files: " + NewParentFolderPath); } +void USFParticipant::SetCurrentBackUpFolderName(FString BackUpFolderName) +{ + CurrentBackUpFolderName = BackUpFolderName; +} + + bool USFParticipant::SetCondition(const USFCondition* NextCondition) { if (!NextCondition) @@ -707,10 +766,16 @@ void USFParticipant::LogCurrentParticipant() const Json->SetNumberField("ParticipantSequenceNumber", ParticipantSequenceNumber); Json->SetStringField("ParticipantID", ParticipantID); bool bFinished = true; + TArray<TSharedPtr<FJsonValue>> StartedConditions; //Cannot use TMap because we want to save to JSON for (USFCondition* Condition : Conditions) { bFinished = bFinished && (Condition->IsFinished() || !Condition->HasRequiredVariables()); + TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>(); + JsonObject->SetStringField("Condition", Condition->CreateIdentifiableName()); + JsonObject->SetBoolField("WasStarted", Condition->WasStarted()); + StartedConditions.Add(MakeShared<FJsonValueObject>(JsonObject)); } + Json->SetArrayField("StartedConditions", StartedConditions); Json->SetBoolField("Finished", bFinished); Json->SetNumberField("CurrentConditionIdx", CurrentConditionIdx); if (USFGameInstance::Get() && USFGameInstance::Get()->GetStudySetup()) diff --git a/Source/StudyFrameworkPlugin/Public/SFCondition.h b/Source/StudyFrameworkPlugin/Public/SFCondition.h index 21522f702dae7912231820ca38c2a0ddf891e86f..c583adcb294ed2178fc94b7084a3c7a28c64aeda 100644 --- a/Source/StudyFrameworkPlugin/Public/SFCondition.h +++ b/Source/StudyFrameworkPlugin/Public/SFCondition.h @@ -37,6 +37,7 @@ public: bool IsFinished() const; bool HasRequiredVariables() const; bool WasStarted() const; + void SetbStarted(bool WasStarted); //this is used to recover study results from the phase long table if a participant's run is continued //return false if this entry does not match this condition diff --git a/Source/StudyFrameworkPlugin/Public/SFParticipant.h b/Source/StudyFrameworkPlugin/Public/SFParticipant.h index eb5096c648417da6e2ada979320f0752bac3d285..0c39bced0b7596127f2ee2c86fd8f194fee82390 100644 --- a/Source/StudyFrameworkPlugin/Public/SFParticipant.h +++ b/Source/StudyFrameworkPlugin/Public/SFParticipant.h @@ -50,6 +50,7 @@ public: static int GetLastParticipantSequenceNumber(); static FString GetLastParticipantID(); static int GetLastParticipantLastConditionStarted(); + static TMap<FString, bool> GetLastParticipantHasStartedConditionValues(); static bool GetLastParticipantFinished(); static ASFStudySetup* GetLastParticipantSetup(); void LoadLastParticipantsIndependentVariables(); @@ -69,15 +70,21 @@ public: void RecoverStudyResultsOfFinishedConditions(); - // the results of all participants are stored in a file per phase (called longtable) - // for the data to be ready to use in statistics software, this methods clears all - // of that data (e.g. if study is entirely restarted) - // So: USE WITH CARE! + // The results of all participants are stored in a file per phase (called longtable) + // for the data to be ready to use in statistics software. This methods clears all + // of that data (e.g. if study is entirely restarted). + // The data can be recovered from /StudyLogs/RecyclingBin static void ClearPhaseLongtables(ASFStudySetup* StudySetup); + // Whenever we delete data in a file (e.g. with by restarting a condition), + // we want to create a backup, to enable data recovery + // EditOperation reflects the reason (e.g. restart condition), which appears in + // recycling bin file name + void CreateLongTableBackUp(const FString PathToSrcFile) const; void StoreTrialInTrialDVLongTable(USFMultipleTrialDependentVariable* DependentVariable, TArray<FString> Values) const; void DeleteStoredDataForConditionFromLongTable(USFCondition* Condition); void DeleteStoredTrialDataForCondition(USFCondition* Condition, USFMultipleTrialDependentVariable* DependentVariable); + void SetCurrentBackUpFolderName(FString BackUpFolderName); protected: bool SetCondition(const USFCondition* NextCondition); @@ -93,7 +100,13 @@ protected: void StoreInIndependentVarLongTable() const; void RemoveLinesOfConditionAndWriteToFile(USFCondition* Condition, FString Filename); - + // This is the parent folder within RecyclingBin, where backups + // of the current operation will be stored. + // E.g. "RestartConditionBackUp-TIMESTAMP" + // Whenever we delete lines within a .csv file we should update this. + // We want to store all files related to one operation in the same + // backup folder, this is why we need this variable. + FString CurrentBackUpFolderName; FString ParticipantID; //sequence number is used for randomization etc. it is also unique per participant and starts at 0 int ParticipantSequenceNumber;