TranscriptWriter.cs
1 using Logging; 2 using Newtonsoft.Json; 3 using System.IO.Compression; 4 5 namespace OverwatchTranscript 6 { 7 public interface ITranscriptWriter 8 { 9 void AddHeader(string key, object value); 10 void Add(DateTime utc, object payload); 11 void IncludeArtifact(string filePath); 12 void Write(string outputFilename); 13 } 14 15 public class TranscriptWriter : ITranscriptWriter 16 { 17 private readonly object _lock = new object(); 18 private readonly MomentReferenceBuilder builder; 19 private readonly string transcriptFile; 20 private readonly string artifactsFolder; 21 private readonly Dictionary<string, string> header = new Dictionary<string, string>(); 22 private readonly BucketSet bucketSet; 23 private readonly ILog log; 24 private readonly string workingDir; 25 private bool closed; 26 27 public TranscriptWriter(ILog log, string workingDir) 28 { 29 closed = false; 30 this.log = log; 31 this.workingDir = workingDir; 32 bucketSet = new BucketSet(log, workingDir); 33 builder = new MomentReferenceBuilder(log, workingDir); 34 transcriptFile = Path.Combine(workingDir, TranscriptConstants.TranscriptFilename); 35 artifactsFolder = Path.Combine(workingDir, TranscriptConstants.ArtifactFolderName); 36 37 if (!Directory.Exists(workingDir)) Directory.CreateDirectory(workingDir); 38 if (File.Exists(transcriptFile) || Directory.Exists(artifactsFolder)) throw new Exception("workingdir not clean"); 39 } 40 41 public void Add(DateTime utc, object payload) 42 { 43 CheckClosed(); 44 bucketSet.Add(utc, payload); 45 } 46 47 public void AddHeader(string key, object value) 48 { 49 CheckClosed(); 50 lock (_lock) 51 { 52 header.Add(key, Json.Serialize(value)); 53 } 54 } 55 56 public void IncludeArtifact(string filePath) 57 { 58 CheckClosed(); 59 if (!File.Exists(filePath)) throw new Exception("File not found: " + filePath); 60 if (!Directory.Exists(artifactsFolder)) Directory.CreateDirectory(artifactsFolder); 61 var name = Path.GetFileName(filePath); 62 File.Copy(filePath, Path.Combine(artifactsFolder, name), overwrite: false); 63 } 64 65 public void Write(string outputFilename) 66 { 67 CheckClosed(); 68 closed = true; 69 70 var momentReferences = builder.Build(bucketSet.FinalizeBuckets()); 71 var model = CreateModel(momentReferences); 72 73 File.WriteAllText(transcriptFile, Json.Serialize(model, Formatting.Indented)); 74 75 ZipFile.CreateFromDirectory(workingDir, outputFilename); 76 log.Debug($"Transcript written to {outputFilename}"); 77 log.Debug($"Common header: {Json.Serialize(model.Header.Common, Formatting.Indented)}"); 78 79 Directory.Delete(workingDir, true); 80 log.Debug($"Workdir {workingDir} deleted"); 81 } 82 83 private OverwatchTranscript CreateModel(OverwatchMomentReference[] momentReferences) 84 { 85 lock (_lock) 86 { 87 var model = new OverwatchTranscript 88 { 89 Header = new OverwatchHeader 90 { 91 Common = CreateCommonHeader(momentReferences), 92 Entries = header.Select(h => 93 { 94 return new OverwatchHeaderEntry 95 { 96 Key = h.Key, 97 Value = h.Value 98 }; 99 }).ToArray() 100 }, 101 MomentReferences = momentReferences 102 }; 103 104 header.Clear(); 105 return model; 106 } 107 } 108 109 private OverwatchCommonHeader CreateCommonHeader(OverwatchMomentReference[] momentReferences) 110 { 111 var moments = momentReferences.Sum(m => m.NumberOfMoments); 112 var events = momentReferences.Sum(m => m.NumberOfEvents); 113 var earliest = momentReferences.Min(m => m.EarliestUtc); 114 var latest = momentReferences.Max(m => m.LatestUtc); 115 116 return new OverwatchCommonHeader 117 { 118 NumberOfMoments = moments, 119 NumberOfEvents = events, 120 EarliestUtc = earliest, 121 LatestUtc = latest 122 }; 123 } 124 125 private void CheckClosed() 126 { 127 if (closed) throw new Exception("Transcript has already been written. Cannot modify or write again."); 128 } 129 } 130 }