Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/PackageRestore.cpp
2025-05-18 13:04:45 +08:00

745 lines
24 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PackageRestore.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Layout/Margin.h"
#include "Input/Reply.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SBoxPanel.h"
#include "Styling/SlateTypes.h"
#include "Widgets/SWindow.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/STableViewBase.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/SListView.h"
#include "Widgets/Input/SCheckBox.h"
#include "Styling/AppStyle.h"
#include "Editor.h"
#include "Misc/MessageDialog.h"
#include "PackageTools.h"
#include "AutoSaveUtils.h"
#include "SWarningOrErrorBox.h"
#define LOCTEXT_NAMESPACE "PackageRestore"
namespace PackageRestore
{
/** An item in the SPackageRestoreDialog package list */
class FPackageRestoreItem : public TSharedFromThis<FPackageRestoreItem>
{
public:
FPackageRestoreItem(const FString& InPackageName, const FString& InPackageLabel, const FString& InPackageFilename, const FString& InAutoSaveFilename, const bool bInIsExistingPackage)
: PackageName(InPackageName)
, PackageLabel(InPackageLabel)
, PackageFilename(InPackageFilename)
, AutoSaveFilename(InAutoSaveFilename)
, bIsExistingPackage(bInIsExistingPackage)
, State(ECheckBoxState::Unchecked)
{
}
/** @return The package name for this item */
const FString& GetPackageName() const
{
return PackageName;
}
/** @return The package label for this item */
const FString& GetPackageLabel() const
{
return PackageLabel.IsEmpty() ? PackageName : PackageLabel;
}
/** @return The package filename for this item */
const FString& GetPackageFilename() const
{
return PackageFilename;
}
/** @return The package auto-save filename for this item */
const FString& GetAutoSaveFilename() const
{
return AutoSaveFilename;
}
/** @return True if this item is to replace an existing package, or false if it is to add a new package */
bool IsExistingPackage() const
{
return bIsExistingPackage;
}
/** @return The state of this item (checked, unchecked) */
ECheckBoxState GetState() const
{
return State;
}
/** Set the state of this item (checked, unchecked) */
void SetState(const ECheckBoxState InState)
{
State = InState;
}
/** @return The tooltip text for this item */
FText GetToolTip() const
{
FFormatNamedArguments Args;
Args.Add(TEXT("PackageName"), LOCTEXT("PackageName", "Package Name"));
Args.Add(TEXT("PackageFile"), LOCTEXT("PackageFile", "Package File"));
Args.Add(TEXT("AutoSaveFile"), LOCTEXT("AutoSaveFile", "Autosave File"));
Args.Add(TEXT("PackageNameStr"), FText::FromString(PackageName));
Args.Add(TEXT("PackageFileStr"), FText::FromString(PackageFilename));
Args.Add(TEXT("AutoSaveFileStr"), FText::FromString(AutoSaveFilename));
return FText::Format(FText::FromString("{PackageName}: {PackageNameStr}\n\n{PackageFile}: {PackageFileStr}\n\n{AutoSaveFile}: {AutoSaveFileStr}"), Args);
}
/** @return Process a request to navigate to the package location */
FReply OnExploreToPackage(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) const
{
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
const FString AbsolutePath = FPaths::GetPath(AbsoluteFilename);
FPlatformProcess::ExploreFolder(*AbsolutePath);
return FReply::Handled();
}
/** @return Process a request to navigate to the auto-save location */
FReply OnExploreToAutoSave(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) const
{
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(AutoSaveFilename);
const FString AbsolutePath = FPaths::GetPath(AbsoluteFilename);
FPlatformProcess::ExploreFolder(*AbsolutePath);
return FReply::Handled();
}
EVisibility GetRestoreOverMoreRecentPackageWarningVisibility() const
{
FString RestoreDstPathname = *FPaths::ConvertRelativePathToFull(PackageFilename);
if (IFileManager::Get().FileExists(*RestoreDstPathname))
{
FDateTime AutoSavedSrcModificationTime = IFileManager::Get().GetStatData(*FPaths::ConvertRelativePathToFull(AutoSaveFilename)).ModificationTime;
FDateTime RestoredDstModificationTime = IFileManager::Get().GetStatData(*RestoreDstPathname).ModificationTime;
return AutoSavedSrcModificationTime > RestoredDstModificationTime ? EVisibility::Collapsed : EVisibility::Visible;
}
return EVisibility::Collapsed; // The destination file doesn't exist, restoring the auto-saved file cannot overwrite anything.
}
private:
FString PackageName;
FString PackageLabel;
FString PackageFilename;
FString AutoSaveFilename;
bool bIsExistingPackage;
ECheckBoxState State;
};
typedef TSharedPtr<FPackageRestoreItem> FPackageRestoreItemPtr;
typedef TArray<FPackageRestoreItemPtr> FPackageRestoreItems;
const FName ColumnID_CheckBoxLabel("PackageCheckboxLabel");
const FName ColumnID_PackageLabel("PackageNameLabel");
const FName ColumnID_FileLabel("PackageFileLabel");
const FName ColumnID_SaveLabel("PackageAutosaveLabel");
/** Widget that represents a row in the PackageRestoreDialog's list view. Generates widgets for each column on demand. */
class SPackageRestoreItemsListRow
: public SMultiColumnTableRow< FPackageRestoreItemPtr >
{
public:
SLATE_BEGIN_ARGS(SPackageRestoreItemsListRow) {}
/** The list item for this row */
SLATE_ARGUMENT(FPackageRestoreItemPtr, Item)
SLATE_END_ARGS()
/** Construct function for this widget */
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
Item = InArgs._Item;
SMultiColumnTableRow< FPackageRestoreItemPtr >::Construct(
FSuperRowType::FArguments().Padding(FMargin(0, 3))
, InOwnerTableView);
}
/** Overridden from SMultiColumnTableRow. Generates a widget for this column of the list row. */
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override
{
check(Item.IsValid());
const FSlateBrush* FolderOpenBrush = FAppStyle::Get().GetBrush("PackageRestore.FolderOpen");
TSharedPtr<SWidget> ItemContentWidget;
if (ColumnName == ColumnID_CheckBoxLabel)
{
ItemContentWidget = SNew(SHorizontalBox)
.ToolTipText(Item->GetToolTip())
+ SHorizontalBox::Slot()
.Padding(7, 0, 2, 0)
.VAlign(VAlign_Center)
[
SNew(SCheckBox)
.IsChecked(Item.Get(), &FPackageRestoreItem::GetState)
.OnCheckStateChanged(Item.Get(), &FPackageRestoreItem::SetState)
];
}
else if (ColumnName == ColumnID_PackageLabel)
{
ItemContentWidget = SNew(SHorizontalBox)
.ToolTipText(Item->GetToolTip())
+ SHorizontalBox::Slot()
.Padding(FMargin(2, 0, 4, 0))
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.WarningWithColor"))
.DesiredSizeOverride(FVector2D(16, 16))
.ToolTipText(LOCTEXT("OverwritingMoreRecentPackageFile", "The auto-saved file is older than the file it restores. You could lose work if the file was updated or modified after the crash but before the Editor was restarted."))
.Visibility(Item.Get(), &FPackageRestoreItem::GetRestoreOverMoreRecentPackageWarningVisibility)
]
+ SHorizontalBox::Slot()
.Padding(FMargin(6, 0, 20, 0))
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
.FillWidth(1)
[
SNew(STextBlock)
.Text(FText::FromString(Item->GetPackageLabel()))
];
}
else if (ColumnName == ColumnID_FileLabel)
{
ItemContentWidget = SNew(SHorizontalBox)
.ToolTipText(Item->GetToolTip())
+ SHorizontalBox::Slot()
.Padding(FMargin(4, 0, 0, 0))
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
.FillWidth(1)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1)
[
SNew(STextBlock)
.Text(FText::FromString(Item->GetPackageFilename()))
]
+ SHorizontalBox::Slot()
.Padding(FMargin(11, 0, 20, 0))
.AutoWidth()
[
SNew(SImage)
.Image(FolderOpenBrush)
.OnMouseButtonDown(Item.Get(), &FPackageRestoreItem::OnExploreToPackage)
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
}
else if (ColumnName == ColumnID_SaveLabel)
{
ItemContentWidget = SNew(SHorizontalBox)
.ToolTipText(Item->GetToolTip())
+ SHorizontalBox::Slot()
.Padding(FMargin(4, 0, 0, 0))
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
.FillWidth(1)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1)
[
SNew(STextBlock)
.Text(FText::FromString(Item->GetAutoSaveFilename()))
]
+ SHorizontalBox::Slot()
.Padding(FMargin(11, 0, 40, 0))
.AutoWidth()
[
SNew(SImage)
.Image(FolderOpenBrush)
.OnMouseButtonDown(Item.Get(), &FPackageRestoreItem::OnExploreToAutoSave)
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
}
return ItemContentWidget.ToSharedRef();
}
private:
/** The item associated with this row of data */
FPackageRestoreItemPtr Item;
};
/** Dialog for letting the user choose which packages they want to restore */
class SPackageRestoreDialog : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SPackageRestoreDialog)
{}
/** Information about which packages to offer restoration for */
SLATE_ATTRIBUTE(FPackageRestoreItems*, PackageRestoreItems)
SLATE_END_ARGS()
/**
* Construct this widget
*
* @param InArgs The declaration data for this widget
*/
void Construct(const FArguments& InArgs)
{
PackageRestoreItems = InArgs._PackageRestoreItems.Get();
ReturnCode = false;
TSharedRef< SHeaderRow > HeaderRowWidget = SNew(SHeaderRow);
HeaderRowWidget->AddColumn(
SHeaderRow::Column(ColumnID_CheckBoxLabel)
[
SNew(SCheckBox)
.IsChecked(this, &SPackageRestoreDialog::GetToggleSelectedState)
.OnCheckStateChanged(this, &SPackageRestoreDialog::OnToggleSelectedCheckBox)
]
.FixedWidth(34.f)
.HAlignHeader(HAlign_Center)
);
HeaderRowWidget->AddColumn(
SHeaderRow::Column(ColumnID_PackageLabel)
.DefaultLabel(LOCTEXT("PackageName", "Package Name"))
.HeaderContentPadding(FMargin(8, 0, 0, 0))
);
HeaderRowWidget->AddColumn(
SHeaderRow::Column(ColumnID_FileLabel)
.DefaultLabel(LOCTEXT("PackageFile", "Package File"))
.HeaderContentPadding(FMargin(8, 0, 0, 0))
);
HeaderRowWidget->AddColumn(
SHeaderRow::Column(ColumnID_SaveLabel)
.DefaultLabel(LOCTEXT("AutoSaveFile", "Autosave File"))
.HeaderContentPadding(FMargin(8, 0, 0, 0))
);
this->ChildSlot
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.Padding(9, 13, 7, 16)
.AutoHeight()
[
SNew(SWarningOrErrorBox)
.MessageStyle(EMessageStyle::Warning)
.Message(LOCTEXT("RestoreInfo", "Unreal Editor detected that it did not shut-down cleanly and that the following packages have auto-saves associated with them.\nWould you like to restore from these auto-saves?"))
]
+SVerticalBox::Slot()
.FillHeight(1)
.Padding(8, 0)
[
SAssignNew(ItemListView, SListView<FPackageRestoreItemPtr>)
.ListItemsSource(PackageRestoreItems)
.OnGenerateRow(this, &SPackageRestoreDialog::MakePackageRestoreListItemWidget)
.HeaderRow(HeaderRowWidget)
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(2, 17, 26, 17)
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding(2)
.AutoWidth()
[
SNew(SButton)
.Text(LOCTEXT("RestoreSelectedPackages", "Restore Selected"))
.OnClicked(this, &SPackageRestoreDialog::OnRestoreSelectedButtonClicked)
.IsEnabled(this, &SPackageRestoreDialog::IsRestoreSelectedButtonEnabled)
]
+SHorizontalBox::Slot()
.Padding(7, 2, 2, 2)
.AutoWidth()
[
SNew(SButton)
.Text(LOCTEXT("SkipRestorePackages", "Skip Restore"))
.OnClicked(this, &SPackageRestoreDialog::OnSkipRestoreButtonClicked)
]
]
]
];
}
/**
* Set the window which owns us (we'll close it when we're finished)
*/
void SetWindow(TSharedRef<SWindow> InWindow)
{
ParentWindowPtr = InWindow;
}
/**
* Makes the widget for the checkbox items in the list view
*/
TSharedRef<ITableRow> MakePackageRestoreListItemWidget(FPackageRestoreItemPtr Item, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(SPackageRestoreItemsListRow, OwnerTable)
.Item(Item);
}
/**
* @return the desired toggle state for the ToggleSelectedCheckBox.
* Returns Unchecked, unless all of the selected packages are Checked.
*/
ECheckBoxState GetToggleSelectedState() const
{
// Default to a Checked state
ECheckBoxState PendingState = ECheckBoxState::Checked;
for(auto It = PackageRestoreItems->CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& ListItem = *It;
if(ListItem->GetState() == ECheckBoxState::Unchecked)
{
// If any package in the selection is Unchecked, then represent the entire set of highlighted packages as Unchecked,
// so that the first (user) toggle of ToggleSelectedCheckBox consistently Checks all highlighted packages
PendingState = ECheckBoxState::Unchecked;
}
}
return PendingState;
}
/**
* Toggles the highlighted packages.
* If no packages are explicitly highlighted, toggles all packages in the list.
*/
void OnToggleSelectedCheckBox(ECheckBoxState InNewState)
{
for(auto It = PackageRestoreItems->CreateIterator(); It; ++It)
{
FPackageRestoreItemPtr& ListItem = *It;
ListItem->SetState(InNewState);
}
ItemListView->RequestListRefresh();
}
/**
* Check to see if the "Restore Selected" button should be enabled
*/
bool IsRestoreSelectedButtonEnabled() const
{
// Enabled if anything is selected
for(auto It = PackageRestoreItems->CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& ListItem = *It;
if(ListItem->GetState() == ECheckBoxState::Checked)
{
return true;
}
}
return false;
}
/**
* Called when the "Restore Selected" button is clicked
*/
FReply OnRestoreSelectedButtonClicked()
{
ReturnCode = true;
if(ParentWindowPtr.IsValid())
{
TSharedPtr<SWindow> ParentWindowPin = ParentWindowPtr.Pin();
ParentWindowPin->RequestDestroyWindow();
}
return FReply::Handled();
}
/**
* Called when the "Skip Restore" button is clicked
*/
FReply OnSkipRestoreButtonClicked()
{
if(ParentWindowPtr.IsValid())
{
TSharedPtr<SWindow> ParentWindowPin = ParentWindowPtr.Pin();
ParentWindowPin->RequestDestroyWindow();
}
return FReply::Handled();
}
/**
* Get the return code for this dlg, as well as some useful information about what was selected
*
* @param SelectedPackageItems Array to fill with the list items the user wants to restore
*
* @return true if we should perform an import, false if the user cancelled
*/
bool GetReturnType(FPackageRestoreItems& SelectedPackageItems) const
{
SelectedPackageItems.Empty();
SelectedPackageItems.Reserve(PackageRestoreItems->Num());
// Get the list of packages selected to be restored
for(auto It = PackageRestoreItems->CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& ListItem = *It;
if(ListItem->GetState() == ECheckBoxState::Checked)
{
SelectedPackageItems.Add(ListItem);
}
}
return ReturnCode;
}
private:
FPackageRestoreItems* PackageRestoreItems;
TWeakPtr<SWindow> ParentWindowPtr;
TSharedPtr< SListView<FPackageRestoreItemPtr> > ItemListView;
bool ReturnCode;
};
void UnloadPackagesBeforeRestore(const FPackageRestoreItems& SelectedPackageItems, FPackageRestoreItems& OutContentPackagesToReload, FPackageRestoreItemPtr& OutWorldPackageToReload)
{
// Get the package for the currently loaded world; if we need to restore this package then we also need to unload the current world
UPackage* const CurrentWorldPackage = CastChecked<UPackage>(GWorld->GetOuter());
// Work out a list of content packages that need unloading, also work out if we need to unload the current world
TArray<UPackage*> PackagesToUnload;
FPackageRestoreItemPtr CurrentWorldRestoreItem;
for(auto It = SelectedPackageItems.CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& RestoreItem = *It;
if(!RestoreItem->IsExistingPackage())
{
continue;
}
UPackage* const Package = FindPackage(nullptr, *RestoreItem->GetPackageName());
if(Package)
{
const bool bIsContentPackage = RestoreItem->GetPackageFilename().EndsWith(FPackageName::GetAssetPackageExtension());
if(bIsContentPackage)
{
// Add this package to the list to be reloaded once we've restored everything
PackagesToUnload.Add(Package);
OutContentPackagesToReload.Add(RestoreItem);
}
else if(Package == CurrentWorldPackage)
{
// If this is the current world, we also need to unload it
CurrentWorldRestoreItem = RestoreItem;
}
}
}
if(CurrentWorldRestoreItem.IsValid())
{
// Replace the current world with an empty world (this may fail)
GEditor->CreateNewMapForEditing();
// See if our world package has been unloaded
UPackage* const EmptyWorldPackage = CastChecked<UPackage>(GWorld->GetOuter());
if(CurrentWorldPackage != EmptyWorldPackage)
{
OutWorldPackageToReload = CurrentWorldRestoreItem;
// If we can still find the package for the old world, forcibly unload it too
UPackage* const Package = FindPackage(nullptr, *CurrentWorldRestoreItem->GetPackageName());
if(Package)
{
PackagesToUnload.Add(Package);
}
}
}
UPackageTools::UnloadPackages(PackagesToUnload);
}
void ReloadPackagesAfterRestore(const FPackageRestoreItems& ContentPackagesToReload, const FPackageRestoreItemPtr& WorldPackageToReload)
{
// Reload any content packages that we unloaded to perform the restore
for(auto It = ContentPackagesToReload.CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& RestoreItem = *It;
UPackageTools::LoadPackage(*RestoreItem->GetPackageName());
}
// Also reload the current world if we caused it to be unloaded
if(WorldPackageToReload.IsValid())
{
FEditorFileUtils::LoadMap(WorldPackageToReload->GetPackageFilename());
}
}
}
FEditorFileUtils::EPromptReturnCode PackageRestore::PromptToRestorePackages(const TMap<FString, TPair<FString, FString>>& PackagesToRestore, TArray<FString>* OutFailedPackages)
{
const FString AutoSaveDir = AutoSaveUtils::GetAutoSaveDir();
FPackageRestoreItems PackageRestoreItems;
PackageRestoreItems.Reserve(PackagesToRestore.Num());
for(auto It = PackagesToRestore.CreateConstIterator(); It; ++It)
{
const FString& PackageFullPath = It.Key();
const FString& PackageAssetLabel = It.Value().Key;
const FString& AutoSavePath = It.Value().Value;
FString PackageFilename;
if(FPackageName::DoesPackageExist(PackageFullPath, &PackageFilename))
{
FPackageRestoreItemPtr PackageItemPtr = MakeShared<FPackageRestoreItem>(PackageFullPath, PackageAssetLabel, PackageFilename, AutoSaveDir / AutoSavePath, true/*bIsExistingPackage*/);
PackageRestoreItems.Add(PackageItemPtr);
}
else
{
// A package may not exist on disk if it was for a newly added or imported asset, which hasn't yet had SaveDirtyPackages called for it
if (FPackageName::TryConvertLongPackageNameToFilename(PackageFullPath, PackageFilename)) // no extension yet
{
PackageFilename += FPaths::GetExtension(AutoSavePath, true/*bIncludeDot*/);
FPackageRestoreItemPtr PackageItemPtr = MakeShared<FPackageRestoreItem>(PackageFullPath, PackageAssetLabel, PackageFilename, AutoSaveDir / AutoSavePath, false/*bIsExistingPackage*/);
PackageRestoreItems.Add(PackageItemPtr);
}
}
}
if(!PackageRestoreItems.Num())
{
// Nothing to restore
return FEditorFileUtils::PR_Success;
}
// Create the dlg to ask the user which packages to restore
TSharedRef<SPackageRestoreDialog> PackageRestoreDlgRef = SNew(SPackageRestoreDialog)
.PackageRestoreItems(&PackageRestoreItems);
// Create the window to host our dlg
TSharedRef<SWindow> PackageRestoreWindowRef = SNew(SWindow)
.Title(LOCTEXT("RestorePackages", "Restore Packages"))
.ClientSize(FVector2D(1000, 550));
PackageRestoreWindowRef->SetContent(PackageRestoreDlgRef);
PackageRestoreDlgRef->SetWindow(PackageRestoreWindowRef);
// Show the dlg in a modal window so we can wait for the result in this function
GEditor->EditorAddModalWindow(PackageRestoreWindowRef);
// Get the return code, and work out what we need to restore
FPackageRestoreItems SelectedPackageItems;
if(!PackageRestoreDlgRef->GetReturnType(SelectedPackageItems))
{
return FEditorFileUtils::PR_Declined;
}
// Try and ensure that these packages are checked-out by the source control system
{
TArray<FString> SelectedPackageNames;
SelectedPackageNames.Reserve(SelectedPackageItems.Num());
// Get an array of selected package names to check out
for(auto It = SelectedPackageItems.CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& SelectedPackageItem = *It;
if(SelectedPackageItem->IsExistingPackage())
{
SelectedPackageNames.Add(SelectedPackageItem->GetPackageName());
}
}
// Note: This may fail and present the user with an error message, however we still
// want to continue as they may have checked out some packages that could now be restored
const bool bErrorIfAlreadyCheckedOut = false; // some of the packages might already be checked out; that isn't an error
FEditorFileUtils::CheckoutPackages(SelectedPackageNames, nullptr, bErrorIfAlreadyCheckedOut);
}
// It's possible that some packages may have already been loaded by the editor
// If they have, we need to forcibly unload them so that we can overwrite their files
FPackageRestoreItems ContentPackagesToReload;
FPackageRestoreItemPtr WorldPackageToReload;
UnloadPackagesBeforeRestore(SelectedPackageItems, ContentPackagesToReload, WorldPackageToReload);
// Copy the auto-save files over the originals
TArray<FString> FailedPackages;
for(auto It = SelectedPackageItems.CreateConstIterator(); It; ++It)
{
const FPackageRestoreItemPtr& SelectedItem = *It;
if(IFileManager::Get().Copy(*SelectedItem->GetPackageFilename(), *SelectedItem->GetAutoSaveFilename()) != COPY_OK)
{
FailedPackages.Add(SelectedItem->GetPackageName());
}
}
// Reload any packages that we unloaded above
ReloadPackagesAfterRestore(ContentPackagesToReload, WorldPackageToReload);
if(FailedPackages.Num())
{
if(OutFailedPackages)
{
*OutFailedPackages = FailedPackages;
}
FString FailedPackagesStr;
for(auto It = FailedPackages.CreateConstIterator(); It; ++It)
{
const FString& PackageName = *It;
if(It.GetIndex() > 0)
{
FailedPackagesStr += "\n";
}
FailedPackagesStr += PackageName;
}
FFormatNamedArguments Args;
Args.Add(TEXT("FailedRestoreMessage"), LOCTEXT("FailedRestoreMessage", "The following packages could not be restored"));
Args.Add(TEXT("FailedPackages"), FText::FromString(FailedPackagesStr));
const FText Message = FText::Format(FText::FromString("{FailedRestoreMessage}:\n{FailedPackages}"), Args);
const FText Title = LOCTEXT("FailedRestoreDlgTitle", "Failed to restore packages!");
FMessageDialog::Open(EAppMsgType::Ok, Message, Title);
return FEditorFileUtils::PR_Failure;
}
return FEditorFileUtils::PR_Success;
}
#undef LOCTEXT_NAMESPACE