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);
}
|