diff options
Diffstat (limited to 'Tests/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs')
-rw-r--r-- | Tests/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs | 272 |
1 files changed, 0 insertions, 272 deletions
diff --git a/Tests/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs b/Tests/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs deleted file mode 100644 index 024d071..0000000 --- a/Tests/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.HomeserverEmulator.Extensions; -using LibMatrix.HomeserverEmulator.Services; -using LibMatrix.Responses; -using Microsoft.AspNetCore.Mvc; - -namespace LibMatrix.HomeserverEmulator.Controllers; - -[ApiController] -[Route("/_matrix/client/{version}/")] -public class SyncController(ILogger<SyncController> logger, TokenService tokenService, UserStore userStore, RoomStore roomStore, HSEConfiguration cfg) : ControllerBase { - [HttpGet("sync")] - [SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action", Justification = "Endpoint is expected to wait until data is available or timeout.")] - public async Task<SyncResponse> Sync([FromQuery] string? since = null, [FromQuery] int? timeout = 5000) { - var sw = Stopwatch.StartNew(); - var token = tokenService.GetAccessToken(HttpContext); - - var user = await userStore.GetUserByToken(token); - if (user == null) - throw new MatrixException() { - ErrorCode = "M_UNKNOWN_TOKEN", - Error = "No such user" - }; - var session = user.AccessTokens[token]; - UserStore.User.SessionInfo.UserSyncState newSyncState = new(); - - SyncResponse syncResp; - if (string.IsNullOrWhiteSpace(since) || !session.SyncStates.ContainsKey(since)) - syncResp = InitialSync(user, session); - else { - var syncState = session.SyncStates[since]; - newSyncState = syncState.Clone(); - - var newSyncToken = Guid.NewGuid().ToString(); - do { - syncResp = IncrementalSync(user, session, syncState); - syncResp.NextBatch = newSyncToken; - } while (!await HasDataOrStall(syncResp) && sw.ElapsedMilliseconds < timeout); - - if (sw.ElapsedMilliseconds > timeout) { - logger.LogTrace("Sync timed out after {Elapsed}", sw.Elapsed); - return new() { - NextBatch = since - }; - } - } - - session.SyncStates[syncResp.NextBatch] = RecalculateSyncStates(newSyncState, syncResp); - logger.LogTrace("Responding to sync after {totalElapsed}", sw.Elapsed); - return syncResp; - } - - private UserStore.User.SessionInfo.UserSyncState RecalculateSyncStates(UserStore.User.SessionInfo.UserSyncState newSyncState, SyncResponse sync) { - logger.LogTrace("Updating sync state"); - var syncStateRecalcSw = Stopwatch.StartNew(); - foreach (var (roomId, roomData) in sync.Rooms?.Join ?? []) { - if (!newSyncState.RoomPositions.ContainsKey(roomId)) - newSyncState.RoomPositions[roomId] = new(); - var state = newSyncState.RoomPositions[roomId]; - - state.TimelinePosition += roomData.Timeline?.Events?.Count ?? 0; - state.LastTimelineEventId = roomData.Timeline?.Events?.LastOrDefault()?.EventId ?? state.LastTimelineEventId; - state.AccountDataPosition += roomData.AccountData?.Events?.Count ?? 0; - state.Joined = true; - } - - foreach (var (roomId, _) in sync.Rooms?.Invite ?? []) { - if (!newSyncState.RoomPositions.ContainsKey(roomId)) - newSyncState.RoomPositions[roomId] = new() { - Joined = false - }; - } - - foreach (var (roomId, _) in sync.Rooms?.Leave ?? []) { - if (newSyncState.RoomPositions.ContainsKey(roomId)) - newSyncState.RoomPositions.Remove(roomId); - } - - logger.LogTrace("Updated sync state in {Elapsed}", syncStateRecalcSw.Elapsed); - - return newSyncState; - } - -#region Initial Sync parts - - private SyncResponse InitialSync(UserStore.User user, UserStore.User.SessionInfo session) { - var response = new SyncResponse() { - NextBatch = Guid.NewGuid().ToString(), - DeviceOneTimeKeysCount = new(), - AccountData = new(events: user.AccountData.ToList()) - }; - - session.SyncStates.Add(response.NextBatch, new()); - - var rooms = roomStore._rooms.Where(x => x.State.Any(y => y.Type == "m.room.member" && y.StateKey == user.UserId)).ToList(); - foreach (var room in rooms) { - response.Rooms ??= new(); - response.Rooms.Join ??= new(); - - response.Rooms.Join[room.RoomId] = new() { - State = new(room.State.ToList()), - Timeline = new(events: room.Timeline.ToList(), limited: false), - AccountData = new(room.AccountData.GetOrCreate(user.UserId, _ => []).ToList()) - }; - } - - return response; - } - - private SyncResponse.RoomsDataStructure.JoinedRoomDataStructure GetInitialSyncRoomData(RoomStore.Room room, UserStore.User user) { - return new() { - State = new(room.State.ToList()), - Timeline = new(room.Timeline.ToList(), false), - AccountData = new(room.AccountData.GetOrCreate(user.UserId, _ => []).ToList()) - }; - } - -#endregion - - private SyncResponse IncrementalSync(UserStore.User user, UserStore.User.SessionInfo session, UserStore.User.SessionInfo.UserSyncState syncState) { - return new SyncResponse { - Rooms = GetIncrementalSyncRooms(user, session, syncState) - }; - } - -#region Incremental Sync parts - - private SyncResponse.RoomsDataStructure GetIncrementalSyncRooms(UserStore.User user, UserStore.User.SessionInfo session, UserStore.User.SessionInfo.UserSyncState syncState) { - SyncResponse.RoomsDataStructure data = new() { - Join = [], - Invite = [], - Leave = [] - }; - - // step 1: check previously synced rooms - foreach (var (roomId, roomPosition) in syncState.RoomPositions) { - var room = roomStore.GetRoomById(roomId); - if (room == null) { - // room no longer exists - data.Leave[roomId] = new(); - continue; - } - - if (roomPosition.Joined) { - var newTimelineEvents = room.Timeline.Skip(roomPosition.TimelinePosition).ToList(); - var newAccountDataEvents = room.AccountData[user.UserId].Skip(roomPosition.AccountDataPosition).ToList(); - if (newTimelineEvents.Count == 0 && newAccountDataEvents.Count == 0) continue; - data.Join[room.RoomId] = new() { - State = new(newTimelineEvents.GetCalculatedState()), - Timeline = new(newTimelineEvents, false) - }; - } - } - - if (data.Join.Count > 0) return data; - - // step 2: check newly joined rooms - var untrackedRooms = roomStore._rooms.Where(r => !syncState.RoomPositions.ContainsKey(r.RoomId)).ToList(); - - var allJoinedRooms = roomStore.GetRoomsByMember(user.UserId).ToArray(); - if (allJoinedRooms.Length == 0) return data; - var rooms = Random.Shared.GetItems(allJoinedRooms, Math.Min(allJoinedRooms.Length, 50)); - foreach (var membership in rooms) { - var membershipContent = membership.TypedContent as RoomMemberEventContent ?? - throw new InvalidOperationException("Membership event content is not RoomMemberEventContent"); - var room = roomStore.GetRoomById(membership.RoomId!); - //handle leave - if (syncState.RoomPositions.TryGetValue(membership.RoomId!, out var syncPosition)) { - // logger.LogTrace("Found sync position {roomId} {value}", room.RoomId, syncPosition.ToJson(indent: false, ignoreNull: false)); - - if (membershipContent.Membership == "join") { - var newTimelineEvents = room.Timeline.Skip(syncPosition.TimelinePosition).ToList(); - var newAccountDataEvents = room.AccountData[user.UserId].Skip(syncPosition.AccountDataPosition).ToList(); - if (newTimelineEvents.Count == 0 && newAccountDataEvents.Count == 0) continue; - data.Join[room.RoomId] = new() { - State = new(newTimelineEvents.GetCalculatedState()), - Timeline = new(newTimelineEvents, false) - }; - } - } - else { - //syncPosisition = null - if (membershipContent.Membership == "join") { - var joinData = data.Join[membership.RoomId!] = new() { - State = new(room.State.ToList()), - Timeline = new(events: room.Timeline.ToList(), limited: false), - AccountData = new(room.AccountData.GetOrCreate(user.UserId, _ => []).ToList()) - }; - } - } - } - - //handle nonexistant rooms - foreach (var roomId in syncState.RoomPositions.Keys) { - if (!roomStore._rooms.Any(x => x.RoomId == roomId)) { - data.Leave[roomId] = new(); - session.SyncStates[session.SyncStates.Last().Key].RoomPositions.Remove(roomId); - } - } - - return data; - } - -#endregion - - private async Task<bool> HasDataOrStall(SyncResponse resp) { - // logger.LogTrace("Checking if sync response has data: {resp}", resp.ToJson(indent: false, ignoreNull: true)); - // if (resp.AccountData?.Events?.Count > 0) return true; - // if (resp.Rooms?.Invite?.Count > 0) return true; - // if (resp.Rooms?.Join?.Count > 0) return true; - // if (resp.Rooms?.Leave?.Count > 0) return true; - // if (resp.Presence?.Events?.Count > 0) return true; - // if (resp.DeviceLists?.Changed?.Count > 0) return true; - // if (resp.DeviceLists?.Left?.Count > 0) return true; - // if (resp.ToDevice?.Events?.Count > 0) return true; - // - // var hasData = - // resp is not { - // AccountData: null or { - // Events: null or { Count: 0 } - // }, - // Rooms: null or { - // Invite: null or { Count: 0 }, - // Join: null or { Count: 0 }, - // Leave: null or { Count: 0 } - // }, - // Presence: null or { - // Events: null or { Count: 0 } - // }, - // DeviceLists: null or { - // Changed: null or { Count: 0 }, - // Left: null or { Count: 0 } - // }, - // ToDevice: null or { - // Events: null or { Count: 0 } - // } - // }; - - var hasData = resp is { - AccountData: { - Events: { Count: > 0 } - } - } or { - Presence: { - Events: { Count: > 0 } - } - } or { - DeviceLists: { - Changed: { Count: > 0 }, - Left: { Count: > 0 } - } - } or { - ToDevice: { - Events: { Count: > 0 } - } - }; - - if (!hasData) { - // hasData = - } - - if (!hasData) { - // logger.LogDebug($"Sync response has no data, stalling for 1000ms: {resp.ToJson(indent: false, ignoreNull: true)}"); - await Task.Delay(10); - } - - return hasData; - } -} \ No newline at end of file |