Files
UnrealEngine/Engine/Source/Programs/CrashReportClient/Private/RecoveryService.cpp
2025-05-18 13:04:45 +08:00

141 lines
5.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RecoveryService.h"
#if CRASH_REPORT_WITH_RECOVERY
#include "HAL/FileManager.h"
#include "CrashReportClient.h" // For CrashReportClientLog
#include "Interfaces/IPluginManager.h"
#include "IMessagingModule.h"
#include "ConcertSettings.h"
#include "ConcertSyncSessionFlags.h"
#include "IConcertServer.h"
#include "IConcertSession.h"
#include "IConcertSyncServer.h"
#include "IConcertSyncServerModule.h"
#include "ConcertMessageData.h"
#include "Runtime/Launch/Resources/Version.h"
#include "ConcertLocalFileSharingService.h"
static const TCHAR RecoveryServiceName[] = TEXT("Disaster Recovery Service");
bool FRecoveryService::CollectFiles(const FString& DestDir, bool bMetaDataOnly, bool bAnonymizeMetaData)
{
auto LogError = [](const TCHAR* Reason)
{
UE_LOG(CrashReportClientLog, Error, TEXT("Failed to collect recovery session file(s). %s"), Reason);
};
if (!Server)
{
LogError(TEXT("The recovery service is not running."));
return false;
}
else if (!IFileManager::Get().DirectoryExists(*DestDir))
{
LogError(TEXT("The destination folder doesn't exist."));
return false;
}
FGuid ExportedSessionId = GetRecoverySessionId();
if (!ExportedSessionId.IsValid())
{
LogError(TEXT("The session session could not be found."));
return false;
}
FText ErrorMsg;
FConcertSessionFilter Filter;
Filter.bMetaDataOnly = bMetaDataOnly;
if (!Server->GetConcertServer()->ExportSession(ExportedSessionId, Filter, DestDir, bAnonymizeMetaData, ErrorMsg))
{
LogError(TEXT("Server failed to export the session."));
return false;
}
return true;
}
bool FRecoveryService::Startup()
{
#if UE_BUILD_SHIPPING && (!defined(PLATFORM_SUPPORTS_MESSAGEBUS) || !PLATFORM_SUPPORTS_MESSAGEBUS)
#error PLATFORM_SUPPORTS_MESSAGEBUS was explicitly defined in CrashReportClient.Target.cs for shipping configuration. MessageBus is required by Concert. Ensure it is still enabled.
#endif
if (!IMessagingModule::Get().GetDefaultBus())
{
UE_LOG(CrashReportClientLog, Error, TEXT("MessageBus is not enabled in this configuration. Recovery service will be disabled!"));
return false;
}
if (!IConcertSyncServerModule::IsAvailable())
{
UE_LOG(CrashReportClientLog, Error, TEXT("ConcertSyncServer Module is missing. Recovery service will be disabled!"));
return false;
}
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("UdpMessaging"));
if (!Plugin || !Plugin->IsEnabled())
{
// The UdpMessaging plugin should be added to the {appname}.Target.cs build file.
UE_LOG(CrashReportClientLog, Error, TEXT("The 'UDP Messaging' plugin is disabled. The Concert server only supports UDP protocol. Recovery service will be disabled!"));
return false;
}
// Setup the disaster recovery server configuration
UConcertServerConfig* ServerConfig = IConcertSyncServerModule::Get().ParseServerSettings(FCommandLine::Get());
ServerConfig->bAutoArchiveOnReboot = false; // Skip archiving, disaster recovery restore a live session by copying it, this saves the step of archiving.
ServerConfig->bAutoArchiveOnShutdown = false; // Skip archiving, this can takes several minutes. It is more efficient to let the session 'live' and 'copy' it when restoring.
ServerConfig->EndpointSettings.RemoteEndpointTimeoutSeconds = 0; // Ensure the endpoints never time out (and are kept alive automatically by Concert).
ServerConfig->bMountDefaultSessionRepository = false; // Let the client mount its own repository to support concurrent recovery server and prevent them from concurrently accessing non-sharable database files.
ServerConfig->AuthorizedClientKeys.Add(ServerConfig->ServerName); // The disaster recovery client is configured to use the unique server name as key to identify itself.
FConcertSessionFilter AutoArchiveSessionFilter;
AutoArchiveSessionFilter.bIncludeIgnoredActivities = true;
// Start disaster recovery server.
Server = IConcertSyncServerModule::Get().CreateServer(TEXT("DisasterRecovery"), AutoArchiveSessionFilter);
Server->SetFileSharingService(MakeShared<FConcertLocalFileSharingService>(Server->GetConcertServer()->GetRole()));
Server->Startup(ServerConfig, EConcertSyncSessionFlags::Default_DisasterRecoverySession);
UE_LOG(CrashReportClientLog, Display, TEXT("%s Initialized (Name: %s, Version: %d.%d, Role: %s)"), RecoveryServiceName, *Server->GetConcertServer()->GetServerInfo().ServerName, ENGINE_MAJOR_VERSION, ENGINE_MINOR_VERSION, *Server->GetConcertServer()->GetRole());
return true;
}
void FRecoveryService::Shutdown()
{
if (Server)
{
Server->Shutdown();
Server.Reset();
UE_LOG(CrashReportClientLog, Display, TEXT("%s Shutdown"), RecoveryServiceName);
}
}
FGuid FRecoveryService::GetRecoverySessionId() const
{
FGuid SessionId;
int32 SessionSeqNum = -1;
// As long as the Concert server is up, the session would remain live (it's going to be archived when the server shutdown or reboot).
for (TSharedPtr<IConcertServerSession>& Session : Server->GetConcertServer()->GetSessions())
{
// As convention, the disaster recovery session names starts with the server name, followed by a sequence number, the project name and date time. (See RecoveryService::MakeSessionName())
if (Session->GetName().StartsWith(Server->GetConcertServer()->GetServerInfo().ServerName))
{
// The user may have enabled/disabled the recovery service few times and as result, several live sessions will be available. Need to pick the last one. The highest sequence number
// in the session name corresponds to the last session created.
int32 SeqNum = 0;
RecoveryService::TokenizeSessionName(Session->GetName(), nullptr, &SeqNum, nullptr, nullptr);
if (SeqNum > SessionSeqNum)
{
SessionId = Session->GetId();
}
}
}
return SessionId; // Uninitialized Guid (invalid) means not found.
}
#endif