diff options
Diffstat (limited to 'ModerationClient/ViewModels/ClientViewModel.cs')
-rw-r--r-- | ModerationClient/ViewModels/ClientViewModel.cs | 155 |
1 files changed, 118 insertions, 37 deletions
diff --git a/ModerationClient/ViewModels/ClientViewModel.cs b/ModerationClient/ViewModels/ClientViewModel.cs index 312b46a..fb3681e 100644 --- a/ModerationClient/ViewModels/ClientViewModel.cs +++ b/ModerationClient/ViewModels/ClientViewModel.cs @@ -1,17 +1,20 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; +using ArcaneLibs; using ArcaneLibs.Collections; +using LibMatrix; using LibMatrix.EventTypes.Spec.State; using LibMatrix.Helpers; using LibMatrix.Responses; -using MatrixUtils.Abstractions; using Microsoft.Extensions.Logging; +using ModerationClient.Models.SpaceTreeNodes; using ModerationClient.Services; namespace ModerationClient.ViewModels; @@ -22,7 +25,13 @@ public partial class ClientViewModel : ViewModelBase { _authService = authService; _cfg = cfg; DisplayedSpaces.Add(_allRoomsNode = new AllRoomsSpaceNode(this)); - _ = Task.Run(Run); + DisplayedSpaces.Add(DirectMessages = new SpaceNode(false) { Name = "Direct messages" }); + _ = Task.Run(Run).ContinueWith(x => { + if (x.IsFaulted) { + Status = "Critical error running client view model: " + x.Exception?.Message; + _logger.LogError(x.Exception, "Error running client view model."); + } + }); } private readonly ILogger<ClientViewModel> _logger; @@ -33,6 +42,9 @@ public partial class ClientViewModel : ViewModelBase { private string _status = "Loading..."; public ObservableCollection<SpaceNode> DisplayedSpaces { get; } = []; public ObservableDictionary<string, RoomNode> AllRooms { get; } = new(); + public SpaceNode DirectMessages { get; } + + public bool Paused { get; set; } = false; public SpaceNode CurrentSpace { get => _currentSpace ?? _allRoomsNode; @@ -45,25 +57,50 @@ public partial class ClientViewModel : ViewModelBase { } public async Task Run() { - Status = "Interrupted."; - return; + Console.WriteLine("Running client view model loop..."); + ArgumentNullException.ThrowIfNull(_authService.Homeserver, nameof(_authService.Homeserver)); + // var sh = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache"))); + var store = new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache")); + Console.WriteLine($"Sync store at {store.TargetPath}"); + + var sh = new SyncHelper(_authService.Homeserver, _logger, storageProvider: store) { + // MinimumDelay = TimeSpan.FromSeconds(1) + }; + Console.WriteLine("Sync helper created."); + + //optimise - we create a new scope here to make ssr go out of scope + // if((await sh.GetUnoptimisedStoreCount()) > 1000) + { + Console.WriteLine("RUN - Optimising sync store..."); + Status = "Optimising sync store, please wait..."; + var ssr = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: store); + Console.WriteLine("Created sync state resolver..."); + Status = "Optimising sync store, please wait... Creating new snapshot..."; + await ssr.OptimiseStore(); + Status = "Optimising sync store, please wait... Deleting old intermediate snapshots..."; + await ssr.RemoveOldSnapshots(); + } + + var unoptimised = await sh.GetUnoptimisedStoreCount(); // this is slow, so we cache Status = "Doing initial sync..."; - var sh = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache"))); - // var res = await sh.SyncAsync(); - //await sh.OptimiseStore(); - while (true) { - // Status = "Syncing..."; - var res = await sh.ContinueAsync(); - Status = $"Processing sync... {res.next.NextBatch}"; - await ApplySpaceChanges(res.next); - //OnPropertyChanged(nameof(CurrentSpace)); - //OnPropertyChanged(nameof(CurrentSpace.ChildRooms)); - // Console.WriteLine($"mow A={AllRooms.Count}|D={DisplayedSpaces.Count}"); - // for (int i = 0; i < GC.MaxGeneration; i++) { - // GC.Collect(i, GCCollectionMode.Forced, blocking: true); - // GC.WaitForPendingFinalizers(); - // } - Status = "Syncing..."; + await foreach (var res in sh.EnumerateSyncAsync()) { + Program.Beep((short)250, 0); + Status = $"Processing sync... {res.NextBatch}"; + await ApplySyncChanges(res); + + Program.Beep(0, 0); + if (Paused) { + Status = "Sync loop interrupted... Press pause/break to resume."; + while (Paused) await Task.Delay(1000); + } + else Status = $"Syncing... {unoptimised++} unoptimised sync responses..."; + } + } + + private async Task ApplySyncChanges(SyncResponse newSync) { + await ApplySpaceChanges(newSync); + if (newSync.AccountData?.Events?.FirstOrDefault(x => x.Type == "m.direct") is { } evt) { + await ApplyDirectMessagesChanges(evt); } } @@ -71,20 +108,25 @@ public partial class ClientViewModel : ViewModelBase { List<Task> tasks = []; foreach (var room in newSync.Rooms?.Join ?? []) { if (!AllRooms.ContainsKey(room.Key)) { - AllRooms.Add(room.Key, new RoomNode { Name = "Loading..." }); + // AllRooms.Add(room.Key, new RoomNode { Name = "Loading..." }); + AllRooms.Add(room.Key, new RoomNode { Name = "", RoomID = room.Key }); } if (room.Value.State?.Events is not null) { var nameEvent = room.Value.State!.Events!.FirstOrDefault(x => x.Type == "m.room.name" && x.StateKey == ""); - AllRooms[room.Key].Name = (nameEvent?.TypedContent as RoomNameEventContent)?.Name ?? ""; - if (string.IsNullOrWhiteSpace(AllRooms[room.Key].Name)) { - AllRooms[room.Key].Name = "Loading..."; - tasks.Add(_authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync().ContinueWith(r => AllRooms[room.Key].Name = r.Result)); - } + if (nameEvent is not null) + AllRooms[room.Key].Name = (nameEvent?.TypedContent as RoomNameEventContent)?.Name ?? ""; + } + + if (string.IsNullOrWhiteSpace(AllRooms[room.Key].Name)) { + AllRooms[room.Key].Name = "Loading..."; + tasks.Add(_authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync().ContinueWith(r => AllRooms[room.Key].Name = r.Result)); + // Status = $"Getting room name for {room.Key}..."; + // AllRooms[room.Key].Name = await _authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync(); } } - - await Task.WhenAll(tasks); + + await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying room changes..."); return; @@ -111,20 +153,59 @@ public partial class ClientViewModel : ViewModelBase { handledRoomIds.Add(roomId); } } -} -public class SpaceNode : RoomNode { - public ObservableCollection<SpaceNode> ChildSpaces { get; set; } = []; - public ObservableCollection<RoomNode> ChildRooms { get; set; } = []; -} + private async Task ApplyDirectMessagesChanges(StateEventResponse evt) { + _logger.LogCritical("Direct messages updated!"); + var dms = evt.RawContent.Deserialize<Dictionary<string, string[]?>>(); + List<Task> tasks = []; + foreach (var (userId, roomIds) in dms) { + if (roomIds is null || roomIds.Length == 0) continue; + var space = DirectMessages.ChildSpaces.FirstOrDefault(x => x.RoomID == userId); + if (space is null) { + space = new SpaceNode { Name = userId, RoomID = userId }; + tasks.Add(_authService.Homeserver!.GetProfileAsync(userId) + .ContinueWith(r => space.Name = string.IsNullOrWhiteSpace(r.Result?.DisplayName) ? userId : r.Result.DisplayName)); + DirectMessages.ChildSpaces.Add(space); + } + + foreach (var roomId in roomIds) { + var room = space.ChildRooms.FirstOrDefault(x => x.RoomID == roomId); + if (room is null) { + room = AllRooms.TryGetValue(roomId, out var existing) ? existing : new RoomNode { Name = "Unknown: " + roomId, RoomID = roomId }; + space.ChildRooms.Add(room); + } + } -public class RoomNode { - public string Name { get; set; } + foreach (var spaceChildRoom in space.ChildRooms.ToList()) { + if (!roomIds.Contains(spaceChildRoom.RoomID)) { + space.ChildRooms.Remove(spaceChildRoom); + } + } + } + + await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying DM changes..."); + } + + private async Task AwaitTasks(List<Task> tasks, string message) { + if (tasks.Count > 0) { + int total = tasks.Count; + while (tasks.Any(x => !x.IsCompleted)) { + int incomplete = tasks.Count(x => !x.IsCompleted); + Program.Beep((short)MathUtil.Map(incomplete, 0, total, 20, 7500), 5); + // Program.Beep(0, 0); + Status = string.Format(message, incomplete, total); + await Task.WhenAny(tasks); + tasks.RemoveAll(x => x.IsCompleted); + } + + Program.Beep(0, 0); + } + } } // implementation details public class AllRoomsSpaceNode : SpaceNode { - public AllRoomsSpaceNode(ClientViewModel vm) { + public AllRoomsSpaceNode(ClientViewModel vm) : base(false) { Name = "All rooms"; vm.AllRooms.CollectionChanged += (_, args) => { switch (args.Action) { |