PathUtil.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "PathUtil.h"
  10. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  11. #include <AzCore/Utils/Utils.h>
  12. #include <AzToolsFramework/API/EditorAssetSystemAPI.h> // for ebus events
  13. #include <AzCore/std/string/conversions.h>
  14. #include <AzFramework/IO/LocalFileIO.h>
  15. #include <QRegularExpression>
  16. namespace Path
  17. {
  18. //////////////////////////////////////////////////////////////////////////
  19. void SplitPath(const QString& rstrFullPathFilename, QString& rstrDriveLetter, QString& rstrDirectory, QString& rstrFilename, QString& rstrExtension)
  20. {
  21. AZStd::string strFullPathString(rstrFullPathFilename.toUtf8().data());
  22. AZStd::string strDriveLetter;
  23. AZStd::string strDirectory;
  24. AZStd::string strFilename;
  25. AZStd::string strExtension;
  26. char* szPath((char*)strFullPathString.c_str());
  27. char* pchLastPosition(szPath);
  28. char* pchCurrentPosition(szPath);
  29. char* pchAuxPosition(szPath);
  30. // Directory named filenames containing ":" are invalid, so we can assume if there is a :
  31. // it will be the drive name.
  32. pchCurrentPosition = strchr(pchLastPosition, ':');
  33. if (pchCurrentPosition == nullptr)
  34. {
  35. rstrDriveLetter = "";
  36. }
  37. else
  38. {
  39. strDriveLetter.assign(pchLastPosition, pchCurrentPosition + 1);
  40. pchLastPosition = pchCurrentPosition + 1;
  41. }
  42. pchCurrentPosition = strrchr(pchLastPosition, '\\');
  43. pchAuxPosition = strrchr(pchLastPosition, '/');
  44. if ((pchCurrentPosition == nullptr) && (pchAuxPosition == nullptr))
  45. {
  46. rstrDirectory = "";
  47. }
  48. else
  49. {
  50. // Since NULL is < valid pointer, so this will work.
  51. if (pchAuxPosition > pchCurrentPosition)
  52. {
  53. pchCurrentPosition = pchAuxPosition;
  54. }
  55. strDirectory.assign(pchLastPosition, pchCurrentPosition + 1);
  56. pchLastPosition = pchCurrentPosition + 1;
  57. }
  58. pchCurrentPosition = strrchr(pchLastPosition, '.');
  59. if (pchCurrentPosition == nullptr)
  60. {
  61. rstrExtension = "";
  62. strFilename.assign(pchLastPosition);
  63. }
  64. else
  65. {
  66. strExtension.assign(pchCurrentPosition);
  67. strFilename.assign(pchLastPosition, pchCurrentPosition);
  68. }
  69. rstrDriveLetter = strDriveLetter.c_str();
  70. rstrDirectory = strDirectory.c_str();
  71. rstrFilename = strFilename.c_str();
  72. rstrExtension = strExtension.c_str();
  73. }
  74. //////////////////////////////////////////////////////////////////////////
  75. void GetDirectoryQueue(const QString& rstrSourceDirectory, QStringList& rcstrDirectoryTree)
  76. {
  77. AZStd::string strCurrentDirectoryName;
  78. AZStd::string strSourceDirectory(rstrSourceDirectory.toUtf8().data());
  79. const char* szSourceDirectory(strSourceDirectory.c_str());
  80. const char* pchCurrentPosition(szSourceDirectory);
  81. const char* pchLastPosition(szSourceDirectory);
  82. rcstrDirectoryTree.clear();
  83. if (strSourceDirectory.empty())
  84. {
  85. return;
  86. }
  87. // It removes as many slashes the path has in its start...
  88. // MAYBE and just maybe we should consider paths starting with
  89. // more than 2 slashes invalid paths...
  90. while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
  91. {
  92. ++pchLastPosition;
  93. ++pchCurrentPosition;
  94. }
  95. do
  96. {
  97. pchCurrentPosition = strpbrk(pchLastPosition, "\\/");
  98. if (pchCurrentPosition == nullptr)
  99. {
  100. break;
  101. }
  102. strCurrentDirectoryName.assign(pchLastPosition, pchCurrentPosition);
  103. pchLastPosition = pchCurrentPosition + 1;
  104. // Again, here we are skipping as many consecutive slashes.
  105. while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
  106. {
  107. ++pchLastPosition;
  108. }
  109. rcstrDirectoryTree.push_back(strCurrentDirectoryName.c_str());
  110. } while (true);
  111. }
  112. //////////////////////////////////////////////////////////////////////////
  113. void ConvertSlashToBackSlash(QString& rstrStringToConvert)
  114. {
  115. rstrStringToConvert.replace('/', '\\');
  116. rstrStringToConvert = CaselessPaths(rstrStringToConvert);
  117. }
  118. //////////////////////////////////////////////////////////////////////////
  119. void ConvertBackSlashToSlash(QString& rstrStringToConvert)
  120. {
  121. rstrStringToConvert.replace('\\', '/');
  122. rstrStringToConvert = CaselessPaths(rstrStringToConvert);
  123. }
  124. //////////////////////////////////////////////////////////////////////////
  125. void SurroundWithQuotes(QString& rstrSurroundString)
  126. {
  127. QString strSurroundString(rstrSurroundString);
  128. if (!strSurroundString.isEmpty())
  129. {
  130. if (strSurroundString[0] != '\"')
  131. {
  132. strSurroundString.insert(0, "\"");
  133. }
  134. if (strSurroundString[strSurroundString.size() - 1] != '\"')
  135. {
  136. strSurroundString.insert(strSurroundString.size(), "\"");
  137. }
  138. }
  139. else
  140. {
  141. strSurroundString.insert(0, "\"");
  142. strSurroundString.insert(strSurroundString.size(), "\"");
  143. }
  144. rstrSurroundString = strSurroundString;
  145. }
  146. //////////////////////////////////////////////////////////////////////////
  147. QString GetExecutableFullPath()
  148. {
  149. return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
  150. }
  151. //////////////////////////////////////////////////////////////////////////
  152. QString GetWindowsTempDirectory()
  153. {
  154. return QDir::tempPath();
  155. }
  156. //////////////////////////////////////////////////////////////////////////
  157. QString GetEngineRootPath()
  158. {
  159. const AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath();
  160. return QString::fromUtf8(engineRoot.c_str(), static_cast<int>(engineRoot.size()));
  161. }
  162. //////////////////////////////////////////////////////////////////////////
  163. QString& ReplaceFilename(const QString& strFilepath, const QString& strFilename, QString& strOutputFilename, bool bCallCaselessPath)
  164. {
  165. QString strDriveLetter;
  166. QString strDirectory;
  167. QString strOriginalFilename;
  168. QString strExtension;
  169. SplitPath(strFilepath, strDriveLetter, strDirectory, strOriginalFilename, strExtension);
  170. strOutputFilename = strDriveLetter;
  171. strOutputFilename += strDirectory;
  172. strOutputFilename += strFilename;
  173. strOutputFilename += strExtension;
  174. if (bCallCaselessPath)
  175. {
  176. strOutputFilename = CaselessPaths(strOutputFilename);
  177. }
  178. return strOutputFilename;
  179. }
  180. bool IsFolder(const char* pPath)
  181. {
  182. return AZ::IO::FileIOBase::GetInstance()->IsDirectory(pPath);
  183. }
  184. //////////////////////////////////////////////////////////////////////////
  185. QString GetUserSandboxFolder()
  186. {
  187. return QString::fromUtf8("@user@/Sandbox/");
  188. }
  189. //////////////////////////////////////////////////////////////////////////
  190. QString GetResolvedUserSandboxFolder()
  191. {
  192. AZ::IO::FixedMaxPath userSandboxFolderPath;
  193. gEnv->pFileIO->ResolvePath(userSandboxFolderPath, GetUserSandboxFolder().toUtf8().constData());
  194. return QString::fromUtf8(userSandboxFolderPath.c_str(), static_cast<int>(userSandboxFolderPath.Native().size()));
  195. }
  196. // internal function, you should use GetEditingGameDataFolder instead.
  197. AZStd::string GetGameAssetsFolder()
  198. {
  199. AZ::IO::Path projectPath;
  200. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  201. {
  202. settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  203. }
  204. return projectPath.Native();
  205. }
  206. /// Get the data folder
  207. AZStd::string GetEditingGameDataFolder()
  208. {
  209. // Define here the mod name
  210. static AZ::IO::FixedMaxPathString s_currentModName;
  211. if (s_currentModName.empty())
  212. {
  213. return GetGameAssetsFolder();
  214. }
  215. AZStd::string str(GetGameAssetsFolder());
  216. str += "Mods\\";
  217. str += s_currentModName.c_str();
  218. return str;
  219. }
  220. AZStd::string MakeModPathFromGamePath(const char* relGamePath)
  221. {
  222. return GetEditingGameDataFolder() + "\\" + relGamePath;
  223. }
  224. QString FullPathToLevelPath(const QString& path)
  225. {
  226. if (path.isEmpty())
  227. {
  228. return "";
  229. }
  230. QString relGamePath;
  231. if (!QFileInfo(path).isRelative())
  232. {
  233. relGamePath = GetRelativePath(path);
  234. }
  235. else
  236. {
  237. relGamePath = path;
  238. }
  239. QString levelpath = GetIEditor()->GetLevelFolder();
  240. QString str = levelpath;
  241. str.replace('/', '\\');
  242. levelpath = CaselessPaths(str);
  243. // Create relative path
  244. QString relLevelPath = QDir(levelpath).relativeFilePath(relGamePath);
  245. if (relLevelPath.isEmpty())
  246. {
  247. assert(0);
  248. return path;
  249. }
  250. relLevelPath.remove(QRegularExpression(QStringLiteral(R"(^[\\/.]*)")));
  251. return relLevelPath;
  252. }
  253. QString Make(const QString& path, const QString& file)
  254. {
  255. if (gEnv->pCryPak->IsAbsPath(file.toUtf8().data()))
  256. {
  257. return file;
  258. }
  259. return CaselessPaths(AddPathSlash(path) + file);
  260. }
  261. QString GetRelativePath(const QString& fullPath, bool bRelativeToGameFolder /*= false*/)
  262. {
  263. if (fullPath.isEmpty())
  264. {
  265. return "";
  266. }
  267. bool relPathFound = false;
  268. AZStd::string relativePath;
  269. AZStd::string fullAssetPath(fullPath.toUtf8().data());
  270. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
  271. relPathFound,
  272. &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath,
  273. fullAssetPath,
  274. relativePath);
  275. if (relPathFound)
  276. {
  277. // do not normalize this path, it will already be an appropriate asset ID.
  278. return CaselessPaths(relativePath.c_str());
  279. }
  280. AZ::IO::FixedMaxPath rootPath = bRelativeToGameFolder ? AZ::Utils::GetProjectPath() : AZ::Utils::GetEnginePath();
  281. AZ::IO::FixedMaxPath resolvedFullPath;
  282. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedFullPath, fullPath.toUtf8().constData());
  283. // Create relative path
  284. return CaselessPaths(resolvedFullPath.LexicallyProximate(rootPath).MakePreferred().c_str());
  285. }
  286. QString GamePathToFullPath(const QString& path)
  287. {
  288. using namespace AzToolsFramework;
  289. AZ_Warning("GamePathToFullPath", path.size() <= AZ::IO::MaxPathLength, "Path exceeds maximum path length of %zu", AZ::IO::MaxPathLength);
  290. if (path.size() <= AZ::IO::MaxPathLength)
  291. {
  292. // first, adjust the file name for mods:
  293. bool fullPathFound = false;
  294. AZ::IO::Path assetFullPath;
  295. AZ::IO::Path adjustedFilePath = path.toUtf8().constData();
  296. AssetSystemRequestBus::BroadcastResult(fullPathFound, &AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath,
  297. adjustedFilePath.Native(), assetFullPath.Native());
  298. if (fullPathFound)
  299. {
  300. //if the bus message succeeds than normalize
  301. return assetFullPath.LexicallyNormal().c_str();
  302. }
  303. // if the bus message didn't succeed, check if he path exist as a resolved path
  304. else
  305. {
  306. // Not all systems have been converted to use local paths. Some editor files save XML files directly, and a full or correctly aliased path is already passed in.
  307. // If the path passed in exists already, then return the resolved filepath
  308. if (AZ::IO::FileIOBase::GetDirectInstance()->Exists(adjustedFilePath.c_str()))
  309. {
  310. AZ::IO::FixedMaxPath resolvedPath;
  311. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedPath, adjustedFilePath);
  312. return QString::fromUtf8(resolvedPath.c_str(), static_cast<int>(resolvedPath.Native().size()));
  313. }
  314. return path;
  315. }
  316. }
  317. else
  318. {
  319. return QString{};
  320. }
  321. }
  322. QString ToUnixPath(const QString& strPath, bool bCallCaselessPath)
  323. {
  324. QString str = strPath;
  325. str.replace('\\', '/');
  326. return bCallCaselessPath ? CaselessPaths(str) : str;
  327. }
  328. QString RemoveBackslash(QString path)
  329. {
  330. if (path.isEmpty())
  331. {
  332. return path;
  333. }
  334. int iLenMinus1 = path.length() - 1;
  335. QChar cLastChar = path[iLenMinus1];
  336. if (cLastChar == '\\' || cLastChar == '/')
  337. {
  338. return CaselessPaths(path.mid(0, iLenMinus1));
  339. }
  340. return CaselessPaths(path);
  341. }
  342. QString SubDirectoryCaseInsensitive(const QString& path, const QStringList& parts)
  343. {
  344. if (parts.isEmpty())
  345. {
  346. return path;
  347. }
  348. QStringList modifiedParts = parts;
  349. auto currentPart = modifiedParts.takeFirst();
  350. // case insensitive iterator
  351. QDirIterator it(path);
  352. while (it.hasNext())
  353. {
  354. it.next();
  355. // the current part already exists, use it, case doesn't matter
  356. auto actualName = it.fileName();
  357. if (QString::compare(actualName, currentPart, Qt::CaseInsensitive) == 0)
  358. {
  359. return SubDirectoryCaseInsensitive(QDir(path).absoluteFilePath(actualName), modifiedParts);
  360. }
  361. }
  362. // the current path doesn't exist yet, so just create the complete path in one rush
  363. return QDir(path).absoluteFilePath(parts.join('/'));
  364. }
  365. }