From a129b321998614b20e4ebb8a7c1632553ebee981 Mon Sep 17 00:00:00 2001 From: "Emma [it/its]@Rory&" Date: Sat, 1 Jun 2024 19:02:28 +0200 Subject: Split event abstractions --- .../LegacyMatrixEvent.cs | 251 +++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs (limited to 'LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs') diff --git a/LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs b/LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs new file mode 100644 index 0000000..3cb9ecb --- /dev/null +++ b/LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs @@ -0,0 +1,251 @@ +#if !DISABLE_LEGACY_EVENTS +using System.Collections.Frozen; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ArcaneLibs; +using ArcaneLibs.Attributes; +using ArcaneLibs.Extensions; +using LibMatrix.LegacyEvents.EventTypes; + +namespace LibMatrix; + +public class LegacyMatrixEvent { + public static FrozenSet KnownStateEventTypes { get; } = new ClassCollector().ResolveFromAllAccessibleAssemblies().ToFrozenSet(); + + public static FrozenDictionary KnownStateEventTypesByName { get; } = KnownStateEventTypes.Aggregate( + new Dictionary(), + (dict, type) => { + var attrs = type.GetCustomAttributes(); + foreach (var attr in attrs) { + if (dict.TryGetValue(attr.EventName, out var existing)) + Console.WriteLine($"Duplicate event type '{attr.EventName}' registered for types '{existing.Name}' and '{type.Name}'"); + dict[attr.EventName] = type; + } + + return dict; + }).OrderBy(x => x.Key).ToFrozenDictionary(); + + public static Type GetStateEventType(string? type) => + string.IsNullOrWhiteSpace(type) ? typeof(UnknownLegacyEventContent) : KnownStateEventTypesByName.GetValueOrDefault(type) ?? typeof(UnknownLegacyEventContent); + + [JsonIgnore] + public Type MappedType => GetStateEventType(Type); + + [JsonIgnore] + public bool IsLegacyType => MappedType.GetCustomAttributes().FirstOrDefault(x => x.EventName == Type)?.Legacy ?? false; + + [JsonIgnore] + public string FriendlyTypeName => MappedType.GetFriendlyNameOrNull() ?? Type; + + [JsonIgnore] + public string FriendlyTypeNamePlural => MappedType.GetFriendlyNamePluralOrNull() ?? Type; + + private static readonly JsonSerializerOptions TypedContentSerializerOptions = new() { + Converters = { + new JsonFloatStringConverter(), + new JsonDoubleStringConverter(), + new JsonDecimalStringConverter() + } + }; + + [JsonIgnore] + [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] + public LegacyEventContent? TypedContent { + get { + // if (Type == "m.receipt") { + // return null; + // } + try { + var mappedType = GetStateEventType(Type); + if (mappedType == typeof(UnknownLegacyEventContent)) + Console.WriteLine($"Warning: unknown event type '{Type}'"); + var deserialisedContent = (LegacyEventContent)RawContent.Deserialize(mappedType, TypedContentSerializerOptions)!; + return deserialisedContent; + } + catch (JsonException e) { + Console.WriteLine(e); + Console.WriteLine("Content:\n" + (RawContent?.ToJson() ?? "null")); + } + + return null; + } + set { + if (value is null) + RawContent?.Clear(); + else + RawContent = JsonSerializer.Deserialize(JsonSerializer.Serialize(value, value.GetType(), + new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull })); + } + } + + [JsonPropertyName("state_key")] + public string? StateKey { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("replaces_state")] + public string? ReplacesState { get; set; } + + private JsonObject? _rawContent; + + [JsonPropertyName("content")] + public JsonObject? RawContent { + get => _rawContent; + set => _rawContent = value; + } + + [JsonIgnore] + public string InternalSelfTypeName { + get { + var res = GetType().Name switch { + "StateEvent`1" => "StateEvent", + _ => GetType().Name + }; + return res; + } + } + + [JsonIgnore] + public string InternalContentTypeName => TypedContent?.GetType().Name ?? "null"; +} + +public class LegacyMatrixEventResponse : LegacyMatrixEvent { + [JsonPropertyName("origin_server_ts")] + public long? OriginServerTs { get; set; } + + [JsonPropertyName("room_id")] + public string? RoomId { get; set; } + + [JsonPropertyName("sender")] + public string? Sender { get; set; } + + [JsonPropertyName("unsigned")] + public UnsignedData? Unsigned { get; set; } + + [JsonPropertyName("event_id")] + public string? EventId { get; set; } + + public class UnsignedData { + [JsonPropertyName("age")] + public ulong? Age { get; set; } + + [JsonPropertyName("redacted_because")] + public object? RedactedBecause { get; set; } + + [JsonPropertyName("transaction_id")] + public string? TransactionId { get; set; } + + [JsonPropertyName("replaces_state")] + public string? ReplacesState { get; set; } + + [JsonPropertyName("prev_sender")] + public string? PrevSender { get; set; } + + [JsonPropertyName("prev_content")] + public JsonObject? PrevContent { get; set; } + } +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ChunkedStateEventResponse))] +internal partial class ChunkedStateEventResponseSerializerContext : JsonSerializerContext; + +public class EventList { + public EventList() { } + + public EventList(List? events) { + Events = events; + } + + [JsonPropertyName("events")] + public List? Events { get; set; } = new(); +} + +public class ChunkedStateEventResponse { + [JsonPropertyName("chunk")] + public List? Chunk { get; set; } = new(); +} + +public class PaginatedChunkedStateEventResponse : ChunkedStateEventResponse { + [JsonPropertyName("start")] + public string? Start { get; set; } + + [JsonPropertyName("end")] + public string? End { get; set; } +} + +public class BatchedChunkedStateEventResponse : ChunkedStateEventResponse { + [JsonPropertyName("next_batch")] + public string? NextBatch { get; set; } + + [JsonPropertyName("prev_batch")] + public string? PrevBatch { get; set; } +} + +public class RecursedBatchedChunkedStateEventResponse : BatchedChunkedStateEventResponse { + [JsonPropertyName("recursion_depth")] + public int? RecursionDepth { get; set; } +} + +#region Unused code + +/* +public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoResolver +{ + public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) + { + JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options); + + Type baseType = typeof(EventContent); + if (jsonTypeInfo.Type == baseType) { + jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { + TypeDiscriminatorPropertyName = "type", + IgnoreUnrecognizedTypeDiscriminators = true, + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType, + + DerivedTypes = StateEvent.KnownStateEventTypesByName.Select(x => new JsonDerivedType(x.Value, x.Key)).ToList() + + // DerivedTypes = new ClassCollector() + // .ResolveFromAllAccessibleAssemblies() + // .SelectMany(t => t.GetCustomAttributes() + // .Select(a => new JsonDerivedType(t, attr.EventName)); + + }; + } + + return jsonTypeInfo; + } +} +*/ + +#endregion +#endif + +public class JsonFloatStringConverter : JsonConverter { + public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => float.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); +} + +public class JsonDoubleStringConverter : JsonConverter { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => double.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); +} + +public class JsonDecimalStringConverter : JsonConverter { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => decimal.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); +} \ No newline at end of file -- cgit 1.4.1