123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "EditorDefs.h"
- #include "PathUtil.h"
- #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
- #include <AzCore/Utils/Utils.h>
- #include <AzToolsFramework/API/EditorAssetSystemAPI.h> // for ebus events
- #include <AzCore/std/string/conversions.h>
- #include <AzFramework/IO/LocalFileIO.h>
- #include <QRegularExpression>
- namespace Path
- {
- //////////////////////////////////////////////////////////////////////////
- void SplitPath(const QString& rstrFullPathFilename, QString& rstrDriveLetter, QString& rstrDirectory, QString& rstrFilename, QString& rstrExtension)
- {
- AZStd::string strFullPathString(rstrFullPathFilename.toUtf8().data());
- AZStd::string strDriveLetter;
- AZStd::string strDirectory;
- AZStd::string strFilename;
- AZStd::string strExtension;
- char* szPath((char*)strFullPathString.c_str());
- char* pchLastPosition(szPath);
- char* pchCurrentPosition(szPath);
- char* pchAuxPosition(szPath);
- // Directory named filenames containing ":" are invalid, so we can assume if there is a :
- // it will be the drive name.
- pchCurrentPosition = strchr(pchLastPosition, ':');
- if (pchCurrentPosition == nullptr)
- {
- rstrDriveLetter = "";
- }
- else
- {
- strDriveLetter.assign(pchLastPosition, pchCurrentPosition + 1);
- pchLastPosition = pchCurrentPosition + 1;
- }
- pchCurrentPosition = strrchr(pchLastPosition, '\\');
- pchAuxPosition = strrchr(pchLastPosition, '/');
- if ((pchCurrentPosition == nullptr) && (pchAuxPosition == nullptr))
- {
- rstrDirectory = "";
- }
- else
- {
- // Since NULL is < valid pointer, so this will work.
- if (pchAuxPosition > pchCurrentPosition)
- {
- pchCurrentPosition = pchAuxPosition;
- }
- strDirectory.assign(pchLastPosition, pchCurrentPosition + 1);
- pchLastPosition = pchCurrentPosition + 1;
- }
- pchCurrentPosition = strrchr(pchLastPosition, '.');
- if (pchCurrentPosition == nullptr)
- {
- rstrExtension = "";
- strFilename.assign(pchLastPosition);
- }
- else
- {
- strExtension.assign(pchCurrentPosition);
- strFilename.assign(pchLastPosition, pchCurrentPosition);
- }
- rstrDriveLetter = strDriveLetter.c_str();
- rstrDirectory = strDirectory.c_str();
- rstrFilename = strFilename.c_str();
- rstrExtension = strExtension.c_str();
- }
- //////////////////////////////////////////////////////////////////////////
- void GetDirectoryQueue(const QString& rstrSourceDirectory, QStringList& rcstrDirectoryTree)
- {
- AZStd::string strCurrentDirectoryName;
- AZStd::string strSourceDirectory(rstrSourceDirectory.toUtf8().data());
- const char* szSourceDirectory(strSourceDirectory.c_str());
- const char* pchCurrentPosition(szSourceDirectory);
- const char* pchLastPosition(szSourceDirectory);
- rcstrDirectoryTree.clear();
- if (strSourceDirectory.empty())
- {
- return;
- }
- // It removes as many slashes the path has in its start...
- // MAYBE and just maybe we should consider paths starting with
- // more than 2 slashes invalid paths...
- while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
- {
- ++pchLastPosition;
- ++pchCurrentPosition;
- }
- do
- {
- pchCurrentPosition = strpbrk(pchLastPosition, "\\/");
- if (pchCurrentPosition == nullptr)
- {
- break;
- }
- strCurrentDirectoryName.assign(pchLastPosition, pchCurrentPosition);
- pchLastPosition = pchCurrentPosition + 1;
- // Again, here we are skipping as many consecutive slashes.
- while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
- {
- ++pchLastPosition;
- }
- rcstrDirectoryTree.push_back(strCurrentDirectoryName.c_str());
- } while (true);
- }
- //////////////////////////////////////////////////////////////////////////
- void ConvertSlashToBackSlash(QString& rstrStringToConvert)
- {
- rstrStringToConvert.replace('/', '\\');
- rstrStringToConvert = CaselessPaths(rstrStringToConvert);
- }
- //////////////////////////////////////////////////////////////////////////
- void ConvertBackSlashToSlash(QString& rstrStringToConvert)
- {
- rstrStringToConvert.replace('\\', '/');
- rstrStringToConvert = CaselessPaths(rstrStringToConvert);
- }
- //////////////////////////////////////////////////////////////////////////
- void SurroundWithQuotes(QString& rstrSurroundString)
- {
- QString strSurroundString(rstrSurroundString);
- if (!strSurroundString.isEmpty())
- {
- if (strSurroundString[0] != '\"')
- {
- strSurroundString.insert(0, "\"");
- }
- if (strSurroundString[strSurroundString.size() - 1] != '\"')
- {
- strSurroundString.insert(strSurroundString.size(), "\"");
- }
- }
- else
- {
- strSurroundString.insert(0, "\"");
- strSurroundString.insert(strSurroundString.size(), "\"");
- }
- rstrSurroundString = strSurroundString;
- }
- //////////////////////////////////////////////////////////////////////////
- QString GetExecutableFullPath()
- {
- return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
- }
- //////////////////////////////////////////////////////////////////////////
- QString GetWindowsTempDirectory()
- {
- return QDir::tempPath();
- }
- //////////////////////////////////////////////////////////////////////////
- QString GetEngineRootPath()
- {
- const AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath();
- return QString::fromUtf8(engineRoot.c_str(), static_cast<int>(engineRoot.size()));
- }
- //////////////////////////////////////////////////////////////////////////
- QString& ReplaceFilename(const QString& strFilepath, const QString& strFilename, QString& strOutputFilename, bool bCallCaselessPath)
- {
- QString strDriveLetter;
- QString strDirectory;
- QString strOriginalFilename;
- QString strExtension;
- SplitPath(strFilepath, strDriveLetter, strDirectory, strOriginalFilename, strExtension);
- strOutputFilename = strDriveLetter;
- strOutputFilename += strDirectory;
- strOutputFilename += strFilename;
- strOutputFilename += strExtension;
- if (bCallCaselessPath)
- {
- strOutputFilename = CaselessPaths(strOutputFilename);
- }
- return strOutputFilename;
- }
- bool IsFolder(const char* pPath)
- {
- return AZ::IO::FileIOBase::GetInstance()->IsDirectory(pPath);
- }
- //////////////////////////////////////////////////////////////////////////
- QString GetUserSandboxFolder()
- {
- return QString::fromUtf8("@user@/Sandbox/");
- }
- //////////////////////////////////////////////////////////////////////////
- QString GetResolvedUserSandboxFolder()
- {
- AZ::IO::FixedMaxPath userSandboxFolderPath;
- gEnv->pFileIO->ResolvePath(userSandboxFolderPath, GetUserSandboxFolder().toUtf8().constData());
- return QString::fromUtf8(userSandboxFolderPath.c_str(), static_cast<int>(userSandboxFolderPath.Native().size()));
- }
- // internal function, you should use GetEditingGameDataFolder instead.
- AZStd::string GetGameAssetsFolder()
- {
- AZ::IO::Path projectPath;
- if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
- {
- settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
- }
- return projectPath.Native();
- }
- /// Get the data folder
- AZStd::string GetEditingGameDataFolder()
- {
- // Define here the mod name
- static AZ::IO::FixedMaxPathString s_currentModName;
- if (s_currentModName.empty())
- {
- return GetGameAssetsFolder();
- }
- AZStd::string str(GetGameAssetsFolder());
- str += "Mods\\";
- str += s_currentModName.c_str();
- return str;
- }
- AZStd::string MakeModPathFromGamePath(const char* relGamePath)
- {
- return GetEditingGameDataFolder() + "\\" + relGamePath;
- }
- QString FullPathToLevelPath(const QString& path)
- {
- if (path.isEmpty())
- {
- return "";
- }
- QString relGamePath;
- if (!QFileInfo(path).isRelative())
- {
- relGamePath = GetRelativePath(path);
- }
- else
- {
- relGamePath = path;
- }
- QString levelpath = GetIEditor()->GetLevelFolder();
- QString str = levelpath;
- str.replace('/', '\\');
- levelpath = CaselessPaths(str);
- // Create relative path
- QString relLevelPath = QDir(levelpath).relativeFilePath(relGamePath);
- if (relLevelPath.isEmpty())
- {
- assert(0);
- return path;
- }
- relLevelPath.remove(QRegularExpression(QStringLiteral(R"(^[\\/.]*)")));
- return relLevelPath;
- }
- QString Make(const QString& path, const QString& file)
- {
- if (gEnv->pCryPak->IsAbsPath(file.toUtf8().data()))
- {
- return file;
- }
- return CaselessPaths(AddPathSlash(path) + file);
- }
- QString GetRelativePath(const QString& fullPath, bool bRelativeToGameFolder /*= false*/)
- {
- if (fullPath.isEmpty())
- {
- return "";
- }
- bool relPathFound = false;
- AZStd::string relativePath;
- AZStd::string fullAssetPath(fullPath.toUtf8().data());
- AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
- relPathFound,
- &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath,
- fullAssetPath,
- relativePath);
- if (relPathFound)
- {
- // do not normalize this path, it will already be an appropriate asset ID.
- return CaselessPaths(relativePath.c_str());
- }
- AZ::IO::FixedMaxPath rootPath = bRelativeToGameFolder ? AZ::Utils::GetProjectPath() : AZ::Utils::GetEnginePath();
- AZ::IO::FixedMaxPath resolvedFullPath;
- AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedFullPath, fullPath.toUtf8().constData());
- // Create relative path
- return CaselessPaths(resolvedFullPath.LexicallyProximate(rootPath).MakePreferred().c_str());
- }
- QString GamePathToFullPath(const QString& path)
- {
- using namespace AzToolsFramework;
- AZ_Warning("GamePathToFullPath", path.size() <= AZ::IO::MaxPathLength, "Path exceeds maximum path length of %zu", AZ::IO::MaxPathLength);
- if (path.size() <= AZ::IO::MaxPathLength)
- {
- // first, adjust the file name for mods:
- bool fullPathFound = false;
- AZ::IO::Path assetFullPath;
- AZ::IO::Path adjustedFilePath = path.toUtf8().constData();
- AssetSystemRequestBus::BroadcastResult(fullPathFound, &AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath,
- adjustedFilePath.Native(), assetFullPath.Native());
- if (fullPathFound)
- {
- //if the bus message succeeds than normalize
- return assetFullPath.LexicallyNormal().c_str();
- }
- // if the bus message didn't succeed, check if he path exist as a resolved path
- else
- {
- // 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.
- // If the path passed in exists already, then return the resolved filepath
- if (AZ::IO::FileIOBase::GetDirectInstance()->Exists(adjustedFilePath.c_str()))
- {
- AZ::IO::FixedMaxPath resolvedPath;
- AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedPath, adjustedFilePath);
- return QString::fromUtf8(resolvedPath.c_str(), static_cast<int>(resolvedPath.Native().size()));
- }
- return path;
- }
- }
- else
- {
- return QString{};
- }
- }
- QString ToUnixPath(const QString& strPath, bool bCallCaselessPath)
- {
- QString str = strPath;
- str.replace('\\', '/');
- return bCallCaselessPath ? CaselessPaths(str) : str;
- }
- QString RemoveBackslash(QString path)
- {
- if (path.isEmpty())
- {
- return path;
- }
- int iLenMinus1 = path.length() - 1;
- QChar cLastChar = path[iLenMinus1];
- if (cLastChar == '\\' || cLastChar == '/')
- {
- return CaselessPaths(path.mid(0, iLenMinus1));
- }
- return CaselessPaths(path);
- }
- QString SubDirectoryCaseInsensitive(const QString& path, const QStringList& parts)
- {
- if (parts.isEmpty())
- {
- return path;
- }
- QStringList modifiedParts = parts;
- auto currentPart = modifiedParts.takeFirst();
- // case insensitive iterator
- QDirIterator it(path);
- while (it.hasNext())
- {
- it.next();
- // the current part already exists, use it, case doesn't matter
- auto actualName = it.fileName();
- if (QString::compare(actualName, currentPart, Qt::CaseInsensitive) == 0)
- {
- return SubDirectoryCaseInsensitive(QDir(path).absoluteFilePath(actualName), modifiedParts);
- }
- }
- // the current path doesn't exist yet, so just create the complete path in one rush
- return QDir(path).absoluteFilePath(parts.join('/'));
- }
- }
|