assetScannerWorker.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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 "native/AssetManager/assetScannerWorker.h"
  9. #include "native/AssetManager/assetScanner.h"
  10. #include "native/utilities/PlatformConfiguration.h"
  11. #include <QDir>
  12. #include <QtConcurrent/QtConcurrentFilter>
  13. using namespace AssetProcessor;
  14. AssetScannerWorker::AssetScannerWorker(PlatformConfiguration* config, QObject* parent)
  15. : QObject(parent)
  16. , m_platformConfiguration(config)
  17. {
  18. }
  19. void AssetScannerWorker::StartScan()
  20. {
  21. // this must be called from the thread operating it and not the main thread.
  22. Q_ASSERT(QThread::currentThread() == this->thread());
  23. m_fileList.clear();
  24. m_folderList.clear();
  25. m_excludedList.clear();
  26. m_doScan = true;
  27. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Scanning file system for changes...\n");
  28. Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::Started);
  29. Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::InProgress);
  30. for (int idx = 0; idx < m_platformConfiguration->GetScanFolderCount(); idx++)
  31. {
  32. const ScanFolderInfo& scanFolderInfo = m_platformConfiguration->GetScanFolderAt(idx);
  33. ScanForSourceFiles(scanFolderInfo, scanFolderInfo);
  34. }
  35. // we want not to emit any signals until we're finished scanning
  36. // so that we don't interleave directory tree walking (IO access to the file table)
  37. // with file access (IO access to file data) caused by sending signals to other classes.
  38. if (!m_doScan)
  39. {
  40. m_fileList.clear();
  41. m_folderList.clear();
  42. m_excludedList.clear();
  43. Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::Stopped);
  44. return;
  45. }
  46. else
  47. {
  48. EmitFiles();
  49. }
  50. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File system scan done.\n");
  51. Q_EMIT ScanningStateChanged(AssetProcessor::AssetScanningStatus::Completed);
  52. }
  53. // note: Call this directly from the main thread!
  54. // do not queue this call.
  55. // Join the thread if you intend to wait until its stopped
  56. void AssetScannerWorker::StopScan()
  57. {
  58. m_doScan = false;
  59. }
  60. void AssetScannerWorker::ScanForSourceFiles(const ScanFolderInfo& scanFolderInfo, const ScanFolderInfo& rootScanFolder)
  61. {
  62. if (!m_doScan)
  63. {
  64. return;
  65. }
  66. QFileInfoList entries;
  67. QDir cacheDir;
  68. AssetUtilities::ComputeProjectCacheRoot(cacheDir);
  69. QString normalizedCachePath = AssetUtilities::NormalizeDirectoryPath(cacheDir.absolutePath());
  70. AZ::IO::Path cachePath(normalizedCachePath.toUtf8().constData());
  71. QString intermediateAssetsFolder = QString::fromUtf8(AssetUtilities::GetIntermediateAssetsFolder(cachePath).c_str());
  72. QString normalizedIntermediateAssetsFolder = AssetUtilities::NormalizeDirectoryPath(intermediateAssetsFolder);
  73. // Implemented non-recursively so that the above functions only have to be called once per scan,
  74. // and so that the performance is easy to analyze in a profiler:
  75. QList<ScanFolderInfo> pathsToScanQueue;
  76. pathsToScanQueue.push_back(scanFolderInfo);
  77. while (!pathsToScanQueue.empty())
  78. {
  79. ScanFolderInfo pathToScan = pathsToScanQueue.back();
  80. pathsToScanQueue.pop_back();
  81. QDir dir(pathToScan.ScanPath());
  82. dir.setSorting(QDir::Unsorted);
  83. // Only scan sub folders if recurseSubFolders flag is set
  84. if (!scanFolderInfo.RecurseSubFolders())
  85. {
  86. entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files);
  87. }
  88. else
  89. {
  90. entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files);
  91. }
  92. for (const QFileInfo& entry : entries)
  93. {
  94. if (!m_doScan) // scan was cancelled!
  95. {
  96. return;
  97. }
  98. QString absPath = entry.absoluteFilePath();
  99. const bool isDirectory = entry.isDir();
  100. QDateTime modTime = entry.lastModified();
  101. AZ::u64 fileSize = isDirectory ? 0 : entry.size();
  102. AssetFileInfo assetFileInfo(absPath, modTime, fileSize, &rootScanFolder, isDirectory);
  103. QString relPath = absPath.mid(rootScanFolder.ScanPath().length() + 1);
  104. if (isDirectory)
  105. {
  106. // in debug, assert that the paths coming from qt directory info iteration is already normalized
  107. // allowing us to skip normalization and know that comparisons like "IsInCacheFolder" will actually succed.
  108. Q_ASSERT(absPath == AssetUtilities::NormalizeDirectoryPath(absPath));
  109. // Filtering out excluded directories immediately (not in a thread pool) since that prevents us from recursing.
  110. // we already know the root scan folder, and can thus chop that part off and call the cheaper IsFileExcludedRelPath:
  111. if (m_platformConfiguration->IsFileExcludedRelPath(relPath))
  112. {
  113. m_excludedList.insert(AZStd::move(assetFileInfo));
  114. continue;
  115. }
  116. // Entry is a directory
  117. // The AP needs to know about all directories so it knows when a delete occurs if the path refers to a folder or a file
  118. m_folderList.insert(AZStd::move(assetFileInfo));
  119. // recurse into this folder.
  120. // Since we only care about source files, we can skip cache folders that are not the Intermediate Assets Folder.
  121. if (absPath.startsWith(normalizedCachePath))
  122. {
  123. // its in the cache. Is it the cache itself?
  124. if (absPath.length() != normalizedCachePath.length())
  125. {
  126. // no. Is it in the intermediateassets?
  127. if (!absPath.startsWith(normalizedIntermediateAssetsFolder))
  128. {
  129. // Its not something in the intermediate assets folder, nor is it the cache itself,
  130. // so it is just a file somewhere in the cache.
  131. continue; // do not recurse.
  132. }
  133. }
  134. }
  135. // then we can recurse. Otherwise, its a non-intermediate-assets-folder
  136. ScanFolderInfo tempScanFolderInfo(absPath, "", "", false, true);
  137. pathsToScanQueue.push_back(tempScanFolderInfo);
  138. }
  139. else
  140. {
  141. // Entry is a file
  142. Q_ASSERT(absPath == AssetUtilities::NormalizeFilePath(absPath));
  143. if (!AssetUtilities::IsInCacheFolder(absPath.toUtf8().constData(), cachePath)) // Ignore files in the cache
  144. {
  145. if (!m_platformConfiguration->IsFileExcludedRelPath(relPath))
  146. {
  147. m_fileList.insert(AZStd::move(assetFileInfo));
  148. }
  149. else
  150. {
  151. m_excludedList.insert(AZStd::move(assetFileInfo));
  152. }
  153. }
  154. }
  155. }
  156. }
  157. }
  158. void AssetScannerWorker::EmitFiles()
  159. {
  160. Q_EMIT FilesFound(m_fileList);
  161. m_fileList.clear();
  162. Q_EMIT FoldersFound(m_folderList);
  163. m_folderList.clear();
  164. Q_EMIT ExcludedFound(m_excludedList);
  165. m_excludedList.clear();
  166. }