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.cs128
1 files changed, 87 insertions, 41 deletions
diff --git a/ModerationClient/ViewModels/ClientViewModel.cs b/ModerationClient/ViewModels/ClientViewModel.cs

index fb3681e..ab4f2da 100644 --- a/ModerationClient/ViewModels/ClientViewModel.cs +++ b/ModerationClient/ViewModels/ClientViewModel.cs
@@ -6,14 +6,18 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using ArcaneLibs; using ArcaneLibs.Collections; +using ArcaneLibs.Extensions; using LibMatrix; using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; using LibMatrix.Responses; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using ModerationClient.Models.SpaceTreeNodes; using ModerationClient.Services; @@ -40,60 +44,111 @@ public partial class ClientViewModel : ViewModelBase { private SpaceNode? _currentSpace; private readonly SpaceNode _allRoomsNode; private string _status = "Loading..."; + private RoomNode? _currentRoom; public ObservableCollection<SpaceNode> DisplayedSpaces { get; } = []; public ObservableDictionary<string, RoomNode> AllRooms { get; } = new(); public SpaceNode DirectMessages { get; } - public bool Paused { get; set; } = false; + public bool Paused { get; set; } = true; + + public string Status { + get => _status + " " + DateTime.Now; + set => SetProperty(ref _status, value); + } public SpaceNode CurrentSpace { get => _currentSpace ?? _allRoomsNode; set => SetProperty(ref _currentSpace, value); } - public string Status { - get => _status + " " + DateTime.Now; - set => SetProperty(ref _status, value); + public RoomNode? CurrentRoom { + get => _currentRoom; + set { + if (SetProperty(ref _currentRoom, value)) OnPropertyChanged(nameof(CurrentRoomViewModel)); + } } + public RoomViewModel? CurrentRoomViewModel => CurrentRoom is not null ? new RoomViewModel(_authService.Homeserver!, CurrentRoom) : null; + public async Task Run() { 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")); + var mediaCache = new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "mediaCache")); Console.WriteLine($"Sync store at {store.TargetPath}"); - var sh = new SyncHelper(_authService.Homeserver, _logger, storageProvider: store) { + var sh = new SyncHelper(_authService.Homeserver, new NullLogger<SyncHelper>(), storageProvider: store) { // MinimumDelay = TimeSpan.FromSeconds(1) }; Console.WriteLine("Sync helper created."); + var unoptimised = await sh.GetUnoptimisedStoreCount(); // this is slow, so we cache //optimise - we create a new scope here to make ssr go out of scope - // if((await sh.GetUnoptimisedStoreCount()) > 1000) - { + if (unoptimised > 100) { 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(); + await ssr.OptimiseStore((remaining, total) => { + if (remaining % 25 == 0) + Status = $"Optimising sync store, please wait... {remaining}/{total} remaining..."; + }); Status = "Optimising sync store, please wait... Deleting old intermediate snapshots..."; await ssr.RemoveOldSnapshots(); + for (int gen = 0; gen < GC.MaxGeneration; gen++) { + Status = $"Collecting garbage #{gen}: {Util.BytesToString(GC.GetGCMemoryInfo().TotalCommittedBytes)}"; + await Task.Delay(1000); + } + + Status = $"Collecting garbage: {GC.GetTotalMemory(true) / 1024 / 1024}MB"; + await Task.Delay(1000); } - var unoptimised = await sh.GetUnoptimisedStoreCount(); // this is slow, so we cache + GC.Collect(); + unoptimised = 0; Status = "Doing initial sync..."; + var currentSyncRes = "init"; + var lastGc = DateTime.Now; await foreach (var res in sh.EnumerateSyncAsync()) { - Program.Beep((short)250, 0); + var sw = Stopwatch.StartNew(); + //log thing + + // Program.Beep((short)250, 0); Status = $"Processing sync... {res.NextBatch}"; + + foreach (var (key, value) in res.Rooms?.Leave ?? []) { + List<StateEventResponse> events = [..value.Timeline?.Events ?? [], ..value.State?.Events ?? []]; + var ownEvents = events.Where(x => x is { StateKey: "@emma:rory.gay", Type: RoomMemberEventContent.EventId }).ToList(); + foreach (var evt in ownEvents) { + var ct = evt.ContentAs<RoomMemberEventContent>()!; + Console.WriteLine($"Room {key,60} removed: {evt.Type} {evt.StateKey} by {evt.Sender,80}, membership: {ct.Membership,6}, reason: {ct.Reason}"); + } + } + + if (res.Rooms?.Leave is { Count: > 0 }) + Console.WriteLine($"Processed {res.Rooms?.Leave?.Count} left rooms"); + // await Task.Delay(10000); + + var applySw = Stopwatch.StartNew(); await ApplySyncChanges(res); + applySw.Stop(); Program.Beep(0, 0); + if (DateTime.Now - lastGc > TimeSpan.FromMinutes(1)) { + lastGc = DateTime.Now; + GC.Collect(); + } + + Console.WriteLine($"Processed sync {currentSyncRes} in {sw.ElapsedMilliseconds}ms (applied in: {applySw.ElapsedMilliseconds}ms)"); if (Paused) { Status = "Sync loop interrupted... Press pause/break to resume."; while (Paused) await Task.Delay(1000); } - else Status = $"Syncing... {unoptimised++} unoptimised sync responses..."; + else Status = $"Syncing... {unoptimised++} unoptimised sync responses, last={currentSyncRes}..."; + + currentSyncRes = res.NextBatch; } } @@ -113,7 +168,7 @@ public partial class ClientViewModel : ViewModelBase { } if (room.Value.State?.Events is not null) { - var nameEvent = room.Value.State!.Events!.FirstOrDefault(x => x.Type == "m.room.name" && x.StateKey == ""); + var nameEvent = room.Value.State!.Events!.FirstOrDefault(x => x is { Type: "m.room.name", StateKey: "" }); if (nameEvent is not null) AllRooms[room.Key].Name = (nameEvent?.TypedContent as RoomNameEventContent)?.Name ?? ""; } @@ -124,36 +179,24 @@ public partial class ClientViewModel : ViewModelBase { // Status = $"Getting room name for {room.Key}..."; // AllRooms[room.Key].Name = await _authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync(); } - } - await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying room changes..."); + if (room.Value.Timeline?.Events is not null) { + foreach (var evt in room.Value.Timeline!.Events!) { + AllRooms[room.Key].Timeline.Add(evt); + } + } - return; - - List<string> handledRoomIds = []; - var spaces = newSync.Rooms?.Join? - .Where(x => x.Value.State?.Events is not null) - .Where(x => x.Value.State!.Events!.Any(y => y.Type == "m.room.create" && (y.TypedContent as RoomCreateEventContent)!.Type == "m.space")) - .ToList(); - Console.WriteLine("spaces: " + spaces.Count); - var nonRootSpaces = spaces - .Where(x => spaces.Any(x => x.Value.State!.Events!.Any(y => y.Type == "m.space.child" && y.StateKey == x.Key))) - .ToDictionary(); - - var rootSpaces = spaces - .Where(x => !nonRootSpaces.ContainsKey(x.Key)) - .ToDictionary(); - // var rootSpaces = spaces - // .Where(x=>!spaces.Any(x=>x.Value.State!.Events!.Any(y=>y.Type == "m.space.child" && y.StateKey == x.Key))) - // .ToList(); - - foreach (var (roomId, room) in rootSpaces) { - var space = new SpaceNode { Name = (room.State!.Events!.First(x => x.Type == "m.room.name")!.TypedContent as RoomNameEventContent).Name }; - DisplayedSpaces.Add(space); - handledRoomIds.Add(roomId); + if (room.Value.State?.Events is not null) { + foreach (var evt in room.Value.State!.Events!) { + AllRooms[room.Key].State.Add(evt); + } + } } + + await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying room changes..."); } + private ExpiringSemaphoreCache<UserProfileResponse> _profileCache = new(); private async Task ApplyDirectMessagesChanges(StateEventResponse evt) { _logger.LogCritical("Direct messages updated!"); var dms = evt.RawContent.Deserialize<Dictionary<string, string[]?>>(); @@ -163,8 +206,10 @@ public partial class ClientViewModel : ViewModelBase { 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)); + // tasks.Add(_authService.Homeserver!.GetProfileAsync(userId) + // .ContinueWith(r => space.Name = string.IsNullOrWhiteSpace(r.Result.DisplayName) ? userId : r.Result.DisplayName)); + tasks.Add(_profileCache.GetOrAdd(userId, async () => await _authService.Homeserver!.GetProfileAsync(userId), TimeSpan.FromMinutes(5)) + .ContinueWith(r => string.IsNullOrWhiteSpace(r.Result.DisplayName) ? userId : r.Result.DisplayName)); DirectMessages.ChildSpaces.Add(space); } @@ -191,9 +236,10 @@ public partial class ClientViewModel : ViewModelBase { 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((short)MathUtil.Map(incomplete, 0, total, 20, 7500), 5); // Program.Beep(0, 0); - Status = string.Format(message, incomplete, total); + if (incomplete < 10 || incomplete % (total / 10) == 0) + Status = string.Format(message, incomplete, total); await Task.WhenAny(tasks); tasks.RemoveAll(x => x.IsCompleted); }