Gems/EMotionFX/Code/MCore/Source/FileSystem.cpp
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;
        }
    }
// Save to file secured by a backup file.
Save to file secured by a backup file.
Save to file secured by a backup file .
bool FileSystem::SaveToFileSecured(const char* filename, const AZStd::function<bool()>& saveFunction, CommandManager* commandManager)
    {
        if (s_secureSavePath.empty())
        {
            return saveFunction();
        }
        using namespace AZ::IO;
        FileIOBase* fileIo = FileIOBase::GetInstance();
        if (fileIo->Exists(filename))
        {
            AZStd::string baseFilename;
            AzFramework::StringFunc::Path::GetFileName(filename, baseFilename);
            AZStd::string extension;
            AzFramework::StringFunc::Path::GetExtension(filename, extension, false );
            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;
            }
            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;
            }
            AZStd::string recoverFilename = backupFilename;
            recoverFilename += ".recover";
            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);
            }
            if (saveFunction())
            {
                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());
                }
                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;
            }
            else
            {
                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;
                }
                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());
                }
                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());
                }
                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;
            }
        }
        else
        {
            if (!saveFunction())
            {
                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;
        }
    }
bool FileSystem :: SaveToFileSecured ( const char * filename , const AZStd :: function < bool ( ) > & saveFunction , CommandManager * commandManager ) { if ( s_secureSavePath . empty ( ) ) { return saveFunction ( ) ; } using namespace AZ :: IO ; FileIOBase * fileIo = FileIOBase :: GetInstance ( ) ; if ( fileIo -> Exists ( filename ) ) { AZStd :: string baseFilename ; AzFramework :: StringFunc :: Path :: GetFileName ( filename , baseFilename ) ; AZStd :: string extension ; AzFramework :: StringFunc :: Path :: GetExtension ( filename , extension , false ) ; 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 ; } if ( fileIo -> Copy ( filename , backupFilename . c_str ( ) ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , filename , backupFilename . c_str ( ) ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; return false ; } AZStd :: string recoverFilename = backupFilename ; recoverFilename += " " ; 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 ( " " , 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 ( " " , false , " " , recoverFilename . c_str ( ) ) ; } fileIo -> Close ( fileHandle ) ; } if ( saveFunction ( ) ) { if ( fileIo -> Remove ( backupFilename . c_str ( ) ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , backupFilename . c_str ( ) ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; } if ( fileIo -> Remove ( recoverFilename . c_str ( ) ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , recoverFilename . c_str ( ) ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; } return true ; } else { if ( fileIo -> Exists ( filename ) && fileIo -> Remove ( filename ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , filename ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; return false ; } if ( fileIo -> Copy ( backupFilename . c_str ( ) , filename ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , backupFilename . c_str ( ) , filename ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; } if ( fileIo -> Remove ( backupFilename . c_str ( ) ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , backupFilename . c_str ( ) ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; } if ( fileIo -> Remove ( recoverFilename . c_str ( ) ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , recoverFilename . c_str ( ) ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; } return false ; } } else { if ( ! saveFunction ( ) ) { if ( fileIo -> Exists ( filename ) && fileIo -> Remove ( filename ) == ResultCode :: Error ) { const AZStd :: string errorMessage = AZStd :: string :: format ( " " , filename ) ; if ( commandManager ) { commandManager -> AddError ( errorMessage ) ; } AZ_Error ( " " , false , errorMessage . c_str ( ) ) ; } return false ; } return true ; } }
Save to file secured by a backup file.
Save to file secured by a backup file .
// If the secure save path is not set, simply call the save function. // 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. // Extract the base filename without extension and the extension without the dot. /* include dot */ // Find a unique backup filename. // 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. // Set the recover filename. // Save the recover json file that contains the link to the original filename. // Call the customized save file function. // Remove the backup file. // Remove the recover file. // Return true even if the files can not be removed because the save succeeded. // Saving failed. // Remove the partially saved file if the file exists. // It's needed because if the file already exists it's not possible to rename. // Copy the backup file over to where the original file was. // Remove the backup file. // Remove the recover file. // If the file doesn't exist. // save the file // Remove the partially saved file if the file exists.