2024-08-02 06:56:49 +00:00
|
|
|
|
using Logging;
|
|
|
|
|
using Newtonsoft.Json;
|
2024-07-25 13:12:25 +00:00
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
|
|
|
|
|
namespace OverwatchTranscript
|
|
|
|
|
{
|
|
|
|
|
public interface ITranscriptWriter
|
|
|
|
|
{
|
|
|
|
|
void AddHeader(string key, object value);
|
|
|
|
|
void Add(DateTime utc, object payload);
|
|
|
|
|
void IncludeArtifact(string filePath);
|
|
|
|
|
void Write(string outputFilename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class TranscriptWriter : ITranscriptWriter
|
|
|
|
|
{
|
2024-07-26 11:21:29 +00:00
|
|
|
|
private readonly object _lock = new object();
|
2024-08-01 14:25:28 +00:00
|
|
|
|
private readonly MomentReferenceBuilder builder;
|
2024-07-25 13:12:25 +00:00
|
|
|
|
private readonly string transcriptFile;
|
|
|
|
|
private readonly string artifactsFolder;
|
|
|
|
|
private readonly Dictionary<string, string> header = new Dictionary<string, string>();
|
2024-08-01 14:25:28 +00:00
|
|
|
|
private readonly BucketSet bucketSet;
|
2024-08-02 06:56:49 +00:00
|
|
|
|
private readonly ILog log;
|
2024-07-25 13:12:25 +00:00
|
|
|
|
private readonly string workingDir;
|
|
|
|
|
private bool closed;
|
|
|
|
|
|
2024-08-02 06:56:49 +00:00
|
|
|
|
public TranscriptWriter(ILog log, string workingDir)
|
2024-07-25 13:12:25 +00:00
|
|
|
|
{
|
|
|
|
|
closed = false;
|
2024-08-02 06:56:49 +00:00
|
|
|
|
this.log = log;
|
2024-07-25 13:12:25 +00:00
|
|
|
|
this.workingDir = workingDir;
|
2024-08-02 06:56:49 +00:00
|
|
|
|
bucketSet = new BucketSet(log, workingDir);
|
2024-08-02 08:44:15 +00:00
|
|
|
|
builder = new MomentReferenceBuilder(log, workingDir);
|
2024-07-25 13:12:25 +00:00
|
|
|
|
transcriptFile = Path.Combine(workingDir, TranscriptConstants.TranscriptFilename);
|
|
|
|
|
artifactsFolder = Path.Combine(workingDir, TranscriptConstants.ArtifactFolderName);
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(workingDir)) Directory.CreateDirectory(workingDir);
|
|
|
|
|
if (File.Exists(transcriptFile) || Directory.Exists(artifactsFolder)) throw new Exception("workingdir not clean");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Add(DateTime utc, object payload)
|
|
|
|
|
{
|
|
|
|
|
CheckClosed();
|
2024-08-01 14:25:28 +00:00
|
|
|
|
bucketSet.Add(utc, payload);
|
2024-07-25 13:12:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void AddHeader(string key, object value)
|
|
|
|
|
{
|
|
|
|
|
CheckClosed();
|
2024-07-26 11:21:29 +00:00
|
|
|
|
lock (_lock)
|
|
|
|
|
{
|
2024-08-21 07:51:22 +00:00
|
|
|
|
header.Add(key, Json.Serialize(value));
|
2024-07-26 11:21:29 +00:00
|
|
|
|
}
|
2024-07-25 13:12:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void IncludeArtifact(string filePath)
|
|
|
|
|
{
|
|
|
|
|
CheckClosed();
|
|
|
|
|
if (!File.Exists(filePath)) throw new Exception("File not found: " + filePath);
|
2024-07-26 07:14:46 +00:00
|
|
|
|
if (!Directory.Exists(artifactsFolder)) Directory.CreateDirectory(artifactsFolder);
|
2024-07-25 13:12:25 +00:00
|
|
|
|
var name = Path.GetFileName(filePath);
|
|
|
|
|
File.Copy(filePath, Path.Combine(artifactsFolder, name), overwrite: false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write(string outputFilename)
|
|
|
|
|
{
|
|
|
|
|
CheckClosed();
|
|
|
|
|
closed = true;
|
|
|
|
|
|
2024-08-01 14:25:28 +00:00
|
|
|
|
var momentReferences = builder.Build(bucketSet.FinalizeBuckets());
|
|
|
|
|
var model = CreateModel(momentReferences);
|
2024-07-26 11:21:29 +00:00
|
|
|
|
|
2024-08-21 07:51:22 +00:00
|
|
|
|
File.WriteAllText(transcriptFile, Json.Serialize(model, Formatting.Indented));
|
2024-07-26 11:21:29 +00:00
|
|
|
|
|
|
|
|
|
ZipFile.CreateFromDirectory(workingDir, outputFilename);
|
2024-08-02 06:56:49 +00:00
|
|
|
|
log.Debug($"Transcript written to {outputFilename}");
|
2024-08-21 07:51:22 +00:00
|
|
|
|
log.Debug($"Common header: {Json.Serialize(model.Header.Common, Formatting.Indented)}");
|
2024-07-26 11:21:29 +00:00
|
|
|
|
|
|
|
|
|
Directory.Delete(workingDir, true);
|
2024-08-02 06:56:49 +00:00
|
|
|
|
log.Debug($"Workdir {workingDir} deleted");
|
2024-07-26 11:21:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 14:25:28 +00:00
|
|
|
|
private OverwatchTranscript CreateModel(OverwatchMomentReference[] momentReferences)
|
2024-07-26 11:21:29 +00:00
|
|
|
|
{
|
|
|
|
|
lock (_lock)
|
2024-07-25 13:12:25 +00:00
|
|
|
|
{
|
2024-07-26 11:21:29 +00:00
|
|
|
|
var model = new OverwatchTranscript
|
2024-07-25 13:12:25 +00:00
|
|
|
|
{
|
2024-07-26 11:21:29 +00:00
|
|
|
|
Header = new OverwatchHeader
|
|
|
|
|
{
|
2024-08-01 14:25:28 +00:00
|
|
|
|
Common = CreateCommonHeader(momentReferences),
|
2024-07-26 11:21:29 +00:00
|
|
|
|
Entries = header.Select(h =>
|
|
|
|
|
{
|
|
|
|
|
return new OverwatchHeaderEntry
|
|
|
|
|
{
|
|
|
|
|
Key = h.Key,
|
|
|
|
|
Value = h.Value
|
|
|
|
|
};
|
|
|
|
|
}).ToArray()
|
|
|
|
|
},
|
2024-08-01 14:25:28 +00:00
|
|
|
|
MomentReferences = momentReferences
|
2024-07-26 11:21:29 +00:00
|
|
|
|
};
|
2024-07-25 13:12:25 +00:00
|
|
|
|
|
2024-07-26 11:21:29 +00:00
|
|
|
|
header.Clear();
|
|
|
|
|
return model;
|
|
|
|
|
}
|
2024-07-25 13:12:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 14:25:28 +00:00
|
|
|
|
private OverwatchCommonHeader CreateCommonHeader(OverwatchMomentReference[] momentReferences)
|
2024-07-29 06:08:17 +00:00
|
|
|
|
{
|
2024-08-01 14:25:28 +00:00
|
|
|
|
var moments = momentReferences.Sum(m => m.NumberOfMoments);
|
|
|
|
|
var events = momentReferences.Sum(m => m.NumberOfEvents);
|
|
|
|
|
var earliest = momentReferences.Min(m => m.EarliestUtc);
|
|
|
|
|
var latest = momentReferences.Max(m => m.LatestUtc);
|
|
|
|
|
|
2024-07-29 06:08:17 +00:00
|
|
|
|
return new OverwatchCommonHeader
|
|
|
|
|
{
|
2024-08-01 14:25:28 +00:00
|
|
|
|
NumberOfMoments = moments,
|
|
|
|
|
NumberOfEvents = events,
|
|
|
|
|
EarliestUtc = earliest,
|
|
|
|
|
LatestUtc = latest
|
2024-07-29 06:08:17 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 13:12:25 +00:00
|
|
|
|
private void CheckClosed()
|
|
|
|
|
{
|
|
|
|
|
if (closed) throw new Exception("Transcript has already been written. Cannot modify or write again.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|