// Copyright Epic Games, Inc. All Rights Reserved. #include "ContentBrowserDataSource.h" #include "Features/IModularFeatures.h" #include "Interfaces/IPluginManager.h" #include "ContentBrowserDataSubsystem.h" #include "ContentBrowserItemData.h" #include "IContentBrowserDataModule.h" #include "Settings/ContentBrowserSettings.h" #include "Misc/PackageName.h" struct FVirtualPathConverterBase { FString ClassesPrefix; FString AllFolderPrefix; TArray MountsToIgnore; TMap VirtualToInternal; FVirtualPathConverterBase() { AllFolderPrefix = TEXT("/All"); ClassesPrefix = TEXT("Classes_"); MountsToIgnore = { TEXT("Game"), TEXT("Engine"), TEXT("Classes_Game"), TEXT("Classes_Engine") }; } void ResetCache() { VirtualToInternal.Reset(); } bool EndConvertingToVirtualPath(const FStringView InPath, FName& OutPath) { const TCHAR* InPathData = InPath.GetData(); TStringBuilder OutPathStr; const UContentBrowserSettings* ContentBrowserSettings = GetDefault(); if (ContentBrowserSettings->ShowAllFolder) { OutPathStr.Append(AllFolderPrefix); } if (ContentBrowserSettings->OrganizeFolders && InPath.Len() > 0) { FStringView MountPointStringView; int32 SecondForwardSlash = INDEX_NONE; if (FStringView(InPath.GetData() + 1, InPath.Len() - 1).FindChar(TEXT('/'), SecondForwardSlash)) { MountPointStringView = FStringView(InPathData + 1, SecondForwardSlash); } else { MountPointStringView = FStringView(InPathData + 1, InPath.Len() - 1); } const bool bHasClassesPrefix = MountPointStringView.StartsWith(ClassesPrefix); if (bHasClassesPrefix) { MountPointStringView.RightInline(MountPointStringView.Len() - ClassesPrefix.Len()); } if (!MountsToIgnore.Contains(MountPointStringView)) { FString MountPointName = FString(MountPointStringView.Len(), MountPointStringView.GetData()); TSharedPtr Plugin = IPluginManager::Get().FindPlugin(MountPointName); if (Plugin.IsValid()) { if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Engine) { OutPathStr.Append(TEXT("/Engine Plugins")); } else { OutPathStr.Append(TEXT("/Plugins")); } const FPluginDescriptor& PluginDescriptor = Plugin->GetDescriptor(); if (!PluginDescriptor.EditorCustomVirtualPath.IsEmpty()) { int32 NumChars = PluginDescriptor.EditorCustomVirtualPath.Len(); if (PluginDescriptor.EditorCustomVirtualPath.EndsWith(TEXT("/"))) { --NumChars; } if (NumChars > 0) { if (!PluginDescriptor.EditorCustomVirtualPath.StartsWith(TEXT("/"))) { OutPathStr.Append(TEXT("/")); } OutPathStr.Append(*PluginDescriptor.EditorCustomVirtualPath, NumChars); } } } else { OutPathStr.Append(TEXT("/Other")); } } } OutPathStr.Append(InPath.GetData(), InPath.Len()); OutPath = *OutPathStr; VirtualToInternal.Add(OutPath, FName(InPath)); return true; } bool EndConvertingToVirtualPath(const FName InPath, FName& OutPath) { TStringBuilder PathStr; InPath.ToString(PathStr); return EndConvertingToVirtualPath(FStringView(PathStr.GetData(), PathStr.Len()), OutPath); } bool BeginConvertingFromVirtualPath(const FStringView InPath, FName& OutPath) { if (const FName* Found = VirtualToInternal.Find(FName(InPath))) { OutPath = *Found; return true; } else { const UContentBrowserSettings* ContentBrowserSettings = GetDefault(); if (ContentBrowserSettings->ShowAllFolder) { if (InPath.StartsWith(AllFolderPrefix)) { return false; } } else if (ContentBrowserSettings->OrganizeFolders) { // Confirm it is a valid mount point FString MountPointName; const TCHAR* InPathData = InPath.GetData(); if (const TCHAR* FoundChar = FCString::Strchr(InPathData + 1, TEXT('/'))) { MountPointName = FString((int32)(FoundChar - InPathData), InPathData); } else { MountPointName = FString(InPath.Len(), InPathData); } if (MountPointName.StartsWith(TEXT("/Classes_"))) { MountPointName.RemoveAt(1, MountPointName.Len() - 8, false); } MountPointName.Append(TEXT("/")); if (!FPackageName::MountPointExists(MountPointName)) { return false; } } } OutPath = FName(InPath); return true; } bool BeginConvertingFromVirtualPath(const FName InPath, FName& OutPath) { TStringBuilder PathStr; InPath.ToString(PathStr); return BeginConvertingFromVirtualPath(FStringView(PathStr.GetData(), PathStr.Len()), OutPath); } }; FVirtualPathConverterBase VirtualPathConverterBase; FName UContentBrowserDataSource::GetModularFeatureTypeName() { static const FName ModularFeatureTypeName = "ContentBrowserDataSource"; return ModularFeatureTypeName; } void UContentBrowserDataSource::RegisterDataSource() { IModularFeatures::Get().RegisterModularFeature(GetModularFeatureTypeName(), this); } void UContentBrowserDataSource::UnregisterDataSource() { IModularFeatures::Get().UnregisterModularFeature(GetModularFeatureTypeName(), this); } void UContentBrowserDataSource::Initialize(const FName InMountRoot, const bool InAutoRegister) { MountRoot = InMountRoot; // Explode the mount root into its hierarchy (eg, "/One/Two" becomes ["/", "/One", "/One/Two"]) { static const FName Slash = "/"; TCHAR MountRootStr[FName::StringBufferSize]; const int32 MountRootLen = MountRoot.ToString(MountRootStr); checkf(MountRootLen > 0 && MountRootStr[0] == TEXT('/'), TEXT("Mount roots must not be empty and must start with a slash!")); MountRootHierarchy.Add(Slash); if (MountRootLen > 1) { for (TCHAR* PathEndPtr = FCString::Strchr(MountRootStr + 1, TEXT('/')); PathEndPtr; PathEndPtr = FCString::Strchr(PathEndPtr + 1, TEXT('/')) ) { *PathEndPtr = 0; MountRootHierarchy.Add(MountRootStr); *PathEndPtr = TEXT('/'); } MountRootHierarchy.Add(MountRoot); } } bIsInitialized = true; if (InAutoRegister) { RegisterDataSource(); } } void UContentBrowserDataSource::Shutdown() { UnregisterDataSource(); bIsInitialized = false; } void UContentBrowserDataSource::BeginDestroy() { Shutdown(); Super::BeginDestroy(); } void UContentBrowserDataSource::SetDataSink(IContentBrowserItemDataSink* InDataSink) { DataSink = InDataSink; } bool UContentBrowserDataSource::IsInitialized() const { return bIsInitialized; } void UContentBrowserDataSource::Tick(const float InDeltaTime) { } FName UContentBrowserDataSource::GetVirtualMountRoot() const { return MountRoot; } TArrayView UContentBrowserDataSource::GetVirtualMountRootHierarchy() const { return MakeArrayView(MountRootHierarchy); } bool UContentBrowserDataSource::IsVirtualPathUnderMountRoot(const FName InPath) const { static const FName RootPath = "/"; if (MountRoot == RootPath) { // If we're mounted at the virtual root then everything is under us return true; } FName AdjustedPath; VirtualPathConverterBase.BeginConvertingFromVirtualPath(InPath, AdjustedPath); TCHAR PathStr[FName::StringBufferSize]; const int32 PathLen = AdjustedPath.ToString(PathStr); TCHAR MountRootStr[FName::StringBufferSize]; int32 MountRootLen = MountRoot.ToString(MountRootStr); // If the path length is shorter than the mount root, then the path cannot be under the mount root if (PathLen < MountRootLen) { return false; } if (PathLen == MountRootLen) { // "Equals" comparison on the local string buffers return FCString::Strnicmp(PathStr, MountRootStr, MountRootLen) == 0; } // Ensure the mount root ends with a / to avoid matching "/Root" against "/Root2/MyFile" if (MountRootLen > 0 && MountRootStr[MountRootLen - 1] != TEXT('/')) { MountRootStr[MountRootLen++] = TEXT('/'); MountRootStr[MountRootLen] = 0; } // "StartsWith" comparison on the local string buffers return FCString::Strnicmp(PathStr, MountRootStr, MountRootLen) == 0; } bool UContentBrowserDataSource::TryConvertVirtualPathToInternal(const FName InPath, FName& OutInternalPath) { static const FName RootPath = "/"; // Special case "/" cannot be converted or remapped if (InPath == RootPath) { OutInternalPath = InPath; return true; } if (MountRoot == RootPath) { // If we're mounted at the virtual root then no re-mapping needs to happen return VirtualPathConverterBase.BeginConvertingFromVirtualPath(InPath, OutInternalPath); } FName AdjustedPath; VirtualPathConverterBase.BeginConvertingFromVirtualPath(InPath, AdjustedPath); TCHAR PathStr[FName::StringBufferSize]; const int32 PathLen = AdjustedPath.ToString(PathStr); TCHAR MountRootStr[FName::StringBufferSize]; const int32 MountRootLen = MountRoot.ToString(MountRootStr); // If the path length is shorter than the mount root, then the path cannot be under the mount root if (PathLen < MountRootLen) { return false; } // "StartsWith" comparison on the local string buffers // This doesn't add the slash to mount root as IsVirtualPathUnderMountRoot // does because we will check that the remaining path starts with a slash if (FCString::Strnicmp(PathStr, MountRootStr, MountRootLen) != 0) { return false; } // If the mount root ended in a slash then we need to consider this as part of the internal path, // as we wouldn't have allowed the duplicate slash in TryConvertInternalPathToVirtual int32 InternalPathStartIndex = MountRootLen; if (InternalPathStartIndex > 0 && MountRootStr[InternalPathStartIndex - 1] == TEXT('/')) { --InternalPathStartIndex; } const TCHAR* InternalPathStr = PathStr + InternalPathStartIndex; if (InternalPathStr[0] == 0) { // If the given path was the mount root itself, then we just return a slash as the internal path static const FName Slash = "/"; OutInternalPath = Slash; return true; } if (InternalPathStr[0] != TEXT('/')) { return false; } OutInternalPath = InternalPathStr; return true; } bool UContentBrowserDataSource::TryConvertInternalPathToVirtual(const FName InInternalPath, FName& OutPath) { static const FName RootPath = "/"; // Special case "/" cannot be converted or remapped if (InInternalPath == RootPath) { OutPath = InInternalPath; return true; } if (MountRoot == RootPath) { VirtualPathConverterBase.EndConvertingToVirtualPath(InInternalPath, OutPath); return true; } int32 PathLen = 0; TCHAR PathStr[FName::StringBufferSize] = {0}; // Append the mount root PathLen += MountRoot.ToString(PathStr + PathLen, FName::StringBufferSize - PathLen); // If the mount root ended in a slash then remove this as appending the internal path would cause a duplicate slash // Note: This assumes that the internal path starts with a slash, which is stated in the contract of this function if (PathLen > 0 && PathStr[PathLen - 1] == TEXT('/')) { PathStr[--PathLen] = 0; } // Append the internal path PathLen += InInternalPath.ToString(PathStr + PathLen, FName::StringBufferSize - PathLen); VirtualPathConverterBase.EndConvertingToVirtualPath(FStringView(PathStr, PathLen), OutPath); return true; } void UContentBrowserDataSource::CompileFilter(const FName InPath, const FContentBrowserDataFilter& InFilter, FContentBrowserDataCompiledFilter& OutCompiledFilter) { } void UContentBrowserDataSource::EnumerateItemsMatchingFilter(const FContentBrowserDataCompiledFilter& InFilter, TFunctionRef InCallback) { } void UContentBrowserDataSource::EnumerateItemsAtPath(const FName InPath, const EContentBrowserItemTypeFilter InItemTypeFilter, TFunctionRef InCallback) { } bool UContentBrowserDataSource::IsDiscoveringItems(FText* OutStatus) { return false; } bool UContentBrowserDataSource::PrioritizeSearchPath(const FName InPath) { return false; } bool UContentBrowserDataSource::IsFolderVisibleIfHidingEmpty(const FName InPath) { return true; } bool UContentBrowserDataSource::CanCreateFolder(const FName InPath, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::CreateFolder(const FName InPath, FContentBrowserItemDataTemporaryContext& OutPendingItem) { return false; } bool UContentBrowserDataSource::DoesItemPassFilter(const FContentBrowserItemData& InItem, const FContentBrowserDataCompiledFilter& InFilter) { return false; } bool UContentBrowserDataSource::GetItemAttribute(const FContentBrowserItemData& InItem, const bool InIncludeMetaData, const FName InAttributeKey, FContentBrowserItemDataAttributeValue& OutAttributeValue) { return false; } bool UContentBrowserDataSource::GetItemAttributes(const FContentBrowserItemData& InItem, const bool InIncludeMetaData, FContentBrowserItemDataAttributeValues& OutAttributeValues) { return false; } bool UContentBrowserDataSource::GetItemPhysicalPath(const FContentBrowserItemData& InItem, FString& OutDiskPath) { return false; } bool UContentBrowserDataSource::IsItemDirty(const FContentBrowserItemData& InItem) { return false; } bool UContentBrowserDataSource::CanEditItem(const FContentBrowserItemData& InItem, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::EditItem(const FContentBrowserItemData& InItem) { return false; } bool UContentBrowserDataSource::BulkEditItems(TArrayView InItems) { bool bSuccess = false; for (const FContentBrowserItemData& Item : InItems) { bSuccess |= EditItem(Item); } return bSuccess; } bool UContentBrowserDataSource::CanPreviewItem(const FContentBrowserItemData& InItem, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::PreviewItem(const FContentBrowserItemData& InItem) { return false; } bool UContentBrowserDataSource::BulkPreviewItems(TArrayView InItems) { bool bSuccess = false; for (const FContentBrowserItemData& Item : InItems) { bSuccess |= PreviewItem(Item); } return bSuccess; } bool UContentBrowserDataSource::CanDuplicateItem(const FContentBrowserItemData& InItem, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::DuplicateItem(const FContentBrowserItemData& InItem, FContentBrowserItemDataTemporaryContext& OutPendingItem) { return false; } bool UContentBrowserDataSource::BulkDuplicateItems(TArrayView InItems, TArray& OutNewItems) { return false; } bool UContentBrowserDataSource::CanSaveItem(const FContentBrowserItemData& InItem, const EContentBrowserItemSaveFlags InSaveFlags, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::SaveItem(const FContentBrowserItemData& InItem, const EContentBrowserItemSaveFlags InSaveFlags) { return false; } bool UContentBrowserDataSource::BulkSaveItems(TArrayView InItems, const EContentBrowserItemSaveFlags InSaveFlags) { bool bSuccess = false; for (const FContentBrowserItemData& Item : InItems) { bSuccess |= SaveItem(Item, InSaveFlags); } return bSuccess; } bool UContentBrowserDataSource::CanDeleteItem(const FContentBrowserItemData& InItem, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::DeleteItem(const FContentBrowserItemData& InItem) { return false; } bool UContentBrowserDataSource::BulkDeleteItems(TArrayView InItems) { bool bSuccess = false; for (const FContentBrowserItemData& Item : InItems) { bSuccess |= DeleteItem(Item); } return bSuccess; } bool UContentBrowserDataSource::CanRenameItem(const FContentBrowserItemData& InItem, const FString* InNewName, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::RenameItem(const FContentBrowserItemData& InItem, const FString& InNewName, FContentBrowserItemData& OutNewItem) { return false; } bool UContentBrowserDataSource::CanCopyItem(const FContentBrowserItemData& InItem, const FName InDestPath, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::CopyItem(const FContentBrowserItemData& InItem, const FName InDestPath) { return false; } bool UContentBrowserDataSource::BulkCopyItems(TArrayView InItems, const FName InDestPath) { bool bSuccess = false; for (const FContentBrowserItemData& Item : InItems) { bSuccess |= CopyItem(Item, InDestPath); } return bSuccess; } bool UContentBrowserDataSource::CanMoveItem(const FContentBrowserItemData& InItem, const FName InDestPath, FText* OutErrorMsg) { return false; } bool UContentBrowserDataSource::MoveItem(const FContentBrowserItemData& InItem, const FName InDestPath) { return false; } bool UContentBrowserDataSource::BulkMoveItems(TArrayView InItems, const FName InDestPath) { bool bSuccess = false; for (const FContentBrowserItemData& Item : InItems) { bSuccess |= MoveItem(Item, InDestPath); } return bSuccess; } bool UContentBrowserDataSource::AppendItemReference(const FContentBrowserItemData& InItem, FString& InOutStr) { return false; } bool UContentBrowserDataSource::UpdateThumbnail(const FContentBrowserItemData& InItem, FAssetThumbnail& InThumbnail) { return false; } TSharedPtr UContentBrowserDataSource::CreateCustomDragOperation(TArrayView InItems) { return nullptr; } bool UContentBrowserDataSource::HandleDragEnterItem(const FContentBrowserItemData& InItem, const FDragDropEvent& InDragDropEvent) { return false; } bool UContentBrowserDataSource::HandleDragOverItem(const FContentBrowserItemData& InItem, const FDragDropEvent& InDragDropEvent) { return false; } bool UContentBrowserDataSource::HandleDragLeaveItem(const FContentBrowserItemData& InItem, const FDragDropEvent& InDragDropEvent) { return false; } bool UContentBrowserDataSource::HandleDragDropOnItem(const FContentBrowserItemData& InItem, const FDragDropEvent& InDragDropEvent) { return false; } bool UContentBrowserDataSource::TryGetCollectionId(const FContentBrowserItemData& InItem, FName& OutCollectionId) { return false; } bool UContentBrowserDataSource::Legacy_TryGetPackagePath(const FContentBrowserItemData& InItem, FName& OutPackagePath) { return false; } bool UContentBrowserDataSource::Legacy_TryGetAssetData(const FContentBrowserItemData& InItem, FAssetData& OutAssetData) { return false; } bool UContentBrowserDataSource::Legacy_TryConvertPackagePathToVirtualPath(const FName InPackagePath, FName& OutPath) { return false; } bool UContentBrowserDataSource::Legacy_TryConvertAssetDataToVirtualPath(const FAssetData& InAssetData, const bool InUseFolderPaths, FName& OutPath) { return false; } void UContentBrowserDataSource::QueueItemDataUpdate(FContentBrowserItemDataUpdate&& InUpdate) { if (DataSink) { DataSink->QueueItemDataUpdate(MoveTemp(InUpdate)); } } void UContentBrowserDataSource::NotifyItemDataRefreshed() { if (DataSink) { DataSink->NotifyItemDataRefreshed(); } } void UContentBrowserDataSource::EnumerateRootPaths(const FContentBrowserDataFilter& InFilter, TFunctionRef InCallback) { checkf(false, TEXT("Must implement EnumerateRootPaths")); } void UContentBrowserDataSource::ExpandVirtualPath(const FName InPath, const FContentBrowserDataFilter& InFilter, FName& OutInternalPath, TSet& OutInternalPaths, TMap>& OutVirtualPaths) { static const FName RootPath = "/"; if (InPath == RootPath) { OutInternalPath = InPath; OutInternalPaths.Add(OutInternalPath); return; } if (TryConvertVirtualPathToInternal(InPath, OutInternalPath)) { OutInternalPaths.Add(OutInternalPath); return; } TStringBuilder PathString; InPath.ToString(PathString); PathString.Append(TEXT('/')); EnumerateRootPaths(InFilter, [this, &InFilter, &PathString, &OutInternalPaths, &OutVirtualPaths](const FName InternalRootPath) { FName VirtualRootPath; if (!TryConvertInternalPathToVirtual(InternalRootPath, VirtualRootPath)) { return; } TStringBuilder VirtualRootPathString; VirtualRootPath.ToString(VirtualRootPathString); const FStringView VirtualRootPathStringView = VirtualRootPathString; if (!VirtualRootPathStringView.StartsWith(PathString, ESearchCase::IgnoreCase)) { return; } if (InFilter.bRecursivePaths) { OutInternalPaths.Add(InternalRootPath); return; } // Add immediate subfolder virtual or otherwise const FStringView RightSide(VirtualRootPathStringView.GetData() + PathString.Len(), VirtualRootPathStringView.Len() - PathString.Len()); int32 FoundIndex; if (RightSide.FindChar(TEXT('/'), FoundIndex)) { const FName VirtualSubPath = FName(PathString.Len() + FoundIndex, VirtualRootPathStringView.GetData()); TArray& InternalSubPaths = OutVirtualPaths.FindOrAdd(VirtualSubPath); InternalSubPaths.Add(InternalRootPath); } else { TArray& InternalSubPaths = OutVirtualPaths.FindOrAdd(VirtualRootPath); InternalSubPaths.Add(InternalRootPath); } }); }