diff options
Diffstat (limited to 'Tests/LibMatrix.HomeserverEmulator/Services/RoomStore.cs')
-rw-r--r-- | Tests/LibMatrix.HomeserverEmulator/Services/RoomStore.cs | 308 |
1 files changed, 0 insertions, 308 deletions
diff --git a/Tests/LibMatrix.HomeserverEmulator/Services/RoomStore.cs b/Tests/LibMatrix.HomeserverEmulator/Services/RoomStore.cs deleted file mode 100644 index 5cdc3ab..0000000 --- a/Tests/LibMatrix.HomeserverEmulator/Services/RoomStore.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Frozen; -using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Text.Json; -using System.Text.Json.Nodes; -using ArcaneLibs; -using ArcaneLibs.Collections; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.HomeserverEmulator.Controllers.Rooms; -using LibMatrix.Responses; - -namespace LibMatrix.HomeserverEmulator.Services; - -public class RoomStore { - private readonly ILogger<RoomStore> _logger; - public ConcurrentBag<Room> _rooms = new(); - private FrozenDictionary<string, Room> _roomsById = FrozenDictionary<string, Room>.Empty; - - public RoomStore(ILogger<RoomStore> logger, HSEConfiguration config) { - _logger = logger; - if (config.StoreData) { - var path = Path.Combine(config.DataStoragePath, "rooms"); - if (!Directory.Exists(path)) Directory.CreateDirectory(path); - foreach (var file in Directory.GetFiles(path)) { - var room = JsonSerializer.Deserialize<Room>(File.ReadAllText(file)); - if (room is not null) _rooms.Add(room); - } - } - else - Console.WriteLine("Data storage is disabled, not loading rooms from disk"); - - RebuildIndexes(); - } - - private SemaphoreSlim a = new(1, 1); - private void RebuildIndexes() { - // a.Wait(); - // lock (_roomsById) - // _roomsById = new ConcurrentDictionary<string, Room>(_rooms.ToDictionary(u => u.RoomId)); - // foreach (var room in _rooms) { - // _roomsById.AddOrUpdate(room.RoomId, room, (key, old) => room); - // } - // - // var roomsArr = _rooms.ToArray(); - // foreach (var (id, room) in _roomsById) { - // if (!roomsArr.Any(x => x.RoomId == id)) - // _roomsById.TryRemove(id, out _); - // } - - // _roomsById = new ConcurrentDictionary<string, Room>(_rooms.ToDictionary(u => u.RoomId)); - _roomsById = _rooms.ToFrozenDictionary(u => u.RoomId); - - // a.Release(); - } - - public Room? GetRoomById(string roomId, bool createIfNotExists = false) { - if (_roomsById.TryGetValue(roomId, out var room)) { - return room; - } - - if (!createIfNotExists) - return null; - - return CreateRoom(new() { }); - } - - public Room CreateRoom(CreateRoomRequest request, UserStore.User? user = null) { - var room = new Room(roomId: $"!{Guid.NewGuid().ToString()}"); - var newCreateEvent = new StateEvent() { - Type = RoomCreateEventContent.EventId, - RawContent = new() { } - }; - - foreach (var (key, value) in request.CreationContent) { - newCreateEvent.RawContent[key] = value.DeepClone(); - } - - if (user != null) { - newCreateEvent.RawContent["creator"] = user.UserId; - var createEvent = room.SetStateInternal(newCreateEvent, user: user); - createEvent.Sender = user.UserId; - - room.SetStateInternal(new() { - Type = RoomMemberEventContent.EventId, - StateKey = user.UserId, - TypedContent = new RoomMemberEventContent() { - Membership = "join", - AvatarUrl = (user.Profile.GetOrNull("avatar_url") as JsonObject)?.GetOrNull("avatar_url")?.GetValue<string>(), - DisplayName = (user.Profile.GetOrNull("displayname") as string) - } - }, user: user); - } - - if (!string.IsNullOrWhiteSpace(request.Name)) - room.SetStateInternal(new StateEvent() { - Type = RoomNameEventContent.EventId, - TypedContent = new RoomNameEventContent() { - Name = request.Name - } - }); - - if (!string.IsNullOrWhiteSpace(request.RoomAliasName)) - room.SetStateInternal(new StateEvent() { - Type = RoomCanonicalAliasEventContent.EventId, - TypedContent = new RoomCanonicalAliasEventContent() { - Alias = $"#{request.RoomAliasName}:localhost" - } - }); - - foreach (var stateEvent in request.InitialState ?? []) { - room.SetStateInternal(stateEvent); - } - - _rooms.Add(room); - // _roomsById.TryAdd(room.RoomId, room); - RebuildIndexes(); - return room; - } - - public Room AddRoom(Room room) { - _rooms.Add(room); - RebuildIndexes(); - - return room; - } - - public class Room : NotifyPropertyChanged { - private CancellationTokenSource _debounceCts = new(); - private ObservableCollection<StateEventResponse> _timeline; - private ObservableDictionary<string, List<StateEventResponse>> _accountData; - private ObservableDictionary<string, ReadMarkersData> _readMarkers; - private FrozenSet<StateEventResponse> _stateCache; - private int _timelineHash; - - public Room(string roomId) { - if (string.IsNullOrWhiteSpace(roomId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(roomId)); - if (roomId[0] != '!') throw new ArgumentException("Room ID must start with !", nameof(roomId)); - RoomId = roomId; - Timeline = new(); - AccountData = new(); - ReadMarkers = new(); - } - - public string RoomId { get; set; } - - public FrozenSet<StateEventResponse> State => _timelineHash == _timeline.GetHashCode() ? _stateCache : RebuildState(); - - public ObservableCollection<StateEventResponse> Timeline { - get => _timeline; - set { - if (Equals(value, _timeline)) return; - _timeline = new(value); - _timeline.CollectionChanged += (sender, args) => { - // we dont want to do this as it's rebuilt when the state is accessed - - // if (args.Action == NotifyCollectionChangedAction.Add) { - // foreach (StateEventResponse state in args.NewItems) { - // if (state.StateKey is not null) - // // we want state to be deduplicated by type and key, and we want the latest state to be the one that is returned - // RebuildState(); - // } - // } - - SaveDebounced(); - }; - // RebuildState(); - OnPropertyChanged(); - } - } - - public ObservableDictionary<string, List<StateEventResponse>> AccountData { - get => _accountData; - set { - if (Equals(value, _accountData)) return; - _accountData = new(value); - _accountData.CollectionChanged += (sender, args) => SaveDebounced(); - OnPropertyChanged(); - } - } - - public ImmutableList<StateEventResponse> JoinedMembers => - State.Where(s => s is { Type: RoomMemberEventContent.EventId, TypedContent: RoomMemberEventContent { Membership: "join" } }).ToImmutableList(); - - public ObservableDictionary<string, ReadMarkersData> ReadMarkers { - get => _readMarkers; - set { - if (Equals(value, _readMarkers)) return; - _readMarkers = new(value); - _readMarkers.CollectionChanged += (sender, args) => SaveDebounced(); - OnPropertyChanged(); - } - } - - internal StateEventResponse SetStateInternal(StateEvent request, string? senderId = null, UserStore.User? user = null) { - var state = request as StateEventResponse ?? new StateEventResponse() { - Type = request.Type, - StateKey = request.StateKey ?? "", - EventId = "$" + Guid.NewGuid().ToString(), - RoomId = RoomId, - OriginServerTs = DateTimeOffset.Now.ToUnixTimeMilliseconds(), - Sender = user?.UserId ?? senderId ?? "", - RawContent = request.RawContent ?? (request.TypedContent is not null - ? new JsonObject() - : JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(request.TypedContent))) - }; - Timeline.Add(state); - if(state.StateKey != null) - RebuildState(); - return state; - } - - public StateEventResponse AddUser(string userId) { - var state = SetStateInternal(new() { - Type = RoomMemberEventContent.EventId, - StateKey = userId, - TypedContent = new RoomMemberEventContent() { - Membership = "join" - }, - }); - - state.Sender = userId; - return state; - } - - // public async Task SaveDebounced() { - // if (!HSEConfiguration.Current.StoreData) return; - // await _debounceCts.CancelAsync(); - // _debounceCts = new CancellationTokenSource(); - // try { - // await Task.Delay(250, _debounceCts.Token); - // // Ensure all state events are in the timeline - // State.Where(s => !Timeline.Contains(s)).ToList().ForEach(s => Timeline.Add(s)); - // var path = Path.Combine(HSEConfiguration.Current.DataStoragePath, "rooms", $"{RoomId}.json"); - // Console.WriteLine($"Saving room {RoomId} to {path}!"); - // await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true)); - // } - // catch (TaskCanceledException) { } - // } - - private SemaphoreSlim saveSemaphore = new(1, 1); - - public async Task SaveDebounced() { - Task.Run(async () => { - await saveSemaphore.WaitAsync(); - try { - var path = Path.Combine(HSEConfiguration.Current.DataStoragePath, "rooms", $"{RoomId}.json"); - Console.WriteLine($"Saving room {RoomId} to {path}!"); - await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true)); - } - finally { - saveSemaphore.Release(); - } - }); - } - - private SemaphoreSlim stateRebuildSemaphore = new(1, 1); - - private FrozenSet<StateEventResponse> RebuildState() { - stateRebuildSemaphore.Wait(); - while (true) - try { - Console.WriteLine($"Rebuilding state for room {RoomId}"); - // ReSharper disable once RedundantEnumerableCastCall - This sometimes happens when the collection is modified during enumeration - List<StateEventResponse>? timeline = null; - lock (_timeline) { - timeline = Timeline.OfType<StateEventResponse>().ToList(); - } - - foreach (var evt in timeline) { - if (evt == null) { - throw new InvalidOperationException("Event is null"); - } - - if (evt.EventId == null) { - evt.EventId = "$" + Guid.NewGuid(); - } - else if (!evt.EventId.StartsWith('$')) { - evt.EventId = "$" + evt.EventId; - Console.WriteLine($"Sanitised invalid event ID {evt.EventId}"); - } - } - - _stateCache = timeline //.Where(s => s.Type == state.Type && s.StateKey == state.StateKey) - .Where(x => x.StateKey != null) - .OrderByDescending(s => s.OriginServerTs) - .DistinctBy(x => (x.Type, x.StateKey)) - .ToFrozenSet(); - - _timelineHash = _timeline.GetHashCode(); - stateRebuildSemaphore.Release(); - return _stateCache; - } - finally { } - } - } - - public List<StateEventResponse> GetRoomsByMember(string userId) { - // return _rooms - // // .Where(r => r.State.Any(s => s.Type == RoomMemberEventContent.EventId && s.StateKey == userId)) - // .Select(r => (Room: r, MemberEvent: r.State.SingleOrDefault(s => s.Type == RoomMemberEventContent.EventId && s.StateKey == userId))) - // .Where(r => r.MemberEvent != null) - // .ToDictionary(x => x.Room, x => x.MemberEvent!); - return _rooms.SelectMany(r => r.State.Where(s => s.Type == RoomMemberEventContent.EventId && s.StateKey == userId)).ToList(); - } -} \ No newline at end of file |