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); 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; }
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 ( IsDynamic() ) { // @todo Dynamic collections } else { // 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; } } // 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::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); 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::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; GenerateHeaderPairs(HeaderPairs); for (TMap<FString,FString>::TConstIterator HeaderIt(HeaderPairs); HeaderIt; ++HeaderIt) { FileOutput += HeaderIt.Key() + TEXT(":") + HeaderIt.Value() + LINE_TERMINATOR; } FileOutput += LINE_TERMINATOR; // Now for the content if ( IsDynamic() ) { // @todo Dynamic collections } else { // Static collection. Save a flat list of all assets in the collection. for (int32 AssetIdx = 0; AssetIdx < AssetList.Num(); ++AssetIdx) { FileOutput += AssetList[AssetIdx].ToString() + 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 = LOCTEXT("Error_WriteFailed", "Failed to write to collection file."); UE_LOG(LogCollectionManager, Error, TEXT("%s %s"), *OutError.ToString(), *CollectionName.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 ) { DiskAssetList = AssetList; } GWarn->EndSlowTask(); UE_LOG(LogCollectionManager, Verbose, TEXT("Saved collection %s in %0.6f seconds"), *CollectionName.ToString(), FPlatformTime::Seconds() - SaveStartTime); return bSaveSuccessful; }