You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/EMotionFX/Code/MCore/Source/FileSystem.cpp

203 lines
8.9 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "FileSystem.h"
#include <AzCore/IO/FileIO.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/std/functional.h>
#include "MCoreCommandManager.h"
#include "StringConversions.h"
namespace MCore
{
// The folder path used to keep a backup in SaveToFileSecured.
StaticString FileSystem::s_secureSavePath;
// Save to file secured by a backup file.
bool FileSystem::SaveToFileSecured(const char* filename, const AZStd::function<bool()>& saveFunction, CommandManager* commandManager)
{
// If the secure save path is not set, simply call the save function.
if (s_secureSavePath.empty())
{
return saveFunction();
}
using namespace AZ::IO;
FileIOBase* fileIo = FileIOBase::GetInstance();
// Check if the file already exists, in this case a backup is needed.
// Backup is needed to make sure we don't lose data when a crash or power failure occurs during saving.
if (fileIo->Exists(filename))
{
// Extract the base filename without extension and the extension without the dot.
AZStd::string baseFilename;
AzFramework::StringFunc::Path::GetFileName(filename, baseFilename);
AZStd::string extension;
AzFramework::StringFunc::Path::GetExtension(filename, extension, false /* include dot */);
// Find a unique backup filename.
AZ::u32 backupFileIndex = 0;
AZStd::string backupFileIndexString;
AZStd::string backupFilename = s_secureSavePath.c_str() + baseFilename + '.' + extension;
while (fileIo->Exists(backupFilename.c_str()))
{
AZStd::to_string(backupFileIndexString, ++backupFileIndex);
backupFilename = s_secureSavePath.c_str() + baseFilename + backupFileIndexString + '.' + extension;
}
// Copy the file to the backup filename.
// Rename is not used to avoid the read-only file state in the secure save path in case the file is in read-only.
if (fileIo->Copy(filename, backupFilename.c_str()) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot copy file '<b>%s</b>' to backup file '<b>%s</b>'.", filename, backupFilename.c_str());
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
return false;
}
// Set the recover filename.
AZStd::string recoverFilename = backupFilename;
recoverFilename += ".recover";
// Save the recover json file that contains the link to the original filename.
HandleType fileHandle = InvalidHandle;
if (fileIo->Open(recoverFilename.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle))
{
rapidjson::Document json;
rapidjson::Value& root = json.SetObject();
rapidjson::Value orgFilenameItem(rapidjson::kStringType);
orgFilenameItem.SetString(filename, json.GetAllocator());
root.AddMember("OriginalFileName", orgFilenameItem, json.GetAllocator());
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
json.Accept(writer);
AZ::u64 bytesWritten = 0;
if (!fileIo->Write(fileHandle, buffer.GetString(), buffer.GetSize(), &bytesWritten))
{
AZ_Error("EMotionFX", false, "Failed to write recover file: %s", recoverFilename.c_str());
}
fileIo->Close(fileHandle);
}
// Call the customized save file function.
if (saveFunction())
{
// Remove the backup file.
if (fileIo->Remove(backupFilename.c_str()) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot delete backup file '<b>%s</b>'.", backupFilename.c_str());
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
}
// Remove the recover file.
if (fileIo->Remove(recoverFilename.c_str()) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot delete recover file '<b>%s</b>'.", recoverFilename.c_str());
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
}
// Return true even if the files can not be removed because the save succeeded.
return true;
}
// Saving failed.
else
{
// Remove the partially saved file if the file exists.
// It's needed because if the file already exists it's not possible to rename.
if (fileIo->Exists(filename) &&
fileIo->Remove(filename) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot delete the partially saved file '<b>%s</b>'.", filename);
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
return false;
}
// Copy the backup file over to where the original file was.
if (fileIo->Copy(backupFilename.c_str(), filename) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot copy backup file '<b>%s</b>' to '<b>%s</b>'.", backupFilename.c_str(), filename);
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
}
// Remove the backup file.
if (fileIo->Remove(backupFilename.c_str()) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot delete backup file '<b>%s</b>'.", backupFilename.c_str());
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
}
// Remove the recover file.
if (fileIo->Remove(recoverFilename.c_str()) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot delete recover file '<b>%s</b>'.", recoverFilename.c_str());
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
}
return false;
}
}
// If the file doesn't exist.
else
{
// save the file
if (!saveFunction())
{
// Remove the partially saved file if the file exists.
if (fileIo->Exists(filename) &&
fileIo->Remove(filename) == ResultCode::Error)
{
const AZStd::string errorMessage = AZStd::string::format("MCore::FileSystem::SaveToFileSecured() - Cannot delete the partially saved file '<b>%s</b>'.", filename);
if (commandManager)
{
commandManager->AddError(errorMessage);
}
AZ_Error("EMotionFX", false, errorMessage.c_str());
}
return false;
}
return true;
}
}
} // namespace MCore