EditorFileMonitor.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 "EditorDefs.h"
  9. #include "EditorFileMonitor.h"
  10. // Editor
  11. #include "CryEdit.h"
  12. #include <AzCore/Utils/Utils.h>
  13. //////////////////////////////////////////////////////////////////////////
  14. CEditorFileMonitor::CEditorFileMonitor()
  15. {
  16. GetIEditor()->RegisterNotifyListener(this);
  17. }
  18. //////////////////////////////////////////////////////////////////////////
  19. CEditorFileMonitor::~CEditorFileMonitor()
  20. {
  21. CFileChangeMonitor::DeleteInstance();
  22. }
  23. //////////////////////////////////////////////////////////////////////////
  24. void CEditorFileMonitor::OnEditorNotifyEvent(EEditorNotifyEvent ev)
  25. {
  26. if (ev == eNotify_OnInit)
  27. {
  28. // We don't want the file monitor to be enabled while
  29. // in console mode...
  30. if (!GetIEditor()->IsInConsolewMode())
  31. {
  32. MonitorDirectories();
  33. }
  34. CFileChangeMonitor::Instance()->Subscribe(this);
  35. }
  36. else if (ev == eNotify_OnQuit)
  37. {
  38. CFileChangeMonitor::Instance()->StopMonitor();
  39. GetIEditor()->UnregisterNotifyListener(this);
  40. }
  41. }
  42. //////////////////////////////////////////////////////////////////////////
  43. bool CEditorFileMonitor::RegisterListener(IFileChangeListener* pListener, const char* sMonitorItem)
  44. {
  45. return RegisterListener(pListener, sMonitorItem, "*");
  46. }
  47. //////////////////////////////////////////////////////////////////////////
  48. static AZStd::string CanonicalizePath(const char* path)
  49. {
  50. auto canon = QFileInfo(path).canonicalFilePath();
  51. return canon.isEmpty() ? AZStd::string(path) : AZStd::string(canon.toUtf8());
  52. }
  53. //////////////////////////////////////////////////////////////////////////
  54. bool CEditorFileMonitor::RegisterListener(IFileChangeListener* pListener, const char* sFolderRelativeToGame, const char* sExtension)
  55. {
  56. bool success = true;
  57. AZStd::string gameFolder = Path::GetEditingGameDataFolder().c_str();
  58. AZStd::string naivePath;
  59. CFileChangeMonitor* fileChangeMonitor = CFileChangeMonitor::Instance();
  60. AZ_Assert(fileChangeMonitor, "CFileChangeMonitor singleton missing.");
  61. naivePath += gameFolder;
  62. // Append slash in preparation for appending the second part.
  63. naivePath = PathUtil::AddSlash(naivePath);
  64. naivePath += sFolderRelativeToGame;
  65. AZ::StringFunc::Replace(naivePath, '/', '\\');
  66. // Remove the final slash if the given item is a folder so the file change monitor correctly picks up on it.
  67. naivePath = PathUtil::RemoveSlash(naivePath);
  68. AZStd::string canonicalizedPath = CanonicalizePath(naivePath.c_str());
  69. if (fileChangeMonitor->IsDirectory(canonicalizedPath.c_str()) || fileChangeMonitor->IsFile(canonicalizedPath.c_str()))
  70. {
  71. if (fileChangeMonitor->MonitorItem(canonicalizedPath.c_str()))
  72. {
  73. m_vecFileChangeCallbacks.push_back(SFileChangeCallback(pListener, sFolderRelativeToGame, sExtension));
  74. }
  75. else
  76. {
  77. CryLogAlways("File Monitor: [%s] not found outside of PAK files. Monitoring disabled for this item", sFolderRelativeToGame);
  78. success = false;
  79. }
  80. }
  81. return success;
  82. }
  83. bool CEditorFileMonitor::UnregisterListener(IFileChangeListener* pListener)
  84. {
  85. bool bRet = false;
  86. // Note that we remove the listener, but we don't currently remove the monitored item
  87. // from the file monitor. This is fine, but inefficient
  88. std::vector<SFileChangeCallback>::iterator iter = m_vecFileChangeCallbacks.begin();
  89. while (iter != m_vecFileChangeCallbacks.end())
  90. {
  91. if (iter->pListener == pListener)
  92. {
  93. iter = m_vecFileChangeCallbacks.erase(iter);
  94. bRet = true;
  95. }
  96. else
  97. {
  98. iter++;
  99. }
  100. }
  101. return bRet;
  102. }
  103. //////////////////////////////////////////////////////////////////////////
  104. void CEditorFileMonitor::MonitorDirectories()
  105. {
  106. QString primaryCD = Path::AddPathSlash(QString(GetIEditor()->GetPrimaryCDFolder()));
  107. // NOTE: Instead of monitoring each sub-directory we monitor the whole root
  108. // folder. This is needed since if the sub-directory does not exist when
  109. // we register it it will never get monitored properly.
  110. CFileChangeMonitor::Instance()->MonitorItem(QStringLiteral("%1/%2/").arg(primaryCD).arg(QString::fromLatin1(Path::GetEditingGameDataFolder().c_str())));
  111. // Add editor directory for scripts
  112. CFileChangeMonitor::Instance()->MonitorItem(QStringLiteral("%1/Editor/").arg(primaryCD));
  113. }
  114. QString RemoveGameName(const QString &filename)
  115. {
  116. // Remove first part of path. File coming in has the game name included
  117. // eg (AutomatedTesting/Animations/Chicken/anim_chicken_flapping.i_caf)->(Animations/Chicken/anim_chicken_flapping.i_caf)
  118. int indexOfFirstSlash = filename.indexOf('/');
  119. int indexOfFirstBackSlash = filename.indexOf('\\');
  120. if (indexOfFirstSlash >= 0)
  121. {
  122. if (indexOfFirstBackSlash >= 0 && indexOfFirstBackSlash < indexOfFirstSlash)
  123. {
  124. indexOfFirstSlash = indexOfFirstBackSlash;
  125. }
  126. }
  127. else
  128. {
  129. indexOfFirstSlash = indexOfFirstBackSlash;
  130. }
  131. return filename.mid(indexOfFirstSlash + 1);
  132. }
  133. ///////////////////////////////////////////////////////////////////////////
  134. // Called when file monitor message is received
  135. void CEditorFileMonitor::OnFileMonitorChange(const SFileChangeInfo& rChange)
  136. {
  137. CCryEditApp* app = CCryEditApp::instance();
  138. if (app == nullptr || app->IsExiting())
  139. {
  140. return;
  141. }
  142. // skip folders!
  143. if (QFileInfo(rChange.filename).isDir())
  144. {
  145. return;
  146. }
  147. // Process updated file.
  148. // Make file relative to PrimaryCD folder.
  149. QString filename = rChange.filename;
  150. // Make path relative to the the project directory
  151. AZ::IO::Path projectPath{ AZ::Utils::GetProjectPath() };
  152. AZ::IO::FixedMaxPath projectRelativeFilePath = AZ::IO::PathView(filename.toUtf8().constData()).LexicallyProximate(
  153. projectPath);
  154. if (!projectRelativeFilePath.empty())
  155. {
  156. AZ::IO::PathView ext = projectRelativeFilePath.Extension();
  157. // Check for File Monitor callback
  158. std::vector<SFileChangeCallback>::iterator iter;
  159. for (iter = m_vecFileChangeCallbacks.begin(); iter != m_vecFileChangeCallbacks.end(); ++iter)
  160. {
  161. SFileChangeCallback& sCallback = *iter;
  162. // We compare against length of callback string, so we get directory matches as well as full filenames
  163. if (sCallback.pListener)
  164. {
  165. if (sCallback.extension == "*" || AZ::IO::PathView(sCallback.extension.toUtf8().constData()) == ext)
  166. {
  167. if (AZ::IO::PathView(sCallback.item.toUtf8().constData()) == projectRelativeFilePath)
  168. {
  169. sCallback.pListener->OnFileChange(qPrintable(projectRelativeFilePath.c_str()), IFileChangeListener::EChangeType(rChange.changeType));
  170. }
  171. }
  172. }
  173. }
  174. // Set this flag to make sure that the viewport will update at least once,
  175. // so that the changes will be shown, even if the app does not have focus.
  176. CCryEditApp::instance()->ForceNextIdleProcessing();
  177. }
  178. }