FileSaver.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/Asset/AssetManagerBus.h>
  9. #include <AzCore/Component/TickBus.h>
  10. #include <AzCore/IO/IStreamer.h>
  11. #include <AzCore/IO/Streamer/FileRequest.h>
  12. #include <AzCore/IO/SystemFile.h>
  13. #include <AzFramework/Asset/AssetSystemBus.h>
  14. #include <AzFramework/IO/FileOperations.h>
  15. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  16. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  17. #include <ScriptCanvas/Assets/ScriptCanvasFileHandling.h>
  18. #include <ScriptCanvas/Core/GraphSerialization.h>
  19. AZ_PUSH_DISABLE_WARNING(4251 4800 4244, "-Wunknown-warning-option")
  20. #include <ScriptCanvas/Components/EditorUtils.h>
  21. AZ_POP_DISABLE_WARNING
  22. #include <Editor/View/Windows/Tools/UpgradeTool/FileSaver.h>
  23. namespace ScriptCanvasEditor
  24. {
  25. namespace VersionExplorer
  26. {
  27. bool FileSaveResult::IsSuccess() const
  28. {
  29. return fileSaveError.empty();
  30. }
  31. FileSaver::FileSaver
  32. ( AZStd::function<bool()> onReadOnlyFile
  33. , AZStd::function<void(const FileSaveResult& result)> onComplete)
  34. : m_onReadOnlyFile(onReadOnlyFile)
  35. , m_onComplete(onComplete)
  36. {}
  37. const SourceHandle& FileSaver::GetSource() const
  38. {
  39. return m_source;
  40. }
  41. void FileSaver::PerformMove
  42. ( AZStd::string tmpFileName
  43. , AZStd::string target
  44. , size_t remainingAttempts)
  45. {
  46. if (remainingAttempts == 0)
  47. {
  48. AZ::SystemTickBus::QueueFunction([this, tmpFileName]()
  49. {
  50. FileSaveResult result;
  51. result.fileSaveError = "Failed to move updated file from temporary location to original destination.";
  52. result.tempFileRemovalError = RemoveTempFile(tmpFileName);
  53. m_onComplete(result);
  54. });
  55. }
  56. else if (remainingAttempts == 2)
  57. {
  58. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  59. // before the last attempt, flush all the caches
  60. AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches();
  61. streamer->SetRequestCompleteCallback(flushRequest
  62. , [this, remainingAttempts, tmpFileName, target]([[maybe_unused]] AZ::IO::FileRequestHandle request)
  63. {
  64. // One last try
  65. AZ::SystemTickBus::QueueFunction(
  66. [this, remainingAttempts, tmpFileName, target]() { PerformMove(tmpFileName, target, remainingAttempts - 1); });
  67. });
  68. streamer->QueueRequest(flushRequest);
  69. }
  70. else
  71. {
  72. // the actual move attempt
  73. auto moveResult = AZ::IO::SmartMove(tmpFileName.c_str(), target.c_str());
  74. if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success)
  75. {
  76. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  77. AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str());
  78. // Bump the slice asset up in the asset processor's queue.
  79. AzFramework::AssetSystemRequestBus::Broadcast
  80. (&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str());
  81. AZ::SystemTickBus::QueueFunction([this, tmpFileName]()
  82. {
  83. FileSaveResult result;
  84. result.absolutePath = m_fullPath;
  85. result.tempFileRemovalError = RemoveTempFile(tmpFileName);
  86. m_onComplete(result);
  87. });
  88. }
  89. else
  90. {
  91. AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false
  92. , "moving converted file to tmpFileName destination failed: %s, trying again", target.c_str());
  93. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  94. AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str());
  95. streamer->SetRequestCompleteCallback(flushRequest
  96. , [this, tmpFileName, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request)
  97. {
  98. // Continue saving.
  99. AZ::SystemTickBus::QueueFunction(
  100. [this, tmpFileName, target, remainingAttempts]() { PerformMove(tmpFileName, target, remainingAttempts - 1); });
  101. });
  102. streamer->QueueRequest(flushRequest);
  103. }
  104. }
  105. }
  106. void FileSaver::OnSourceFileReleased(const SourceHandle& source)
  107. {
  108. AZStd::string fullPath = m_fullPath.c_str();
  109. AZStd::string tmpFileName;
  110. // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file.
  111. // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing.
  112. // Temp files are ignored by AP.
  113. if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName))
  114. {
  115. FileSaveResult result;
  116. result.fileSaveError = "Failure to create temporary file name";
  117. m_onComplete(result);
  118. return;
  119. }
  120. AZStd::string saveError;
  121. AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText);
  122. if (fileStream.IsOpen())
  123. {
  124. auto serializeResult = Serialize(*source.Data(), fileStream);
  125. if (!serializeResult)
  126. {
  127. saveError = serializeResult.m_errors;
  128. }
  129. fileStream.Close();
  130. }
  131. if (!saveError.empty())
  132. {
  133. FileSaveResult result;
  134. result.fileSaveError = AZStd::string::format("Save asset data to temporary file failed: %s", saveError.c_str());
  135. m_onComplete(result);
  136. return;
  137. }
  138. AzToolsFramework::SourceControlCommandBus::Broadcast
  139. ( &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit
  140. , fullPath.c_str()
  141. , true
  142. , [this, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info)
  143. {
  144. constexpr const size_t k_maxAttemps = 10;
  145. if (!info.IsReadOnly())
  146. {
  147. PerformMove(tmpFileName, fullPath, k_maxAttemps);
  148. }
  149. else if (m_onReadOnlyFile && m_onReadOnlyFile())
  150. {
  151. AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true);
  152. PerformMove(tmpFileName, fullPath, k_maxAttemps);
  153. }
  154. else
  155. {
  156. FileSaveResult result;
  157. result.fileSaveError = "Source file was and remained read-only";
  158. result.tempFileRemovalError = RemoveTempFile(tmpFileName);
  159. m_onComplete(result);
  160. }
  161. });
  162. }
  163. AZStd::string FileSaver::RemoveTempFile(AZStd::string_view tempFile)
  164. {
  165. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  166. if (!fileIO)
  167. {
  168. return "No FileIO instance";
  169. }
  170. if (fileIO->Exists(tempFile.data()) && !fileIO->Remove(tempFile.data()))
  171. {
  172. return AZStd::string::format("Failed to remove temporary file: %s", tempFile.data());
  173. }
  174. return "";
  175. }
  176. void FileSaver::Save(const SourceHandle& source, const AZ::IO::Path& absolutePath)
  177. {
  178. m_fullPath.clear();
  179. m_source = source;
  180. m_fullPath = absolutePath;
  181. if (m_fullPath.empty())
  182. {
  183. FileSaveResult result;
  184. result.fileSaveError = "No save location specified";
  185. m_onComplete(result);
  186. }
  187. else
  188. {
  189. auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
  190. AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(m_fullPath.c_str());
  191. streamer->SetRequestCompleteCallback(flushRequest, [this]([[maybe_unused]] AZ::IO::FileRequestHandle request)
  192. {
  193. AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
  194. if (!m_sourceFileReleased)
  195. {
  196. m_sourceFileReleased = true;
  197. AZ::SystemTickBus::QueueFunction([this]() { this->OnSourceFileReleased(m_source); });
  198. }
  199. });
  200. streamer->QueueRequest(flushRequest);
  201. }
  202. }
  203. }
  204. }