about summary refs log tree commit diff
path: root/Tests/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs
blob: 1653110e13f9a4a05f42c7cde7783fb00fa7d898 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text.Json.Nodes;
using ArcaneLibs.Extensions;
using LibMatrix.HomeserverEmulator.Services;
using LibMatrix.Responses;
using LibMatrix.Services;
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);
        if (token == null)
            throw new MatrixException() {
                ErrorCode = "M_MISSING_TOKEN",
                Error = "Missing token"
            };

        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];

        if (string.IsNullOrWhiteSpace(since))
            return InitialSync(user, session);

        if (!session.SyncStates.TryGetValue(since, out var syncState))
            if (!cfg.UnknownSyncTokenIsInitialSync)
                throw new MatrixException() {
                    ErrorCode = "M_UNKNOWN",
                    Error = "Unknown sync token."
                };
            else
                return InitialSync(user, session);

        var response = new SyncResponse() {
            NextBatch = Guid.NewGuid().ToString(),
            DeviceOneTimeKeysCount = new()
        };

        session.SyncStates.Add(response.NextBatch, new() {
            RoomPositions = syncState.RoomPositions.ToDictionary(x => x.Key, x => new UserStore.User.SessionInfo.UserSyncState.SyncRoomPosition() {
                StatePosition = roomStore._rooms.First(y => y.RoomId == x.Key).State.Count,
                TimelinePosition = roomStore._rooms.First(y => y.RoomId == x.Key).Timeline.Count,
                AccountDataPosition = roomStore._rooms.First(y => y.RoomId == x.Key).AccountData[user.UserId].Count
            })
        });

        if (!string.IsNullOrWhiteSpace(since)) {
            while (sw.ElapsedMilliseconds < timeout && response.Rooms?.Join is not { Count: > 0 }) {
                await Task.Delay(100);
                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) {
                    var roomPositions = syncState.RoomPositions[room.RoomId];

                    response.Rooms ??= new();
                    response.Rooms.Join ??= new();
                    response.Rooms.Join[room.RoomId] = new() {
                        State = new(room.State.Skip(roomPositions.StatePosition).ToList()),
                        Timeline = new(events: room.Timeline.Skip(roomPositions.TimelinePosition).ToList(), limited: false),
                        AccountData = new(room.AccountData.GetOrCreate(user.UserId, _ => []).Skip(roomPositions.AccountDataPosition).ToList())
                    };
                    session.SyncStates[response.NextBatch].RoomPositions[room.RoomId] = new() {
                        StatePosition = room.State.Count,
                        TimelinePosition = room.Timeline.Count,
                        AccountDataPosition = room.AccountData[user.UserId].Count
                    };

                    if (response.Rooms.Join[room.RoomId].State.Events.Count == 0 &&
                        response.Rooms.Join[room.RoomId].Timeline.Events.Count == 0 &&
                        response.Rooms.Join[room.RoomId].AccountData.Events.Count == 0
                       )
                        response.Rooms.Join.Remove(room.RoomId);
                }
            }
        }

        return response;
    }

    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())
            };
            session.SyncStates[response.NextBatch].RoomPositions[room.RoomId] = new() {
                StatePosition = room.State.Count,
                TimelinePosition = room.Timeline.Count,
                AccountDataPosition = room.AccountData[user.UserId].Count
            };
        }

        return response;
    }
}