// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using HordeServer.Api; using HordeServer.Utilities; using ICSharpCode.SharpZipLib.BZip2; using Microsoft.AspNetCore.Routing.Constraints; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace HordeServer.Logs { /// /// Pending data for a sub-chunk /// public class LogSubChunkData { /// /// Type of data stored in this subchunk /// public LogType Type { get; } /// /// Offset within the file of this sub-chunk /// public long Offset { get; } /// /// Length of this sub-chunk /// public int Length { get; } /// /// Index of the first line in this sub-chunk /// public int LineIndex { get; } /// /// Gets the number of lines in this sub-chunk /// public int LineCount { get; } /// /// Text data /// ILogText? TextInternal; /// /// Compressed text data /// ReadOnlyMemory CompressedTextInternal; /// /// The index for this sub-chunk /// LogIndexData? IndexInternal; /// /// The log text /// public ILogText InflateText() { if (TextInternal == null) { TextInternal = new ReadOnlyLogText(CompressedTextInternal.DecompressBzip2()); } return TextInternal; } /// /// The compressed log text /// public ReadOnlyMemory DeflateText() { if(CompressedTextInternal.IsEmpty) { CompressedTextInternal = TextInternal!.Data.CompressBzip2(); } return CompressedTextInternal; } /// /// Index for tokens in this chunk /// public LogIndexData BuildIndex() { if(IndexInternal == null) { ILogText PlainText = InflateText(); if (Type != LogType.Text) { PlainText = PlainText.ToPlainText(); } LogIndexBlock[] Blocks = new LogIndexBlock[1]; Blocks[0] = new LogIndexBlock(LineIndex, LineCount, PlainText, PlainText.Data.CompressBzip2()); IndexInternal = new LogIndexData(null, 0, Blocks); } return IndexInternal; } /// /// Constructor /// /// Type of data stored in this subchunk /// Offset within the file of this sub-chunk /// Index of the first line in this sub-chunk /// Text to add public LogSubChunkData(LogType Type, long Offset, int LineIndex, ILogText Text) { this.Type = Type; this.Offset = Offset; this.Length = Text.Data.Length; this.LineIndex = LineIndex; this.LineCount = Text.LineCount; this.TextInternal = Text; } /// /// Constructor for raw data /// /// Type of data stored in this subchunk /// Offset within the file of this sub-chunk /// Length of the uncompressed data /// Index of the first line /// Number of lines in the uncompressed text /// Compressed text data /// Index data public LogSubChunkData(LogType Type, long Offset, int Length, int LineIndex, int LineCount, ReadOnlyMemory CompressedText, LogIndexData? Index) { this.Type = Type; this.Offset = Offset; this.Length = Length; this.LineIndex = LineIndex; this.LineCount = LineCount; this.CompressedTextInternal = CompressedText; this.IndexInternal = Index; } /// /// Constructs a sub-chunk from a block of memory. Uses slices of the given memory buffer rather than copying the data. /// /// The reader to read from /// Offset of the sub-chunk within this file /// Index of the first line in this subchunk /// New subchunk data public static LogSubChunkData Read(MemoryReader Reader, long Offset, int LineIndex) { int Version = Reader.ReadInt32(); // Version placeholder if (Version == 0) { LogType Type = (LogType)Reader.ReadInt32(); int Length = Reader.ReadInt32(); int LineCount = Reader.ReadInt32(); ReadOnlyMemory CompressedText = Reader.ReadVariableLengthBytes(); Reader.ReadVariableLengthBytes(); return new LogSubChunkData(Type, Offset, Length, LineIndex, LineCount, CompressedText, null); } else if (Version == 1) { LogType Type = (LogType)Reader.ReadInt32(); int Length = Reader.ReadInt32(); int LineCount = Reader.ReadInt32(); ReadOnlyMemory CompressedText = Reader.ReadVariableLengthBytes(); ReadOnlyMemory CompressedPlainText = Reader.ReadVariableLengthBytes(); ReadOnlyTrie Trie = Reader.ReadTrie(); LogIndexBlock IndexBlock = new LogIndexBlock(LineIndex, LineCount, null, CompressedPlainText); LogIndexData Index = new LogIndexData(Trie, 0, new[] { IndexBlock }); return new LogSubChunkData(Type, Offset, Length, LineIndex, LineCount, CompressedText, Index); } else { LogType Type = (LogType)Reader.ReadInt32(); int Length = Reader.ReadInt32(); int LineCount = Reader.ReadInt32(); ReadOnlyMemory CompressedText = Reader.ReadVariableLengthBytes(); LogIndexData Index = Reader.ReadLogIndexData(); Index.SetBaseLineIndex(LineIndex); // Fix for incorrectly saved data return new LogSubChunkData(Type, Offset, Length, LineIndex, LineCount, CompressedText, Index); } } /// /// Serializes the sub-chunk to a stream /// /// Writer to output to public void Write(MemoryWriter Writer) { Writer.WriteInt32(2); // Version placeholder Writer.WriteInt32((int)Type); Writer.WriteInt32(Length); Writer.WriteInt32(LineCount); Writer.WriteVariableLengthBytes(DeflateText().Span); Writer.WriteLogIndexData(BuildIndex()); } /// /// Serializes this object to a byte array /// /// Byte array 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 serialized data /// public int GetSerializedSize() { return sizeof(int) + sizeof(int) + sizeof(int) + sizeof(int) + (sizeof(int) + DeflateText().Length) + BuildIndex().GetSerializedSize(); } } /// /// Pending data for a sub-chunk /// public static class LogSubChunkDataExtensions { /// /// Constructs a sub-chunk from a block of memory. Uses slices of the given memory buffer rather than copying the data. /// /// The reader to read from /// Offset of this sub-chunk within the file /// Index of the first line within this chunk /// New subchunk data public static LogSubChunkData ReadLogSubChunkData(this MemoryReader Reader, long Offset, int LineIndex) { return LogSubChunkData.Read(Reader, Offset, LineIndex); } /// /// Serializes the sub-chunk to a stream /// /// Writer to output to /// The sub-chunk data to write public static void WriteLogSubChunkData(this MemoryWriter Writer, LogSubChunkData SubChunkData) { SubChunkData.Write(Writer); } } }