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) {
diff --git a/ModerationClient/ViewModels/MainWindowViewModel.cs b/ModerationClient/ViewModels/MainWindowViewModel.cs
index be64de4..5cd5c45 100644
--- a/ModerationClient/ViewModels/MainWindowViewModel.cs
+++ b/ModerationClient/ViewModels/MainWindowViewModel.cs
@@ -1,7 +1,6 @@
using System;
using Avalonia;
using ModerationClient.Services;
-using ModerationClient.Views;
namespace ModerationClient.ViewModels;
@@ -9,10 +8,10 @@ public partial class MainWindowViewModel(MatrixAuthenticationService authService
// public MainWindow? MainWindow { get; set; }
private float _scale = 1.0f;
- private ViewModelBase _currentViewModel = new LoginViewModel(authService);
+ private ViewModelBase? _currentViewModel = null;
private Size _physicalSize = new Size(300, 220);
- public ViewModelBase CurrentViewModel {
+ public ViewModelBase? CurrentViewModel {
get => _currentViewModel;
set => SetProperty(ref _currentViewModel, value);
}
diff --git a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
index 7a2ad63..90020d6 100644
--- a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
+++ b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
@@ -1,20 +1,10 @@
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.Collections;
using ArcaneLibs.Extensions;
-using LibMatrix.EventTypes.Spec.State;
-using LibMatrix.Helpers;
using LibMatrix.Homeservers;
using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses;
-using LibMatrix.Responses;
-using MatrixUtils.Abstractions;
using Microsoft.Extensions.Logging;
using ModerationClient.Services;
@@ -51,6 +41,7 @@ public partial class UserManagementViewModel : ViewModelBase {
}
await foreach (var user in synapse.Admin.SearchUsersAsync(chunkLimit: 100)) {
+ Program.Beep(250, 1);
Console.WriteLine("USERMANAGER GOT USER: " + user.ToJson(indent:false, ignoreNull: true));
Users.Add(JsonSerializer.Deserialize<User>(user.ToJson())!);
}
@@ -58,6 +49,6 @@ public partial class UserManagementViewModel : ViewModelBase {
}
}
-public class User : AdminUserListResult.AdminUserListResultUser {
+public class User : SynapseAdminUserListResult.SynapseAdminUserListResultUser {
}
\ No newline at end of file
|