void FBuildPatchAppManifest::GetOutdatedFiles(FBuildPatchAppManifestPtr OldManifest, FBuildPatchAppManifestRef NewManifest, const FString& InstallDirectory, TArray< FString >& OutDatedFiles) { if (!OldManifest.IsValid()) { // All files are outdated if no OldManifest NewManifest->FileManifestLookup.GetKeys(OutDatedFiles); } else { // Enumerate files in the NewManifest file list, that do not exist, or have different hashes in the OldManifest // to be files no longer required by the build for (const auto& NewFile : NewManifest->Data->FileManifestList) { const int64 ExistingFileSize = IFileManager::Get().FileSize(*(InstallDirectory / NewFile.Filename)); // Check changed if (IsFileOutdated(OldManifest.ToSharedRef(), NewManifest, NewFile.Filename)) { OutDatedFiles.Add(NewFile.Filename); } // Double check an unchanged file is not missing (size will be -1) or is incorrect size else if (ExistingFileSize != NewFile.GetFileSize()) { OutDatedFiles.Add(NewFile.Filename); } } } }
IBuildInstallerPtr FBuildPatchServicesModule::StartBuildInstall( IBuildManifestPtr CurrentManifest, IBuildManifestPtr InstallManifest, const FString& InstallDirectory, FBuildPatchBoolManifestDelegate OnCompleteDelegate ) { // Using a local bool for this check will improve the assert message that gets displayed const bool bIsCalledFromMainThread = IsInGameThread(); check( bIsCalledFromMainThread ); // Cast manifest parameters FBuildPatchAppManifestPtr CurrentManifestInternal = StaticCastSharedPtr< FBuildPatchAppManifest >( CurrentManifest ); FBuildPatchAppManifestPtr InstallManifestInternal = StaticCastSharedPtr< FBuildPatchAppManifest >( InstallManifest ); if( !InstallManifestInternal.IsValid() ) { // We must have an install manifest to continue return NULL; } // Make directory IFileManager::Get().MakeDirectory( *InstallDirectory, true ); if( !IFileManager::Get().DirectoryExists( *InstallDirectory ) ) { return NULL; } // Make sure the http wrapper is already created FBuildPatchHTTP::Initialize(); // Run the install thread BuildPatchInstallers.Add( MakeShareable( new FBuildPatchInstaller( OnCompleteDelegate, CurrentManifestInternal, InstallManifestInternal.ToSharedRef(), InstallDirectory, GetStagingDirectory(), InstallationInfo, false ) ) ); return BuildPatchInstallers.Top(); }
/* FBuildPatchChunkCache implementation *****************************************************************************/ FBuildPatchChunkCache::FBuildPatchChunkCache( const FBuildPatchAppManifestRef& InInstallManifet, const FBuildPatchAppManifestPtr& InCurrentManifest, const FString& InChunkCacheStage, const FString& InCurrentInstallDir, FBuildPatchProgress* InBuildProgress, TArray<FString>& FilesToConstruct, FBuildPatchInstallationInfo& InstallationInfoRef ) : ChunkCacheStage(InChunkCacheStage) , CurrentInstallDir( InCurrentInstallDir ) , InstallManifet( InInstallManifet ) , CurrentManifest( InCurrentManifest ) , BuildProgress( InBuildProgress ) , bEnableChunkRecycle( InCurrentManifest.IsValid() ) , bDownloadsStarted( 0 ) , SkippedChunkDownloadSize( 0 ) , InstallationInfo( InstallationInfoRef ) { // Setup chunk information InstallManifet->GetChunksRequiredForFiles( FilesToConstruct, FullChunkRefList, false ); for( int32 FullChunkRefListIdx = FullChunkRefList.Num() - 1; FullChunkRefListIdx >= 0; --FullChunkRefListIdx ) { const FGuid& ChunkGuid = FullChunkRefList[ FullChunkRefListIdx ]; ChunkOrigins.Add( ChunkGuid, EChunkOrigin::Download ); if( ChunkUseOrderStack.Num() == 0 || ChunkUseOrderStack.Last() != ChunkGuid ) { ChunkUseOrderStack.Push( ChunkGuid ); } } // Get unique list of downloads InstallManifet->GetChunksRequiredForFiles( FilesToConstruct, ChunksToDownload, true ); // Check for chunks that can be constructed locally TArray< FGuid > ChunksToConstruct; InstallationInfo.EnumerateProducibleChunks(FullChunkRefList, ChunksToConstruct); for( auto ChunksToConstructIt = ChunksToConstruct.CreateConstIterator(); ChunksToConstructIt; ++ChunksToConstructIt ) { const FGuid& ChunkGuid = *ChunksToConstructIt; ChunkOrigins.Add( ChunkGuid, EChunkOrigin::Recycle ); ChunksToDownload.Remove( ChunkGuid ); } // Init chunk stats NumFilesToConstruct = FilesToConstruct.Num(); NumChunksToDownload = ChunksToDownload.Num(); NumChunksToRecycle = ChunksToConstruct.Num(); NumRequiredChunks = NumChunksToDownload + NumChunksToRecycle; TotalChunkDownloadSize = InstallManifet->GetDataSize(ChunksToDownload); NumChunksRecycled.Reset(); NumChunksCacheBooted.Reset(); NumDriveCacheChunkLoads.Reset(); NumRecycleFailures.Reset(); NumDriveCacheLoadFailures.Reset(); }
bool FBuildPatchChunkCache::RecycleChunkFromBuild( const FGuid& ChunkGuid ) { // Must never double acquire check( ChunkCache.Contains( ChunkGuid ) == false ); // Debug leaving any files open bool bSuccess = true; // Get the app manifest that this chunk can be sourced from FBuildPatchAppManifestPtr ChunkSourceAppManifest = InstallationInfo.GetManifestContainingChunk(ChunkGuid); if (!ChunkSourceAppManifest.IsValid()) { return false; } // Get the install directory for this manifest const FString ChunkSourceInstallDir = InstallationInfo.GetManifestInstallDir(ChunkSourceAppManifest); if(ChunkSourceInstallDir.Len() <= 0) { return false; } // We need to generate an inventory of all chunk parts in this build that refer to the chunk that we require TMap< FGuid, TArray< FFileChunkPart > > ChunkPartInventory; TArray< FGuid > Array; Array.Add( ChunkGuid ); ChunkSourceAppManifest->EnumerateChunkPartInventory(Array, ChunkPartInventory); // Attempt construction of the chunk from the parts FArchive* BuildFileIn = NULL; FString BuildFileOpened; int64 BuildFileInSize = 0; // We must have a hash for this chunk or else we cant verify it uint8 HashType = 0; uint64 ChunkHash = 0; FSHAHashData ChunkShaHash; if (InstallManifet->GetChunkShaHash(ChunkGuid, ChunkShaHash)) { HashType = FChunkHeader::HASH_SHA1; } else if (ChunkSourceAppManifest->GetChunkHash(ChunkGuid, ChunkHash)) { HashType = FChunkHeader::HASH_ROLLING; } TArray< FFileChunkPart >* FileChunkPartsPtr = ChunkPartInventory.Find( ChunkGuid ); bSuccess = (FileChunkPartsPtr != NULL && HashType != 0); if( bSuccess ) { const TArray< FFileChunkPart >& FileChunkParts = *FileChunkPartsPtr; TArray< uint8 > TempArray; TempArray.AddUninitialized( FBuildPatchData::ChunkDataSize ); uint8* TempChunkConstruction = TempArray.GetData(); FMemory::Memzero( TempChunkConstruction, FBuildPatchData::ChunkDataSize ); bSuccess = FileChunkParts.Num() > 0; for( auto FileChunkPartIt = FileChunkParts.CreateConstIterator(); FileChunkPartIt && bSuccess && !FBuildPatchInstallError::HasFatalError(); ++FileChunkPartIt ) { const FFileChunkPart& FileChunkPart = *FileChunkPartIt; FString FullFilename = ChunkSourceInstallDir / FileChunkPart.Filename; // Close current build file ? if( BuildFileIn != NULL && BuildFileOpened != FullFilename ) { BuildFileIn->Close(); delete BuildFileIn; BuildFileIn = NULL; BuildFileOpened = TEXT( "" ); BuildFileInSize = 0; } // Open build file ? if( BuildFileIn == NULL ) { BuildFileIn = IFileManager::Get().CreateFileReader( *FullFilename ); bSuccess = BuildFileIn != NULL; if( !bSuccess ) { BuildFileOpened = TEXT( "" ); FBuildPatchAnalytics::RecordChunkCacheError( ChunkGuid, FileChunkPart.Filename, FPlatformMisc::GetLastError(), TEXT( "ChunkRecycle" ), TEXT( "Source File Missing" ) ); GWarn->Logf( TEXT( "BuildPatchChunkConstruction: Warning: Failed to load source file for chunk. %s" ), *FullFilename ); } else { BuildFileOpened = FullFilename; BuildFileInSize = BuildFileIn->TotalSize(); } } // Grab the section of the chunk if( BuildFileIn != NULL ) { // Make sure we don't attempt to read off the end of the file const int64 LastRequiredByte = FileChunkPart.FileOffset + FileChunkPart.ChunkPart.Size; if( BuildFileInSize >= LastRequiredByte ) { BuildFileIn->Seek( FileChunkPart.FileOffset ); BuildFileIn->Serialize( TempChunkConstruction + FileChunkPart.ChunkPart.Offset, FileChunkPart.ChunkPart.Size ); } else { bSuccess = false; FBuildPatchAnalytics::RecordChunkCacheError( ChunkGuid, FileChunkPart.Filename, INDEX_NONE, TEXT( "ChunkRecycle" ), TEXT( "Source File Too Small" ) ); GWarn->Logf( TEXT( "BuildPatchChunkConstruction: Warning: Source file too small for chunk position. %s" ), *FullFilename ); } } } // Check no other fatal errors were registered in the meantime bSuccess = bSuccess && !FBuildPatchInstallError::HasFatalError(); // Check chunk hash if( bSuccess ) { FSHAHashData ShaHashCheck; switch (HashType) { case FChunkHeader::HASH_ROLLING: bSuccess = FRollingHash< FBuildPatchData::ChunkDataSize >::GetHashForDataSet(TempChunkConstruction) == ChunkHash; break; case FChunkHeader::HASH_SHA1: FSHA1::HashBuffer(TempChunkConstruction, FBuildPatchData::ChunkDataSize, ShaHashCheck.Hash); bSuccess = ShaHashCheck == ChunkShaHash; break; default: bSuccess = false; } if( !bSuccess ) { FBuildPatchAnalytics::RecordChunkCacheError( ChunkGuid, TEXT( "" ), INDEX_NONE, TEXT( "ChunkRecycle" ), TEXT( "Chunk Hash Fail" ) ); GWarn->Logf( TEXT( "BuildPatchChunkConstruction: Warning: Hash check failed for recycled chunk %s" ), *ChunkGuid.ToString() ); } } // Save the chunk to cache if all went well if( bSuccess ) { // It was added asynchronously!! check( ChunkCache.Contains( ChunkGuid ) == false ); // Create the ChunkFile data structure FChunkFile* NewChunkFile = new FChunkFile( GetRemainingReferenceCount( ChunkGuid ), true ); // Lock data FChunkHeader* ChunkHeader; uint8* ChunkData; NewChunkFile->GetDataLock( &ChunkData, &ChunkHeader ); // Copy the data FMemoryReader MemReader( TempArray ); MemReader.Serialize( ChunkData, FBuildPatchData::ChunkDataSize ); // Setup the header ChunkHeader->Guid = ChunkGuid; ChunkHeader->StoredAs = FChunkHeader::STORED_RAW; ChunkHeader->DataSize = FBuildPatchData::ChunkDataSize; // This would change if compressing/encrypting ChunkHeader->HashType = HashType; ChunkHeader->RollingHash = ChunkHash; ChunkHeader->SHAHash = ChunkShaHash; // Release data NewChunkFile->ReleaseDataLock(); // Count chunk NumChunksRecycled.Increment(); // Add it to our cache. ChunkCache.Add( ChunkGuid, NewChunkFile ); } // Close any open file if( BuildFileIn != NULL ) { BuildFileIn->Close(); delete BuildFileIn; BuildFileIn = NULL; } } return bSuccess; }