You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
========================== MAJOR FEATURES + CHANGES ========================== Change 2719147 on 2015/10/07 by Mark.Satterthwaite Allow the shader cache to perform some precompilation synchronously on load before falling back to asynchronous compilation to balance load times against total time spent precompiling. Added a stat to the group that reports how long the precompile has been running until it completes so it is easier to track. Change 2719182 on 2015/10/07 by Mark.Satterthwaite Refactor the ShaderCache's internal data structures and change the way we handle recording whether a particular predraw state has been submitted to try and make it more efficient. Change 2719185 on 2015/10/07 by Mark.Satterthwaite Merging CL #2717701: Try and fix random crashes on Mac when manipulating bound-shader-states caused by ShaderCache potentially providing a bogus shader state pointer on exit from predraw. Change 2719434 on 2015/10/07 by Mark.Satterthwaite Make sure that Mac ensures reports have a source context and a sane callstack when sent to the crash-reports server. Change 2724764 on 2015/10/12 by Josh.Adams [Initial AppleTV support] Merging //depot/YakBranch/... to //UE4/Dev-Platform/... Change 2726266 on 2015/10/13 by Lee.Clark PS4 - Calc reserve size required for DMA copy when using unsafe command buffers Change 2726401 on 2015/10/13 by Mark.Satterthwaite Merging CL #2716418: Fix UE-15228 'Crash Report Client doesn't restart into project editor on Mac' by reporting the original command line supplied by LaunchMac, not the modified one that strips the project name. The CRC can then relaunch as expected. #jira UE-15228 Change 2726421 on 2015/10/13 by Lee.Clark PS4 - Don't try to clear invalid targets Change 2727040 on 2015/10/13 by Michael.Trepka Merging CL 2724777 - Fixed splash screen rendering for images with DPI different than 72 Change 2729783 on 2015/10/15 by Keith.Judge Fix huge memory leak in Test/Shipping configurations, caused because I am a numpty. Change 2729847 on 2015/10/15 by Mark.Satterthwaite Merging CL #2729846: On OS X unconstrain windows from the dimension of the parent display when in Windowed mode - it is OK for them to be larger in this case. They do need to be repositioned if on the Primary display so that they don't creep under the menu bar and become unmovable/unclosable and Fullscreen windows still need to be constrained to a single display. We can now take screenshots of windows that are larger than the display & not get grey bars beyond the cutoff. #jira UE-21992 Change 2729865 on 2015/10/15 by Keith.Judge Fast semantics - Finish up resource transitions, adding resource decompression where appropriate and using non-fast clears where we can't determine the resource transition. Change 2729897 on 2015/10/15 by Keith.Judge Fast Semantics - Make sure all GetData() calls are made safe with GPU fences. Change 2729972 on 2015/10/15 by Keith.Judge Removed the last vestiges of ID3D11DeviceContext/ID3D11DeviceContext1 from the Xbox RHI. Everything now uses ID3D11DeviceContextX directly. This should be marginally quicker as it stops a double call to ClearState(). Change 2731503 on 2015/10/16 by Keith.Judge Added _XDK_VERSION to the DDC key for textures, which should solve the issue of the tiling mode changing in August XDK (and future changes Microsoft may inflict). Change 2731596 on 2015/10/16 by Keith.Judge Fast Semantics - Add deferred resource deletion queue to make deleted resources be actually deleted a number of frames later so that the GPU is definitely finished with them. Hooked up the temporary SRVs for dynamic VBs as a first step. Change 2731928 on 2015/10/16 by Michael.Trepka PR #1659: Mac/Build.sh handles additional arguments (Contributed by judgeaxl) Change 2731934 on 2015/10/16 by Michael.Trepka PR #1618: added clang 3.7.0 -Wshift-negative-value ignore in JpegImageWrapper.cpp (Contributed by bsekura) Change 2732018 on 2015/10/16 by Mark.Satterthwaite Emit a shader code cache for each platforms requested shader formats, this is separate to the targeted formats as not all can or need to be cached. - The implementation extends the ShaderCache's hooks in FShaderResource's serialisation function to capture the required shaders. - Each target platform has its own list of cached shader formats, analogous to the list of targeted RHIs. Presently only the Mac implements this. - Code cached shaders are now compressed (for size) to reduce the overhead associated with keeping all the shader code around - this works esp. well for text-based formats like GLSL. Change 2732365 on 2015/10/16 by Josh.Adams - Packaging a TVOS .ipa now works (still haven't tried any of the Editor integration like Launch On) Change 2733170 on 2015/10/18 by Terence.Burns Fix for Android IAP query not returning entire inventory. Change 2733174 on 2015/10/18 by Terence.Burns Fix Movie player issue where wait for movie to finish isnt being respected. Seems a stray bUserCanceled event flag was causing this not to be observed. Added some verbose logging to apple movie player. Change 2733488 on 2015/10/19 by Mark.Satterthwaite Added the ability to merge the .ushadercache files used by the ShaderCache to store shader & draw state information. - Fixed a bug that would cause invalid shader membership and draw state information to be logged. - Added a separate command-line tool to merge shader cache files, currently Mac-only but in theory should work on other platforms too. Change 2735226 on 2015/10/20 by Mark.Satterthwaite Fix temporal AA rendering on GL/Mac OS X - you can't rely on EyeAdaptation values unless SM5 is available so only perform that code on SM5 & we must correctly clamp saturate(NaN) to 0 as the current hlslcc won't do that for us (& is required by the HLSL spec). The latter used to be clamped in the AA_ALPHA && AA_VELOCITY_WEIGHTING code block that was removed recently. #jira UE-21214 #jira UE-19913 Change 2736722 on 2015/10/21 by Daniel.Lamb Improved performance of cooking stats system. Change 2737172 on 2015/10/21 by Daniel.Lamb Improved cooking stats performance for ddc stats.
1178 lines
37 KiB
C++
1178 lines
37 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CollectionManagerPrivatePCH.h"
|
|
#include "ISourceControlModule.h"
|
|
#include "TextFilterExpressionEvaluator.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "CollectionManager"
|
|
|
|
struct FCollectionUtils
|
|
{
|
|
static void AppendCollectionToArray(const TSet<FName>& InObjectSet, TArray<FName>& OutObjectArray)
|
|
{
|
|
OutObjectArray.Reserve(OutObjectArray.Num() + InObjectSet.Num());
|
|
for (const FName& ObjectName : InObjectSet)
|
|
{
|
|
OutObjectArray.Add(ObjectName);
|
|
}
|
|
}
|
|
};
|
|
|
|
FCollection::FCollection(const FString& InFilename, bool InUseSCC, ECollectionStorageMode::Type InStorageMode)
|
|
{
|
|
ensure(InFilename.Len() > 0);
|
|
|
|
bUseSCC = InUseSCC;
|
|
SourceFilename = InFilename;
|
|
CollectionName = FName(*FPaths::GetBaseFilename(InFilename));
|
|
|
|
StorageMode = InStorageMode;
|
|
|
|
CollectionGuid = FGuid::NewGuid();
|
|
|
|
// Initialize the file version to the most recent
|
|
FileVersion = ECollectionVersion::CurrentVersion;
|
|
}
|
|
|
|
TSharedRef<FCollection> FCollection::Clone(const FString& InFilename, bool InUseSCC, ECollectionCloneMode InCloneMode) const
|
|
{
|
|
TSharedRef<FCollection> NewCollection = MakeShareable(new FCollection(*this));
|
|
|
|
// Set the new collection name and path
|
|
NewCollection->bUseSCC = InUseSCC;
|
|
NewCollection->SourceFilename = InFilename;
|
|
NewCollection->CollectionName = FName(*FPaths::GetBaseFilename(InFilename));
|
|
|
|
NewCollection->StorageMode = StorageMode;
|
|
|
|
// Create a new GUID?
|
|
if (InCloneMode == ECollectionCloneMode::Unique)
|
|
{
|
|
NewCollection->CollectionGuid = FGuid::NewGuid();
|
|
}
|
|
|
|
return NewCollection;
|
|
}
|
|
|
|
bool FCollection::Load(FText& OutError)
|
|
{
|
|
Empty();
|
|
|
|
FString FullFileContentsString;
|
|
if (!FFileHelper::LoadFileToString(FullFileContentsString, *SourceFilename))
|
|
{
|
|
OutError = FText::Format(LOCTEXT("LoadError_FailedToLoadFile", "Failed to load the collection '{0}' from disk."), FText::FromString(SourceFilename));
|
|
return false;
|
|
}
|
|
|
|
// Normalize line endings and parse into array
|
|
TArray<FString> FileContents;
|
|
FullFileContentsString.ReplaceInline(TEXT("\r"), TEXT(""));
|
|
FullFileContentsString.ParseIntoArray(FileContents, TEXT("\n"), /*bCullEmpty=*/false);
|
|
|
|
if ( FileContents.Num() == 0 )
|
|
{
|
|
// Empty file, assume static collection with no items
|
|
return true;
|
|
}
|
|
|
|
// Load the header from the contents array
|
|
TMap<FString,FString> HeaderPairs;
|
|
while ( FileContents.Num() )
|
|
{
|
|
// Pop the 0th element from the contents array and read it
|
|
const FString Line = FileContents[0].Trim().TrimTrailing();
|
|
FileContents.RemoveAt(0);
|
|
|
|
if (Line.Len() == 0)
|
|
{
|
|
// Empty line. Done reading headers.
|
|
break;
|
|
}
|
|
|
|
FString Key;
|
|
FString Value;
|
|
if ( Line.Split(TEXT(":"), &Key, &Value) )
|
|
{
|
|
HeaderPairs.Add(Key, Value);
|
|
}
|
|
}
|
|
|
|
// Now process the header pairs to prepare and validate this collection
|
|
if ( !LoadHeaderPairs(HeaderPairs) )
|
|
{
|
|
// Bad header
|
|
OutError = FText::Format(LOCTEXT("LoadError_BadHeader", "The collection file '{0}' contains a bad header and could not be loaded."), FText::FromString(SourceFilename));
|
|
return false;
|
|
}
|
|
|
|
// Now load the content if the header load was successful
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
// Static collection, a flat list of asset paths
|
|
for (FString Line : FileContents)
|
|
{
|
|
Line.Trim();
|
|
Line.TrimTrailing();
|
|
|
|
if ( Line.Len() )
|
|
{
|
|
AddObjectToCollection(FName(*Line));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Dynamic collection, a single query line
|
|
DynamicQueryText = (FileContents.Num() > 0) ? FileContents[0] : FString();
|
|
|
|
DynamicQueryText.Trim();
|
|
DynamicQueryText.TrimTrailing();
|
|
}
|
|
|
|
DiskSnapshot.TakeSnapshot(*this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FCollection::Save(FText& OutError)
|
|
{
|
|
if ( !ensure(SourceFilename.Len()) )
|
|
{
|
|
OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
return false;
|
|
}
|
|
|
|
// Store the start time for profiling reasons
|
|
double SaveStartTime = FPlatformTime::Seconds();
|
|
|
|
// Keep track of save progress to update the slow task dialog
|
|
const int32 SaveProgressDenominator = 3;
|
|
int32 SaveProgressNumerator = 0;
|
|
|
|
|
|
GWarn->BeginSlowTask( FText::Format( LOCTEXT("SavingCollection", "Saving Collection {0}"), FText::FromName( CollectionName ) ), true);
|
|
GWarn->UpdateProgress(SaveProgressNumerator++, SaveProgressDenominator);
|
|
|
|
if ( bUseSCC )
|
|
{
|
|
// Checkout the file
|
|
if ( !CheckoutCollection(OutError) )
|
|
{
|
|
UE_LOG(LogCollectionManager, Error, TEXT("Failed to check out a collection file: %s"), *CollectionName.ToString());
|
|
GWarn->EndSlowTask();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GWarn->UpdateProgress(SaveProgressNumerator++, SaveProgressDenominator);
|
|
|
|
// Generate a string with the file contents
|
|
FString FileOutput;
|
|
|
|
// Start with the header
|
|
TMap<FString,FString> HeaderPairs;
|
|
SaveHeaderPairs(HeaderPairs);
|
|
for (const auto& HeaderPair : HeaderPairs)
|
|
{
|
|
FileOutput += HeaderPair.Key + TEXT(":") + HeaderPair.Value + LINE_TERMINATOR;
|
|
}
|
|
FileOutput += LINE_TERMINATOR;
|
|
|
|
// Now for the content
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
// Write out the set as a sorted array to keep things in a known order for diffing
|
|
TArray<FName> ObjectList = ObjectSet.Array();
|
|
ObjectList.Sort();
|
|
|
|
// Static collection. Save a flat list of all objects in the collection.
|
|
for (const FName& ObjectName : ObjectList)
|
|
{
|
|
FileOutput += ObjectName.ToString() + LINE_TERMINATOR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Dynamic collection, a single query line
|
|
FileOutput += DynamicQueryText + LINE_TERMINATOR;
|
|
}
|
|
|
|
// Attempt to save the file
|
|
bool bSaveSuccessful = false;
|
|
if ( ensure(FileOutput.Len()) )
|
|
{
|
|
// We have some output, write it to file
|
|
if ( FFileHelper::SaveStringToFile(FileOutput, *SourceFilename) )
|
|
{
|
|
bSaveSuccessful = true;
|
|
}
|
|
else
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_WriteFailed", "Failed to write to collection file: {0}"), FText::FromString(SourceFilename));
|
|
UE_LOG(LogCollectionManager, Error, TEXT("%s"), *OutError.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
|
|
GWarn->UpdateProgress(SaveProgressNumerator++, SaveProgressDenominator);
|
|
|
|
if ( bSaveSuccessful )
|
|
{
|
|
if ( bUseSCC )
|
|
{
|
|
// Check in the file if the save was successful
|
|
if ( bSaveSuccessful )
|
|
{
|
|
if ( !CheckinCollection(OutError) )
|
|
{
|
|
UE_LOG(LogCollectionManager, Error, TEXT("Failed to check in a collection successfully saving: %s"), *CollectionName.ToString());
|
|
bSaveSuccessful = false;
|
|
}
|
|
}
|
|
|
|
// If the save was not successful or the checkin failed, revert
|
|
if ( !bSaveSuccessful )
|
|
{
|
|
FText Unused;
|
|
if ( !RevertCollection(Unused) )
|
|
{
|
|
// The revert failed... file will be left on disk as it was saved.
|
|
// DiskAssetList will still hold the version of the file when this collection was last loaded or saved successfully so nothing will be out of sync.
|
|
// If the user closes the editor before successfully saving, this file may not be exactly what was seen at the time the editor closed.
|
|
UE_LOG(LogCollectionManager, Warning, TEXT("Failed to revert a checked out collection after failing to save or checkin: %s"), *CollectionName.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GWarn->UpdateProgress(SaveProgressNumerator++, SaveProgressDenominator);
|
|
|
|
if ( bSaveSuccessful )
|
|
{
|
|
// Files are always saved at the latest version as loading should take care of data upgrades
|
|
FileVersion = ECollectionVersion::CurrentVersion;
|
|
|
|
DiskSnapshot.TakeSnapshot(*this);
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
|
|
UE_LOG(LogCollectionManager, Verbose, TEXT("Saved collection %s in %0.6f seconds"), *CollectionName.ToString(), FPlatformTime::Seconds() - SaveStartTime);
|
|
|
|
return bSaveSuccessful;
|
|
}
|
|
|
|
bool FCollection::Update(FText& OutError)
|
|
{
|
|
if ( !ensure(SourceFilename.Len()) )
|
|
{
|
|
OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
return false;
|
|
}
|
|
|
|
if ( !bUseSCC )
|
|
{
|
|
// Not under SCC control, so already up-to-date
|
|
return true;
|
|
}
|
|
|
|
FScopedSlowTask SlowTask(1.0f, FText::Format(LOCTEXT("UpdatingCollection", "Updating Collection {0}"), FText::FromName(CollectionName )));
|
|
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if ( !ISourceControlModule::Get().IsEnabled() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCDisabled", "Source control is not enabled. Enable source control in the preferences menu.");
|
|
return false;
|
|
}
|
|
|
|
if ( !SourceControlProvider.IsAvailable() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCNotAvailable", "Source control is currently not available. Check your connection and try again.");
|
|
return false;
|
|
}
|
|
|
|
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
|
|
// If not at the head revision, sync up
|
|
if (SourceControlState.IsValid() && !SourceControlState->IsCurrent())
|
|
{
|
|
if ( SourceControlProvider.Execute(ISourceControlOperation::Create<FSync>(), AbsoluteFilename) == ECommandResult::Failed )
|
|
{
|
|
// Could not sync up with the head revision
|
|
OutError = FText::Format(LOCTEXT("Error_SCCSync", "Failed to sync collection '{0}' to the head revision."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
|
|
// Check to see if the file exists at the head revision
|
|
if ( IFileManager::Get().FileExists(*SourceFilename) )
|
|
{
|
|
// File found! Load it and merge with our local changes
|
|
FText LoadErrorText;
|
|
FCollection NewCollection(SourceFilename, false, ECollectionStorageMode::Static);
|
|
if ( !NewCollection.Load(LoadErrorText) )
|
|
{
|
|
// Failed to load the head revision file so it isn't safe to delete it
|
|
OutError = FText::Format(LOCTEXT("Error_SCCBadHead", "Failed to load the collection '{0}' at the head revision. {1}"), FText::FromName(CollectionName), LoadErrorText);
|
|
return false;
|
|
}
|
|
|
|
// Loaded the head revision, now merge up so the files are in a consistent state
|
|
MergeWithCollection(NewCollection);
|
|
}
|
|
|
|
// Make sure we get a fresh state from the server
|
|
SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
|
|
// Got an updated version?
|
|
if (SourceControlState.IsValid() && !SourceControlState->IsCurrent())
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCNotCurrent", "Collection '{0}' is not at head revision after sync."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FCollection::Merge(const FCollection& NewCollection)
|
|
{
|
|
return MergeWithCollection(NewCollection);
|
|
}
|
|
|
|
bool FCollection::DeleteSourceFile(FText& OutError)
|
|
{
|
|
bool bSuccessfullyDeleted = false;
|
|
|
|
if ( SourceFilename.Len() )
|
|
{
|
|
if ( bUseSCC )
|
|
{
|
|
bSuccessfullyDeleted = DeleteFromSourceControl(OutError);
|
|
}
|
|
else
|
|
{
|
|
bSuccessfullyDeleted = IFileManager::Get().Delete(*SourceFilename);
|
|
if ( !bSuccessfullyDeleted )
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_DiskDeleteFailed", "Failed to delete the collection file: {0}"), FText::FromString(SourceFilename));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No source file. Since it doesn't exist we will say it is deleted.
|
|
bSuccessfullyDeleted = true;
|
|
}
|
|
|
|
if ( bSuccessfullyDeleted )
|
|
{
|
|
DiskSnapshot = FCollectionSnapshot();
|
|
}
|
|
|
|
return bSuccessfullyDeleted;
|
|
}
|
|
|
|
void FCollection::Empty()
|
|
{
|
|
ObjectSet.Reset();
|
|
DynamicQueryText.Reset();
|
|
DynamicQueryExpressionEvaluatorPtr.Reset();
|
|
|
|
DiskSnapshot.TakeSnapshot(*this);
|
|
}
|
|
|
|
bool FCollection::AddObjectToCollection(FName ObjectPath)
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static && !ObjectSet.Contains(ObjectPath))
|
|
{
|
|
ObjectSet.Add(ObjectPath);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FCollection::RemoveObjectFromCollection(FName ObjectPath)
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
return ObjectSet.Remove(ObjectPath) > 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FCollection::GetAssetsInCollection(TArray<FName>& Assets) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
for (const FName& ObjectName : ObjectSet)
|
|
{
|
|
if (!ObjectName.ToString().StartsWith(TEXT("/Script/")))
|
|
{
|
|
Assets.Add(ObjectName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollection::GetClassesInCollection(TArray<FName>& Classes) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
for (const FName& ObjectName : ObjectSet)
|
|
{
|
|
if (ObjectName.ToString().StartsWith(TEXT("/Script/")))
|
|
{
|
|
Classes.Add(ObjectName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollection::GetObjectsInCollection(TArray<FName>& Objects) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
FCollectionUtils::AppendCollectionToArray(ObjectSet, Objects);
|
|
}
|
|
}
|
|
|
|
bool FCollection::IsObjectInCollection(FName ObjectPath) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
return ObjectSet.Contains(ObjectPath);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FCollection::IsRedirectorInCollection(FName ObjectPath) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
// Redirectors are fixed up in-memory once the asset registry has finished loading,
|
|
// so we need to test our on-disk set of objects rather than our in-memory set of objects
|
|
return DiskSnapshot.ObjectSet.Contains(ObjectPath);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FCollection::SetDynamicQueryText(const FString& InQueryText)
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Dynamic)
|
|
{
|
|
DynamicQueryText = InQueryText;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FString FCollection::GetDynamicQueryText() const
|
|
{
|
|
return (StorageMode == ECollectionStorageMode::Dynamic) ? DynamicQueryText : FString();
|
|
}
|
|
|
|
bool FCollection::TestDynamicQuery(const ITextFilterExpressionContext& InContext) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Dynamic)
|
|
{
|
|
if (!DynamicQueryExpressionEvaluatorPtr.IsValid())
|
|
{
|
|
DynamicQueryExpressionEvaluatorPtr = MakeShareable(new FTextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::Complex));
|
|
}
|
|
|
|
if (!DynamicQueryExpressionEvaluatorPtr->GetFilterText().ToString().Equals(DynamicQueryText, ESearchCase::CaseSensitive))
|
|
{
|
|
DynamicQueryExpressionEvaluatorPtr->SetFilterText(FText::FromString(DynamicQueryText));
|
|
}
|
|
|
|
return DynamicQueryExpressionEvaluatorPtr->TestTextFilter(InContext);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FCollectionStatusInfo FCollection::GetStatusInfo() const
|
|
{
|
|
FCollectionStatusInfo StatusInfo;
|
|
|
|
StatusInfo.bIsDirty = IsDirty();
|
|
StatusInfo.bIsEmpty = IsEmpty();
|
|
StatusInfo.bUseSCC = bUseSCC;
|
|
|
|
StatusInfo.NumObjects = ObjectSet.Num();
|
|
|
|
if (bUseSCC && ISourceControlModule::Get().IsEnabled())
|
|
{
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if (SourceControlProvider.IsAvailable())
|
|
{
|
|
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
|
|
StatusInfo.SCCState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::Use);
|
|
}
|
|
}
|
|
|
|
return StatusInfo;
|
|
}
|
|
|
|
bool FCollection::IsDirty() const
|
|
{
|
|
if (ParentCollectionGuid != DiskSnapshot.ParentCollectionGuid)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool bHasChanges = false;
|
|
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
TArray<FName> ObjectsAdded;
|
|
TArray<FName> ObjectsRemoved;
|
|
GetObjectDifferencesFromDisk(ObjectsAdded, ObjectsRemoved);
|
|
|
|
bHasChanges = ObjectsAdded.Num() != 0 || ObjectsRemoved.Num() != 0;
|
|
}
|
|
else
|
|
{
|
|
bHasChanges = DynamicQueryText != DiskSnapshot.DynamicQueryText;
|
|
}
|
|
|
|
return bHasChanges;
|
|
}
|
|
|
|
bool FCollection::IsEmpty() const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
return ObjectSet.Num() == 0;
|
|
}
|
|
else
|
|
{
|
|
return DynamicQueryText.IsEmpty();
|
|
}
|
|
}
|
|
|
|
void FCollection::PrintCollection() const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
UE_LOG(LogCollectionManager, Log, TEXT(" Printing static elements of collection %s"), *CollectionName.ToString());
|
|
UE_LOG(LogCollectionManager, Log, TEXT(" ============================="));
|
|
|
|
// Print the set as a sorted array to keep things in a sane order
|
|
TArray<FName> ObjectList = ObjectSet.Array();
|
|
ObjectList.Sort();
|
|
|
|
for (const FName& ObjectName : ObjectList)
|
|
{
|
|
UE_LOG(LogCollectionManager, Log, TEXT(" %s"), *ObjectName.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCollectionManager, Log, TEXT(" Printing dynamic query of collection %s"), *CollectionName.ToString());
|
|
UE_LOG(LogCollectionManager, Log, TEXT(" ============================="));
|
|
UE_LOG(LogCollectionManager, Log, TEXT(" %s"), *DynamicQueryText);
|
|
}
|
|
}
|
|
|
|
void FCollection::SaveHeaderPairs(TMap<FString,FString>& OutHeaderPairs) const
|
|
{
|
|
// These pairs will appear at the top of the file followed by a newline
|
|
OutHeaderPairs.Add(TEXT("FileVersion"), FString::FromInt(ECollectionVersion::CurrentVersion)); // Files are always saved at the latest version as loading should take care of data upgrades
|
|
OutHeaderPairs.Add(TEXT("Type"), ECollectionStorageMode::ToString(StorageMode));
|
|
OutHeaderPairs.Add(TEXT("Guid"), CollectionGuid.ToString(EGuidFormats::DigitsWithHyphens));
|
|
OutHeaderPairs.Add(TEXT("ParentGuid"), ParentCollectionGuid.ToString(EGuidFormats::DigitsWithHyphens));
|
|
}
|
|
|
|
bool FCollection::LoadHeaderPairs(const TMap<FString,FString>& InHeaderPairs)
|
|
{
|
|
// These pairs will appeared at the top of the file being loaded
|
|
// First find all the known pairs
|
|
const FString* Version = InHeaderPairs.Find(TEXT("FileVersion"));
|
|
if ( !Version )
|
|
{
|
|
// FileVersion is required
|
|
return false;
|
|
}
|
|
|
|
const FString* Type = InHeaderPairs.Find(TEXT("Type"));
|
|
if ( !Type )
|
|
{
|
|
// Type is required
|
|
return false;
|
|
}
|
|
|
|
StorageMode = ECollectionStorageMode::FromString(**Type);
|
|
|
|
FileVersion = (ECollectionVersion::Type)FCString::Atoi(**Version);
|
|
|
|
if (FileVersion >= ECollectionVersion::AddedCollectionGuid)
|
|
{
|
|
const FString* GuidStr = InHeaderPairs.Find(TEXT("Guid"));
|
|
if ( !GuidStr || !FGuid::Parse(*GuidStr, CollectionGuid) )
|
|
{
|
|
// Guid is required
|
|
return false;
|
|
}
|
|
|
|
const FString* ParentGuidStr = InHeaderPairs.Find(TEXT("ParentGuid"));
|
|
if ( !ParentGuidStr || !FGuid::Parse(*ParentGuidStr, ParentCollectionGuid) )
|
|
{
|
|
ParentCollectionGuid = FGuid();
|
|
}
|
|
}
|
|
|
|
return FileVersion > 0 && FileVersion <= ECollectionVersion::CurrentVersion;
|
|
}
|
|
|
|
bool FCollection::MergeWithCollection(const FCollection& Other)
|
|
{
|
|
bool bHasChanges = ParentCollectionGuid != Other.ParentCollectionGuid;
|
|
|
|
ParentCollectionGuid = Other.ParentCollectionGuid;
|
|
|
|
if (StorageMode != Other.StorageMode)
|
|
{
|
|
bHasChanges = true;
|
|
StorageMode = Other.StorageMode;
|
|
|
|
// Storage mode has changed! Empty the collection so we just copy over the new data verbatim
|
|
Empty();
|
|
}
|
|
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
// Work out whether we have any changes compared to the other collection
|
|
TArray<FName> ObjectsAdded;
|
|
TArray<FName> ObjectsRemoved;
|
|
GetObjectDifferences(ObjectSet, Other.ObjectSet, ObjectsAdded, ObjectsRemoved);
|
|
|
|
bHasChanges = bHasChanges || ObjectsAdded.Num() > 0 || ObjectsRemoved.Num() > 0;
|
|
|
|
if (bHasChanges)
|
|
{
|
|
// Gather the differences from the file on disk
|
|
ObjectsAdded.Reset();
|
|
ObjectsRemoved.Reset();
|
|
GetObjectDifferencesFromDisk(ObjectsAdded, ObjectsRemoved);
|
|
|
|
// Copy asset list from other collection
|
|
ObjectSet = Other.ObjectSet;
|
|
|
|
// Add the objects that were added before the merge
|
|
for (const FName& AddedObjectName : ObjectsAdded)
|
|
{
|
|
ObjectSet.Add(AddedObjectName);
|
|
}
|
|
|
|
// Remove the objects that were removed before the merge
|
|
for (const FName& RemovedObjectName : ObjectsRemoved)
|
|
{
|
|
ObjectSet.Remove(RemovedObjectName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bHasChanges = bHasChanges || DynamicQueryText != Other.DynamicQueryText;
|
|
DynamicQueryText = Other.DynamicQueryText;
|
|
}
|
|
|
|
DiskSnapshot = Other.DiskSnapshot;
|
|
|
|
return bHasChanges;
|
|
}
|
|
|
|
void FCollection::GetObjectDifferences(const TSet<FName>& BaseSet, const TSet<FName>& NewSet, TArray<FName>& ObjectsAdded, TArray<FName>& ObjectsRemoved)
|
|
{
|
|
// Find the objects that were removed compared to the base set
|
|
for (const FName& BaseObjectName : BaseSet)
|
|
{
|
|
if (!NewSet.Contains(BaseObjectName))
|
|
{
|
|
ObjectsRemoved.Add(BaseObjectName);
|
|
}
|
|
}
|
|
|
|
// Find the objects that were added compare to the base set
|
|
for (const FName& NewObjectName : NewSet)
|
|
{
|
|
if (!BaseSet.Contains(NewObjectName))
|
|
{
|
|
ObjectsAdded.Add(NewObjectName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollection::GetObjectDifferencesFromDisk(TArray<FName>& ObjectsAdded, TArray<FName>& ObjectsRemoved) const
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
GetObjectDifferences(DiskSnapshot.ObjectSet, ObjectSet, ObjectsAdded, ObjectsRemoved);
|
|
}
|
|
}
|
|
|
|
bool FCollection::CheckoutCollection(FText& OutError)
|
|
{
|
|
if ( !ensure(SourceFilename.Len()) )
|
|
{
|
|
OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
return false;
|
|
}
|
|
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if ( !ISourceControlModule::Get().IsEnabled() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCDisabled", "Source control is not enabled. Enable source control in the preferences menu.");
|
|
return false;
|
|
}
|
|
|
|
if ( !SourceControlProvider.IsAvailable() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCNotAvailable", "Source control is currently not available. Check your connection and try again.");
|
|
return false;
|
|
}
|
|
|
|
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
|
|
bool bSuccessfullyCheckedOut = false;
|
|
|
|
if (SourceControlState.IsValid() && SourceControlState->IsDeleted())
|
|
{
|
|
// Revert our delete
|
|
if ( !RevertCollection(OutError) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Make sure we get a fresh state from the server
|
|
SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
}
|
|
|
|
// If not at the head revision, sync up
|
|
if (SourceControlState.IsValid() && !SourceControlState->IsCurrent())
|
|
{
|
|
if ( SourceControlProvider.Execute(ISourceControlOperation::Create<FSync>(), AbsoluteFilename) == ECommandResult::Failed )
|
|
{
|
|
// Could not sync up with the head revision
|
|
OutError = FText::Format(LOCTEXT("Error_SCCSync", "Failed to sync collection '{0}' to the head revision."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
|
|
// Check to see if the file exists at the head revision
|
|
if ( IFileManager::Get().FileExists(*SourceFilename) )
|
|
{
|
|
// File found! Load it and merge with our local changes
|
|
FText LoadErrorText;
|
|
FCollection NewCollection(SourceFilename, false, ECollectionStorageMode::Static);
|
|
if ( !NewCollection.Load(LoadErrorText) )
|
|
{
|
|
// Failed to load the head revision file so it isn't safe to delete it
|
|
OutError = FText::Format(LOCTEXT("Error_SCCBadHead", "Failed to load the collection '{0}' at the head revision. {1}"), FText::FromName(CollectionName), LoadErrorText);
|
|
return false;
|
|
}
|
|
|
|
// Loaded the head revision, now merge up so the files are in a consistent state
|
|
MergeWithCollection(NewCollection);
|
|
}
|
|
|
|
// Make sure we get a fresh state from the server
|
|
SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
}
|
|
|
|
if(SourceControlState.IsValid())
|
|
{
|
|
if(!SourceControlState->IsSourceControlled())
|
|
{
|
|
// Not yet in the depot. We'll add it when we call CheckinCollection
|
|
bSuccessfullyCheckedOut = true;
|
|
}
|
|
else if(SourceControlState->IsAdded() || SourceControlState->IsCheckedOut())
|
|
{
|
|
// Already checked out or opened for add
|
|
bSuccessfullyCheckedOut = true;
|
|
}
|
|
else if(SourceControlState->CanCheckout())
|
|
{
|
|
// In depot and needs to be checked out
|
|
bSuccessfullyCheckedOut = (SourceControlProvider.Execute(ISourceControlOperation::Create<FCheckOut>(), AbsoluteFilename) == ECommandResult::Succeeded);
|
|
if (!bSuccessfullyCheckedOut)
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCCheckout", "Failed to check out collection '{0}'"), FText::FromName(CollectionName));
|
|
}
|
|
}
|
|
else if(!SourceControlState->IsCurrent())
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCNotCurrent", "Collection '{0}' is not at head revision after sync."), FText::FromName(CollectionName));
|
|
}
|
|
else if(SourceControlState->IsCheckedOutOther())
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCCheckedOutOther", "Collection '{0}' is checked out by another user."), FText::FromName(CollectionName));
|
|
}
|
|
else
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCUnknown", "Could not determine source control state for collection '{0}'"), FText::FromName(CollectionName));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutError = LOCTEXT("Error_SCCInvalid", "Source control state is invalid.");
|
|
}
|
|
|
|
return bSuccessfullyCheckedOut;
|
|
}
|
|
|
|
bool FCollection::CheckinCollection(FText& OutError)
|
|
{
|
|
if ( !ensure(SourceFilename.Len()) )
|
|
{
|
|
OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
return false;
|
|
}
|
|
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if ( !ISourceControlModule::Get().IsEnabled() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCDisabled", "Source control is not enabled. Enable source control in the preferences menu.");
|
|
return false;
|
|
}
|
|
|
|
if ( !SourceControlProvider.IsAvailable() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCNotAvailable", "Source control is currently not available. Check your connection and try again.");
|
|
return false;
|
|
}
|
|
|
|
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
|
|
if (SourceControlState.IsValid() && !SourceControlState->IsSourceControlled())
|
|
{
|
|
// Not yet in the depot. Add it.
|
|
const bool bWasAdded = (SourceControlProvider.Execute(ISourceControlOperation::Create<FMarkForAdd>(), AbsoluteFilename) == ECommandResult::Succeeded);
|
|
if (!bWasAdded)
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCAdd", "Failed to add collection '{0}' to source control."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
}
|
|
|
|
if ( SourceControlState.IsValid() && !(SourceControlState->IsCheckedOut() || SourceControlState->IsAdded()) )
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCNotCheckedOut", "Collection '{0}' not checked out or open for add."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
|
|
// Form an appropriate summary for the changelist
|
|
const FText CollectionNameText = FText::FromName( CollectionName );
|
|
FTextBuilder ChangelistDescBuilder;
|
|
|
|
if (SourceControlState.IsValid() && SourceControlState->IsAdded())
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionAddedNewDesc", "Added collection '{0}'"), CollectionNameText);
|
|
}
|
|
else
|
|
{
|
|
if (StorageMode == ECollectionStorageMode::Static)
|
|
{
|
|
// Gather differences from disk
|
|
TArray<FName> ObjectsAdded;
|
|
TArray<FName> ObjectsRemoved;
|
|
GetObjectDifferencesFromDisk(ObjectsAdded, ObjectsRemoved);
|
|
|
|
ObjectsAdded.Sort();
|
|
ObjectsRemoved.Sort();
|
|
|
|
// Report added files
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("FirstObjectAdded"), ObjectsAdded.Num() > 0 ? FText::FromName(ObjectsAdded[0]) : NSLOCTEXT("Core", "None", "None"));
|
|
Args.Add(TEXT("NumberAdded"), FText::AsNumber(ObjectsAdded.Num()));
|
|
Args.Add(TEXT("FirstObjectRemoved"), ObjectsRemoved.Num() > 0 ? FText::FromName(ObjectsRemoved[0]) : NSLOCTEXT("Core", "None", "None"));
|
|
Args.Add(TEXT("NumberRemoved"), FText::AsNumber(ObjectsRemoved.Num()));
|
|
Args.Add(TEXT("CollectionName"), CollectionNameText);
|
|
|
|
if (ObjectsAdded.Num() == 1)
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionAddedSingleDesc", "Added '{FirstObjectAdded}' to collection '{CollectionName}'"), Args);
|
|
}
|
|
else if (ObjectsAdded.Num() > 1)
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionAddedMultipleDesc", "Added {NumberAdded} objects to collection '{CollectionName}':"), Args);
|
|
|
|
ChangelistDescBuilder.Indent();
|
|
for (const FName& AddedObjectName : ObjectsAdded)
|
|
{
|
|
ChangelistDescBuilder.AppendLine(FText::FromName(AddedObjectName));
|
|
}
|
|
ChangelistDescBuilder.Unindent();
|
|
}
|
|
|
|
if ( ObjectsRemoved.Num() == 1 )
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionRemovedSingleDesc", "Removed '{FirstObjectRemoved}' from collection '{CollectionName}'"), Args);
|
|
}
|
|
else if (ObjectsRemoved.Num() > 1)
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionRemovedMultipleDesc", "Removed {NumberRemoved} objects from collection '{CollectionName}'"), Args);
|
|
|
|
ChangelistDescBuilder.Indent();
|
|
for (const FName& RemovedObjectName : ObjectsRemoved)
|
|
{
|
|
ChangelistDescBuilder.AppendLine(FText::FromName(RemovedObjectName));
|
|
}
|
|
ChangelistDescBuilder.Unindent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DiskSnapshot.DynamicQueryText != DynamicQueryText)
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionChangedDynamicQueryDesc", "Changed the dynamic query of collection '{0}' to '{1}'"), CollectionNameText, FText::FromString(DynamicQueryText));
|
|
}
|
|
}
|
|
|
|
// Parent change?
|
|
if (DiskSnapshot.ParentCollectionGuid != ParentCollectionGuid)
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionChangedParentDesc", "Changed the parent of collection '{0}'"), CollectionNameText);
|
|
}
|
|
|
|
// Version bump?
|
|
if (FileVersion < ECollectionVersion::CurrentVersion)
|
|
{
|
|
ChangelistDescBuilder.AppendLineFormat(LOCTEXT("CollectionUpgradedDesc", "Upgraded collection '{0}' (was version {1}, now version {2})"), CollectionNameText, FText::AsNumber(FileVersion), FText::AsNumber(ECollectionVersion::CurrentVersion));
|
|
}
|
|
}
|
|
|
|
FText ChangelistDesc = ChangelistDescBuilder.ToText();
|
|
if (ChangelistDesc.IsEmpty())
|
|
{
|
|
// No changes could be detected
|
|
ChangelistDesc = FText::Format(LOCTEXT("CollectionNotModifiedDesc", "Collection '{0}' not modified"), CollectionNameText);
|
|
}
|
|
|
|
// Finally check in the file
|
|
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
|
|
CheckInOperation->SetDescription( ChangelistDesc );
|
|
if ( SourceControlProvider.Execute( CheckInOperation, AbsoluteFilename ) )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCCheckIn", "Failed to check in collection '{0}'."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FCollection::RevertCollection(FText& OutError)
|
|
{
|
|
if ( !ensure(SourceFilename.Len()) )
|
|
{
|
|
OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
return false;
|
|
}
|
|
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if ( !ISourceControlModule::Get().IsEnabled() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCDisabled", "Source control is not enabled. Enable source control in the preferences menu.");
|
|
return false;
|
|
}
|
|
|
|
if ( !SourceControlProvider.IsAvailable() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCNotAvailable", "Source control is currently not available. Check your connection and try again.");
|
|
return false;
|
|
}
|
|
|
|
FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
|
|
if ( SourceControlState.IsValid() && !(SourceControlState->IsCheckedOut() || SourceControlState->IsAdded()) )
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCNotCheckedOut", "Collection '{0}' not checked out or open for add."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
|
|
if ( SourceControlProvider.Execute(ISourceControlOperation::Create<FRevert>(), AbsoluteFilename) == ECommandResult::Succeeded)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCRevert", "Could not revert collection '{0}'"), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FCollection::DeleteFromSourceControl(FText& OutError)
|
|
{
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if ( !ISourceControlModule::Get().IsEnabled() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCDisabled", "Source control is not enabled. Enable source control in the preferences menu.");
|
|
return false;
|
|
}
|
|
|
|
if ( !SourceControlProvider.IsAvailable() )
|
|
{
|
|
OutError = LOCTEXT("Error_SCCNotAvailable", "Source control is currently not available. Check your connection and try again.");
|
|
return false;
|
|
}
|
|
|
|
bool bDeletedSuccessfully = false;
|
|
|
|
const int32 DeleteProgressDenominator = 2;
|
|
int32 DeleteProgressNumerator = 0;
|
|
|
|
const FText CollectionNameText = FText::FromName( CollectionName );
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("CollectionName"), CollectionNameText );
|
|
const FText StatusUpdate = FText::Format( LOCTEXT("DeletingCollection", "Deleting Collection {CollectionName}"), Args );
|
|
|
|
GWarn->BeginSlowTask( StatusUpdate, true );
|
|
GWarn->UpdateProgress(DeleteProgressNumerator++, DeleteProgressDenominator);
|
|
|
|
FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SourceFilename);
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
|
|
GWarn->UpdateProgress(DeleteProgressNumerator++, DeleteProgressDenominator);
|
|
|
|
// If checked out locally for some reason, revert
|
|
if (SourceControlState.IsValid() && (SourceControlState->IsAdded() || SourceControlState->IsCheckedOut() || SourceControlState->IsDeleted()))
|
|
{
|
|
if ( !RevertCollection(OutError) )
|
|
{
|
|
// Failed to revert, just bail out
|
|
GWarn->EndSlowTask();
|
|
return false;
|
|
}
|
|
|
|
// Make sure we get a fresh state from the server
|
|
SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
}
|
|
|
|
// If not at the head revision, sync up
|
|
if (SourceControlState.IsValid() && !SourceControlState->IsCurrent())
|
|
{
|
|
if ( SourceControlProvider.Execute(ISourceControlOperation::Create<FSync>(), AbsoluteFilename) == ECommandResult::Failed)
|
|
{
|
|
// Could not sync up with the head revision
|
|
GWarn->EndSlowTask();
|
|
OutError = FText::Format(LOCTEXT("Error_SCCSync", "Failed to sync collection '{0}' to the head revision."), FText::FromName(CollectionName));
|
|
return false;
|
|
}
|
|
|
|
// Check to see if the file exists at the head revision
|
|
if ( !IFileManager::Get().FileExists(*SourceFilename) )
|
|
{
|
|
// File was already deleted, consider this a success
|
|
GWarn->EndSlowTask();
|
|
return true;
|
|
}
|
|
|
|
FCollection NewCollection(SourceFilename, false, ECollectionStorageMode::Static);
|
|
FText LoadErrorText;
|
|
if ( !NewCollection.Load(LoadErrorText) )
|
|
{
|
|
// Failed to load the head revision file so it isn't safe to delete it
|
|
GWarn->EndSlowTask();
|
|
OutError = FText::Format(LOCTEXT("Error_SCCBadHead", "Failed to load the collection '{0}' at the head revision. {1}"), FText::FromName(CollectionName), LoadErrorText);
|
|
return false;
|
|
}
|
|
|
|
// Loaded the head revision, now merge up so the files are in a consistent state
|
|
MergeWithCollection(NewCollection);
|
|
|
|
// Make sure we get a fresh state from the server
|
|
SourceControlState = SourceControlProvider.GetState(AbsoluteFilename, EStateCacheUsage::ForceUpdate);
|
|
}
|
|
|
|
GWarn->UpdateProgress(DeleteProgressNumerator++, DeleteProgressDenominator);
|
|
|
|
if(SourceControlState.IsValid())
|
|
{
|
|
if(SourceControlState->IsAdded() || SourceControlState->IsCheckedOut())
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCDeleteWhileCheckedOut", "Failed to delete collection '{0}' in source control because it is checked out or open for add."), FText::FromName(CollectionName));
|
|
}
|
|
else if(SourceControlState->CanCheckout())
|
|
{
|
|
if ( SourceControlProvider.Execute(ISourceControlOperation::Create<FDelete>(), AbsoluteFilename) == ECommandResult::Succeeded )
|
|
{
|
|
// Now check in the delete
|
|
const FText ChangelistDesc = FText::Format( LOCTEXT("CollectionDeletedDesc", "Deleted collection: {CollectionName}"), CollectionNameText );
|
|
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
|
|
CheckInOperation->SetDescription(ChangelistDesc);
|
|
if ( SourceControlProvider.Execute( CheckInOperation, AbsoluteFilename ) )
|
|
{
|
|
// Deleted successfully!
|
|
bDeletedSuccessfully = true;
|
|
}
|
|
else
|
|
{
|
|
FText Unused;
|
|
if ( !RevertCollection(Unused) )
|
|
{
|
|
UE_LOG(LogCollectionManager, Warning, TEXT("Failed to revert collection '%s' after failing to check in the file that was marked for delete."), *CollectionName.ToString());
|
|
}
|
|
|
|
OutError = FText::Format(LOCTEXT("Error_SCCCheckIn", "Failed to check in collection '{0}'."), FText::FromName(CollectionName));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCDeleteFailed", "Failed to delete collection '{0}' in source control."), FText::FromName(CollectionName));
|
|
}
|
|
}
|
|
else if(!SourceControlState->IsSourceControlled())
|
|
{
|
|
// Not yet in the depot or deleted. We can just delete it from disk.
|
|
bDeletedSuccessfully = IFileManager::Get().Delete(*AbsoluteFilename);
|
|
if ( !bDeletedSuccessfully )
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_DiskDeleteFailed", "Failed to delete the collection file: {0}"), FText::FromString(AbsoluteFilename));
|
|
}
|
|
}
|
|
else if (!SourceControlState->IsCurrent())
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCNotCurrent", "Collection '{0}' is not at head revision after sync."), FText::FromName(CollectionName));
|
|
}
|
|
else if(SourceControlState->IsCheckedOutOther())
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCCheckedOutOther", "Collection '{0}' is checked out by another user."), FText::FromName(CollectionName));
|
|
}
|
|
else
|
|
{
|
|
OutError = FText::Format(LOCTEXT("Error_SCCUnknown", "Could not determine source control state for collection '{0}'"), FText::FromName(CollectionName));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutError = LOCTEXT("Error_SCCInvalid", "Source control state is invalid.");
|
|
}
|
|
|
|
GWarn->UpdateProgress(DeleteProgressNumerator++, DeleteProgressDenominator);
|
|
|
|
GWarn->EndSlowTask();
|
|
|
|
return bDeletedSuccessfully;
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|