From d105d1a7ec709ddb6240a286bbd7be292a9acd1c Mon Sep 17 00:00:00 2001
From: Rory&
Date: Sun, 21 Sep 2025 16:03:44 +0200
Subject: Various fixes
---
Benchmarks/Benchmarks.csproj | 2 +-
LibMatrix | 2 +-
.../MatrixUtils.RoomUpgradeCLI.csproj | 2 +-
.../appsettings.Development.json | 2 +-
.../appsettings.SynapseDev.json | 0
.../MatrixUtils.Web.Server.csproj | 2 +-
MatrixUtils.Web/App.razor | 9 +-
.../Classes/LocalStorageProviderService.cs | 18 +-
MatrixUtils.Web/Classes/RmuSessionStore.cs | 128 ++++++++++---
MatrixUtils.Web/Classes/UserAuth.cs | 14 +-
MatrixUtils.Web/MatrixUtils.Web.csproj | 11 +-
MatrixUtils.Web/Pages/Dev/DevOptions.razor | 16 +-
.../SynapseRoomShutdownWindowContent.razor | 2 +
.../Pages/HSAdmin/Synapse/RoomQuery.razor | 32 ++--
.../RoomsIndex2ByRoomTypeTab.razor | 1 -
MatrixUtils.Web/Pages/Rooms/PolicyList.razor | 203 +++++++++++++++-----
.../PolicyListCategoryComponent.razor | 55 +++---
.../PolicyListEditorHeader.razor | 47 +++--
.../PolicyListRowComponent.razor | 92 +++++++---
MatrixUtils.Web/Pages/Rooms/Space.razor | 3 +-
MatrixUtils.Web/Pages/Tools/Index.razor | 1 +
.../Pages/Tools/Room/SpacePermissions.razor | 204 +++++++++++++++++++++
.../Pages/Tools/User/StickerManager.razor | 80 ++++++++
MatrixUtils.Web/Program.cs | 5 +-
MatrixUtils.Web/appsettings.Development.json | 2 +
MatrixUtils.Web/wwwroot/css/app.css | 5 +
26 files changed, 753 insertions(+), 185 deletions(-)
delete mode 100644 MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json
create mode 100644 MatrixUtils.Web/Pages/Tools/Room/SpacePermissions.razor
create mode 100644 MatrixUtils.Web/Pages/Tools/User/StickerManager.razor
diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj
index e61ef31..a72013b 100644
--- a/Benchmarks/Benchmarks.csproj
+++ b/Benchmarks/Benchmarks.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/LibMatrix b/LibMatrix
index 013f169..91319ba 160000
--- a/LibMatrix
+++ b/LibMatrix
@@ -1 +1 @@
-Subproject commit 013f1693885a5de01ae357af2909589e925863d5
+Subproject commit 91319ba62de889bde645b6f1df4dd6a960ee7de4
diff --git a/MatrixUtils.RoomUpgradeCLI/MatrixUtils.RoomUpgradeCLI.csproj b/MatrixUtils.RoomUpgradeCLI/MatrixUtils.RoomUpgradeCLI.csproj
index 4d0299a..b0167e2 100644
--- a/MatrixUtils.RoomUpgradeCLI/MatrixUtils.RoomUpgradeCLI.csproj
+++ b/MatrixUtils.RoomUpgradeCLI/MatrixUtils.RoomUpgradeCLI.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/MatrixUtils.RoomUpgradeCLI/appsettings.Development.json b/MatrixUtils.RoomUpgradeCLI/appsettings.Development.json
index f42db52..621d281 100644
--- a/MatrixUtils.RoomUpgradeCLI/appsettings.Development.json
+++ b/MatrixUtils.RoomUpgradeCLI/appsettings.Development.json
@@ -9,7 +9,7 @@
"LibMatrixBot": {
// Homeserver to connect to.
// Note: Homeserver resolution is applied here, but a direct base URL can be used.
- "Homeserver": "rory.gay",
+// "Homeserver": "rory.gay",
// Absolute path to the file containing the access token
"AccessTokenPath": "/home/Rory/matrix_access_token"
diff --git a/MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json b/MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json
deleted file mode 100644
index e69de29..0000000
diff --git a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
index f446bf3..11781a8 100644
--- a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
+++ b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/MatrixUtils.Web/App.razor b/MatrixUtils.Web/App.razor
index a8cf817..7e8e1c3 100644
--- a/MatrixUtils.Web/App.razor
+++ b/MatrixUtils.Web/App.razor
@@ -1,4 +1,5 @@
-
+@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
+
@@ -10,3 +11,9 @@
+
+@code {
+
+ public static WebAssemblyHost Host { get; set; } = null!;
+
+}
diff --git a/MatrixUtils.Web/Classes/LocalStorageProviderService.cs b/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
index 0e99cd4..ddf3eed 100644
--- a/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
+++ b/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
@@ -3,26 +3,20 @@ using LibMatrix.Interfaces.Services;
namespace MatrixUtils.Web.Classes;
-public class LocalStorageProviderService : IStorageProvider {
- private readonly ILocalStorageService _localStorageService;
-
- public LocalStorageProviderService(ILocalStorageService localStorageService) {
- _localStorageService = localStorageService;
- }
-
+public class LocalStorageProviderService(ILocalStorageService localStorageService) : IStorageProvider {
Task IStorageProvider.SaveAllChildrenAsync(string key, T value) {
throw new NotImplementedException();
}
Task IStorageProvider.LoadAllChildrenAsync(string key) where T : default => throw new NotImplementedException();
- async Task IStorageProvider.SaveObjectAsync(string key, T value) => await _localStorageService.SetItemAsync(key, value);
+ async Task IStorageProvider.SaveObjectAsync(string key, T value) => await localStorageService.SetItemAsync(key, value);
- async Task IStorageProvider.LoadObjectAsync(string key) where T : default => await _localStorageService.GetItemAsync(key);
+ async Task IStorageProvider.LoadObjectAsync(string key) where T : default => await localStorageService.GetItemAsync(key);
- async Task IStorageProvider.ObjectExistsAsync(string key) => await _localStorageService.ContainKeyAsync(key);
+ async Task IStorageProvider.ObjectExistsAsync(string key) => await localStorageService.ContainKeyAsync(key);
- async Task> IStorageProvider.GetAllKeysAsync() => (await _localStorageService.KeysAsync()).ToList();
+ async Task> IStorageProvider.GetAllKeysAsync() => (await localStorageService.KeysAsync()).ToList();
- async Task IStorageProvider.DeleteObjectAsync(string key) => await _localStorageService.RemoveItemAsync(key);
+ async Task IStorageProvider.DeleteObjectAsync(string key) => await localStorageService.RemoveItemAsync(key);
}
diff --git a/MatrixUtils.Web/Classes/RmuSessionStore.cs b/MatrixUtils.Web/Classes/RmuSessionStore.cs
index 9df8837..1611b83 100644
--- a/MatrixUtils.Web/Classes/RmuSessionStore.cs
+++ b/MatrixUtils.Web/Classes/RmuSessionStore.cs
@@ -26,6 +26,11 @@ public class RmuSessionStore(
public async Task GetSession(string sessionId) {
await LoadStorage();
+ if (string.IsNullOrEmpty(sessionId)) {
+ logger.LogWarning("No session ID provided.");
+ return null;
+ }
+
if (SessionCache.TryGetValue(sessionId, out var cachedSession))
return cachedSession;
@@ -39,6 +44,11 @@ public class RmuSessionStore(
if (CurrentSession is not null) return CurrentSession;
var currentSessionId = await storageService.DataStorageProvider!.LoadObjectAsync("rmu.session");
+ if (currentSessionId == null) {
+ if (log) logger.LogWarning("No current session ID found in storage.");
+ return null;
+ }
+
return await GetSession(currentSessionId);
}
@@ -52,25 +62,31 @@ public class RmuSessionStore(
SessionId = sessionId
};
+ await SaveStorage();
if (CurrentSession == null) await SetCurrentSession(sessionId);
- else await SaveStorage();
return sessionId;
}
public async Task RemoveSession(string sessionId) {
await LoadStorage();
- logger.LogTrace("Removing session {sessionId}.", sessionId);
- var tokens = await GetAllSessions();
- if (tokens == null) {
+ if (SessionCache.Count == 0) {
+ logger.LogWarning("No sessions found.");
return;
}
+ logger.LogTrace("Removing session {sessionId}.", sessionId);
+
if ((await GetCurrentSession())?.SessionId == sessionId)
- await SetCurrentSession(tokens.First(x => x.Key != sessionId).Key);
+ await SetCurrentSession(SessionCache.FirstOrDefault(x => x.Key != sessionId).Key);
- if (tokens.Remove(sessionId))
- await SaveStorage();
+ if (SessionCache.Remove(sessionId)) {
+ logger.LogInformation("RemoveSession: Removed session {sessionId}.", sessionId);
+ logger.LogInformation("RemoveSession: Remaining sessions: {sessionIds}.", string.Join(", ", SessionCache.Keys));
+ await SaveStorage(log: true);
+ }
+ else
+ logger.LogWarning("RemoveSession: Session {sessionId} not found.", sessionId);
}
public async Task SetCurrentSession(string? sessionId) {
@@ -134,6 +150,53 @@ public class RmuSessionStore(
}
}
+ public async IAsyncEnumerable TryGetAllHomeservers(bool log = true, bool ignoreFailures = true) {
+ await LoadStorage();
+ if (log) logger.LogTrace("Getting all homeservers.");
+ var tasks = SessionCache.Values.Select(async session => {
+ if (ignoreFailures && session.Auth.LastFailureReason != null && session.Auth.LastFailureReason != UserAuth.FailureReason.None) {
+ if (log) logger.LogTrace("Skipping session {sessionId} due to previous failure: {reason}", session.SessionId, session.Auth.LastFailureReason);
+ return null;
+ }
+
+ try {
+ var hs = await GetHomeserver(session.SessionId, log: false);
+ if (session.Auth.LastFailureReason != null) {
+ SessionCache[session.SessionId].Auth.LastFailureReason = null;
+ await SaveStorage();
+ }
+
+ return hs;
+ }
+ catch (Exception e) {
+ logger.LogError("TryGetAllHomeservers: Failed to get homeserver for {userId} via {homeserver}: {ex}", session.Auth.UserId, session.Auth.Homeserver, e);
+ var reason = SessionCache[session.SessionId].Auth.LastFailureReason = e switch {
+ MatrixException { ErrorCode: MatrixException.ErrorCodes.M_UNKNOWN_TOKEN } => UserAuth.FailureReason.InvalidToken,
+ HttpRequestException => UserAuth.FailureReason.NetworkError,
+ _ => UserAuth.FailureReason.UnknownError
+ };
+ await SaveStorage(log: true);
+
+ // await LoadStorage(true);
+ if (SessionCache[session.SessionId].Auth.LastFailureReason != reason) {
+ await Console.Error.WriteLineAsync(
+ $"Warning: Session {session.SessionId} failure reason changed during reload from {reason} to {SessionCache[session.SessionId].Auth.LastFailureReason}");
+ }
+
+ throw;
+ }
+ }).ToList();
+
+ while (tasks.Count != 0) {
+ var finished = await Task.WhenAny(tasks);
+ tasks.Remove(finished);
+ if (finished.IsFaulted) continue;
+
+ var result = await finished;
+ if (result != null) yield return result;
+ }
+ }
+
#endregion
#region Storage
@@ -170,7 +233,8 @@ public class RmuSessionStore(
CurrentSession = currentSession;
}
- private async Task SaveStorage() {
+ private async Task SaveStorage(bool log = false) {
+ if (log) logger.LogWarning("Saving {count} sessions to storage.", SessionCache.Count);
await storageService.DataStorageProvider!.SaveObjectAsync("rmu.sessions",
SessionCache.ToDictionary(
x => x.Key,
@@ -178,6 +242,7 @@ public class RmuSessionStore(
)
);
await storageService.DataStorageProvider.SaveObjectAsync("rmu.session", CurrentSession?.SessionId);
+ if (log) logger.LogWarning("{count} sessions saved to storage.", SessionCache.Count);
}
#endregion
@@ -190,29 +255,42 @@ public class RmuSessionStore(
}
private async Task MigrateFromMru() {
- logger.LogInformation("Migrating from MRU token namespace!");
var dsp = storageService.DataStorageProvider!;
- if (await dsp.ObjectExistsAsync("token")) {
- var oldToken = await dsp.LoadObjectAsync("token");
- if (oldToken != null) {
- await dsp.SaveObjectAsync("rmu.token", oldToken);
- await dsp.DeleteObjectAsync("token");
+ if (await dsp.ObjectExistsAsync("token") || await dsp.ObjectExistsAsync("tokens")) {
+ logger.LogInformation("Migrating from unnamespaced localstorage!");
+ if (await dsp.ObjectExistsAsync("token")) {
+ var oldToken = await dsp.LoadObjectAsync("token");
+ if (oldToken != null) {
+ await dsp.SaveObjectAsync("mru.token", oldToken);
+ await dsp.DeleteObjectAsync("token");
+ }
}
- }
- if (await dsp.ObjectExistsAsync("tokens")) {
- var oldTokens = await dsp.LoadObjectAsync>("tokens");
- if (oldTokens != null) {
- await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
- await dsp.DeleteObjectAsync("tokens");
+ if (await dsp.ObjectExistsAsync("tokens")) {
+ var oldTokens = await dsp.LoadObjectAsync>("tokens");
+ if (oldTokens != null) {
+ await dsp.SaveObjectAsync("mru.tokens", oldTokens);
+ await dsp.DeleteObjectAsync("tokens");
+ }
}
}
- if (await dsp.ObjectExistsAsync("mru.tokens")) {
- var oldTokens = await dsp.LoadObjectAsync>("mru.tokens");
- if (oldTokens != null) {
- await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
- await dsp.DeleteObjectAsync("mru.tokens");
+ if (await dsp.ObjectExistsAsync("mru.token") || await dsp.ObjectExistsAsync("mru.tokens")) {
+ logger.LogInformation("Migrating from MRU token namespace!");
+ if (await dsp.ObjectExistsAsync("mru.token")) {
+ var oldToken = await dsp.LoadObjectAsync("mru.token");
+ if (oldToken != null) {
+ await dsp.SaveObjectAsync("rmu.token", oldToken);
+ await dsp.DeleteObjectAsync("mru.token");
+ }
+ }
+
+ if (await dsp.ObjectExistsAsync("mru.tokens")) {
+ var oldTokens = await dsp.LoadObjectAsync>("mru.tokens");
+ if (oldTokens != null) {
+ await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
+ await dsp.DeleteObjectAsync("mru.tokens");
+ }
}
}
}
diff --git a/MatrixUtils.Web/Classes/UserAuth.cs b/MatrixUtils.Web/Classes/UserAuth.cs
index 66476ae..16bb758 100644
--- a/MatrixUtils.Web/Classes/UserAuth.cs
+++ b/MatrixUtils.Web/Classes/UserAuth.cs
@@ -1,9 +1,11 @@
+using System.Text.Json.Serialization;
using LibMatrix.Responses;
namespace MatrixUtils.Web.Classes;
public class UserAuth : LoginResponse {
public UserAuth() { }
+
public UserAuth(LoginResponse login) {
Homeserver = login.Homeserver;
UserId = login.UserId;
@@ -12,4 +14,14 @@ public class UserAuth : LoginResponse {
}
public string? Proxy { get; set; }
-}
+
+ public FailureReason? LastFailureReason { get; set; }
+
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum FailureReason {
+ None,
+ InvalidToken,
+ NetworkError,
+ UnknownError
+ }
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj
index 18204d0..44fce2d 100644
--- a/MatrixUtils.Web/MatrixUtils.Web.csproj
+++ b/MatrixUtils.Web/MatrixUtils.Web.csproj
@@ -39,11 +39,12 @@
-
-
-
-
-
+
+
+
+
+
+
diff --git a/MatrixUtils.Web/Pages/Dev/DevOptions.razor b/MatrixUtils.Web/Pages/Dev/DevOptions.razor
index 33e577f..281cf07 100644
--- a/MatrixUtils.Web/Pages/Dev/DevOptions.razor
+++ b/MatrixUtils.Web/Pages/Dev/DevOptions.razor
@@ -21,7 +21,7 @@
Manage local sessions
-
+
@if (userSettings is not null) {
@@ -40,10 +40,14 @@
@code {
private RmuSessionStore.Settings? userSettings { get; set; }
+
protected override async Task OnInitializedAsync() {
- // userSettings = await TieredStorage.DataStorageProvider.LoadObjectAsync("rmu.settings");
-
- await base.OnInitializedAsync();
+ await (Task)typeof(RmuSessionStore).GetMethod("LoadStorage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ ?.Invoke(sessionStore, [true])!;
+ await foreach (var _ in sessionStore.TryGetAllHomeservers()) { }
+
+ await (Task)typeof(RmuSessionStore).GetMethod("SaveStorage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ ?.Invoke(sessionStore, [true])!;
}
private async Task LogStuff() {
@@ -58,8 +62,9 @@
foreach (var key in keys) {
data.Add(key, await TieredStorage.DataStorageProvider.LoadObjectAsync