AsyncSaveRunner.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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 <SaveUtilities/AsyncSaveRunner.h>
  9. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  10. #include <AzCore/IO/SystemFile.h>
  11. #include <ActionOutput.h>
  12. namespace AZ
  13. {
  14. /* -------------------------- *
  15. * == Save Operation Cache == *
  16. * -------------------------- */
  17. SaveOperationController::SaveOperationCache::SaveOperationCache(const AZStd::string& fullPath, SynchronousSaveOperation saveOperation, SaveOperationController& owner, bool isDelete)
  18. : m_fullSavePath(fullPath)
  19. , m_saveOperation(saveOperation)
  20. , m_owner(owner)
  21. , m_isDelete(isDelete)
  22. {
  23. }
  24. void SaveOperationController::SaveOperationCache::Run(const AZStd::shared_ptr<ActionOutput>& actionOutput)
  25. {
  26. if (m_isDelete)
  27. {
  28. RunDelete(actionOutput);
  29. return;
  30. }
  31. // Create the callback to pass to the SourceControlAPI
  32. AzToolsFramework::SourceControlResponseCallback callback =
  33. [this, actionOutput](bool success, const AzToolsFramework::SourceControlFileInfo& info)
  34. {
  35. if (success || !info.IsReadOnly())
  36. {
  37. if (m_saveOperation && !m_saveOperation(m_fullSavePath, actionOutput))
  38. {
  39. success = false;
  40. if (actionOutput)
  41. {
  42. actionOutput->AddError("Failed to save entries/dependencies", m_fullSavePath);
  43. }
  44. }
  45. }
  46. if (!success && actionOutput)
  47. {
  48. AZStd::string message;
  49. AZStd::string details = m_fullSavePath;
  50. // If there's no attempt to save any data it's assumed that this function was called to add an existing file to source control.
  51. // Rather than report this as an error, report it as a warning as no data was lost.
  52. bool reportAsWarning = !m_saveOperation;
  53. bool moreDetails = true;
  54. // Be more specific with errors so as to give the user the best chance at fixing them
  55. if (!info.HasFlag(AzToolsFramework::SCF_OpenByUser))
  56. {
  57. if (info.HasFlag(AzToolsFramework::SCF_OutOfDate))
  58. {
  59. message = "The file being worked on doesn't contain the latest changes from source control";
  60. moreDetails = false;
  61. }
  62. else if (info.IsLockedByOther())
  63. {
  64. message = "The file is already exclusively opened by another user";
  65. details = info.m_StatusUser + " -> " + m_fullSavePath;
  66. moreDetails = false;
  67. }
  68. else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown)
  69. {
  70. message = "Failed to put entries/dependencies into source control as the provider is not available.\n";
  71. reportAsWarning = true;
  72. }
  73. else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid)
  74. {
  75. message = "Failed to put entries/dependencies into source control as the source control has an invalid certificate.\n";
  76. }
  77. else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderError)
  78. {
  79. message = "Failed to put entries/dependencies into source control as the provider reported an error.\n";
  80. }
  81. else if (!info.IsManaged())
  82. {
  83. message = "Failed to put entries/dependencies into source control as they are outside the current workspace mapping.\n";
  84. reportAsWarning = true;
  85. }
  86. else
  87. {
  88. message = "Make sure the disk is not full or the file is not write-protected or not currently in use.\n";
  89. }
  90. }
  91. else
  92. {
  93. message = "File marked as 'Open By User' but still failed.\n";
  94. }
  95. if (moreDetails)
  96. {
  97. message += "Please see the source control icon in the status bar for further details";
  98. }
  99. if (reportAsWarning)
  100. {
  101. actionOutput->AddWarning(message, details);
  102. success = true;
  103. }
  104. else
  105. {
  106. actionOutput->AddError(message, details);
  107. }
  108. }
  109. m_owner.HandleOperationComplete(this, success);
  110. };
  111. using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
  112. SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, m_fullSavePath.c_str(), true, callback);
  113. }
  114. void SaveOperationController::SaveOperationCache::RunDelete(const AZStd::shared_ptr<ActionOutput>& actionOutput)
  115. {
  116. // Create the callback to pass to the SourceControlAPI
  117. AzToolsFramework::SourceControlResponseCallback callback =
  118. [this, actionOutput](bool success, const AzToolsFramework::SourceControlFileInfo& info)
  119. {
  120. if (success || !info.IsManaged())
  121. {
  122. success = true;
  123. if (m_saveOperation && !m_saveOperation(m_fullSavePath, actionOutput))
  124. {
  125. success = false;
  126. if (actionOutput)
  127. {
  128. actionOutput->AddError("Failed to delete entry", m_fullSavePath.c_str());
  129. }
  130. }
  131. }
  132. else if (actionOutput)
  133. {
  134. // Be more specific with errors so as to give the user the best chance at fixing them
  135. if (!info.HasFlag(AzToolsFramework::SCF_OpenByUser))
  136. {
  137. if (info.HasFlag(AzToolsFramework::SourceControlFlags::SCF_OutOfDate))
  138. {
  139. actionOutput->AddError("Source Control Issue - You do not have latest changes from source control for file", m_fullSavePath);
  140. }
  141. else if (info.IsLockedByOther())
  142. {
  143. actionOutput->AddError("Source Control Issue - File exclusively opened by another user", info.m_StatusUser + " -> " + m_fullSavePath);
  144. }
  145. else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown
  146. || info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid
  147. || info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderError)
  148. {
  149. actionOutput->AddError("Source Control Issue - Failed to remove file from source control, check your connection to your source control service", m_fullSavePath.c_str());
  150. }
  151. else
  152. {
  153. actionOutput->AddError("Unknown Issue with source control.", m_fullSavePath);
  154. }
  155. }
  156. else
  157. {
  158. actionOutput->AddError("Source Control Issue - File marked as 'Open By User' but still failed.", m_fullSavePath);
  159. }
  160. }
  161. m_owner.HandleOperationComplete(this, success);
  162. };
  163. using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
  164. SCCommandBus::Broadcast(&SCCommandBus::Events::RequestDelete, m_fullSavePath.c_str(), callback);
  165. }
  166. /* ------------------------------- *
  167. * == Save Operation Controller == *
  168. * ------------------------------- */
  169. SaveOperationController::SaveOperationController(AsyncSaveRunner& owner)
  170. : m_owner(owner)
  171. , m_completedCount(0)
  172. {
  173. }
  174. void SaveOperationController::AddDeleteOperation(const AZStd::string& fullPath, SynchronousSaveOperation saveOperation)
  175. {
  176. m_allSaveOperations.push_back(AZStd::make_shared<SaveOperationCache>(fullPath, saveOperation, *this, true));
  177. }
  178. void SaveOperationController::AddSaveOperation(const AZStd::string& fullPath, SynchronousSaveOperation saveOperation)
  179. {
  180. m_allSaveOperations.push_back(AZStd::make_shared<SaveOperationCache>(fullPath, saveOperation, *this));
  181. }
  182. void SaveOperationController::SetOnCompleteCallback(SaveCompleteCallback onThisRunnerComplete)
  183. {
  184. m_onSaveComplete = onThisRunnerComplete;
  185. }
  186. void SaveOperationController::RunAll(const AZStd::shared_ptr<ActionOutput>& actionOutput)
  187. {
  188. m_completedCount = 0;
  189. // If for some reason there are no save operations in this controller, then we need
  190. // to notify the runner and return so that this controller can be properly counted
  191. // as being completed.
  192. if (m_allSaveOperations.empty())
  193. {
  194. m_owner.HandleRunnerFinished(this, true);
  195. return;
  196. }
  197. for (AZStd::shared_ptr<SaveOperationCache>& saveOperation : m_allSaveOperations)
  198. {
  199. saveOperation->Run(actionOutput);
  200. }
  201. }
  202. void SaveOperationController::HandleOperationComplete([[maybe_unused]] SaveOperationCache* saveOperation, bool success)
  203. {
  204. if (!success)
  205. {
  206. m_currentSaveResult = false;
  207. }
  208. AZ_Assert(AZStd::find_if(m_allSaveOperations.begin(), m_allSaveOperations.end(),
  209. [saveOperation](const AZStd::shared_ptr<SaveOperationCache>& target) -> bool
  210. {
  211. return target.get() == saveOperation;
  212. }) != m_allSaveOperations.end(),
  213. "Attempting to cleanup completed save operation failed. Operation not found. Target file was: '%s'",
  214. saveOperation->m_fullSavePath.c_str());
  215. m_completedCount++;
  216. if (m_completedCount >= m_allSaveOperations.size())
  217. {
  218. if (m_onSaveComplete)
  219. {
  220. m_onSaveComplete(m_currentSaveResult);
  221. }
  222. m_owner.HandleRunnerFinished(this, m_currentSaveResult);
  223. }
  224. }
  225. /* ----------------------- *
  226. * == Async Save Runner == *
  227. * ----------------------- */
  228. AZStd::shared_ptr<SaveOperationController> AsyncSaveRunner::GenerateController()
  229. {
  230. AZStd::shared_ptr<SaveOperationController> saveEntryController = AZStd::make_shared<SaveOperationController>(*this);
  231. m_allSaveControllers.push_back(saveEntryController);
  232. return saveEntryController;
  233. }
  234. void AsyncSaveRunner::Run(const AZStd::shared_ptr<ActionOutput>& actionOutput, SaveCompleteCallback onSaveAllComplete, ControllerOrder order)
  235. {
  236. m_counter = 0;
  237. m_order = order;
  238. m_actionOutput = actionOutput;
  239. m_onSaveAllComplete = onSaveAllComplete;
  240. // If for some reason there are no save operations in this runner, then we need to run
  241. // the callback now and return so that the caller is properly notified.
  242. if (m_allSaveControllers.empty())
  243. {
  244. if (m_onSaveAllComplete)
  245. {
  246. m_onSaveAllComplete(true);
  247. }
  248. return;
  249. }
  250. if (order == ControllerOrder::Random)
  251. {
  252. for (auto& saveOp : m_allSaveControllers)
  253. {
  254. saveOp->RunAll(actionOutput);
  255. }
  256. }
  257. else if (order == ControllerOrder::Sequential)
  258. {
  259. m_allSaveControllers[0]->RunAll(actionOutput);
  260. }
  261. else
  262. {
  263. AZ_Assert(false, "Invalid ControllerOrder: %i", order);
  264. }
  265. }
  266. void AsyncSaveRunner::HandleRunnerFinished([[maybe_unused]] SaveOperationController* runner, bool success)
  267. {
  268. if (!success)
  269. {
  270. m_allWereSuccessfull = false;
  271. }
  272. if (m_order == ControllerOrder::Random)
  273. {
  274. AZ_Assert(AZStd::find_if(m_allSaveControllers.begin(), m_allSaveControllers.end(),
  275. [runner](const AZStd::shared_ptr<SaveOperationController>& target) -> bool
  276. {
  277. return target.get() == runner;
  278. }) != m_allSaveControllers.end(), "Attempting to cleanup completed save runner failed");
  279. m_counter++;
  280. }
  281. else if (m_order == ControllerOrder::Sequential)
  282. {
  283. AZ_Assert(m_counter < m_allSaveControllers.size(),
  284. "Counter for save controllers has become invalid (%i vs. %i).", static_cast<int>(m_counter), m_allSaveControllers.size());
  285. AZ_Assert(m_allSaveControllers[m_counter].get() == runner, "Completed incorrect save runner for index %i.", static_cast<int>(m_counter));
  286. m_counter++;
  287. if (m_counter < m_allSaveControllers.size())
  288. {
  289. m_allSaveControllers[m_counter]->RunAll(m_actionOutput);
  290. }
  291. }
  292. else
  293. {
  294. AZ_Assert(false, "Invalid ControllerOrder: %i", m_order);
  295. }
  296. if (m_counter >= m_allSaveControllers.size())
  297. {
  298. m_actionOutput.reset();
  299. if (m_onSaveAllComplete)
  300. {
  301. m_onSaveAllComplete(m_allWereSuccessfull);
  302. }
  303. }
  304. }
  305. }