about summary refs log tree commit diff
path: root/ModerationClient/ViewModels/ClientViewModel.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ModerationClient/ViewModels/ClientViewModel.cs')
-rw-r--r--ModerationClient/ViewModels/ClientViewModel.cs155
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) {