// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using HordeServer.Api; using HordeServer.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace HordeServer.Logs { /// /// Data for a log chunk /// public class LogChunkData { /// /// Offset of this chunk /// public long Offset { get; } /// /// Length of this chunk /// public int Length { get; } /// /// Line index of this chunk /// public int LineIndex { get; } /// /// Number of lines in this chunk /// public int LineCount { get; } /// /// List of sub-chunks /// public IReadOnlyList SubChunks { get; } /// /// Offset of each sub-chunk within this chunk /// public int[] SubChunkOffset { get; } /// /// Total line count of the chunk after each sub-chunk /// public int[] SubChunkLineIndex { get; } /// /// Constructor /// /// Offset of this chunk /// First line index of this chunk /// Sub-chunks for this chunk public LogChunkData(long Offset, int LineIndex, IReadOnlyList SubChunks) { this.Offset = Offset; this.LineIndex = LineIndex; this.SubChunks = SubChunks; this.SubChunkOffset = CreateSumLookup(SubChunks, x => x.Length); this.SubChunkLineIndex = CreateSumLookup(SubChunks, x => x.LineCount); if(SubChunks.Count > 0) { int LastSubChunkIdx = SubChunks.Count - 1; Length = SubChunkOffset[LastSubChunkIdx] + SubChunks[LastSubChunkIdx].Length; LineCount = SubChunkLineIndex[LastSubChunkIdx] + SubChunks[LastSubChunkIdx].LineCount; } } /// /// Creates a lookup table by summing a field across a list of subchunks /// /// /// /// static int[] CreateSumLookup(IReadOnlyList SubChunks, Func Field) { int[] Total = new int[SubChunks.Count]; int Value = 0; for (int Idx = 0; Idx + 1 < SubChunks.Count; Idx++) { Value += Field(SubChunks[Idx]); Total[Idx + 1] = Value; } return Total; } /// /// Gets the offset of a line within the file /// /// The line index /// Offset of the line within the file public long GetLineOffsetWithinChunk(int LineIdx) { int SubChunkIdx = SubChunkLineIndex.BinarySearch(LineIdx); if (SubChunkIdx < 0) { SubChunkIdx = ~SubChunkIdx - 1; } return SubChunkOffset[SubChunkIdx] + SubChunks[SubChunkIdx].InflateText().LineOffsets[LineIdx - SubChunkLineIndex[SubChunkIdx]]; } /// /// Gets the sub chunk index for the given line /// /// Line index within the chunk /// Subchunk line index public int GetSubChunkForLine(int ChunkLineIdx) { int SubChunkIdx = SubChunkLineIndex.BinarySearch(ChunkLineIdx); if (SubChunkIdx < 0) { SubChunkIdx = ~SubChunkIdx - 1; } return SubChunkIdx; } /// /// Gets the index of the sub-chunk containing the given offset /// /// Offset to search for /// Index of the sub-chunk public int GetSubChunkForOffsetWithinChunk(int Offset) { int SubChunkIdx = SubChunkOffset.BinarySearch(Offset); if (SubChunkIdx < 0) { SubChunkIdx = ~SubChunkIdx - 1; } return SubChunkIdx; } /// /// Signature for output data /// const int CurrentSignature = 'L' | ('C' << 8) | ('D' << 16); /// /// Read a log chunk from the given stream /// /// The reader to read from /// Offset of this chunk within the file /// Line index of this chunk /// New log chunk data public static LogChunkData Read(MemoryReader Reader, long Offset, int LineIndex) { int Signature = Reader.ReadInt32(); if ((Signature & 0xffffff) != CurrentSignature) { List SubChunks = new List(); SubChunks.Add(new LogSubChunkData(LogType.Json, Offset, LineIndex, new ReadOnlyLogText(Reader.Memory))); Reader.Offset = Reader.Memory.Length; return new LogChunkData(Offset, LineIndex, SubChunks); } int Version = Signature >> 24; if (Version == 0) { List SubChunks = ReadSubChunkList(Reader, Offset, LineIndex); Reader.ReadVariableLengthBytes(); return new LogChunkData(Offset, LineIndex, SubChunks); } else { List SubChunks = ReadSubChunkList(Reader, Offset, LineIndex); return new LogChunkData(Offset, LineIndex, SubChunks); } } /// /// Read a list of sub-chunks from the given stream /// /// The reader to read from /// Offset of this chunk within the file /// Line index of this chunk /// List of sub-chunks static List ReadSubChunkList(MemoryReader Reader, long SubChunkOffset, int SubChunkLineIndex) { int NumSubChunks = Reader.ReadInt32(); List SubChunks = new List(); for (int Idx = 0; Idx < NumSubChunks; Idx++) { LogSubChunkData SubChunkData = Reader.ReadLogSubChunkData(SubChunkOffset, SubChunkLineIndex); SubChunkOffset += SubChunkData.Length; SubChunkLineIndex += SubChunkData.LineCount; SubChunks.Add(SubChunkData); } return SubChunks; } /// /// Construct an object from flat memory buffer /// /// Memory buffer /// Offset of this chunk within the file /// Line index of this chunk /// Log chunk data public static LogChunkData FromMemory(ReadOnlyMemory Memory, long Offset, int LineIndex) { MemoryReader Reader = new MemoryReader(Memory); LogChunkData ChunkData = Read(Reader, Offset, LineIndex); Reader.CheckOffset(Memory.Length); return ChunkData; } /// /// Write the chunk data to a stream /// /// Serialized data public void Write(MemoryWriter Writer) { Writer.WriteInt32(CurrentSignature | (1 << 24)); Writer.WriteInt32(SubChunks.Count); foreach (LogSubChunkData SubChunk in SubChunks) { Writer.WriteLogSubChunkData(SubChunk); } } /// /// Construct an object from flat memory buffer /// /// Log chunk data public byte[] ToByteArray() { byte[] Data = new byte[GetSerializedSize()]; MemoryWriter Writer = new MemoryWriter(Data); Write(Writer); Writer.CheckOffset(Data.Length); return Data; } /// /// Determines the size of the serialized buffer /// public int GetSerializedSize() { return sizeof(int) + (sizeof(int) + SubChunks.Sum(x => x.GetSerializedSize())); } } /// /// Extensions for the log chunk data /// static class LogChunkDataExtensions { /// /// Read a log chunk from the given stream /// /// The reader to read from /// Offset of this chunk within the file /// Line index of this chunk /// New log chunk data public static LogChunkData ReadLogChunkData(this MemoryReader Reader, long Offset, int LineIndex) { return LogChunkData.Read(Reader, Offset, LineIndex); } /// /// Write the chunk data to a stream /// /// Serialized data public static void WriteLogChunkData(this MemoryWriter Writer, LogChunkData ChunkData) { ChunkData.Write(Writer); } } }