SourceFileRelocator.cpp 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  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 "SourceFileRelocator.h"
  9. #include "FileStateCache.h"
  10. #include <AzCore/IO/SystemFile.h>
  11. #include <QDir>
  12. #include <AzFramework/IO/LocalFileIO.h>
  13. #include <AzCore/StringFunc/StringFunc.h>
  14. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  15. #include <AzCore/std/parallel/binary_semaphore.h>
  16. #include <AzCore/Component/TickBus.h>
  17. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  18. #include <native/utilities/BatchApplicationManager.h>
  19. #include <cinttypes>
  20. namespace AssetProcessor
  21. {
  22. // note that this will be true if EITHER the source control is valid, connected, and working
  23. // OR if source control is completely disabled and thus will just transparently pass thru to the file system
  24. // it will only return false if the source control plugin is ACTIVE but is failing to function due to a configuration
  25. // problem, or the executable being missing.
  26. bool IsSourceControlValid()
  27. {
  28. using SCRequest = AzToolsFramework::SourceControlConnectionRequestBus;
  29. AzToolsFramework::SourceControlState state = AzToolsFramework::SourceControlState::Disabled;
  30. SCRequest::BroadcastResult(state, &SCRequest::Events::GetSourceControlState);
  31. return (state != AzToolsFramework::SourceControlState::ConfigurationInvalid);
  32. }
  33. bool WaitForSourceControl(AZStd::binary_semaphore& waitSignal)
  34. {
  35. constexpr int MaxWaitTimeMS = 10000;
  36. constexpr int SleepTimeMS = 50;
  37. int retryCount = MaxWaitTimeMS / SleepTimeMS;
  38. AZ::TickBus::ExecuteQueuedEvents();
  39. while (!waitSignal.try_acquire_for(AZStd::chrono::milliseconds(SleepTimeMS)) && retryCount >= 0)
  40. {
  41. --retryCount;
  42. AZ::TickBus::ExecuteQueuedEvents();
  43. }
  44. if (retryCount < 0)
  45. {
  46. AZ_Error("SourceFileRelocator", false, "Timed out waiting for response from source control component.");
  47. return false;
  48. }
  49. return true;
  50. }
  51. void WildcardHelper(AZStd::string& path)
  52. {
  53. path.replace(path.length() - 1, 1, "...");
  54. }
  55. void AdjustWildcardForPerforce(AZStd::string& source)
  56. {
  57. if (source.ends_with('*'))
  58. {
  59. WildcardHelper(source);
  60. }
  61. }
  62. void AdjustWildcardForPerforce(AZStd::string& source, AZStd::string& destination)
  63. {
  64. if (source.ends_with('*') && destination.ends_with('*'))
  65. {
  66. WildcardHelper(source);
  67. WildcardHelper(destination);
  68. }
  69. }
  70. SourceFileRelocator::SourceFileRelocator(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> stateData, PlatformConfiguration* platformConfiguration)
  71. : m_stateData(AZStd::move(stateData))
  72. , m_platformConfig(platformConfiguration)
  73. {
  74. AZ::Interface<ISourceFileRelocation>::Register(this);
  75. m_additionalHelpTextMap[AZStd::string("seed")] = AZStd::string("\t\tPlease note that path hints in the seed file might not be correct as a result of this file reference fixup,\
  76. you can update the path hints by running the AssetBundlerBatch. Please run AssetBundlerBatch --help to find the correct command for updating the path hints for seed files.\
  77. Please note that only those seed files will get updated that are active for your current game project. If there are seed files that are not active for your current game project and does contain\
  78. references to files that are being moved, then asset processor won't be able to catch these references and perform the fixup and the user would have to update them manually.\n");
  79. }
  80. SourceFileRelocator::~SourceFileRelocator()
  81. {
  82. AZ::Interface<ISourceFileRelocation>::Unregister(this);
  83. }
  84. AZ::Outcome<void, AZStd::string> SourceFileRelocator::GetScanFolderAndRelativePath(const AZStd::string& normalizedSource, bool allowNonexistentPath, const ScanFolderInfo*& scanFolderInfo, AZStd::string& relativePath) const
  85. {
  86. scanFolderInfo = nullptr;
  87. bool isRelative = AzFramework::StringFunc::Path::IsRelative(normalizedSource.c_str());
  88. if (isRelative)
  89. {
  90. // Relative paths can match multiple files/folders, search each scan folder for a valid match
  91. QString matchedPath;
  92. QString tempRelativeName(normalizedSource.c_str());
  93. for(int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
  94. {
  95. const AssetProcessor::ScanFolderInfo* scanFolderInfoCheck = &m_platformConfig->GetScanFolderAt(i);
  96. if ((!scanFolderInfoCheck->RecurseSubFolders()) && (tempRelativeName.contains('/')))
  97. {
  98. // the name is a deeper relative path, but we don't recurse this scan folder, so it can't win
  99. continue;
  100. }
  101. QDir rooted(scanFolderInfoCheck->ScanPath());
  102. QString absolutePath = rooted.absoluteFilePath(tempRelativeName);
  103. bool fileExists = false;
  104. auto* fileStateInterface = AZ::Interface<IFileStateRequests>::Get();
  105. if(fileStateInterface)
  106. {
  107. fileExists = fileStateInterface->Exists(absolutePath);
  108. }
  109. if (fileExists)
  110. {
  111. if (matchedPath.isEmpty())
  112. {
  113. matchedPath = AssetUtilities::NormalizeFilePath(absolutePath);
  114. scanFolderInfo = scanFolderInfoCheck;
  115. }
  116. else
  117. {
  118. return AZ::Failure(AZStd::string::format("Relative path matched multiple files/folders. Please narrow your query by using an absolute path or, if using wildcards, try making your query path more specific.\nMatch 1: %s\nMatch 2: %s\n",
  119. matchedPath.toUtf8().constData(),
  120. absolutePath.toUtf8().constData()));
  121. }
  122. }
  123. }
  124. if(allowNonexistentPath && !scanFolderInfo)
  125. {
  126. // We didn't find a match, so assume the path refers to a folder in the highest priority scanfolder
  127. scanFolderInfo = &m_platformConfig->GetScanFolderAt(0);
  128. }
  129. }
  130. else
  131. {
  132. scanFolderInfo = m_platformConfig->GetScanFolderForFile(normalizedSource.c_str());
  133. }
  134. if (!scanFolderInfo)
  135. {
  136. return AZ::Failure(AZStd::string::format("Path %s points to a file outside the current project's scan folders.\n", normalizedSource.c_str()));
  137. }
  138. if (isRelative)
  139. {
  140. relativePath = normalizedSource;
  141. }
  142. else
  143. {
  144. QString relativePathQString;
  145. if (!PlatformConfiguration::ConvertToRelativePath(normalizedSource.c_str(), scanFolderInfo, relativePathQString))
  146. {
  147. return AZ::Failure(AZStd::string::format("Failed to convert path to relative path. %s\n", normalizedSource.c_str()));
  148. }
  149. relativePath = relativePathQString.toUtf8().constData();
  150. }
  151. return AZ::Success();
  152. }
  153. QHash<QString, int> SourceFileRelocator::GetSources(QStringList pathMatches, const ScanFolderInfo* scanFolderInfo,
  154. SourceFileRelocationContainer& sources,
  155. bool allowNonDatabaseFiles) const
  156. {
  157. auto* uuidInterface = AZ::Interface<AssetProcessor::IUuidRequests>::Get();
  158. AZ_Assert(uuidInterface, "Programmer Error - IUuidRequests interface is not available.");
  159. QHash<QString, int> sourceIndexMap;
  160. QSet<QString> filesNotInAssetDatabase;
  161. for (auto& file : pathMatches)
  162. {
  163. QString databaseSourceName;
  164. int sourcesSize = aznumeric_cast<int>(sources.size());
  165. PlatformConfiguration::ConvertToRelativePath(file, scanFolderInfo, databaseSourceName);
  166. filesNotInAssetDatabase.insert(databaseSourceName);
  167. m_stateData->QuerySourceBySourceNameScanFolderID(
  168. databaseSourceName.toUtf8().constData(),
  169. scanFolderInfo->ScanFolderID(),
  170. [this, &sources, &scanFolderInfo, &sourceIndexMap, &databaseSourceName, &filesNotInAssetDatabase, uuidInterface](
  171. const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  172. {
  173. const bool isMetadataType = uuidInterface->IsGenerationEnabledForFile(databaseSourceName.toUtf8().constData());
  174. sources.emplace_back(
  175. entry, GetProductMapForSource(entry.m_sourceID), entry.m_sourceName, scanFolderInfo, isMetadataType);
  176. filesNotInAssetDatabase.remove(databaseSourceName);
  177. sourceIndexMap[databaseSourceName] = aznumeric_cast<int>(sources.size() - 1);
  178. return true;
  179. });
  180. // If allowNonDatabaseFiles is true add any source files that have no database entry so that they may be moved/deleted
  181. if (sources.size() == sourcesSize && allowNonDatabaseFiles)
  182. {
  183. AZStd::unordered_map<int, AzToolsFramework::AssetDatabase::ProductDatabaseEntry> products;
  184. AzToolsFramework::AssetDatabase::SourceDatabaseEntry entry;
  185. entry.m_scanFolderPK = scanFolderInfo->ScanFolderID();
  186. entry.m_sourceName = databaseSourceName.toUtf8().constData();
  187. sources.emplace_back(entry, products, entry.m_sourceName, scanFolderInfo, false);
  188. filesNotInAssetDatabase.remove(databaseSourceName);
  189. sourceIndexMap[databaseSourceName] = aznumeric_cast<int>(sources.size() - 1);
  190. }
  191. }
  192. for (const QString& file : filesNotInAssetDatabase)
  193. {
  194. AZ_Printf("AssetProcessor", "File `%s` was found/matched but is not a source asset. Skipping.\n", file.toUtf8().constData());
  195. }
  196. return sourceIndexMap;
  197. }
  198. void SourceFileRelocator::HandleMetaDataFiles(QStringList pathMatches, QHash<QString, int>& sourceIndexMap, const ScanFolderInfo* scanFolderInfo, SourceFileRelocationContainer& metadataFiles, bool excludeMetaDataFiles) const
  199. {
  200. QSet<QString> metaDataFileEntries;
  201. // Remove all the metadata files
  202. if (excludeMetaDataFiles)
  203. {
  204. pathMatches.erase(AZStd::remove_if(pathMatches.begin(), pathMatches.end(), [this](const QString& file)
  205. {
  206. for (int idx = 0; idx < m_platformConfig->MetaDataFileTypesCount(); idx++)
  207. {
  208. const auto& [metadataType, extension] = m_platformConfig->GetMetaDataFileTypeAt(idx);
  209. if (file.endsWith("." + metadataType, Qt::CaseInsensitive))
  210. {
  211. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Metadata file %s will be ignored because --excludeMetadataFiles was specified in the command line.\n",
  212. file.toUtf8().constData());
  213. return true;
  214. }
  215. }
  216. return false;
  217. }),
  218. pathMatches.end());
  219. }
  220. for (const QString& file : pathMatches)
  221. {
  222. for (int idx = 0; idx < m_platformConfig->MetaDataFileTypesCount(); idx++)
  223. {
  224. const auto& [metadataType, extension] = m_platformConfig->GetMetaDataFileTypeAt(idx);
  225. if (file.endsWith("." + metadataType, Qt::CaseInsensitive))
  226. {
  227. const QString normalizedFilePath = AssetUtilities::NormalizeFilePath(file);
  228. if (!metaDataFileEntries.contains(normalizedFilePath))
  229. {
  230. SourceFileRelocationInfo metaDataFile(file.toUtf8().data(), scanFolderInfo);
  231. metaDataFile.m_metadataIndex = idx;
  232. metadataFiles.emplace_back(metaDataFile);
  233. metaDataFileEntries.insert(normalizedFilePath);
  234. }
  235. }
  236. else if (!excludeMetaDataFiles && (file.endsWith("." + extension, Qt::CaseInsensitive) || extension.isEmpty()))
  237. {
  238. // if we are here it implies that a metadata file might exists for this source file,
  239. // add metadata file only if it exists and is not added already
  240. AZStd::string metadataFilePath(file.toUtf8().data());
  241. if (extension.isEmpty())
  242. {
  243. metadataFilePath.append(AZStd::string::format(".%s", metadataType.toUtf8().data()));
  244. }
  245. else
  246. {
  247. AZ::StringFunc::Path::ReplaceExtension(metadataFilePath, metadataType.toUtf8().data());
  248. };
  249. // The metadata file can have a different case than the source file,
  250. // We are trying to finding the correct case for the file here
  251. QString metadaFileCorrectCase;
  252. QFileInfo fileInfo(metadataFilePath.c_str());
  253. QStringList fileEntries = fileInfo.absoluteDir().entryList(QDir::Files);
  254. for (const QString& fileEntry : fileEntries)
  255. {
  256. if (QString::compare(fileEntry, fileInfo.fileName(), Qt::CaseInsensitive) == 0)
  257. {
  258. metadaFileCorrectCase = AssetUtilities::NormalizeFilePath(fileInfo.absoluteDir().filePath(fileEntry));
  259. break;
  260. }
  261. }
  262. if (QFile::exists(metadataFilePath.c_str()) && metaDataFileEntries.find(metadaFileCorrectCase) == metaDataFileEntries.end())
  263. {
  264. QString databaseSourceName;
  265. PlatformConfiguration::ConvertToRelativePath(file, scanFolderInfo, databaseSourceName);
  266. auto sourceFileIndex = sourceIndexMap.find(databaseSourceName);
  267. if (sourceFileIndex != sourceIndexMap.end())
  268. {
  269. SourceFileRelocationInfo metaDataFile(metadaFileCorrectCase.toUtf8().data(), scanFolderInfo);
  270. metaDataFile.m_metadataIndex = idx;
  271. metaDataFile.m_sourceFileIndex = sourceFileIndex.value();
  272. metadataFiles.emplace_back(metaDataFile);
  273. metaDataFileEntries.insert(metadaFileCorrectCase);
  274. }
  275. }
  276. }
  277. }
  278. }
  279. }
  280. AZStd::unordered_map<int, AzToolsFramework::AssetDatabase::ProductDatabaseEntry> SourceFileRelocator::GetProductMapForSource(AZ::s64 sourceId) const
  281. {
  282. AZStd::unordered_map<int, AzToolsFramework::AssetDatabase::ProductDatabaseEntry> products;
  283. m_stateData->QueryProductBySourceID(sourceId, [&products](const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry)
  284. {
  285. products.emplace(entry.m_subID, entry);
  286. return true;
  287. });
  288. return products;
  289. }
  290. bool SourceFileRelocator::GetFilesFromSourceControl(
  291. SourceFileRelocationContainer& sources,
  292. const ScanFolderInfo* scanFolderInfo,
  293. QString absolutePath,
  294. bool excludeMetaDataFiles,
  295. bool allowNonDatabaseFiles) const
  296. {
  297. QStringList pathMatches;
  298. AZStd::binary_semaphore waitSignal;
  299. AzToolsFramework::SourceControlResponseCallbackBulk filesInfoCallback = [&](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> filesInfo)
  300. {
  301. if (success)
  302. {
  303. for (const auto& fileInfo : filesInfo)
  304. {
  305. if (AZ::IO::SystemFile::Exists(fileInfo.m_filePath.c_str()))
  306. {
  307. pathMatches.append(fileInfo.m_filePath.c_str());
  308. }
  309. }
  310. QHash<QString, int> sourceIndexMap = GetSources(pathMatches, scanFolderInfo, sources, allowNonDatabaseFiles);
  311. HandleMetaDataFiles(pathMatches, sourceIndexMap, scanFolderInfo, sources, excludeMetaDataFiles);
  312. }
  313. waitSignal.release();
  314. };
  315. AZStd::string adjustedPerforceSearchString(absolutePath.toUtf8().data());
  316. AdjustWildcardForPerforce(adjustedPerforceSearchString);
  317. AZStd::unordered_set<AZStd::string> normalizedDir = { adjustedPerforceSearchString };
  318. AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommands::GetBulkFileInfo, normalizedDir, filesInfoCallback);
  319. WaitForSourceControl(waitSignal);
  320. return !pathMatches.isEmpty();
  321. }
  322. AZ::Outcome<void, AZStd::string> SourceFileRelocator::GetSourcesByPath(
  323. const AZStd::string& normalizedSource,
  324. SourceFileRelocationContainer& sources,
  325. const ScanFolderInfo*& scanFolderInfoOut,
  326. bool excludeMetaDataFiles,
  327. bool allowNonDatabaseFiles) const
  328. {
  329. // nothing below will succeed if source control is active but invalid, so early out with a clear
  330. // warning to the user.
  331. if (!IsSourceControlValid())
  332. {
  333. // no point in continuing, it will always fail.
  334. return AZ::Failure(AZStd::string("The Source Control plugin is active but the configuration is invalid.\n"
  335. "Either disable it by right-clicking the source control icon in the editor status bar,\n"
  336. "or fix the configuration of it in that same right-click menu.\n"));
  337. }
  338. if(normalizedSource.find("**") != AZStd::string::npos)
  339. {
  340. return AZ::Failure(AZStd::string("Consecutive wildcards are not allowed. Please remove extra wildcards from your query.\n"));
  341. }
  342. bool fileExists = false;
  343. bool isWildcard = normalizedSource.find('*') != AZStd::string_view::npos || normalizedSource.find('?') != AZStd::string_view::npos;
  344. if (isWildcard)
  345. {
  346. bool isRelative = AzFramework::StringFunc::Path::IsRelative(normalizedSource.c_str());
  347. scanFolderInfoOut = nullptr;
  348. if (isRelative)
  349. {
  350. // For relative wildcard paths, we'll test the source path in each scan folder to see if we find any matches
  351. bool foundMatch = false;
  352. bool sourceContainsSlash = normalizedSource.find('/') != normalizedSource.npos;
  353. for (int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
  354. {
  355. const ScanFolderInfo* scanFolderInfo = &m_platformConfig->GetScanFolderAt(i);
  356. if(!scanFolderInfo->RecurseSubFolders() && sourceContainsSlash)
  357. {
  358. continue;
  359. }
  360. QString relativeFileName(normalizedSource.c_str());
  361. QDir rooted(scanFolderInfo->ScanPath());
  362. QString absolutePath = rooted.absoluteFilePath(relativeFileName);
  363. if (GetFilesFromSourceControl(sources, scanFolderInfo, absolutePath, excludeMetaDataFiles, allowNonDatabaseFiles))
  364. {
  365. if (foundMatch)
  366. {
  367. return AZ::Failure(AZStd::string::format("Wildcard query %s matched files in multiple scanfolders. Files can only be moved from one scanfolder at a time. Please narrow your query.\nMatch 1: %s\nMatch 2: %s\n",
  368. normalizedSource.c_str(),
  369. scanFolderInfoOut->ScanPath().toUtf8().constData(),
  370. scanFolderInfo->ScanPath().toUtf8().constData()));
  371. }
  372. foundMatch = true;
  373. fileExists = true;
  374. scanFolderInfoOut = scanFolderInfo;
  375. }
  376. }
  377. }
  378. else
  379. {
  380. if (!normalizedSource.ends_with('*') && AZ::IO::FileIOBase::GetInstance()->IsDirectory(normalizedSource.c_str()))
  381. {
  382. return AZ::Failure(AZStd::string("Cannot operate on directories. Please specify a file or use a wildcard to select all files within a directory.\n"));
  383. }
  384. // Absolute path: just look up the scanfolder and convert to a relative path
  385. AZStd::string pathOnly;
  386. AzFramework::StringFunc::Path::GetFullPath(normalizedSource.c_str(), pathOnly);
  387. scanFolderInfoOut = m_platformConfig->GetScanFolderForFile(pathOnly.c_str());
  388. if (!scanFolderInfoOut)
  389. {
  390. return AZ::Failure(AZStd::string::format("Path %s points to a folder outside the current project's scan folders.\n", pathOnly.c_str()));
  391. }
  392. fileExists = GetFilesFromSourceControl(
  393. sources, scanFolderInfoOut, normalizedSource.c_str(), excludeMetaDataFiles, allowNonDatabaseFiles);
  394. }
  395. if(sources.empty())
  396. {
  397. if (fileExists)
  398. {
  399. return AZ::Failure(AZStd::string("Wildcard search matched one or more files but none are source assets. This utility only handles source assets.\n"));
  400. }
  401. else
  402. {
  403. return AZ::Failure(AZStd::string("Wildcard search did not match any files.\n"));
  404. }
  405. }
  406. }
  407. else // Non-wildcard search
  408. {
  409. AZStd::string relativePath;
  410. auto result = GetScanFolderAndRelativePath(normalizedSource, false, scanFolderInfoOut, relativePath);
  411. if (!result.IsSuccess())
  412. {
  413. return result;
  414. }
  415. AZStd::string absoluteSourcePath;
  416. AzFramework::StringFunc::Path::Join(scanFolderInfoOut->ScanPath().toUtf8().constData(), relativePath.c_str(), absoluteSourcePath);
  417. if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(absoluteSourcePath.c_str()))
  418. {
  419. return AZ::Failure(AZStd::string("Cannot operate on directories. Please specify a file or use a wildcard to select all files within a directory.\n"));
  420. }
  421. fileExists = GetFilesFromSourceControl(
  422. sources, scanFolderInfoOut, absoluteSourcePath.c_str(), excludeMetaDataFiles, allowNonDatabaseFiles);
  423. if (sources.empty())
  424. {
  425. if (fileExists)
  426. {
  427. return AZ::Failure(AZStd::string("Search matched an existing file but it is not a source asset. This utility only handles source assets.\n"));
  428. }
  429. else
  430. {
  431. return AZ::Failure(AZStd::string("File not found.\n"));
  432. }
  433. }
  434. }
  435. return AZ::Success();
  436. }
  437. void SourceFileRelocator::PopulateDependencies(SourceFileRelocationContainer& relocationContainer) const
  438. {
  439. for (auto& relocationInfo : relocationContainer)
  440. {
  441. if (relocationInfo.m_isMetadataEnabledType)
  442. {
  443. // Metadata enabled files do not use dependency fixup system
  444. continue;
  445. }
  446. m_stateData->QuerySourceDependencyByDependsOnSource(relocationInfo.m_sourceEntry.m_sourceGuid, relocationInfo.m_sourceEntry.m_sourceName.c_str(), relocationInfo.m_oldAbsolutePath.c_str(),
  447. AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any, [&relocationInfo](AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& dependencyEntry)
  448. {
  449. relocationInfo.m_sourceDependencyEntries.push_back(dependencyEntry);
  450. relocationInfo.m_hasPathDependencies |= !dependencyEntry.m_fromAssetId;
  451. return true;
  452. });
  453. m_stateData->QueryProductDependenciesThatDependOnProductBySourceId(relocationInfo.m_sourceEntry.m_sourceID, [this, &relocationInfo](const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
  454. {
  455. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
  456. m_stateData->QuerySourceByProductID(entry.m_productPK, [&sourceEntry](const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  457. {
  458. sourceEntry = entry;
  459. return false;
  460. });
  461. // Don't count self-referencing product dependencies as there shouldn't be a case where a file has a hardcoded reference to itself.
  462. if(sourceEntry.m_sourceID != relocationInfo.m_sourceEntry.m_sourceID)
  463. {
  464. relocationInfo.m_productDependencyEntries.push_back(entry);
  465. relocationInfo.m_hasPathDependencies |= !entry.m_fromAssetId;
  466. }
  467. return true;
  468. });
  469. }
  470. }
  471. void SourceFileRelocator::MakePathRelative(const AZStd::string& parentPath, const AZStd::string& childPath, AZStd::string& parentRelative, AZStd::string& childRelative)
  472. {
  473. auto parentItr = parentPath.begin();
  474. auto childItr = childPath.begin();
  475. while (*parentItr == *childItr && parentItr != parentPath.end() && childItr != childPath.end())
  476. {
  477. ++parentItr;
  478. ++childItr;
  479. }
  480. parentRelative = AZStd::string(parentItr, parentPath.end());
  481. childRelative = AZStd::string(childItr, childPath.end());
  482. }
  483. AZ::Outcome<AZStd::string, AZStd::string> SourceFileRelocator::HandleWildcard(AZStd::string_view absFile, AZStd::string_view absSearch, AZStd::string destination)
  484. {
  485. AZStd::string searchAsRegex = absSearch;
  486. AZStd::regex specialCharacters(R"([.?^$+(){}[\]-])");
  487. // Escape the regex special characters
  488. searchAsRegex = AZStd::regex_replace(searchAsRegex, specialCharacters, R"(\$0)");
  489. // Replace * with .*
  490. searchAsRegex = AZStd::regex_replace(searchAsRegex, AZStd::regex(R"(\*)"), R"((.*))");
  491. AZStd::smatch result;
  492. // Match absSearch against absFile to find what each * expands to
  493. if(AZStd::regex_search(absFile.begin(), absFile.end(), result, AZStd::regex(searchAsRegex, AZStd::regex::icase)))
  494. {
  495. // For each * expansion, replace the * in the destination with the expanded result
  496. for (size_t i = 1; i < result.size(); ++i)
  497. {
  498. auto matchedString = result[i].str();
  499. // Only the last match can match across directory levels
  500. if(matchedString.find('/') != matchedString.npos && i < result.size() - 1)
  501. {
  502. return AZ::Failure(AZStd::string("Wildcard cannot match across directory levels. Please simplify your search or put a wildcard at the end of the search to match across directories.\n"));
  503. }
  504. destination.replace(destination.find('*'), 1, result[i].str().c_str());
  505. }
  506. }
  507. return AZ::Success(destination);
  508. }
  509. void SourceFileRelocator::FixDestinationMissingFilename(AZStd::string& destination, const AZStd::string& source)
  510. {
  511. if (destination.ends_with(AZ_CORRECT_DATABASE_SEPARATOR))
  512. {
  513. size_t lastSlash = source.find_last_of(AZ_CORRECT_DATABASE_SEPARATOR);
  514. if (lastSlash == source.npos)
  515. {
  516. lastSlash = 0;
  517. }
  518. else
  519. {
  520. ++lastSlash; // Skip the slash itself
  521. }
  522. AZStd::string filename = source.substr(lastSlash);
  523. destination = destination + filename;
  524. }
  525. }
  526. AZ::Outcome<void, AZStd::string> SourceFileRelocator::ComputeDestination(SourceFileRelocationContainer& relocationContainer, const ScanFolderInfo* sourceScanFolder, const AZStd::string& source, AZStd::string destination, const ScanFolderInfo*& destinationScanFolderOut) const
  527. {
  528. if(destination.find("..") != AZStd::string::npos)
  529. {
  530. return AZ::Failure(AZStd::string("Destination cannot contain any path navigation. Please specify an absolute or relative path that does not contain ..\n"));
  531. }
  532. FixDestinationMissingFilename(destination, source);
  533. if(destination.find_first_of("<|>?\"") != destination.npos)
  534. {
  535. return AZ::Failure(AZStd::string("Destination string contains invalid characters.\n"));
  536. }
  537. if (!AzFramework::StringFunc::Path::IsRelative(destination.c_str()))
  538. {
  539. destinationScanFolderOut = m_platformConfig->GetScanFolderForFile(destination.c_str());
  540. if (!destinationScanFolderOut)
  541. {
  542. return AZ::Failure(AZStd::string("Destination must exist within a scanfolder.\n"));
  543. }
  544. }
  545. AZ::s64 sourceWildcardCount = std::count(source.begin(), source.end(), '*');
  546. AZ::s64 destinationWildcardCount = std::count(destination.begin(), destination.end(), '*');
  547. if(sourceWildcardCount != destinationWildcardCount)
  548. {
  549. return AZ::Failure(AZStd::string("Source and destination paths must have the same number of wildcards.\n"));
  550. }
  551. AZStd::string lastError;
  552. for (SourceFileRelocationInfo& relocationInfo : relocationContainer)
  553. {
  554. AZStd::string newDestinationPath;
  555. // A valid sourceFile Index ( i.e non negative) indicates that it is a metadafile and therefore
  556. // we would have to determine the destination info from the source file itself.
  557. if (relocationInfo.m_sourceFileIndex == AssetProcessor::SourceFileRelocationInvalidIndex)
  558. {
  559. AZStd::string oldAbsolutePath, selectionSourceAbsolutePath;
  560. AzFramework::StringFunc::Path::ConstructFull(sourceScanFolder->ScanPath().toUtf8().constData(), relocationInfo.m_oldRelativePath.c_str(), oldAbsolutePath, true);
  561. if (AzFramework::StringFunc::Path::IsRelative(source.c_str()))
  562. {
  563. AzFramework::StringFunc::Path::Join(sourceScanFolder->ScanPath().toUtf8().constData(), source.c_str(), selectionSourceAbsolutePath, true, false);
  564. }
  565. else
  566. {
  567. selectionSourceAbsolutePath = source;
  568. }
  569. AZStd::replace(selectionSourceAbsolutePath.begin(), selectionSourceAbsolutePath.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  570. AZStd::replace(oldAbsolutePath.begin(), oldAbsolutePath.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  571. auto result = HandleWildcard(oldAbsolutePath, selectionSourceAbsolutePath, destination);
  572. if (!result.IsSuccess())
  573. {
  574. lastError = result.TakeError();
  575. continue;
  576. }
  577. newDestinationPath = AssetUtilities::NormalizeFilePath(result.TakeValue().c_str()).toUtf8().constData();
  578. }
  579. else
  580. {
  581. const auto& metadataInfo = m_platformConfig->GetMetaDataFileTypeAt(relocationInfo.m_metadataIndex);
  582. newDestinationPath = relocationContainer[relocationInfo.m_sourceFileIndex].m_newAbsolutePath;
  583. if (!metadataInfo.second.isEmpty())
  584. {
  585. // Replace extension
  586. newDestinationPath = AZ::IO::FixedMaxPath(newDestinationPath).ReplaceExtension(metadataInfo.first.toUtf8().constData()).c_str();
  587. }
  588. else
  589. {
  590. // Append extension
  591. newDestinationPath += ".";
  592. newDestinationPath += metadataInfo.first.toUtf8().constData();
  593. }
  594. }
  595. if (!AzFramework::StringFunc::Path::IsRelative(newDestinationPath.c_str()))
  596. {
  597. QString relativePath;
  598. QString scanFolderName;
  599. m_platformConfig->ConvertToRelativePath(newDestinationPath.c_str(), relativePath, scanFolderName);
  600. relocationInfo.m_newAbsolutePath = newDestinationPath;
  601. relocationInfo.m_newRelativePath = relativePath.toUtf8().constData();
  602. }
  603. else
  604. {
  605. if (!destinationScanFolderOut)
  606. {
  607. destinationScanFolderOut = sourceScanFolder;
  608. }
  609. relocationInfo.m_newRelativePath = newDestinationPath;
  610. AzFramework::StringFunc::Path::ConstructFull(destinationScanFolderOut->ScanPath().toUtf8().constData(), relocationInfo.m_newRelativePath.c_str(), relocationInfo.m_newAbsolutePath, true);
  611. }
  612. relocationInfo.m_newRelativePath = AssetUtilities::NormalizeFilePath(relocationInfo.m_newRelativePath.c_str()).toUtf8().constData();
  613. relocationInfo.m_newAbsolutePath = AssetUtilities::NormalizeFilePath(relocationInfo.m_newAbsolutePath.c_str()).toUtf8().constData();
  614. relocationInfo.m_newUuid = AssetUtilities::CreateSafeSourceUUIDFromName(relocationInfo.m_newRelativePath.c_str());
  615. }
  616. if(!destinationScanFolderOut)
  617. {
  618. return AZ::Failure(lastError);
  619. }
  620. return AZ::Success();
  621. }
  622. AZStd::string BuildTaskFailureReport(const FileUpdateTasks& updateTasks)
  623. {
  624. AZStd::string report;
  625. AZStd::string skippedReport;
  626. for (const FileUpdateTask& task : updateTasks)
  627. {
  628. if (task.m_skipTask)
  629. {
  630. if (skippedReport.empty())
  631. {
  632. skippedReport.append("UPDATE SKIP REPORT:\nThe following files have a dependency on file(s) that failed to move. These files were not updated:\n");
  633. }
  634. skippedReport.append(AZStd::string::format("\t%s\n", task.m_absPathFileToUpdate.c_str()));
  635. }
  636. else if (!task.m_succeeded)
  637. {
  638. if (report.empty())
  639. {
  640. report.append("UPDATE FAILURE REPORT:\nThe following files have a dependency on file(s) that were moved and failed to be updated automatically. They will need to be updated manually to fix broken references to moved files:\n");
  641. }
  642. report.append(AZStd::string::format("\tFILE: %s\n", task.m_absPathFileToUpdate.c_str()));
  643. for(int i = 0; i < task.m_oldStrings.size(); ++i)
  644. {
  645. report.append(AZStd::string::format("\t\tPOSSIBLE REFERENCE: %s -> UPDATE TO: %s\n", task.m_oldStrings[i].c_str(), task.m_newStrings[i].c_str()));
  646. }
  647. }
  648. }
  649. if(!report.empty())
  650. {
  651. report.append("\n");
  652. }
  653. report.append(skippedReport);
  654. return report;
  655. }
  656. AZStd::string SourceFileRelocator::BuildChangeReport(const SourceFileRelocationContainer& relocationEntries, const FileUpdateTasks& updateTasks) const
  657. {
  658. AZStd::string report;
  659. for (const SourceFileRelocationInfo& relocationInfo : relocationEntries)
  660. {
  661. if (!relocationInfo.m_sourceDependencyEntries.empty())
  662. {
  663. AZStd::string reportString = AZStd::string::format("%s:\n", "The following files have a source / job dependency on this file, we will attempt to fix the references but they may still break.");
  664. report.append(reportString);
  665. for (const auto& sourceDependency : relocationInfo.m_sourceDependencyEntries)
  666. {
  667. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
  668. m_stateData->QuerySourceBySourceGuid(
  669. sourceDependency.m_sourceGuid,
  670. [&sourceEntry](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  671. {
  672. sourceEntry = entry;
  673. return false;
  674. });
  675. report = AZStd::string::format(
  676. "%s\nPATH: %s, TYPE: %d, %s\n",
  677. report.c_str(),
  678. sourceEntry.m_sourceName.c_str(),
  679. sourceDependency.m_typeOfDependency,
  680. sourceDependency.m_fromAssetId ? "AssetId-based" : "Path-based");
  681. AZStd::string fileExtension;
  682. AZ::StringFunc::Path::GetExtension(sourceEntry.m_sourceName.c_str(), fileExtension, false);
  683. auto found = m_additionalHelpTextMap.find(fileExtension);
  684. if (found != m_additionalHelpTextMap.end())
  685. {
  686. report.append(found->second);
  687. }
  688. }
  689. }
  690. if (!relocationInfo.m_productDependencyEntries.empty())
  691. {
  692. AZStd::string reportString = AZStd::string::format("%s:\n", "The following files have a product dependency on one or more of the products generated by this file, we will attempt to fix the references but they may still break");
  693. report.append(reportString);
  694. for (const auto& productDependency : relocationInfo.m_productDependencyEntries)
  695. {
  696. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
  697. AzToolsFramework::AssetDatabase::ProductDatabaseEntry productEntry;
  698. AzToolsFramework::AssetDatabase::ProductDatabaseEntry thisFilesProductEntry;
  699. m_stateData->QuerySourceByProductID(productDependency.m_productPK, [&sourceEntry](const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  700. {
  701. sourceEntry = entry;
  702. return false;
  703. });
  704. m_stateData->QueryProductByProductID(productDependency.m_productPK, [&productEntry](const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry)
  705. {
  706. productEntry = entry;
  707. return false;
  708. });
  709. m_stateData->QueryCombinedBySourceGuidProductSubId(productDependency.m_dependencySourceGuid, productDependency.m_dependencySubID, [&thisFilesProductEntry](const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& entry)
  710. {
  711. thisFilesProductEntry = static_cast<AzToolsFramework::AssetDatabase::ProductDatabaseEntry>(entry);
  712. return false;
  713. }, AZ::Uuid::CreateNull(), nullptr, productDependency.m_platform.c_str());
  714. report = AZStd::string::format("%s\nPATH: %s, DEPENDS ON PRODUCT: %s, ASSETID: %s, TYPE: %d, %s\n", report.c_str(), productEntry.m_productName.c_str(), thisFilesProductEntry.m_productName.c_str(), AZ::Data::AssetId(sourceEntry.m_sourceGuid, productEntry.m_subID).ToString<AZStd::string>().c_str(), productDependency.m_dependencyType, productDependency.m_fromAssetId ? "AssetId-based" : "Path-based");
  715. }
  716. }
  717. }
  718. report.append(BuildTaskFailureReport(updateTasks));
  719. return report;
  720. }
  721. AZStd::string SourceFileRelocator::BuildReport(const SourceFileRelocationContainer& relocationEntries, const FileUpdateTasks& updateTasks, bool isMove, bool updateReference) const
  722. {
  723. AZStd::string report;
  724. report.append("FILE REPORT:\n");
  725. for (const SourceFileRelocationInfo& relocationInfo : relocationEntries)
  726. {
  727. if (relocationInfo.m_metadataIndex != SourceFileRelocationInvalidIndex)
  728. {
  729. report.append(AZStd::string::format(
  730. "Metadata file CURRENT PATH: %s, NEW PATH: %s\n",
  731. relocationInfo.m_oldRelativePath.c_str(),
  732. relocationInfo.m_newRelativePath.c_str()));
  733. }
  734. else
  735. {
  736. if (isMove)
  737. {
  738. report.append(AZStd::string::format(
  739. "SOURCEID: %lld, CURRENT PATH: %s, NEW PATH: %s, CURRENT GUID: %s, NEW GUID: %s\n",
  740. relocationInfo.m_sourceEntry.m_sourceID,
  741. relocationInfo.m_oldRelativePath.c_str(),
  742. relocationInfo.m_newRelativePath.c_str(),
  743. relocationInfo.m_sourceEntry.m_sourceGuid.ToString<AZStd::string>().c_str(),
  744. relocationInfo.m_newUuid.ToString<AZStd::string>().c_str()));
  745. }
  746. else
  747. {
  748. report.append(AZStd::string::format(
  749. "SOURCEID: %lld, CURRENT PATH: %s, CURRENT GUID: %s\n",
  750. relocationInfo.m_sourceEntry.m_sourceID,
  751. relocationInfo.m_oldRelativePath.c_str(),
  752. relocationInfo.m_sourceEntry.m_sourceGuid.ToString<AZStd::string>().c_str()));
  753. }
  754. }
  755. if (!relocationInfo.m_sourceDependencyEntries.empty())
  756. {
  757. AZStd::string reportString = AZStd::string::format("\t%s:\n", updateReference ? " The following files have a source / job dependency on this file, we will attempt to fix the references but they may still break"
  758. : "The following files have a source / job dependency on this file and will break");
  759. report.append(reportString);
  760. for (const auto& sourceDependency : relocationInfo.m_sourceDependencyEntries)
  761. {
  762. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
  763. m_stateData->QuerySourceBySourceGuid(
  764. sourceDependency.m_sourceGuid,
  765. [&sourceEntry](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  766. {
  767. sourceEntry = entry;
  768. return false;
  769. });
  770. report = AZStd::string::format("%s\t\tUUID: %s, TYPE: %d, %s\n", report.c_str(), sourceEntry.m_sourceName.c_str(), sourceDependency.m_typeOfDependency, sourceDependency.m_fromAssetId ? "AssetId-based" : "Path-based");
  771. AZStd::string fileExtension;
  772. AZ::StringFunc::Path::GetExtension(sourceEntry.m_sourceName.c_str(), fileExtension, false);
  773. auto found = m_additionalHelpTextMap.find(fileExtension);
  774. if (found != m_additionalHelpTextMap.end())
  775. {
  776. report.append(found->second);
  777. }
  778. }
  779. }
  780. if (!relocationInfo.m_productDependencyEntries.empty())
  781. {
  782. AZStd::string reportString = AZStd::string::format("\t%s:\n", updateReference ? " The following files have a product dependency on one or more of the products generated by this file, we will attempt to fix the references but they may still break"
  783. : "The following files have a product dependency on one or more of the products generated by this file and will break");
  784. report.append(reportString);
  785. for (const auto& productDependency : relocationInfo.m_productDependencyEntries)
  786. {
  787. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
  788. AzToolsFramework::AssetDatabase::ProductDatabaseEntry productEntry;
  789. AzToolsFramework::AssetDatabase::ProductDatabaseEntry thisFilesProductEntry;
  790. m_stateData->QuerySourceByProductID(productDependency.m_productPK, [&sourceEntry](const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  791. {
  792. sourceEntry = entry;
  793. return false;
  794. });
  795. m_stateData->QueryProductByProductID(productDependency.m_productPK, [&productEntry](const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry)
  796. {
  797. productEntry = entry;
  798. return false;
  799. });
  800. m_stateData->QueryCombinedBySourceGuidProductSubId(productDependency.m_dependencySourceGuid, productDependency.m_dependencySubID, [&thisFilesProductEntry](const AzToolsFramework::AssetDatabase::CombinedDatabaseEntry& entry)
  801. {
  802. thisFilesProductEntry = static_cast<AzToolsFramework::AssetDatabase::ProductDatabaseEntry>(entry);
  803. return false;
  804. }, AZ::Uuid::CreateNull(), nullptr, productDependency.m_platform.c_str());
  805. report = AZStd::string::format("%s\t\tPATH: %s, DEPENDS ON PRODUCT: %s, ASSETID: %s, TYPE: %d, %s\n", report.c_str(), productEntry.m_productName.c_str(), thisFilesProductEntry.m_productName.c_str(), AZ::Data::AssetId(sourceEntry.m_sourceGuid, productEntry.m_subID).ToString<AZStd::string>().c_str(), productDependency.m_dependencyType, productDependency.m_fromAssetId ? "AssetId-based" : "Path-based");
  806. }
  807. }
  808. }
  809. report.append(BuildTaskFailureReport(updateTasks));
  810. return report;
  811. }
  812. AZ::Outcome<RelocationSuccess, MoveFailure> SourceFileRelocator::Move(const AZStd::string& source, const AZStd::string& destination, int flags )
  813. {
  814. AZStd::string normalizedSource = source;
  815. AZStd::string normalizedDestination = destination;
  816. bool previewOnly = (flags & RelocationParameters_PreviewOnlyFlag) != 0 ? true : false;
  817. bool allowDependencyBreaking = (flags & RelocationParameters_AllowDependencyBreakingFlag) != 0 ? true : false;
  818. bool removeEmptyFolders = (flags & RelocationParameters_RemoveEmptyFoldersFlag) != 0 ? true : false;
  819. bool updateReferences = (flags & RelocationParameters_UpdateReferencesFlag) != 0 ? true : false;
  820. bool excludeMetaDataFiles = (flags & RelocationParameters_ExcludeMetaDataFilesFlag) != 0 ? true : false;
  821. bool allowNonDatabaseFiles = (flags & RelocationParameters_AllowNonDatabaseFilesFlag) != 0 ? true : false;
  822. // Just make sure we have uniform slashes, don't normalize because we need to keep slashes at the end of the path and wildcards, etc, which tend to get stripped out by normalize functions
  823. AZStd::replace(normalizedSource.begin(), normalizedSource.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  824. AZStd::replace(normalizedDestination.begin(), normalizedDestination.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
  825. SourceFileRelocationContainer relocationContainer;
  826. const ScanFolderInfo* sourceScanFolderInfo{};
  827. const ScanFolderInfo* destinationScanFolderInfo{};
  828. auto result =
  829. GetSourcesByPath(normalizedSource, relocationContainer, sourceScanFolderInfo, excludeMetaDataFiles, allowNonDatabaseFiles);
  830. if (!result.IsSuccess())
  831. {
  832. return AZ::Failure(MoveFailure(result.TakeError(), false));
  833. }
  834. // If no files were found, just early out
  835. if(relocationContainer.empty())
  836. {
  837. return AZ::Success(RelocationSuccess());
  838. }
  839. PopulateDependencies(relocationContainer);
  840. result = ComputeDestination(relocationContainer, sourceScanFolderInfo, normalizedSource, normalizedDestination, destinationScanFolderInfo);
  841. if(!result.IsSuccess())
  842. {
  843. return AZ::Failure(MoveFailure(result.TakeError(), false));
  844. }
  845. int errorCount = 0;
  846. FileUpdateTasks updateTasks;
  847. if(!previewOnly)
  848. {
  849. if(!updateReferences && !allowDependencyBreaking)
  850. {
  851. for (const SourceFileRelocationInfo& relocationInfo : relocationContainer)
  852. {
  853. if(!relocationInfo.m_productDependencyEntries.empty() || !relocationInfo.m_sourceDependencyEntries.empty())
  854. {
  855. return AZ::Failure(MoveFailure(AZStd::string("Move failed. There are files that have dependencies that may break as a result of being moved/renamed.\n"), true));
  856. }
  857. }
  858. }
  859. bool sourceControlEnabled = false;
  860. AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(sourceControlEnabled, &AzToolsFramework::SourceControlConnectionRequestBus::Events::IsActive);
  861. errorCount = DoSourceControlMoveFiles(normalizedSource, normalizedDestination, relocationContainer, sourceScanFolderInfo, destinationScanFolderInfo, removeEmptyFolders);
  862. if (updateReferences)
  863. {
  864. updateTasks = UpdateReferences(relocationContainer, sourceControlEnabled);
  865. }
  866. }
  867. int relocationCount = aznumeric_caster(relocationContainer.size());
  868. int updateTotalCount = aznumeric_caster(updateTasks.size());
  869. int updateSuccessCount = 0;
  870. for (const auto& task : updateTasks)
  871. {
  872. if (task.m_succeeded)
  873. {
  874. ++updateSuccessCount;
  875. }
  876. }
  877. int updateFailureCount = updateTotalCount - updateSuccessCount;
  878. return AZ::Success(RelocationSuccess(
  879. relocationCount - errorCount, errorCount, relocationCount,
  880. updateSuccessCount, updateFailureCount, updateTotalCount,
  881. AZStd::move(relocationContainer),
  882. AZStd::move(updateTasks)));
  883. }
  884. AZ::Outcome<RelocationSuccess, AZStd::string> SourceFileRelocator::Delete(const AZStd::string& source, int flags)
  885. {
  886. bool previewOnly = (flags & RelocationParameters_PreviewOnlyFlag) != 0 ? true : false;
  887. bool allowDependencyBreaking = (flags & RelocationParameters_AllowDependencyBreakingFlag) != 0 ? true : false;
  888. bool removeEmptyFolders = (flags & RelocationParameters_RemoveEmptyFoldersFlag) != 0 ? true : false;
  889. bool excludeMetaDataFiles = (flags & RelocationParameters_ExcludeMetaDataFilesFlag) != 0 ? true : false;
  890. bool allowNonDatabaseFiles = (flags & RelocationParameters_AllowNonDatabaseFilesFlag) != 0 ? true : false;
  891. AZStd::string normalizedSource = AssetUtilities::NormalizeFilePath(source.c_str()).toUtf8().constData();
  892. SourceFileRelocationContainer relocationContainer;
  893. const ScanFolderInfo* scanFolderInfo{};
  894. auto result = GetSourcesByPath(normalizedSource, relocationContainer, scanFolderInfo, excludeMetaDataFiles, allowNonDatabaseFiles);
  895. if (!result.IsSuccess())
  896. {
  897. return AZ::Failure(result.TakeError());
  898. }
  899. // If no files were found, just early out
  900. if (relocationContainer.empty())
  901. {
  902. return AZ::Success(RelocationSuccess());
  903. }
  904. PopulateDependencies(relocationContainer);
  905. int errorCount = 0;
  906. if (!previewOnly)
  907. {
  908. if (!allowDependencyBreaking)
  909. {
  910. for (const SourceFileRelocationInfo& relocationInfo : relocationContainer)
  911. {
  912. if (!relocationInfo.m_productDependencyEntries.empty() || !relocationInfo.m_sourceDependencyEntries.empty())
  913. {
  914. return AZ::Failure(AZStd::string("Delete failed. There are files that have dependencies that may break as a result of being deleted.\n"));
  915. }
  916. }
  917. }
  918. errorCount = DoSourceControlDeleteFiles(normalizedSource, relocationContainer, scanFolderInfo, removeEmptyFolders);
  919. }
  920. int relocationCount = aznumeric_caster(relocationContainer.size());
  921. return AZ::Success(RelocationSuccess(
  922. relocationCount - errorCount, errorCount, relocationCount,
  923. 0, 0, 0,
  924. AZStd::move(relocationContainer),
  925. {}));
  926. }
  927. void RemoveEmptyFolders(const SourceFileRelocationContainer& relocationContainer)
  928. {
  929. for (const SourceFileRelocationInfo& info : relocationContainer)
  930. {
  931. AZStd::string oldParentFolder;
  932. AzFramework::StringFunc::Path::GetFullPath(info.m_oldAbsolutePath.c_str(), oldParentFolder);
  933. // Not checking the return value since non-empty folders will fail, we just want to delete empty folders.
  934. AZ::IO::SystemFile::DeleteDir(oldParentFolder.c_str());
  935. }
  936. }
  937. void HandleSourceControlResult(SourceFileRelocationContainer& relocationContainer, AZStd::binary_semaphore& waitSignal, int& errorCount,
  938. AzToolsFramework::SourceControlFlags checkFlag, bool checkNewPath, bool /*success*/, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
  939. {
  940. for (SourceFileRelocationInfo& entry : relocationContainer)
  941. {
  942. bool found = false;
  943. bool readOnly = false;
  944. for (const AzToolsFramework::SourceControlFileInfo& scInfo : info)
  945. {
  946. const char* checkPath = checkNewPath ? entry.m_newAbsolutePath.c_str() : entry.m_oldAbsolutePath.c_str();
  947. if (AssetUtilities::NormalizeFilePath(scInfo.m_filePath.c_str()) == AssetUtilities::NormalizeFilePath(checkPath))
  948. {
  949. found = true;
  950. readOnly = !scInfo.HasFlag(AzToolsFramework::SourceControlFlags::SCF_Writeable);
  951. entry.m_operationStatus = (scInfo.m_status == AzToolsFramework::SCS_OpSuccess && scInfo.HasFlag(checkFlag)) ?
  952. SourceFileRelocationStatus::Succeeded : SourceFileRelocationStatus::Failed;
  953. break;
  954. }
  955. }
  956. if (!found && entry.m_sourceFileIndex != AssetProcessor::SourceFileRelocationInvalidIndex)
  957. {
  958. // if we are here it implies that it is an meta data file.
  959. // it is very much possible that the source control won't be able to find all the meta data files
  960. // from the source search path and therefore we will handle them separately.
  961. continue;
  962. }
  963. if(entry.m_operationStatus == SourceFileRelocationStatus::Failed)
  964. {
  965. ++errorCount;
  966. if (!found)
  967. {
  968. AZ_Printf("SourceFileRelocator", "Error: file is not tracked by source control %s\n", entry.m_oldAbsolutePath.c_str());
  969. }
  970. else
  971. {
  972. AZ_Printf("SourceFileRelocator", "Error: operation failed for file %s. Note: File is %s.\n", entry.m_oldAbsolutePath.c_str(), readOnly ? "read-only" : "writable (this is not the source of the error)");
  973. }
  974. }
  975. }
  976. waitSignal.release();
  977. }
  978. AZStd::string ToAbsolutePath(AZStd::string& normalizedPath, const ScanFolderInfo* scanFolderInfo)
  979. {
  980. AZStd::string absolutePath;
  981. if (AzFramework::StringFunc::Path::IsRelative(normalizedPath.c_str()))
  982. {
  983. AzFramework::StringFunc::AssetDatabasePath::Join(scanFolderInfo->ScanPath().toUtf8().constData(), normalizedPath.c_str(), absolutePath, true, false);
  984. }
  985. else
  986. {
  987. absolutePath = normalizedPath;
  988. }
  989. return absolutePath;
  990. }
  991. int SourceFileRelocator::DoSourceControlMoveFiles(AZStd::string normalizedSource, AZStd::string normalizedDestination, SourceFileRelocationContainer& relocationContainer, const ScanFolderInfo* sourceScanFolderInfo, const ScanFolderInfo* destinationScanFolderInfo, bool removeEmptyFolders) const
  992. {
  993. using namespace AzToolsFramework;
  994. AZ_Assert(sourceScanFolderInfo, "sourceScanFolderInfo cannot be null");
  995. AZ_Assert(destinationScanFolderInfo, "destinationScanFolderInfo cannot be null");
  996. for(const auto& relocationInfo : relocationContainer)
  997. {
  998. const char* oldPath = relocationInfo.m_oldAbsolutePath.c_str();
  999. const char* newPath = relocationInfo.m_newAbsolutePath.c_str();
  1000. if(AZ::StringFunc::Equal(newPath, oldPath, true))
  1001. {
  1002. // It's not really an error to rename a file to the same thing. This can happen (unintentionally) with wildcard renames
  1003. continue;
  1004. }
  1005. if(AZ::StringFunc::Equal(newPath, oldPath, false))
  1006. {
  1007. AZ_Printf("SourceFileRelocator", "Error: Changing the case of a filename is not supported due to potential source control restrictions. OldPath: %s, NewPath: %s\n", oldPath, newPath);
  1008. return 1;
  1009. }
  1010. bool newExists = AZ::IO::SystemFile::Exists(newPath);
  1011. if (newExists)
  1012. {
  1013. AZ_Printf("SourceFileRelocator", "Warning: Destination file %s already exists, rename will fail\n", newPath);
  1014. }
  1015. }
  1016. FixDestinationMissingFilename(normalizedDestination, normalizedSource);
  1017. AdjustWildcardForPerforce(normalizedSource, normalizedDestination);
  1018. AZStd::string absoluteSource = ToAbsolutePath(normalizedSource, sourceScanFolderInfo);
  1019. AZStd::string absoluteDestination = ToAbsolutePath(normalizedDestination, destinationScanFolderInfo);
  1020. AZ_Printf("SourceFileRelocator", "From: %s, To: %s\n", absoluteSource.c_str(), absoluteDestination.c_str());
  1021. AZStd::binary_semaphore waitSignal;
  1022. int errorCount = 0;
  1023. AzToolsFramework::SourceControlResponseCallbackBulk callback = [&](bool success, AZStd::vector<SourceControlFileInfo> info)
  1024. {
  1025. HandleSourceControlResult(
  1026. relocationContainer, waitSignal, errorCount,
  1027. SCF_OpenByUser, // If a file is moved from A -> B and then again from B -> A, the result is just an "edit", so we're just going
  1028. // to assume success if the file is checked out, regardless of state
  1029. true, success, info);
  1030. };
  1031. AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestRenameBulkExtended,
  1032. absoluteSource.c_str(), absoluteDestination.c_str(), true, callback);
  1033. if(!WaitForSourceControl(waitSignal))
  1034. {
  1035. return errorCount + 1;
  1036. }
  1037. // Source control rename operation by source path might not result in moving metadata files,
  1038. // therefore we will have to move all those metadata files individually whose associated source files got renamed.
  1039. for (const auto& relocationInfo : relocationContainer)
  1040. {
  1041. if (relocationInfo.m_operationStatus == SourceFileRelocationStatus::Succeeded || relocationInfo.m_sourceFileIndex == AssetProcessor::SourceFileRelocationInvalidIndex)
  1042. {
  1043. // we do not want to retry if the move operation already succeeded or if it is a source file
  1044. continue;
  1045. }
  1046. AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestRenameBulkExtended,
  1047. relocationInfo.m_oldAbsolutePath.c_str(), relocationInfo.m_newAbsolutePath.c_str(), true, callback);
  1048. if (!WaitForSourceControl(waitSignal))
  1049. {
  1050. return errorCount + 1;
  1051. }
  1052. }
  1053. if (removeEmptyFolders)
  1054. {
  1055. RemoveEmptyFolders(relocationContainer);
  1056. }
  1057. return errorCount;
  1058. }
  1059. int SourceFileRelocator::DoSourceControlDeleteFiles(AZStd::string normalizedSource, SourceFileRelocationContainer& relocationContainer, const ScanFolderInfo* sourceScanFolderInfo, bool removeEmptyFolders) const
  1060. {
  1061. using namespace AzToolsFramework;
  1062. AdjustWildcardForPerforce(normalizedSource);
  1063. AZStd::string absoluteSource = ToAbsolutePath(normalizedSource, sourceScanFolderInfo);
  1064. AZ_Printf("SourceFileRelocator", "Delete %s\n", absoluteSource.c_str());
  1065. bool sourceControlEnabled = false;
  1066. AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(sourceControlEnabled, &AzToolsFramework::SourceControlConnectionRequestBus::Events::IsActive);
  1067. SourceControlFlags checkFlag = SCF_PendingDelete;
  1068. if(!sourceControlEnabled)
  1069. {
  1070. // When using the local SC component, the only flag set is the writable flag when a file is deleted
  1071. checkFlag = SCF_Writeable;
  1072. }
  1073. AZStd::binary_semaphore waitSignal;
  1074. int errorCount = 0;
  1075. AzToolsFramework::SourceControlResponseCallbackBulk callback = AZStd::bind(&HandleSourceControlResult, AZStd::ref(relocationContainer), AZStd::ref(waitSignal), AZStd::ref(errorCount),
  1076. checkFlag, false, AZStd::placeholders::_1, AZStd::placeholders::_2);
  1077. AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestDeleteBulkExtended, absoluteSource.c_str(), true, callback);
  1078. if(!WaitForSourceControl(waitSignal))
  1079. {
  1080. return errorCount + 1;
  1081. }
  1082. // Source control delete operation by source path might not result in deleting metadata files,
  1083. // therefore we will have to delete all those metadata files individually whose associated source files got removed.
  1084. for (auto& entry : relocationContainer)
  1085. {
  1086. if (entry.m_operationStatus == SourceFileRelocationStatus::Succeeded || entry.m_sourceFileIndex == AssetProcessor::SourceFileRelocationInvalidIndex)
  1087. {
  1088. // we do not want to retry if the move operation already succeeded or if it is a source file
  1089. continue;
  1090. }
  1091. AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestDeleteBulkExtended, entry.m_oldAbsolutePath.c_str(), true, callback);
  1092. if (!WaitForSourceControl(waitSignal))
  1093. {
  1094. return errorCount + 1;
  1095. }
  1096. }
  1097. if (!sourceControlEnabled)
  1098. {
  1099. // Do an extra check to make sure the files were deleted since the flags provided aren't very informative
  1100. for(auto& relocationInfo : relocationContainer)
  1101. {
  1102. if(relocationInfo.m_operationStatus == SourceFileRelocationStatus::Succeeded && AZ::IO::SystemFile::Exists(relocationInfo.m_oldAbsolutePath.c_str()))
  1103. {
  1104. relocationInfo.m_operationStatus = SourceFileRelocationStatus::Failed;
  1105. ++errorCount;
  1106. }
  1107. }
  1108. }
  1109. if (removeEmptyFolders)
  1110. {
  1111. RemoveEmptyFolders(relocationContainer);
  1112. }
  1113. return errorCount;
  1114. }
  1115. AZStd::string FileToString(const char* fullPath)
  1116. {
  1117. AZ::IO::FileIOStream fileStream(fullPath, AZ::IO::OpenMode::ModeRead);
  1118. if (!fileStream.IsOpen())
  1119. {
  1120. AZ_Error("SourceFileRelocator", false, "Failed to open file for read %s", fullPath);
  1121. return {};
  1122. }
  1123. AZ::IO::SizeType length = fileStream.GetLength();
  1124. if (length == 0)
  1125. {
  1126. return {};
  1127. }
  1128. AZStd::vector<char> charBuffer;
  1129. charBuffer.resize_no_construct(length);
  1130. fileStream.Read(length, charBuffer.data());
  1131. return AZStd::string(charBuffer.data(), charBuffer.data() + length);
  1132. }
  1133. void StringToFile(const char* fullPath, const AZStd::string& str)
  1134. {
  1135. AZ::IO::FileIOStream fileStream(fullPath, AZ::IO::OpenMode::ModeWrite);
  1136. if(!fileStream.IsOpen())
  1137. {
  1138. AZ_Error("SourceFileRelocator", false, "Failed to open file for write %s", fullPath);
  1139. return;
  1140. }
  1141. fileStream.Write(str.size(), str.data());
  1142. }
  1143. bool ReplaceAll(AZStd::string& str, const AZStd::string& oldStr, const AZStd::string& newStr)
  1144. {
  1145. return AzFramework::StringFunc::Replace(str, oldStr.c_str(), newStr.c_str());
  1146. }
  1147. bool SourceFileRelocator::UpdateFileReferences(const FileUpdateTask& updateTask)
  1148. {
  1149. if(updateTask.m_skipTask)
  1150. {
  1151. return false;
  1152. }
  1153. const char* fullPath = updateTask.m_absPathFileToUpdate.c_str();
  1154. AZStd::string fileAsString = FileToString(fullPath);
  1155. if(fileAsString.empty())
  1156. {
  1157. return false;
  1158. }
  1159. bool didReplace = false;
  1160. for (int i = 0; i < updateTask.m_oldStrings.size(); ++i)
  1161. {
  1162. didReplace |= ReplaceAll(fileAsString, updateTask.m_oldStrings[i], updateTask.m_newStrings[i]);
  1163. }
  1164. if (didReplace)
  1165. {
  1166. StringToFile(fullPath, fileAsString);
  1167. }
  1168. AZ_TracePrintf("SourceFileRelocator", "Updated %s - %s\n", fullPath, didReplace ? "SUCCESS" : "FAIL");
  1169. return didReplace;
  1170. }
  1171. bool SourceFileRelocator::ComputeProductDependencyUpdatePaths(const SourceFileRelocationInfo& relocationInfo, const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& productDependency,
  1172. AZStd::vector<AZStd::string>& oldPaths, AZStd::vector<AZStd::string>& newPaths, AZStd::string& absPathFileToUpdate) const
  1173. {
  1174. AZStd::string sourceName;
  1175. AZStd::string scanPath;
  1176. // Look up the source file and scanfolder of the product (productPK) that references this file
  1177. m_stateData->QuerySourceByProductID(productDependency.m_productPK, [this, &sourceName, &scanPath](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  1178. {
  1179. sourceName = entry.m_sourceName;
  1180. m_stateData->QueryScanFolderByScanFolderID(entry.m_scanFolderPK, [&scanPath](AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& entry)
  1181. {
  1182. scanPath = entry.m_scanFolder;
  1183. return false;
  1184. });
  1185. return false;
  1186. });
  1187. // Find the product this dependency refers to
  1188. auto iterator = relocationInfo.m_products.find(productDependency.m_dependencySubID);
  1189. if(iterator == relocationInfo.m_products.end())
  1190. {
  1191. AZ_Warning("SourceFileRelocator", false, "Can't automatically update references to product, failed to find product with subId %d in product list for file %s", productDependency.m_dependencySubID, relocationInfo.m_oldAbsolutePath.c_str());
  1192. return false;
  1193. }
  1194. // See if the product filename and source file name are the same (not including extension), if so, we can proceed.
  1195. // The names need to be the same because if the source is renamed, we have no way of knowing how that will affect the product name.
  1196. const AZStd::string& productName = iterator->second.m_productName;
  1197. AZStd::string productFileName, sourceFileName;
  1198. AzFramework::StringFunc::Path::GetFileName(productName.c_str(), productFileName);
  1199. AzFramework::StringFunc::Path::GetFileName(relocationInfo.m_oldAbsolutePath.c_str(), sourceFileName);
  1200. if(!AzFramework::StringFunc::Equal(sourceFileName.c_str(), productFileName.c_str(), false))
  1201. {
  1202. AZ_Warning("SourceFileRelocator", false, "Can't automatically update references to product because product name (%s) is different from source name (%s)", productFileName.c_str(), sourceFileName.c_str());
  1203. return false;
  1204. }
  1205. // The names are the same, so just take the source path and replace the extension
  1206. // We're computing the old path as well because the productName includes the platform and gamename which shouldn't be included in hardcoded references
  1207. AZStd::string productExtension;
  1208. AZStd::string oldProductPath = relocationInfo.m_oldRelativePath;
  1209. AZStd::string newProductPath = relocationInfo.m_newRelativePath;
  1210. // Product paths should be all lowercase
  1211. AZStd::to_lower(oldProductPath.begin(), oldProductPath.end());
  1212. AZStd::to_lower(newProductPath.begin(), newProductPath.end());
  1213. AzFramework::StringFunc::Path::GetExtension(productName.c_str(), productExtension);
  1214. AzFramework::StringFunc::Path::ReplaceExtension(oldProductPath, productExtension.c_str());
  1215. AzFramework::StringFunc::Path::ReplaceExtension(newProductPath, productExtension.c_str());
  1216. // This is the full path to the file we need to fix up
  1217. AzFramework::StringFunc::Path::ConstructFull(scanPath.c_str(), sourceName.c_str(), absPathFileToUpdate);
  1218. absPathFileToUpdate = AssetUtilities::NormalizeFilePath(absPathFileToUpdate.c_str()).toUtf8().constData();
  1219. oldPaths.push_back(oldProductPath);
  1220. oldPaths.push_back(relocationInfo.m_oldRelativePath); // If we fail to find a reference to the product, we'll try to find a reference to the source
  1221. newPaths.push_back(newProductPath);
  1222. newPaths.push_back(relocationInfo.m_newRelativePath);
  1223. return true;
  1224. }
  1225. FileUpdateTasks SourceFileRelocator::UpdateReferences(const SourceFileRelocationContainer& relocationContainer, bool useSourceControl) const
  1226. {
  1227. FileUpdateTasks updateTasks;
  1228. AZStd::unordered_set<AZStd::string> filesToEdit;
  1229. AZStd::unordered_map<AZStd::string, AZStd::string> movedFileMap;
  1230. // Make a record of all moved files. We might need to edit some of them and if they've already moved,
  1231. // we need to make sure we edit the files at the new location
  1232. for(const SourceFileRelocationInfo& relocationInfo : relocationContainer)
  1233. {
  1234. if (relocationInfo.m_operationStatus == SourceFileRelocationStatus::Succeeded)
  1235. {
  1236. movedFileMap.insert(AZStd::make_pair(
  1237. AssetUtilities::NormalizeFilePath(relocationInfo.m_oldAbsolutePath.c_str()).toUtf8().constData(),
  1238. AssetUtilities::NormalizeFilePath(relocationInfo.m_newAbsolutePath.c_str()).toUtf8().constData()));
  1239. }
  1240. }
  1241. auto pathFixupFunc = [&movedFileMap](const char* filePath) -> AZStd::string
  1242. {
  1243. auto iterator = movedFileMap.find(AssetUtilities::NormalizeFilePath(filePath).toUtf8().constData());
  1244. if (iterator != movedFileMap.end())
  1245. {
  1246. return iterator->second;
  1247. }
  1248. return filePath;
  1249. };
  1250. // Gather a list of all the files to edit and the edits that need to be made
  1251. for(const SourceFileRelocationInfo& relocationInfo : relocationContainer)
  1252. {
  1253. bool skipTask = relocationInfo.m_operationStatus == SourceFileRelocationStatus::Failed;
  1254. for (const auto& sourceDependency : relocationInfo.m_sourceDependencyEntries)
  1255. {
  1256. AzToolsFramework::AssetDatabase::SourceDatabaseEntry sourceEntry;
  1257. m_stateData->QuerySourceBySourceGuid(
  1258. sourceDependency.m_sourceGuid,
  1259. [&sourceEntry](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
  1260. {
  1261. sourceEntry = entry;
  1262. return false;
  1263. });
  1264. AZStd::string fullPath = m_platformConfig->FindFirstMatchingFile(sourceEntry.m_sourceName.c_str()).toUtf8().constData();
  1265. fullPath = pathFixupFunc(fullPath.c_str());
  1266. updateTasks.emplace(AZStd::vector<AZStd::string>{relocationInfo.m_sourceEntry.m_sourceGuid.ToString<AZStd::string>(), relocationInfo.m_oldRelativePath},
  1267. AZStd::vector<AZStd::string>{relocationInfo.m_newUuid.ToString<AZStd::string>(), relocationInfo.m_newRelativePath}, fullPath, sourceDependency.m_fromAssetId, skipTask);
  1268. filesToEdit.insert(AZStd::move(fullPath));
  1269. }
  1270. for (const auto& productDependency : relocationInfo.m_productDependencyEntries)
  1271. {
  1272. AZStd::string fullPath;
  1273. AZStd::vector<AZStd::string> oldPaths, newPaths;
  1274. if (ComputeProductDependencyUpdatePaths(relocationInfo, productDependency, oldPaths, newPaths, fullPath))
  1275. {
  1276. fullPath = pathFixupFunc(fullPath.c_str());
  1277. oldPaths.push_back(relocationInfo.m_sourceEntry.m_sourceGuid.ToString<AZStd::string>());
  1278. newPaths.push_back(relocationInfo.m_newUuid.ToString<AZStd::string>());
  1279. updateTasks.emplace(oldPaths, newPaths, fullPath, productDependency.m_fromAssetId, skipTask);
  1280. filesToEdit.insert(AZStd::move(fullPath));
  1281. }
  1282. }
  1283. }
  1284. // No work to do? Early out
  1285. if(filesToEdit.empty())
  1286. {
  1287. return {};
  1288. }
  1289. if (useSourceControl)
  1290. {
  1291. // Mark all the files for edit
  1292. AZStd::binary_semaphore waitSignal;
  1293. AzToolsFramework::SourceControlResponseCallbackBulk callback = [&waitSignal](bool /*success*/, const AZStd::vector<AzToolsFramework::SourceControlFileInfo>& /*info*/)
  1294. {
  1295. waitSignal.release();
  1296. };
  1297. AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEditBulk, filesToEdit, true, callback);
  1298. // Wait for the edit command to finish before trying to actually edit the files
  1299. if(!WaitForSourceControl(waitSignal))
  1300. {
  1301. return updateTasks;
  1302. }
  1303. }
  1304. // Update all the files
  1305. for (FileUpdateTask& updateTask : updateTasks)
  1306. {
  1307. updateTask.m_succeeded = UpdateFileReferences(updateTask);
  1308. }
  1309. return updateTasks;
  1310. }
  1311. } // namespace AssetProcessor