diff --git a/Tests/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs b/Tests/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs
deleted file mode 100644
index 73b0d23..0000000
--- a/Tests/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System.Collections;
-using System.Diagnostics.CodeAnalysis;
-using ArcaneLibs.Extensions;
-
-namespace LibMatrix.HomeserverEmulator.Services;
-
-public class HSEConfiguration {
- private static ILogger<HSEConfiguration> _logger;
- public static HSEConfiguration Current { get; set; }
-
- [RequiresUnreferencedCode("Uses reflection binding")]
- public HSEConfiguration(ILogger<HSEConfiguration> logger, IConfiguration config, HostBuilderContext host) {
- Current = this;
- _logger = logger;
- logger.LogInformation("Loading configuration for environment: {}...", host.HostingEnvironment.EnvironmentName);
- config.GetSection("HomeserverEmulator").Bind(this);
- if (StoreData) {
- DataStoragePath = ExpandPath(DataStoragePath ?? throw new NullReferenceException("DataStoragePath is not set"));
- CacheStoragePath = ExpandPath(CacheStoragePath ?? throw new NullReferenceException("CacheStoragePath is not set"));
- }
-
- _logger.LogInformation("Configuration loaded: {}", this.ToJson());
- }
-
- public string CacheStoragePath { get; set; }
-
- public string DataStoragePath { get; set; }
-
- public bool StoreData { get; set; } = true;
-
- public bool UnknownSyncTokenIsInitialSync { get; set; } = true;
-
- private static string ExpandPath(string path, bool retry = true) {
- _logger.LogInformation("Expanding path `{}`", path);
-
- if (path.StartsWith('~')) {
- path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), path[1..]);
- }
-
- Environment.GetEnvironmentVariables().Cast<DictionaryEntry>().OrderByDescending(x => x.Key.ToString()!.Length).ToList().ForEach(x => {
- path = path.Replace($"${x.Key}", x.Value.ToString());
- });
-
- _logger.LogInformation("Expanded path to `{}`", path);
- var tries = 0;
- while (retry && path.ContainsAnyOf("~$".Split())) {
- if (tries++ > 100)
- throw new Exception($"Path `{path}` contains unrecognised environment variables");
- path = ExpandPath(path, false);
- }
-
- if(path.StartsWith("./"))
- path = Path.Join(Directory.GetCurrentDirectory(), path[2..].Replace("/", Path.DirectorySeparatorChar.ToString()));
-
- return path;
- }
-}
\ No newline at end of file
diff --git a/Tests/LibMatrix.HomeserverEmulator/Services/MediaStore.cs b/Tests/LibMatrix.HomeserverEmulator/Services/MediaStore.cs
deleted file mode 100644
index 00f2a42..0000000
--- a/Tests/LibMatrix.HomeserverEmulator/Services/MediaStore.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Text.Json;
-using LibMatrix.Services;
-
-namespace LibMatrix.HomeserverEmulator.Services;
-
-public class MediaStore {
- private readonly HSEConfiguration _config;
- private readonly HomeserverResolverService _hsResolver;
- private List<MediaInfo> index = new();
-
- public MediaStore(HSEConfiguration config, HomeserverResolverService hsResolver) {
- _config = config;
- _hsResolver = hsResolver;
- if (config.StoreData) {
- var path = Path.Combine(config.DataStoragePath, "media");
- if (!Directory.Exists(path)) Directory.CreateDirectory(path);
- if (File.Exists(Path.Combine(path, "index.json")))
- index = JsonSerializer.Deserialize<List<MediaInfo>>(File.ReadAllText(Path.Combine(path, "index.json")));
- }
- else
- Console.WriteLine("Data storage is disabled, not loading rooms from disk");
- }
-
- // public async Task<object> UploadMedia(string userId, string mimeType, Stream stream, string? filename = null) {
- // var mediaId = $"mxc://{Guid.NewGuid().ToString()}";
- // var path = Path.Combine(_config.DataStoragePath, "media", mediaId);
- // if (!Directory.Exists(path)) Directory.CreateDirectory(path);
- // var file = Path.Combine(path, filename ?? "file");
- // await using var fs = File.Create(file);
- // await stream.CopyToAsync(fs);
- // index.Add(new() { });
- // return media;
- // }
-
- public async Task<Stream> GetRemoteMedia(string serverName, string mediaId) {
- if (_config.StoreData) {
- var path = Path.Combine(_config.DataStoragePath, "media", serverName, mediaId);
- if (!File.Exists(path)) {
- var mediaUrl = await _hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}");
- if (mediaUrl is null)
- throw new MatrixException() {
- ErrorCode = "M_NOT_FOUND",
- Error = "Media not found"
- };
- using var client = new HttpClient();
- var stream = await client.GetStreamAsync(mediaUrl);
- await using var fs = File.Create(path);
- await stream.CopyToAsync(fs);
- }
- return new FileStream(path, FileMode.Open);
- }
- else {
- var mediaUrl = await _hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}");
- if (mediaUrl is null)
- throw new MatrixException() {
- ErrorCode = "M_NOT_FOUND",
- Error = "Media not found"
- };
- using var client = new HttpClient();
- return await client.GetStreamAsync(mediaUrl);
- }
- }
- public class MediaInfo { }
-}
\ No newline at end of file
diff --git a/Tests/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs b/Tests/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs
deleted file mode 100644
index 0128ba6..0000000
--- a/Tests/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-namespace LibMatrix.HomeserverEmulator.Services;
-
-public class PaginationTokenResolverService(ILogger<PaginationTokenResolverService> logger, RoomStore roomStore, UserStore userStore) {
- public async Task<long?> ResolveTokenToTimestamp(string token) {
- logger.LogTrace("ResolveTokenToTimestamp({token})", token);
- if (token.StartsWith('$')) {
- //we have an event ID
- foreach (var room in roomStore._rooms) {
- var evt = await ResolveTokenToEvent(token, room);
- if (evt is not null) return evt.OriginServerTs;
- }
-
- // event not found
- throw new NotImplementedException();
- }
- else {
- // we have a sync token
- foreach (var user in userStore._users) {
- foreach (var (_, session) in user.AccessTokens) {
- if (!session.SyncStates.TryGetValue(token, out var syncState)) continue;
- long? maxTs = 0;
- foreach (var room in syncState.RoomPositions) {
- var roomObj = roomStore.GetRoomById(room.Key);
- if (roomObj is null)
- continue;
- var ts = roomObj.Timeline.Last().OriginServerTs;
- if (ts > maxTs) maxTs = ts;
- }
-
- return maxTs;
- }
- }
-
- throw new NotImplementedException();
- }
- }
-
- public async Task<StateEventResponse?> ResolveTokenToEvent(string token, RoomStore.Room room) {
- if (token.StartsWith('$')) {
- //we have an event ID
- logger.LogTrace("ResolveTokenToEvent(EventId({token}), Room({room})): searching for event...", token, room.RoomId);
-
- var evt = room.Timeline.SingleOrDefault(x => x.EventId == token);
- if (evt is not null) return evt;
- logger.LogTrace("ResolveTokenToEvent({token}, Room({room})): event not in requested room...", token, room.RoomId);
- return null;
- }
- else {
- // we have a sync token
- logger.LogTrace("ResolveTokenToEvent(SyncToken({token}), Room({room}))", token, room.RoomId);
- throw new NotImplementedException();
- }
- }
-}
\ No newline at end of file
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
diff --git a/Tests/LibMatrix.HomeserverEmulator/Services/TokenService.cs b/Tests/LibMatrix.HomeserverEmulator/Services/TokenService.cs
deleted file mode 100644
index cf79aae..0000000
--- a/Tests/LibMatrix.HomeserverEmulator/Services/TokenService.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace LibMatrix.HomeserverEmulator.Services;
-
-public class TokenService{
- public string? GetAccessTokenOrNull(HttpContext ctx) {
- //qry
- if (ctx.Request.Query.TryGetValue("access_token", out var token)) {
- return token;
- }
- //header
- if (ctx.Request.Headers.TryGetValue("Authorization", out var auth)) {
- var parts = auth.ToString().Split(' ');
- if (parts is ["Bearer", _]) {
- return parts[1];
- }
- }
- return null;
- }
-
- public string GetAccessToken(HttpContext ctx) {
- return GetAccessTokenOrNull(ctx) ?? throw new MatrixException() {
- ErrorCode = MatrixException.ErrorCodes.M_UNKNOWN_TOKEN,
- Error = "Missing token"
- };
- }
-
- public string? GenerateServerName(HttpContext ctx) {
- return ctx.Request.Host.ToString();
- }
-}
\ No newline at end of file
diff --git a/Tests/LibMatrix.HomeserverEmulator/Services/UserStore.cs b/Tests/LibMatrix.HomeserverEmulator/Services/UserStore.cs
deleted file mode 100644
index 4ce9f92..0000000
--- a/Tests/LibMatrix.HomeserverEmulator/Services/UserStore.cs
+++ /dev/null
@@ -1,250 +0,0 @@
-using System.Collections.Concurrent;
-using System.Collections.ObjectModel;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using ArcaneLibs;
-using ArcaneLibs.Collections;
-using ArcaneLibs.Extensions;
-using LibMatrix.EventTypes.Spec.State;
-using LibMatrix.Filters;
-using LibMatrix.Responses;
-
-namespace LibMatrix.HomeserverEmulator.Services;
-
-public class UserStore {
- public ConcurrentBag<User> _users = new();
- private readonly RoomStore _roomStore;
-
- public UserStore(HSEConfiguration config, RoomStore roomStore) {
- _roomStore = roomStore;
- if (config.StoreData) {
- var dataDir = Path.Combine(HSEConfiguration.Current.DataStoragePath, "users");
- if (!Directory.Exists(dataDir)) Directory.CreateDirectory(dataDir);
- foreach (var userId in Directory.GetDirectories(dataDir)) {
- var tokensDir = Path.Combine(dataDir, userId, "tokens.json");
- var path = Path.Combine(dataDir, userId, $"user.json");
-
- var user = JsonSerializer.Deserialize<User>(File.ReadAllText(path));
- user!.AccessTokens = JsonSerializer.Deserialize<ObservableDictionary<string, User.SessionInfo>>(File.ReadAllText(tokensDir))!;
- _users.Add(user);
- }
-
- Console.WriteLine($"Loaded {_users.Count} users from disk");
- }
- else {
- Console.WriteLine("Data storage is disabled, not loading users from disk");
- }
- }
-
- public async Task<User?> GetUserById(string userId, bool createIfNotExists = false) {
- if (_users.Any(x => x.UserId == userId))
- return _users.First(x => x.UserId == userId);
-
- if (!createIfNotExists)
- return null;
-
- return await CreateUser(userId);
- }
-
- public async Task<User?> GetUserByTokenOrNull(string token, bool createIfNotExists = false, string? serverName = null) {
- if (_users.Any(x => x.AccessTokens.ContainsKey(token)))
- return _users.First(x => x.AccessTokens.ContainsKey(token));
-
- if (!createIfNotExists)
- return null;
- if (string.IsNullOrWhiteSpace(serverName)) throw new NullReferenceException("Server name was not passed");
- var uid = $"@{Guid.NewGuid().ToString()}:{serverName}";
- return await CreateUser(uid);
- }
-
- public async Task<User> GetUserByToken(string token, bool createIfNotExists = false, string? serverName = null) {
- return await GetUserByTokenOrNull(token, createIfNotExists, serverName) ?? throw new MatrixException() {
- ErrorCode = MatrixException.ErrorCodes.M_UNKNOWN_TOKEN,
- Error = "Invalid token."
- };
- }
-
- public async Task<User> CreateUser(string userId, Dictionary<string, object>? profile = null) {
- profile ??= new();
- if (!profile.ContainsKey("displayname")) profile.Add("displayname", userId.Split(":")[0]);
- if (!profile.ContainsKey("avatar_url")) profile.Add("avatar_url", null);
- var user = new User() {
- UserId = userId,
- AccountData = new() {
- new StateEventResponse() {
- Type = "im.vector.analytics",
- RawContent = new JsonObject() {
- ["pseudonymousAnalyticsOptIn"] = false
- },
- },
- new StateEventResponse() {
- Type = "im.vector.web.settings",
- RawContent = new JsonObject() {
- ["developerMode"] = true
- }
- },
- }
- };
- user.Profile.AddRange(profile);
- _users.Add(user);
- if (!_roomStore._rooms.IsEmpty)
- foreach (var item in Random.Shared.GetItems(_roomStore._rooms.ToArray(), Math.Min(_roomStore._rooms.Count, 400))) {
- item.AddUser(userId);
- }
-
- int random = Random.Shared.Next(10);
- for (int i = 0; i < random; i++) {
- var room = _roomStore.CreateRoom(new());
- room.AddUser(userId);
- }
-
- return user;
- }
-
- public class User : NotifyPropertyChanged {
- public User() {
- AccessTokens = new();
- Filters = new();
- Profile = new();
- AccountData = new();
- RoomKeys = new();
- AuthorizedSessions = new();
- }
-
- private CancellationTokenSource _debounceCts = new();
- private string _userId;
- private ObservableDictionary<string, SessionInfo> _accessTokens;
- private ObservableDictionary<string, SyncFilter> _filters;
- private ObservableDictionary<string, object> _profile;
- private ObservableCollection<StateEventResponse> _accountData;
- private ObservableDictionary<string, RoomKeysResponse> _roomKeys;
- private ObservableDictionary<string, AuthorizedSession> _authorizedSessions;
-
- public string UserId {
- get => _userId;
- set => SetField(ref _userId, value);
- }
-
- public ObservableDictionary<string, SessionInfo> AccessTokens {
- get => _accessTokens;
- set {
- if (value == _accessTokens) return;
- _accessTokens = new(value);
- _accessTokens.CollectionChanged += async (sender, args) => await SaveDebounced();
- OnPropertyChanged();
- }
- }
-
- public ObservableDictionary<string, SyncFilter> Filters {
- get => _filters;
- set {
- if (value == _filters) return;
- _filters = new(value);
- _filters.CollectionChanged += async (sender, args) => await SaveDebounced();
- OnPropertyChanged();
- }
- }
-
- public ObservableDictionary<string, object> Profile {
- get => _profile;
- set {
- if (value == _profile) return;
- _profile = new(value);
- _profile.CollectionChanged += async (sender, args) => await SaveDebounced();
- OnPropertyChanged();
- }
- }
-
- public ObservableCollection<StateEventResponse> AccountData {
- get => _accountData;
- set {
- if (value == _accountData) return;
- _accountData = new(value);
- _accountData.CollectionChanged += async (sender, args) => await SaveDebounced();
- OnPropertyChanged();
- }
- }
-
- public ObservableDictionary<string, RoomKeysResponse> RoomKeys {
- get => _roomKeys;
- set {
- if (value == _roomKeys) return;
- _roomKeys = new(value);
- _roomKeys.CollectionChanged += async (sender, args) => await SaveDebounced();
- OnPropertyChanged();
- }
- }
-
- public ObservableDictionary<string, AuthorizedSession> AuthorizedSessions {
- get => _authorizedSessions;
- set {
- if (value == _authorizedSessions) return;
- _authorizedSessions = new(value);
- _authorizedSessions.CollectionChanged += async (sender, args) => await SaveDebounced();
- OnPropertyChanged();
- }
- }
-
- public async Task SaveDebounced() {
- if (!HSEConfiguration.Current.StoreData) return;
- await _debounceCts.CancelAsync();
- _debounceCts = new CancellationTokenSource();
- try {
- await Task.Delay(250, _debounceCts.Token);
- var dataDir = Path.Combine(HSEConfiguration.Current.DataStoragePath, "users", _userId);
- if (!Directory.Exists(dataDir)) Directory.CreateDirectory(dataDir);
- var tokensDir = Path.Combine(dataDir, "tokens.json");
- var path = Path.Combine(dataDir, $"user.json");
- Console.WriteLine($"Saving user {_userId} to {path}!");
- await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true));
- await File.WriteAllTextAsync(tokensDir, AccessTokens.ToJson(ignoreNull: true));
- }
- catch (TaskCanceledException) { }
- catch (InvalidOperationException) { } // We don't care about 100% data safety, this usually happens when something is updated while serialising
- }
-
- public class SessionInfo {
- public string DeviceId { get; set; } = Guid.NewGuid().ToString();
- public Dictionary<string, UserSyncState> SyncStates { get; set; } = new();
-
- public class UserSyncState {
- public Dictionary<string, SyncRoomPosition> RoomPositions { get; set; } = new();
- public string FilterId { get; set; }
- public DateTime SyncStateCreated { get; set; } = DateTime.Now;
-
- public class SyncRoomPosition {
- public int TimelinePosition { get; set; }
- public string LastTimelineEventId { get; set; }
- public int AccountDataPosition { get; set; }
- public bool Joined { get; set; }
- }
-
- public UserSyncState Clone() {
- return new() {
- FilterId = FilterId,
- RoomPositions = RoomPositions.ToDictionary(x => x.Key, x => new SyncRoomPosition() {
- TimelinePosition = x.Value.TimelinePosition,
- AccountDataPosition = x.Value.AccountDataPosition
- })
- };
- }
- }
- }
-
- public LoginResponse Login() {
- var session = new SessionInfo();
- AccessTokens.Add(Guid.NewGuid().ToString(), session);
- SaveDebounced();
- return new LoginResponse() {
- AccessToken = AccessTokens.Keys.Last(),
- DeviceId = session.DeviceId,
- UserId = UserId
- };
- }
-
- public class AuthorizedSession {
- public string Homeserver { get; set; }
- public string AccessToken { get; set; }
- }
- }
-}
\ No newline at end of file
|