2
0
mirror of synced 2025-02-23 13:38:07 +00:00

working transcript writer and reader

This commit is contained in:
benbierens 2024-07-25 15:12:25 +02:00
parent 8064051f2f
commit 802b18e990
No known key found for this signature in database
GPG Key ID: 877D2C2E09A22F3A
8 changed files with 394 additions and 62 deletions

View File

@ -0,0 +1,72 @@
namespace OverwatchTranscript
{
[Serializable]
public partial class OverwatchHeader
{
public OverwatchCodexHeader? CodexHeader { get; set; }
}
[Serializable]
public partial class OverwatchEvent
{
public OverwatchCodexEvent? CodexEvent { get; set; }
}
[Serializable]
public class OverwatchCodexHeader
{
public int TotalNumberOfNodes { get; set; }
}
[Serializable]
public class OverwatchCodexEvent
{
public NodeStartedEvent? NodeStarted { get; set; }
public NodeStoppedEvent? NodeStopped { get; set; }
public FileUploadedEvent? FileUploaded { get; set; }
public FileDownloadedEvent? FileDownloaded { get; set; }
public BlockReceivedEvent? BlockReceived { get; set; }
}
#region Scenario Generated Events
[Serializable]
public class NodeStartedEvent
{
public string Name { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
public string Args { get; set; } = string.Empty;
}
[Serializable]
public class NodeStoppedEvent
{
public string Name { get; set; } = string.Empty;
}
[Serializable]
public class FileUploadedEvent
{
public ulong ByteSize { get; set; }
public string Cid { get; set; } = string.Empty;
}
[Serializable]
public class FileDownloadedEvent
{
public string Cid { get; set; } = string.Empty;
}
#endregion
#region Codex Generated Events
[Serializable]
public class BlockReceivedEvent
{
public string BlockAddress { get; set; } = string.Empty;
public string PeerId { get; set; } = string.Empty;
}
#endregion
}

View File

@ -17,6 +17,7 @@
<ProjectReference Include="..\..\Framework\NethereumWorkflow\NethereumWorkflow.csproj" />
<ProjectReference Include="..\..\Framework\Utils\Utils.csproj" />
<ProjectReference Include="..\..\ProjectPlugins\CodexContractsPlugin\CodexContractsPlugin.csproj" />
<ProjectReference Include="..\..\Tools\OverwatchTranscript\OverwatchTranscript.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,103 @@
using Newtonsoft.Json;
using NUnit.Framework;
using OverwatchTranscript;
namespace FrameworkTests.OverwatchTranscript
{
[TestFixture]
public class TranscriptTests
{
private const string TranscriptFilename = "testtranscript.json";
private const string HeaderKey = "testHeader";
private const string HeaderData = "abcdef";
private const string EventData0 = "12345";
private const string EventData1 = "678";
private const string EventData2 = "90";
private readonly DateTime t0 = DateTime.UtcNow;
private readonly DateTime t1 = DateTime.UtcNow.AddMinutes(1);
private readonly DateTime t2 = DateTime.UtcNow.AddMinutes(2);
[Test]
public void WriteAndRun()
{
var workdir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
WriteTranscript(workdir);
ReadTranscript(workdir);
File.Delete(TranscriptFilename);
}
private void WriteTranscript(string workdir)
{
var writer = new TranscriptWriter(workdir);
writer.AddHeader(HeaderKey, new TestHeader
{
HeaderData = HeaderData
});
writer.Add(t0, new MyEvent
{
EventData = EventData0
});
writer.Add(t2, new MyEvent
{
EventData = EventData2
});
writer.Add(t1, new MyEvent
{
EventData = EventData1
});
writer.Write(TranscriptFilename);
}
private void ReadTranscript(string workdir)
{
var reader = new TranscriptReader(workdir, TranscriptFilename);
var header = reader.GetHeader<TestHeader>(HeaderKey);
Assert.That(header.HeaderData, Is.EqualTo(HeaderData));
var events = new List<MyEvent>();
reader.AddHandler<MyEvent>((utc, e) =>
{
e.CheckUtc = utc;
events.Add(e);
});
Assert.That(events.Count, Is.EqualTo(0));
reader.Next();
Assert.That(events.Count, Is.EqualTo(1));
reader.Next();
Assert.That(events.Count, Is.EqualTo(2));
reader.Next();
Assert.That(events.Count, Is.EqualTo(3));
reader.Next();
Assert.That(events.Count, Is.EqualTo(3));
Assert.That(events[0].CheckUtc, Is.EqualTo(t0));
Assert.That(events[0].EventData, Is.EqualTo(EventData0));
Assert.That(events[1].CheckUtc, Is.EqualTo(t1));
Assert.That(events[1].EventData, Is.EqualTo(EventData1));
Assert.That(events[2].CheckUtc, Is.EqualTo(t2));
Assert.That(events[2].EventData, Is.EqualTo(EventData2));
reader.Close();
}
}
public class TestHeader
{
public string HeaderData { get; set; } = string.Empty;
}
public class MyEvent
{
public string EventData { get; set; } = string.Empty;
[JsonIgnore]
public DateTime CheckUtc { get; set; }
}
}

View File

@ -1,76 +1,30 @@
namespace OverwatchTranscript
{
[Serializable]
public class Transcript
public class OverwatchTranscript
{
public Header Header { get; set; } = new();
public Event[] Events { get; set; } = Array.Empty<Event>();
public OverwatchHeader Header { get; set; } = new();
public OverwatchEvent[] Events { get; set; } = Array.Empty<OverwatchEvent>();
}
[Serializable]
public class Header
public class OverwatchHeader
{
public int TotalNumberOfNodes { get; set; }
public OverwatchHeaderEntry[] Entries { get; set; } = Array.Empty<OverwatchHeaderEntry>();
}
[Serializable]
public class Event
public class OverwatchHeaderEntry
{
public string Key { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
[Serializable]
public class OverwatchEvent
{
public DateTime Utc { get; set; }
public ScenarioFinishedEvent? ScenarioFinished { get; set; }
public NodeStartedEvent? NodeStarted { get; set; }
public NodeStoppedEvent? NodeStopped { get; set; }
public FileUploadedEvent? FileUploaded { get; set; }
public FileDownloadedEvent? FileDownloaded { get; set; }
public BlockReceivedEvent? BlockReceived { get; set; }
public string Type { get; set; } = string.Empty;
public string Payload { get; set; } = string.Empty;
}
#region Scenario Generated Events
[Serializable]
public class ScenarioFinishedEvent
{
public bool Success { get; set; }
public string Result { get; set; } = string.Empty;
}
[Serializable]
public class NodeStartedEvent
{
public string Name { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
public string Args { get; set; } = string.Empty;
}
[Serializable]
public class NodeStoppedEvent
{
public string Name { get; set; } = string.Empty;
}
[Serializable]
public class FileUploadedEvent
{
public ulong ByteSize { get; set; }
public string Cid { get; set; } = string.Empty;
}
[Serializable]
public class FileDownloadedEvent
{
public string Cid { get; set; } = string.Empty;
}
#endregion
#region Codex Generated Events
[Serializable]
public class BlockReceivedEvent
{
public string BlockAddress { get; set; } = string.Empty;
public string PeerId { get; set; } = string.Empty;
}
#endregion
}
}

View File

@ -6,4 +6,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
namespace OverwatchTranscript
{
public static class TranscriptConstants
{
public const string TranscriptFilename = "transcript.json";
public const string ArtifactFolderName = "artifacts";
}
}

View File

@ -0,0 +1,92 @@
using Newtonsoft.Json;
using System.IO.Compression;
namespace OverwatchTranscript
{
public class TranscriptReader
{
private readonly string transcriptFile;
private readonly string artifactsFolder;
private readonly Dictionary<string, Action<DateTime, string>> handlers = new Dictionary<string, Action<DateTime, string>>();
private readonly string workingDir;
private OverwatchTranscript model = null!;
private int eventIndex = 0;
private bool closed;
public TranscriptReader(string workingDir, string inputFilename)
{
closed = false;
this.workingDir = workingDir;
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");
LoadModel(inputFilename);
}
public T GetHeader<T>(string key)
{
CheckClosed();
var value = model.Header.Entries.First(e => e.Key == key).Value;
return JsonConvert.DeserializeObject<T>(value)!;
}
public void AddHandler<T>(Action<DateTime, T> handler)
{
CheckClosed();
var typeName = typeof(T).FullName;
if (string.IsNullOrEmpty(typeName)) throw new Exception("Empty typename for payload");
handlers.Add(typeName, (utc, s) =>
{
handler(utc, JsonConvert.DeserializeObject<T>(s)!);
});
}
public void Next()
{
CheckClosed();
if (eventIndex >= model.Events.Length) return;
var @event = model.Events[eventIndex];
eventIndex++;
PlayEvent(@event);
}
public void Close()
{
CheckClosed();
Directory.Delete(workingDir, true);
closed = true;
}
private void PlayEvent(OverwatchEvent @event)
{
if (!handlers.ContainsKey(@event.Type)) return;
var handler = handlers[@event.Type];
handler(@event.Utc, @event.Payload);
}
private void LoadModel(string inputFilename)
{
ZipFile.ExtractToDirectory(inputFilename, workingDir);
if (!File.Exists(transcriptFile))
{
closed = true;
throw new Exception("Is not a transcript file. Unzipped to: " + workingDir);
}
model = JsonConvert.DeserializeObject<OverwatchTranscript>(File.ReadAllText(transcriptFile))!;
}
private void CheckClosed()
{
if (closed) throw new Exception("Transcript has already been written. Cannot modify or write again.");
}
}
}

View File

@ -0,0 +1,98 @@
using Newtonsoft.Json;
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
{
private readonly string transcriptFile;
private readonly string artifactsFolder;
private readonly Dictionary<string, string> header = new Dictionary<string, string>();
private readonly SortedList<DateTime, OverwatchEvent> buffer = new SortedList<DateTime, OverwatchEvent>();
private readonly string workingDir;
private bool closed;
public TranscriptWriter(string workingDir)
{
closed = false;
this.workingDir = workingDir;
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();
var typeName = payload.GetType().FullName;
if (string.IsNullOrEmpty(typeName)) throw new Exception("Empty typename for payload");
buffer.Add(utc, new OverwatchEvent
{
Utc = utc,
Type = typeName,
Payload = JsonConvert.SerializeObject(payload)
});
}
public void AddHeader(string key, object value)
{
CheckClosed();
header.Add(key, JsonConvert.SerializeObject(value));
}
public void IncludeArtifact(string filePath)
{
CheckClosed();
if (!File.Exists(filePath)) throw new Exception("File not found: " + filePath);
var name = Path.GetFileName(filePath);
File.Copy(filePath, Path.Combine(artifactsFolder, name), overwrite: false);
}
public void Write(string outputFilename)
{
CheckClosed();
closed = true;
var model = new OverwatchTranscript
{
Header = new OverwatchHeader
{
Entries = header.Select(h =>
{
return new OverwatchHeaderEntry
{
Key = h.Key,
Value = h.Value
};
}).ToArray()
},
Events = buffer.Values.ToArray()
};
header.Clear();
buffer.Clear();
File.WriteAllText(transcriptFile, JsonConvert.SerializeObject(model, Formatting.Indented));
ZipFile.CreateFromDirectory(workingDir, outputFilename);
Directory.Delete(workingDir, true);
}
private void CheckClosed()
{
if (closed) throw new Exception("Transcript has already been written. Cannot modify or write again.");
}
}
}