ProductAssetDetailsPanel.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  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 "ProductAssetDetailsPanel.h"
  9. #include "AssetTreeFilterModel.h"
  10. #include "ProductAssetTreeItemData.h"
  11. #include "native/utilities/assetUtils.h"
  12. #include "native/utilities/MissingDependencyScanner.h"
  13. #include <AssetDatabase/AssetDatabase.h>
  14. #include <AzToolsFramework/API/AssetDatabaseBus.h>
  15. #include <AzCore/std/algorithm.h>
  16. #include <AzQtComponents/Components/StyleManager.h>
  17. #include <native/ui/ui_GoToButton.h>
  18. #include <native/ui/ui_ProductAssetDetailsPanel.h>
  19. #include <QDateTime>
  20. #include <QDesktopServices>
  21. #include <QDir>
  22. #include <QStringLiteral>
  23. #include <QUrl>
  24. #include <native/ui/GoToButtonDelegate.h>
  25. #include <AzCore/Jobs/JobFunction.h>
  26. namespace AssetProcessor
  27. {
  28. ProductAssetDetailsPanel::ProductAssetDetailsPanel(QWidget* parent) : AssetDetailsPanel(parent), m_ui(new Ui::ProductAssetDetailsPanel)
  29. {
  30. m_ui->setupUi(this);
  31. m_ui->scrollAreaWidgetContents->setLayout(m_ui->scrollableVerticalLayout);
  32. m_ui->MissingProductDependenciesTable->setColumnWidth(static_cast<int>(MissingDependencyTableColumns::ScanTime), 160);
  33. ResetText();
  34. connect(m_ui->MissingProductDependenciesSupport, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnSupportClicked);
  35. connect(m_ui->ScanMissingDependenciesButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnScanFileClicked);
  36. connect(m_ui->ScanFolderButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnScanFolderClicked);
  37. connect(m_ui->ClearMissingDependenciesButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnClearScanFileClicked);
  38. connect(m_ui->ClearScanFolderButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnClearScanFolderClicked);
  39. auto* missingDependenciesDelegate = new GoToButtonDelegate(this);
  40. connect(
  41. missingDependenciesDelegate,
  42. &GoToButtonDelegate::Clicked,
  43. [this](const GoToButtonData& buttonData)
  44. {
  45. GoToProduct(buttonData.m_destination);
  46. });
  47. m_ui->MissingProductDependenciesTable->setItemDelegate(missingDependenciesDelegate);
  48. }
  49. ProductAssetDetailsPanel::~ProductAssetDetailsPanel()
  50. {
  51. }
  52. void ProductAssetDetailsPanel::SetupDependencyGraph(QTreeView* productAssetsTreeView, AZStd::shared_ptr<AssetDatabaseConnection> assetDatabaseConnection)
  53. {
  54. m_outgoingDependencyTreeModel =
  55. new ProductDependencyTreeModel(assetDatabaseConnection, m_productFilterModel, DependencyTreeType::Outgoing, this);
  56. m_ui->OutgoingProductDependenciesTreeView->setModel(m_outgoingDependencyTreeModel);
  57. m_ui->OutgoingProductDependenciesTreeView->setRootIsDecorated(true);
  58. m_ui->OutgoingProductDependenciesTreeView->setItemDelegate(new ProductDependencyTreeDelegate(this, this));
  59. connect(
  60. productAssetsTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, m_outgoingDependencyTreeModel,
  61. &ProductDependencyTreeModel::AssetDataSelectionChanged);
  62. m_incomingDependencyTreeModel =
  63. new ProductDependencyTreeModel(assetDatabaseConnection, m_productFilterModel, DependencyTreeType::Incoming, this);
  64. m_ui->IncomingProductDependenciesTreeView->setModel(m_incomingDependencyTreeModel);
  65. m_ui->IncomingProductDependenciesTreeView->setRootIsDecorated(true);
  66. m_ui->IncomingProductDependenciesTreeView->setItemDelegate(new ProductDependencyTreeDelegate(this, this));
  67. connect(
  68. productAssetsTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, m_incomingDependencyTreeModel,
  69. &ProductDependencyTreeModel::AssetDataSelectionChanged);
  70. AzQtComponents::StyleManager::setStyleSheet(m_ui->OutgoingProductDependenciesTreeView, QStringLiteral("style:AssetProcessor.qss"));
  71. AzQtComponents::StyleManager::setStyleSheet(m_ui->IncomingProductDependenciesTreeView, QStringLiteral("style:AssetProcessor.qss"));
  72. }
  73. QTreeView* ProductAssetDetailsPanel::GetOutgoingProductDependenciesTreeView() const
  74. {
  75. return m_ui->OutgoingProductDependenciesTreeView;
  76. }
  77. QTreeView* ProductAssetDetailsPanel::GetIncomingProductDependenciesTreeView() const
  78. {
  79. return m_ui->IncomingProductDependenciesTreeView;
  80. }
  81. void ProductAssetDetailsPanel::SetScanQueueEnabled(bool enabled)
  82. {
  83. // Don't change state if it's already the same.
  84. if (m_ui->ScanMissingDependenciesButton->isEnabled() == enabled)
  85. {
  86. return;
  87. }
  88. m_ui->ScanMissingDependenciesButton->setEnabled(enabled);
  89. m_ui->ScanFolderButton->setEnabled(enabled);
  90. if (enabled)
  91. {
  92. m_ui->ScanMissingDependenciesButton->setToolTip(tr("Scans this file for missing dependencies. This may take some time."));
  93. m_ui->ScanFolderButton->setToolTip(tr("Scans all files in this folder and subfolders for missing dependencies. This may take some time."));
  94. }
  95. else
  96. {
  97. QString disabledTooltip(tr("Scanning disabled until asset processing completes."));
  98. m_ui->ScanMissingDependenciesButton->setToolTip(disabledTooltip);
  99. m_ui->ScanFolderButton->setToolTip(disabledTooltip);
  100. }
  101. }
  102. void ProductAssetDetailsPanel::AssetDataSelectionChanged(const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
  103. {
  104. // Even if multi-select is enabled, only display the first selected item.
  105. if (selected.indexes().count() == 0 || !selected.indexes()[0].isValid())
  106. {
  107. ResetText();
  108. return;
  109. }
  110. QModelIndex productModelIndex = m_productFilterModel->mapToSource(selected.indexes()[0]);
  111. if (!productModelIndex.isValid())
  112. {
  113. return;
  114. }
  115. m_currentItem = static_cast<AssetTreeItem*>(productModelIndex.internalPointer());
  116. RefreshUI();
  117. }
  118. void ProductAssetDetailsPanel::RefreshUI()
  119. {
  120. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
  121. m_ui->assetNameLabel->setText(m_currentItem->GetData()->m_name);
  122. if (m_currentItem->GetData()->m_isFolder || !productItemData)
  123. {
  124. // Folders don't have details.
  125. SetDetailsVisible(false);
  126. return;
  127. }
  128. SetDetailsVisible(true);
  129. AZ::Data::AssetId assetId;
  130. m_assetDatabaseConnection->QuerySourceByProductID(
  131. productItemData->m_databaseInfo.m_productID,
  132. [&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
  133. {
  134. assetId = AZ::Data::AssetId(sourceEntry.m_sourceGuid, productItemData->m_databaseInfo.m_subID);
  135. // Use a decimal value to display the sub ID and not hex. Open 3D Engine is not consistent about
  136. // how sub IDs are displayed, so it's important to double check what format a sub ID is in before using it elsewhere.
  137. m_ui->productAssetIdValueLabel->setText(assetId.ToString<AZStd::string>(AZ::Data::AssetId::SubIdDisplayType::Decimal).c_str());
  138. // Make sure this is the only connection to the button.
  139. m_ui->gotoAssetButton->m_ui->goToPushButton->disconnect();
  140. connect(m_ui->gotoAssetButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
  141. GoToSource(SourceAssetReference(sourceEntry.m_scanFolderPK, sourceEntry.m_sourceName.c_str()).AbsolutePath().c_str());
  142. });
  143. m_ui->sourceAssetValueLabel->setText(sourceEntry.m_sourceName.c_str());
  144. return true;
  145. });
  146. AZStd::string platform;
  147. m_assetDatabaseConnection->QueryJobByProductID(
  148. productItemData->m_databaseInfo.m_productID,
  149. [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
  150. {
  151. QDateTime lastTimeProcessed = QDateTime::fromMSecsSinceEpoch(jobEntry.m_lastLogTime);
  152. m_ui->lastTimeProcessedValueLabel->setText(lastTimeProcessed.toString());
  153. m_ui->jobKeyValueLabel->setText(jobEntry.m_jobKey.c_str());
  154. platform = jobEntry.m_platform;
  155. m_ui->platformValueLabel->setText(jobEntry.m_platform.c_str());
  156. return true;
  157. });
  158. BuildOutgoingProductDependencies(productItemData, platform);
  159. BuildIncomingProductDependencies(productItemData, assetId, platform);
  160. BuildMissingProductDependencies(productItemData);
  161. }
  162. void ProductAssetDetailsPanel::BuildOutgoingProductDependencies(
  163. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
  164. const AZStd::string& platform)
  165. {
  166. m_ui->outgoingUnmetPathProductDependenciesList->clear();
  167. int productDependencyCount = 0;
  168. int productPathDependencyCount = 0;
  169. m_assetDatabaseConnection->QueryProductDependencyByProductId(
  170. productItemData->m_databaseInfo.m_productID,
  171. [&](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& dependency)
  172. {
  173. if (!dependency.m_dependencySourceGuid.IsNull())
  174. {
  175. m_assetDatabaseConnection->QueryProductBySourceGuidSubID(
  176. dependency.m_dependencySourceGuid,
  177. dependency.m_dependencySubID,
  178. [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product)
  179. {
  180. bool platformMatches = false;
  181. m_assetDatabaseConnection->QueryJobByJobID(
  182. product.m_jobPK,
  183. [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
  184. {
  185. if (platform.compare(jobEntry.m_platform) == 0)
  186. {
  187. platformMatches = true;
  188. }
  189. return true;
  190. });
  191. if (platformMatches)
  192. {
  193. ++productDependencyCount;
  194. }
  195. return true;
  196. });
  197. }
  198. // If there is both a path and an asset ID on this dependency, then something has gone wrong.
  199. // Other tooling should have reported this error. In the UI, show both the asset ID and path.
  200. if (!dependency.m_unresolvedPath.empty())
  201. {
  202. QListWidgetItem* listWidgetItem = new QListWidgetItem();
  203. listWidgetItem->setText(dependency.m_unresolvedPath.c_str());
  204. m_ui->outgoingUnmetPathProductDependenciesList->addItem(listWidgetItem);
  205. ++productPathDependencyCount;
  206. }
  207. return true;
  208. });
  209. m_ui->outgoingProductDependenciesValueLabel->setText(QString::number(productDependencyCount));
  210. m_ui->outgoingUnmetPathProductDependenciesValueLabel->setText(QString::number(productPathDependencyCount));
  211. if (productPathDependencyCount == 0)
  212. {
  213. QListWidgetItem* listWidgetItem = new QListWidgetItem();
  214. listWidgetItem->setText(tr("No unmet dependencies"));
  215. m_ui->outgoingUnmetPathProductDependenciesList->addItem(listWidgetItem);
  216. ++productPathDependencyCount;
  217. }
  218. int height = m_ui->outgoingUnmetPathProductDependenciesList->sizeHintForRow(0) * AZStd::min<int>(productPathDependencyCount, 4) +
  219. 2 * m_ui->outgoingUnmetPathProductDependenciesList->frameWidth();
  220. m_ui->outgoingUnmetPathProductDependenciesList->setMinimumHeight(height);
  221. m_ui->outgoingUnmetPathProductDependenciesList->setMaximumHeight(height);
  222. m_ui->outgoingUnmetPathProductDependenciesList->adjustSize();
  223. }
  224. void ProductAssetDetailsPanel::BuildIncomingProductDependencies(
  225. [[maybe_unused]] const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
  226. const AZ::Data::AssetId& assetId,
  227. const AZStd::string& platform)
  228. {
  229. int incomingProductDependencyCount = 0;
  230. m_assetDatabaseConnection->QueryDirectReverseProductDependenciesBySourceGuidSubId(
  231. assetId.m_guid,
  232. assetId.m_subId,
  233. [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& incomingDependency)
  234. {
  235. bool platformMatches = false;
  236. m_assetDatabaseConnection->QueryJobByJobID(
  237. incomingDependency.m_jobPK,
  238. [&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
  239. {
  240. if (platform.compare(jobEntry.m_platform) == 0)
  241. {
  242. platformMatches = true;
  243. }
  244. return true;
  245. });
  246. if (platformMatches)
  247. {
  248. ++incomingProductDependencyCount;
  249. }
  250. return true;
  251. });
  252. m_ui->incomingProductDependenciesValueLabel->setText(QString::number(incomingProductDependencyCount));
  253. }
  254. struct MissingDependencyTableInfo
  255. {
  256. AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry m_databaseEntry;
  257. AZStd::string m_missingProductName;
  258. };
  259. void ProductAssetDetailsPanel::BuildMissingProductDependencies(
  260. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
  261. {
  262. // Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
  263. m_ui->MissingProductDependenciesTable->setRowCount(0);
  264. int missingDependencyRowCount = 0;
  265. int missingDependencyCount = 0;
  266. // Sort missing dependencies by scan time.
  267. AZStd::vector<MissingDependencyTableInfo> missingDependenciesByScanTime;
  268. m_assetDatabaseConnection->QueryMissingProductDependencyByProductId(
  269. productItemData->m_databaseInfo.m_productID,
  270. [&](AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& missingDependency)
  271. {
  272. AZStd::string missingProductName;
  273. m_assetDatabaseConnection->QueryProductBySourceGuidSubID(
  274. missingDependency.m_dependencySourceGuid,
  275. missingDependency.m_dependencySubId,
  276. [&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& missingProduct)
  277. {
  278. missingProductName = missingProduct.m_productName;
  279. return false; // There should only be one matching product, stop looking.
  280. });
  281. AZStd::vector<MissingDependencyTableInfo>::iterator insertPosition = AZStd::upper_bound(
  282. missingDependenciesByScanTime.begin(),
  283. missingDependenciesByScanTime.end(),
  284. missingDependency.m_scanTimeSecondsSinceEpoch,
  285. [](AZ::u64 left, const MissingDependencyTableInfo& right) {
  286. return left > right.m_databaseEntry.m_scanTimeSecondsSinceEpoch;
  287. });
  288. MissingDependencyTableInfo missingDependencyInfo;
  289. missingDependencyInfo.m_databaseEntry = missingDependency;
  290. missingDependencyInfo.m_missingProductName = missingProductName;
  291. missingDependenciesByScanTime.insert(insertPosition, missingDependencyInfo);
  292. return true;
  293. });
  294. bool hasMissingDependency = false;
  295. for (const auto& missingDependency : missingDependenciesByScanTime)
  296. {
  297. m_ui->MissingProductDependenciesTable->insertRow(missingDependencyRowCount);
  298. // To track if files have been scanned at all, rows with invalid source guids are added on a
  299. // scan that had no missing dependencies. Don't show a button for those rows.
  300. if (!missingDependency.m_databaseEntry.m_dependencySourceGuid.IsNull())
  301. {
  302. hasMissingDependency = true;
  303. ++missingDependencyCount;
  304. auto* goToWidget = new QTableWidgetItem();
  305. goToWidget->setData(0, QVariant::fromValue(GoToButtonData(missingDependency.m_missingProductName)));
  306. m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount,
  307. static_cast<int>(MissingDependencyTableColumns::GoToButton), goToWidget);
  308. }
  309. QTableWidgetItem* scanTime = new QTableWidgetItem(missingDependency.m_databaseEntry.m_lastScanTime.c_str());
  310. m_ui->MissingProductDependenciesTable->setItem(
  311. missingDependencyRowCount, static_cast<int>(MissingDependencyTableColumns::ScanTime), scanTime);
  312. QTableWidgetItem* rowName = new QTableWidgetItem(missingDependency.m_databaseEntry.m_missingDependencyString.c_str());
  313. m_ui->MissingProductDependenciesTable->setItem(
  314. missingDependencyRowCount, static_cast<int>(MissingDependencyTableColumns::Dependency), rowName);
  315. ++missingDependencyRowCount;
  316. }
  317. m_ui->MissingProductDependenciesValueLabel->setText(QString::number(missingDependencyCount));
  318. if (missingDependencyRowCount == 0)
  319. {
  320. m_ui->MissingProductDependenciesTable->insertRow(missingDependencyRowCount);
  321. QTableWidgetItem* rowName = new QTableWidgetItem(tr("File has not been scanned."));
  322. // Put this text in the scan time column, not the missing dependency column, for layout purposes.
  323. m_ui->MissingProductDependenciesTable->setItem(
  324. missingDependencyRowCount, static_cast<int>(MissingDependencyTableColumns::ScanTime), rowName);
  325. ++missingDependencyRowCount;
  326. }
  327. else
  328. {
  329. m_ui->missingDependencyErrorIcon->setVisible(hasMissingDependency);
  330. }
  331. // Because this is a table nested in a scroll view, Qt struggles to automatically resize the width.
  332. // Set the width manually, to the size of the columns.
  333. m_ui->MissingProductDependenciesTable->resizeColumnToContents(static_cast<int>(MissingDependencyTableColumns::ScanTime));
  334. int width = 0;
  335. for (int columnIndex = 0; columnIndex < static_cast<int>(MissingDependencyTableColumns::Max); ++columnIndex)
  336. {
  337. width += m_ui->MissingProductDependenciesTable->columnWidth(columnIndex);
  338. }
  339. m_ui->MissingProductDependenciesTable->setMinimumWidth(width);
  340. m_ui->MissingProductDependenciesTable->adjustSize();
  341. }
  342. void ProductAssetDetailsPanel::ResetText()
  343. {
  344. m_ui->assetNameLabel->setText(tr("Select an asset to see details"));
  345. SetDetailsVisible(false);
  346. }
  347. void ProductAssetDetailsPanel::SetDetailsVisible(bool visible)
  348. {
  349. // The folder selected description has opposite visibility from everything else.
  350. m_ui->folderSelectedDescription->setVisible(!visible);
  351. m_ui->ScanFolderButton->setVisible(!visible);
  352. m_ui->ClearScanFolderButton->setVisible(!visible);
  353. m_ui->MissingProductDependenciesFolderTitleLabel->setVisible(!visible);
  354. m_ui->productAssetIdTitleLabel->setVisible(visible);
  355. m_ui->productAssetIdValueLabel->setVisible(visible);
  356. m_ui->lastTimeProcessedTitleLabel->setVisible(visible);
  357. m_ui->lastTimeProcessedValueLabel->setVisible(visible);
  358. m_ui->jobKeyTitleLabel->setVisible(visible);
  359. m_ui->jobKeyValueLabel->setVisible(visible);
  360. m_ui->platformTitleLabel->setVisible(visible);
  361. m_ui->platformValueLabel->setVisible(visible);
  362. m_ui->sourceAssetTitleLabel->setVisible(visible);
  363. m_ui->sourceAssetValueLabel->setVisible(visible);
  364. m_ui->gotoAssetButton->setVisible(visible);
  365. m_ui->ProductAssetDetailTabs->setVisible(visible);
  366. m_ui->MissingProductDependenciesTitleLabel->setVisible(visible);
  367. m_ui->MissingProductDependenciesValueLabel->setVisible(visible);
  368. m_ui->MissingProductDependenciesTable->setVisible(visible);
  369. m_ui->MissingProductDependenciesSupport->setVisible(visible);
  370. m_ui->ScanMissingDependenciesButton->setVisible(visible);
  371. m_ui->ClearMissingDependenciesButton->setVisible(visible);
  372. m_ui->missingDependencyErrorIcon->setVisible(false);
  373. }
  374. void ProductAssetDetailsPanel::OnSupportClicked([[maybe_unused]] bool checked)
  375. {
  376. QDesktopServices::openUrl(
  377. QStringLiteral("https://o3de.org/docs/user-guide/packaging/asset-bundler/assets-resolving/"));
  378. }
  379. void ProductAssetDetailsPanel::OnScanFileClicked([[maybe_unused]] bool checked)
  380. {
  381. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
  382. ScanFileForMissingDependencies(productItemData->m_name, productItemData);
  383. }
  384. void ProductAssetDetailsPanel::ScanFileForMissingDependencies(QString scanName, const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
  385. {
  386. // If the file is already in the queue to scan, don't add it.
  387. if (m_productIdToScanName.contains(productItemData->m_databaseInfo.m_productID))
  388. {
  389. return;
  390. }
  391. AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer existingDependencies;
  392. m_assetDatabaseConnection->QueryProductDependencyByProductId(
  393. productItemData->m_databaseInfo.m_productID,
  394. [&](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
  395. {
  396. existingDependencies.emplace_back() = AZStd::move(entry);
  397. return true; // return true to keep iterating over further rows.
  398. });
  399. QDir cacheRootDir;
  400. AssetUtilities::ComputeProjectCacheRoot(cacheRootDir);
  401. QString pathOnDisk = cacheRootDir.filePath(productItemData->m_databaseInfo.m_productName.c_str());
  402. AddProductIdToScanCount(productItemData->m_databaseInfo.m_productID, scanName);
  403. // Run the scan on another thread so the UI remains responsive.
  404. auto* job = AZ::CreateJobFunction([=]() {
  405. MissingDependencyScannerRequestBus::Broadcast(&MissingDependencyScannerRequestBus::Events::ScanFile,
  406. pathOnDisk.toUtf8().constData(),
  407. MissingDependencyScanner::DefaultMaxScanIteration,
  408. productItemData->m_databaseInfo.m_productID,
  409. existingDependencies,
  410. m_assetDatabaseConnection,
  411. /*queueDbCommandsOnMainThread*/ true,
  412. [=]([[maybe_unused]] AZStd::string relativeDependencyFilePath) {
  413. RemoveProductIdFromScanCount(productItemData->m_databaseInfo.m_productID, scanName);
  414. // The MissingDependencyScannerRequestBus callback always runs on the main thread, so no need to queue again.
  415. AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Broadcast(
  416. &AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Events::OnProductFileChanged, productItemData->m_databaseInfo);
  417. if (m_currentItem)
  418. {
  419. // Refresh the UI if the scan that just finished is selected.
  420. const AZStd::shared_ptr<const ProductAssetTreeItemData> currentItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
  421. if (currentItemData == productItemData)
  422. {
  423. RefreshUI();
  424. }
  425. }
  426. });
  427. }, true);
  428. job->Start();
  429. }
  430. void ProductAssetDetailsPanel::AddProductIdToScanCount(AZ::s64 scannedProductId, QString scanName)
  431. {
  432. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_scanCountMutex);
  433. m_productIdToScanName.insert(scannedProductId, scanName);
  434. QHash<QString, MissingDependencyScanGUIInfo>::iterator scanNameIter = m_scanNameToScanGUIInfo.find(scanName);
  435. if (scanNameIter == m_scanNameToScanGUIInfo.end())
  436. {
  437. MissingDependencyScanGUIInfo scanGUIInfo;
  438. scanGUIInfo.m_scanWidgetRow = new QListWidgetItem();
  439. scanGUIInfo.m_scanTimeStart = QDateTime::currentDateTime();
  440. if (m_missingDependencyScanResults)
  441. {
  442. m_missingDependencyScanResults->addItem(scanGUIInfo.m_scanWidgetRow);
  443. // New items are added to the bottom, scroll to them when they are added.
  444. m_missingDependencyScanResults->scrollToBottom();
  445. }
  446. scanNameIter = m_scanNameToScanGUIInfo.insert(scanName, scanGUIInfo);
  447. }
  448. // Update the remaining file count for this scan.
  449. scanNameIter.value().m_remainingFiles++;
  450. UpdateScannerUI(scanNameIter.value(), scanName);
  451. }
  452. void ProductAssetDetailsPanel::RemoveProductIdFromScanCount(AZ::s64 scannedProductId, QString scanName)
  453. {
  454. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_scanCountMutex);
  455. m_productIdToScanName.remove(scannedProductId);
  456. QHash<QString, MissingDependencyScanGUIInfo>::iterator scanNameIter = m_scanNameToScanGUIInfo.find(scanName);
  457. if (scanNameIter != m_scanNameToScanGUIInfo.end())
  458. {
  459. // Update the remaining file count for this scan.
  460. scanNameIter.value().m_remainingFiles--;
  461. UpdateScannerUI(scanNameIter.value(), scanName);
  462. if (scanNameIter.value().m_remainingFiles <= 0)
  463. {
  464. m_scanNameToScanGUIInfo.remove(scanName);
  465. }
  466. }
  467. }
  468. void ProductAssetDetailsPanel::UpdateScannerUI(MissingDependencyScanGUIInfo& scannerUIInfo, QString scanName)
  469. {
  470. if (scannerUIInfo.m_scanWidgetRow == nullptr)
  471. {
  472. return;
  473. }
  474. if (scannerUIInfo.m_remainingFiles == 0)
  475. {
  476. qint64 scanTimeInSeconds = scannerUIInfo.m_scanTimeStart.secsTo(QDateTime::currentDateTime());
  477. scannerUIInfo.m_scanWidgetRow->setText(tr("Completed scanning %1 in %2 seconds").
  478. arg(scanName).
  479. arg(scanTimeInSeconds));
  480. }
  481. else
  482. {
  483. scannerUIInfo.m_scanWidgetRow->setText(tr("%1: Scanning %2 files for %3").
  484. arg(QLocale::system().toString(scannerUIInfo.m_scanTimeStart, QLocale::ShortFormat)).
  485. arg(scannerUIInfo.m_remainingFiles).
  486. arg(scanName));
  487. }
  488. }
  489. void ProductAssetDetailsPanel::OnScanFolderClicked([[maybe_unused]] bool checked)
  490. {
  491. if (!m_currentItem)
  492. {
  493. return;
  494. }
  495. ScanFolderForMissingDependencies(m_currentItem->GetData()->m_name, *m_currentItem);
  496. }
  497. void ProductAssetDetailsPanel::ScanFolderForMissingDependencies(QString scanName, AssetTreeItem& folder)
  498. {
  499. for (int childIndex = 0; childIndex < folder.getChildCount(); ++childIndex)
  500. {
  501. AssetTreeItem* child = folder.GetChild(childIndex);
  502. if (child->GetData()->m_isFolder)
  503. {
  504. ScanFolderForMissingDependencies(scanName, *child);
  505. }
  506. else
  507. {
  508. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(child->GetData());
  509. ScanFileForMissingDependencies(scanName, productItemData);
  510. }
  511. }
  512. }
  513. void ProductAssetDetailsPanel::OnClearScanFileClicked([[maybe_unused]] bool checked)
  514. {
  515. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
  516. ClearMissingDependenciesForFile(productItemData);
  517. }
  518. void ProductAssetDetailsPanel::OnClearScanFolderClicked([[maybe_unused]] bool checked)
  519. {
  520. if (!m_currentItem)
  521. {
  522. return;
  523. }
  524. ClearMissingDependenciesForFolder(*m_currentItem);
  525. }
  526. void ProductAssetDetailsPanel::ClearMissingDependenciesForFile(const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
  527. {
  528. m_assetDatabaseConnection->DeleteMissingProductDependencyByProductId(productItemData->m_databaseInfo.m_productID);
  529. AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Broadcast(
  530. &AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Events::OnProductFileChanged, productItemData->m_databaseInfo);
  531. const AZStd::shared_ptr<const ProductAssetTreeItemData> currentItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
  532. if (currentItemData == productItemData)
  533. {
  534. RefreshUI();
  535. }
  536. }
  537. void ProductAssetDetailsPanel::ClearMissingDependenciesForFolder(AssetTreeItem& folder)
  538. {
  539. for (int childIndex = 0; childIndex < folder.getChildCount(); ++childIndex)
  540. {
  541. AssetTreeItem* child = folder.GetChild(childIndex);
  542. if (child->GetData()->m_isFolder)
  543. {
  544. ClearMissingDependenciesForFolder(*child);
  545. }
  546. else
  547. {
  548. const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(child->GetData());
  549. ClearMissingDependenciesForFile(productItemData);
  550. }
  551. }
  552. }
  553. }