// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using Tools.DotNETCommon;
using Tools.DotNETCommon.Perforce;
namespace BuildAgent.Workspace.Common
{
///
/// Represents a file within a stream
///
class StreamFileInfo
{
///
/// Name of this file
///
public readonly string Name;
///
/// Length of the file, as reported by the server (actual size on disk may be different due to workspace options).
///
public readonly long Length;
///
/// Content id for this file
///
public readonly FileContentId ContentId;
///
/// The parent directory
///
public readonly StreamDirectoryInfo Directory;
///
/// The depot file and revision that need to be synced for this file
///
public readonly string DepotFileAndRevision;
///
/// Constructor
///
/// Name of this file
/// Length of the file on the server
/// Content id for this file
/// The parent directory
/// The depot file and revision that need to be synced for this file
public StreamFileInfo(string Name, long Length, FileContentId ContentId, StreamDirectoryInfo Directory, string DepotFileAndRevision)
{
this.Name = Name;
this.Length = Length;
this.ContentId = ContentId;
this.Directory = Directory;
this.DepotFileAndRevision = DepotFileAndRevision;
}
///
/// Constructor for reading a file info from disk
///
/// Binary reader to read data from
/// Parent directory
public StreamFileInfo(BinaryReader Reader, StreamDirectoryInfo Directory)
{
this.Name = Reader.ReadString();
this.Length = Reader.ReadInt64();
this.ContentId = new FileContentId(Reader);
this.Directory = Directory;
this.DepotFileAndRevision = Reader.ReadString();
}
///
/// Save the file info to disk
///
/// Writer to output to
public void Write(BinaryWriter Writer)
{
Writer.Write(Name);
Writer.Write(Length);
ContentId.Write(Writer);
Writer.Write(DepotFileAndRevision);
}
///
/// Get the path to this file relative to the root of the stream
///
/// Relative path to the file
public string GetRelativePath()
{
StringBuilder Builder = new StringBuilder();
Directory.AppendPath(Builder);
Builder.Append(Name);
return Builder.ToString();
}
///
/// Format the path to the file for the debugger
///
/// Path to the file
public override string ToString()
{
return GetRelativePath();
}
}
///
/// Information about a directory within a stream
///
class StreamDirectoryInfo
{
///
/// The current signature for saved directory objects
///
static readonly byte[] CurrentSignature = { (byte)'W', (byte)'S', (byte)'D', 1 };
///
/// The directory name
///
public readonly string Name;
///
/// The parent directory
///
public readonly StreamDirectoryInfo ParentDirectory;
///
/// Map of name to file within the directory
///
public Dictionary NameToFile = new Dictionary(StringComparer.Ordinal);
///
/// Map of name to subdirectory
///
public Dictionary NameToSubDirectory = new Dictionary(FileUtils.PlatformPathComparer);
///
/// Constructor
///
public StreamDirectoryInfo()
: this("", null)
{
}
///
/// Constructor
///
/// Name of the directory
/// Parent directory
public StreamDirectoryInfo(string Name, StreamDirectoryInfo ParentDirectory)
{
this.Name = Name;
this.ParentDirectory = ParentDirectory;
}
///
/// Constructor for reading from disk
///
/// Reader to read data from
/// The parent directory to initialize this directory with
public StreamDirectoryInfo(BinaryReader Reader, StreamDirectoryInfo ParentDirectory)
{
this.Name = Reader.ReadString();
this.ParentDirectory = ParentDirectory;
int NumFiles = Reader.ReadInt32();
for(int Idx = 0; Idx < NumFiles; Idx++)
{
StreamFileInfo File = new StreamFileInfo(Reader, this);
NameToFile.Add(File.Name, File);
}
int NumDirectories = Reader.ReadInt32();
for(int Idx = 0; Idx < NumDirectories; Idx++)
{
StreamDirectoryInfo SubDirectory = new StreamDirectoryInfo(Reader, this);
NameToSubDirectory.Add(SubDirectory.Name, SubDirectory);
}
}
///
/// Writes the contents of this stream to disk
///
/// Writer to serialize to
public void Write(BinaryWriter Writer)
{
Writer.Write(Name);
Writer.Write(NameToFile.Count);
foreach(StreamFileInfo File in NameToFile.Values)
{
File.Write(Writer);
}
Writer.Write(NameToSubDirectory.Count);
foreach(StreamDirectoryInfo SubDirectory in NameToSubDirectory.Values)
{
SubDirectory.Write(Writer);
}
}
///
/// Load a stream directory from a file on disk
///
/// File to read from
/// New StreamDirectoryInfo object
public static StreamDirectoryInfo Load(FileReference InputFile)
{
using(FileStream Stream = File.Open(InputFile.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
byte[] Signature = new byte[CurrentSignature.Length];
if(Stream.Read(Signature, 0, CurrentSignature.Length) != CurrentSignature.Length)
{
throw new InvalidDataException(String.Format("Unable to read signature bytes from {0}", InputFile));
}
if(!Enumerable.SequenceEqual(Signature, CurrentSignature))
{
throw new InvalidDataException(String.Format("Cached stream contents at {0} has incorrect signature", InputFile));
}
using(GZipStream CompressedStream = new GZipStream(Stream, CompressionMode.Decompress))
{
using(BinaryReader Reader = new BinaryReader(CompressedStream, Encoding.UTF8, true))
{
return new StreamDirectoryInfo(Reader, null);
}
}
}
}
///
/// Saves the contents of this object to disk
///
/// The output file to write to
public void Save(FileReference OutputFile)
{
using(FileStream Stream = File.Open(OutputFile.FullName, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
{
Stream.Write(CurrentSignature, 0, CurrentSignature.Length);
using(GZipStream CompressedStream = new GZipStream(Stream, CompressionMode.Compress))
{
using(BinaryWriter Writer = new BinaryWriter(CompressedStream, Encoding.UTF8, true))
{
Write(Writer);
}
}
}
}
///
/// Get all the files in this directory
///
/// List of files
public List GetFiles()
{
List Files = new List();
AppendFiles(Files);
return Files;
}
///
/// Append the contents of this directory and subdirectories to a list
///
/// List to append to
public void AppendFiles(List Files)
{
foreach(StreamDirectoryInfo SubDirectory in NameToSubDirectory.Values)
{
SubDirectory.AppendFiles(Files);
}
Files.AddRange(NameToFile.Values);
}
///
/// Gets the relative path to this directory, with a trailing slash
///
/// Relative path to this directory
public string GetRelativePath()
{
StringBuilder Builder = new StringBuilder();
AppendPath(Builder);
return Builder.ToString();
}
///
/// Append the relative path to this directory to the given string builder
///
/// String builder to append to
public void AppendPath(StringBuilder Builder)
{
if(ParentDirectory != null)
{
ParentDirectory.AppendPath(Builder);
Builder.Append(Name);
}
Builder.Append('/');
}
///
/// Format the path to the directory for debugging
///
/// Path to the directory
public override string ToString()
{
return GetRelativePath();
}
}
}