diff --git a/LibMatrix b/LibMatrix
-Subproject c660b3e620de241c1158d08edaf0a9902836497
+Subproject 013f1693885a5de01ae357af2909589e925863d
diff --git a/MatrixUtils.RoomUpgradeCLI/Commands/DevCommands/DevDeleteAllRoomsCommand.cs b/MatrixUtils.RoomUpgradeCLI/Commands/DevCommands/DevDeleteAllRoomsCommand.cs
new file mode 100644
index 0000000..abae488
--- /dev/null
+++ b/MatrixUtils.RoomUpgradeCLI/Commands/DevCommands/DevDeleteAllRoomsCommand.cs
@@ -0,0 +1,32 @@
+using LibMatrix.Homeservers;
+
+namespace MatrixUtils.RoomUpgradeCLI.Commands;
+
+public class DevDeleteAllRoomsCommand(ILogger<DevDeleteAllRoomsCommand> logger, IHost host, RuntimeContext ctx, AuthenticatedHomeserverGeneric hs) : IHostedService {
+ public async Task StartAsync(CancellationToken cancellationToken) {
+ var synapse = hs as AuthenticatedHomeserverSynapse;
+ await foreach (var room in synapse.Admin.SearchRoomsAsync())
+ {
+ try
+ {
+ await synapse.Admin.DeleteRoom(room.RoomId, new() { ForcePurge = true });
+ Console.WriteLine($"Deleted room: {room.RoomId}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to delete room {room.RoomId}: {ex.Message}");
+ }
+ }
+
+ await host.StopAsync(cancellationToken);
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken) { }
+
+ private async Task PrintHelp() {
+ Console.WriteLine("Usage: execute [filename]");
+ Console.WriteLine("Options:");
+ Console.WriteLine(" --help Show this help message");
+ await host.StopAsync();
+ }
+}
\ No newline at end of file
diff --git a/MatrixUtils.RoomUpgradeCLI/Commands/ModifyCommand.cs b/MatrixUtils.RoomUpgradeCLI/Commands/ModifyCommand.cs
index 35c7bb1..3860448 100644
--- a/MatrixUtils.RoomUpgradeCLI/Commands/ModifyCommand.cs
+++ b/MatrixUtils.RoomUpgradeCLI/Commands/ModifyCommand.cs
@@ -33,40 +33,7 @@ public class ModifyCommand(ILogger<ModifyCommand> logger, IHost host, RuntimeCon
private async Task PrintHelp() {
Console.WriteLine("Usage: new [filename] [options]");
Console.WriteLine("Options:");
- Console.WriteLine(" --help Show this help message");
- Console.WriteLine(" --version <version> Set the room version (e.g. 9, 10, 11, 12)");
- Console.WriteLine("-- New room options --");
- Console.WriteLine(" --alias <alias> Set the room alias (local part)");
- Console.WriteLine(" --avatar-url <url> Set the room avatar URL");
- Console.WriteLine(" --copy-avatar <roomId> Copy the avatar from an existing room");
- Console.WriteLine(" --copy-powerlevels <roomId> Copy power levels from an existing room");
- Console.WriteLine(" --invite-admin <userId> Invite a user as an admin (userId must start with '@')");
- Console.WriteLine(" --invite <userId> Invite a user (userId must start with '@')");
- Console.WriteLine(" --name <name> Set the room name (can be multiple words)");
- Console.WriteLine(" --topic <topic> Set the room topic (can be multiple words)");
- Console.WriteLine(" --federate <true|false> Set whether the room is federatable");
- Console.WriteLine(" --public Set the room join rule to public");
- Console.WriteLine(" --invite-only Set the room join rule to invite-only");
- Console.WriteLine(" --knock Set the room join rule to knock");
- Console.WriteLine(" --restricted Set the room join rule to restricted");
- Console.WriteLine(" --knock_restricted Set the room join rule to knock_restricted");
- Console.WriteLine(" --private Set the room join rule to private");
- Console.WriteLine(" --join-rule <rule> Set the room join rule (public, invite, knock, restricted, knock_restricted, private)");
- Console.WriteLine(" --history-visibility <visibility> Set the room history visibility (shared, invited, joined, world_readable)");
- Console.WriteLine(" --type <type> Set the room type (e.g. m.space, m.room, support.feline.policy.list.msc.v1 etc.)");
- // upgrade opts
- Console.WriteLine("-- Upgrade options --");
- Console.WriteLine(
- " --upgrade <roomId> Create a room upgrade file instead of a new room file - WARNING: incompatible with non-upgrade options");
- Console.WriteLine(" --invite-members Invite members during room upgrade");
- Console.WriteLine(" --invite-powerlevel-users Invite users with power levels during room upgrade");
- Console.WriteLine(" --migrate-bans Migrate bans during room upgrade");
- Console.WriteLine(" --migrate-empty-state-events Migrate empty state events during room upgrade");
- Console.WriteLine(" --upgrade-unstable-values Upgrade unstable values during room upgrade");
- Console.WriteLine(" --msc4321-policy-list-upgrade <move|transition> Upgrade MSC4321 policy list");
- Console.WriteLine(" --force-upgrade Force upgrade even if you don't have the required permissions");
- Console.WriteLine(
- "WARNING: The --upgrade option is incompatible with options listed under \"New room\", please use the equivalent options in the `modify` command instead.");
+
await host.StopAsync();
}
}
\ No newline at end of file
diff --git a/MatrixUtils.RoomUpgradeCLI/Extensions/RoomBuilderExtensions.cs b/MatrixUtils.RoomUpgradeCLI/Extensions/RoomBuilderExtensions.cs
index 000dd6b..75852bc 100644
--- a/MatrixUtils.RoomUpgradeCLI/Extensions/RoomBuilderExtensions.cs
+++ b/MatrixUtils.RoomUpgradeCLI/Extensions/RoomBuilderExtensions.cs
@@ -56,7 +56,7 @@ public static class RoomBuilderExtensions {
break;
case "--federate":
- rb.IsFederatable = bool.Parse(args[++i]);
+ rb.IsFederatable = GetBoolArg(args, ref i, true);
break;
case "--public":
case "--invite-only":
@@ -118,6 +118,7 @@ public static class RoomBuilderExtensions {
if (rb.Encryption.Algorithm == "null")
rb.Encryption.Algorithm = null; // disable encryption
}
+
break;
// upgrade options
case "--invite-members":
@@ -125,7 +126,7 @@ public static class RoomBuilderExtensions {
throw new InvalidOperationException("Invite members can only be used with room upgrades");
}
- upgradeBuilder.UpgradeOptions.InviteMembers = true;
+ upgradeBuilder.UpgradeOptions.InviteMembers = GetBoolArg(args, ref i, true);
break;
case "--invite-powerlevel-users":
case "--invite-power-level-users":
@@ -133,28 +134,31 @@ public static class RoomBuilderExtensions {
throw new InvalidOperationException("Invite powerlevel users can only be used with room upgrades");
}
- upgradeBuilderInvite.UpgradeOptions.InvitePowerlevelUsers = true;
+ upgradeBuilderInvite.UpgradeOptions.InvitePowerlevelUsers = GetBoolArg(args, ref i, true);
+ break;
+ case "--synapse-admin-join-local-users":
+ rb.SynapseAdminAutoAcceptLocalInvites = GetBoolArg(args, ref i, true);
break;
case "--migrate-bans":
if (rb is not RoomUpgradeBuilder upgradeBuilderBan) {
throw new InvalidOperationException("Migrate bans can only be used with room upgrades");
}
- upgradeBuilderBan.UpgradeOptions.MigrateBans = true;
+ upgradeBuilderBan.UpgradeOptions.MigrateBans = GetBoolArg(args, ref i, true);
break;
case "--migrate-empty-state-events":
if (rb is not RoomUpgradeBuilder upgradeBuilderEmpty) {
throw new InvalidOperationException("Migrate empty state events can only be used with room upgrades");
}
- upgradeBuilderEmpty.UpgradeOptions.MigrateEmptyStateEvents = true;
+ upgradeBuilderEmpty.UpgradeOptions.MigrateEmptyStateEvents = GetBoolArg(args, ref i, true);
break;
case "--upgrade-unstable-values":
if (rb is not RoomUpgradeBuilder upgradeBuilderUnstable) {
throw new InvalidOperationException("Update unstable values can only be used with room upgrades");
}
- upgradeBuilderUnstable.UpgradeOptions.UpgradeUnstableValues = true;
+ upgradeBuilderUnstable.UpgradeOptions.UpgradeUnstableValues = GetBoolArg(args, ref i, true);
break;
case "--msc4321-policy-list-upgrade":
if (rb is not RoomUpgradeBuilder upgradeBuilderPolicy) {
@@ -173,9 +177,15 @@ public static class RoomBuilderExtensions {
throw new InvalidOperationException("Force upgrade can only be used with room upgrades");
}
- upgradeBuilderForce.UpgradeOptions.ForceUpgrade = true;
+ upgradeBuilderForce.UpgradeOptions.ForceUpgrade = GetBoolArg(args, ref i, true);
break;
+ case "--noop-upgrade":
+ if (rb is not RoomUpgradeBuilder upgradeBuilderNoop) {
+ throw new InvalidOperationException("No-op upgrade can only be used with room upgrades");
+ }
+ upgradeBuilderNoop.UpgradeOptions.NoopUpgrade = GetBoolArg(args, ref i, true);
+ break;
case "--upgrade":
if (rb is not RoomUpgradeBuilder upgradeBuilderUpgrade) {
throw new InvalidOperationException("Upgrade can only be used with room upgrades");
@@ -188,11 +198,55 @@ public static class RoomBuilderExtensions {
break;
case "--help":
- // await PrintHelp();
+ PrintHelpAndExit();
return;
default:
throw new ArgumentException("Unknown argument: " + args[i]);
}
}
}
+
+ private static bool GetBoolArg(string[] args, ref int i, bool defaultValue) {
+ if (i + 1 < args.Length && bool.TryParse(args[i + 1], out var result)) {
+ i++;
+ return result;
+ }
+
+ return defaultValue;
+ }
+
+ private static void PrintHelpAndExit() {
+ Console.WriteLine("""
+ --help Show this help message
+ --version <version> Set the room version (e.g. 9, 10, 11, 12)
+ -- New room options --
+ --federate [True|false] Set whether the room is federatable [WARNING: Cannot be updated later!]
+ --type <type> Set the room type (e.g. m.space, m.room, support.feline.policy.list.msc.v1 etc.) [WARNING: Cannot be updated later!]
+ --alias <alias> Set the room alias (local part)
+ --avatar-url <url> Set the room avatar URL
+ --copy-avatar <roomId> Copy the avatar from an existing room
+ --copy-powerlevels <roomId> Copy power levels from an existing room
+ --invite <userId> Invite a user (userId must start with '@')
+ --invite-admin <userId> Invite a user as an admin (userId must start with '@')
+ --synapse-admin-join-local-users [True|false] Automatically accept local user invites during room creation (Synapse only, requires synapse admin access)
+ --name <name> Set the room name (can be multiple words)
+ --topic <topic> Set the room topic (can be multiple words)
+ --join-rule <rule> Set the room join rule (public, invite, knock, restricted, knock_restricted, private)
+ Aliases: --public, --invite, --knock, --restricted, --knock_restricted, --private
+ --history-visibility <visibility> Set the room history visibility (shared, invited, joined, world_readable)
+ -- Upgrade options --
+ --upgrade <roomId> Create a room upgrade file instead of a new room file - WARNING: incompatible with non-upgrade options
+ --invite-members [True|false] Invite members during room upgrade
+ --invite-local-users [True|false] Invite local users during room upgrade (also see --synapse-admin-join-local-users)
+ --invite-powerlevel-users [True|false] Invite users with power levels during room upgrade
+ --migrate-bans [True|false] Migrate bans during room upgrade
+ --migrate-empty-state-events [True|false] Migrate empty state events during room upgrade
+ --upgrade-unstable-values [True|false] Upgrade unstable values during room upgrade
+ --msc4321-policy-list-upgrade <move|transition> Upgrade MSC4321 policy list
+ --force-upgrade [True|false] Force upgrade even if you don't have the required permissions
+ --noop-upgrade [True|false] Perform the upgrade, but do not tombstone the old room
+ WARNING: The --upgrade option is incompatible with options listed under "New room", please use the equivalent options in the `modify` command instead.
+ """);
+ Environment.Exit(0);
+ }
}
\ No newline at end of file
diff --git a/MatrixUtils.RoomUpgradeCLI/Program.cs b/MatrixUtils.RoomUpgradeCLI/Program.cs
index a93a329..e169830 100644
--- a/MatrixUtils.RoomUpgradeCLI/Program.cs
+++ b/MatrixUtils.RoomUpgradeCLI/Program.cs
@@ -29,6 +29,7 @@ foreach (var group in args.Split(";")) {
else if (argGroup[0] == "execute") builder.Services.AddHostedService<ExecuteCommand>();
// dev cmds
else if (argGroup[0] == "dev-delete-room") builder.Services.AddHostedService<DevDeleteRoomCommand>();
+ else if (argGroup[0] == "dev-delete-all-rooms") builder.Services.AddHostedService<DevDeleteAllRoomsCommand>();
else if (argGroup[0] == "dev-get-room-dir-state") builder.Services.AddHostedService<DevGetRoomDirStateCommand>();
else {
Console.WriteLine("Unknown command. Use 'new', 'modify', 'import-upgrade-state' or 'execute'.");
diff --git a/MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json b/MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
index 594ff35..e9d0cd2 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
@@ -3,6 +3,7 @@
@using ArcaneLibs.Extensions
@using LibMatrix
@using LibMatrix.EventTypes.Spec
+@using LibMatrix.StructuredData
<h3>Homeserver Administration - Block media</h3>
@if (Homeserver is not null) {
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index cdb5894..412d8c9 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -6,6 +6,7 @@
@using LibMatrix.RoomTypes
@using System.Collections.Frozen
@using System.Reflection
+@using System.Text.Json
@using ArcaneLibs.Attributes
@using LibMatrix.EventTypes
@using LibMatrix.EventTypes.Interop.Draupnir
@@ -136,6 +137,9 @@ else {
[Parameter]
public required string RoomId { get; set; }
+ [Parameter, SupplyParameterFromQuery]
+ public bool RenderEventInfo { get; set; }
+
private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
public StateEventResponse? ServerPolicyToMakePermanent {
@@ -172,12 +176,13 @@ else {
}
private async Task LoadStateAsync(bool firstLoad = false) {
+ // preload workers in task pool
+ // await Task.WhenAll(Enumerable.Range(0, WebWorkerService.MaxWorkerCount).Select(async _ => (await WebWorkerService.TaskPool.GetWorkerAsync()).WhenReady).ToList());
var sw = Stopwatch.StartNew();
// Loading = true;
// var states = Room.GetFullStateAsync();
var states = await Room.GetFullStateAsListAsync();
// PolicyEventsByType.Clear();
-
logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}");
foreach (var type in KnownPolicyTypes) {
@@ -215,7 +220,8 @@ else {
var key = (evt.Type, evt.StateKey!);
var policyInfo = new PolicyCollection.PolicyInfo {
Policy = evt,
- MadeRedundantBy = []
+ MadeRedundantBy = [],
+ DuplicatedBy = []
};
if (evt.RawContent is null or { Count: 0 } || string.IsNullOrWhiteSpace(evt.RawContent?["recommendation"]?.GetValue<string>())) {
collection.ActivePolicies.Remove(key);
@@ -238,76 +244,187 @@ else {
logger.LogInformation($"Policy collection {collection.Key.FullName} has {collection.Value.ActivePolicies.Count} active and {collection.Value.RemovedPolicies.Count} removed policies.");
}
+ Loading = false;
+ StateHasChanged();
+ await Task.Delay(100);
+
logger.LogInformation("LoadStatesAsync: Scanning for redundant policies...");
- Loading = false;
- var allPolicies = PolicyCollections.Values
+ var scanSw = Stopwatch.StartNew();
+ var allPolicyInfos = PolicyCollections.Values
.SelectMany(x => x.ActivePolicies.Values)
- .Select(x => (x, x.Policy.TypedContent as PolicyRuleEventContent))
.ToList();
- var wildcardPolicies = allPolicies
- .Where(x => x.Item2!.IsGlobRule() || x.Item2 is ServerPolicyRuleEventContent)
- .ToList();
- Console.WriteLine($"Got {allPolicies.Count} total policies, {wildcardPolicies.Count} wildcard policies.");
- int i = 0;
- int hits = 0;
- int redundant = 0;
- int duplicates = 0;
- foreach (var policy in allPolicies) {
- if (policy.Item2 is null) continue;
- var matchingPolicies = wildcardPolicies
- .Where(x =>
- !StateEvent.TypeKeyPairMatches(policy.x.Policy, x.x.Policy)
- && x.Item2!.EntityMatches(policy.Item2.Entity!)
- )
- .ToList();
-
- if (matchingPolicies.Count > 0) {
- logger.LogInformation($"{i} Got {matchingPolicies.Count} hits for {policy.x.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.x.Policy.RawContent).ToJson()}");
- foreach (var match in matchingPolicies) {
- policy.x.MadeRedundantBy.Add(match.x.Policy);
- }
-
- hits++;
- redundant += matchingPolicies.Count;
+ // var allPolicies = allPolicyInfos
+ // .Select<PolicyCollection.PolicyInfo, (PolicyCollection.PolicyInfo PolicyInfo, PolicyRuleEventContent TypedContent)>(x => (x, (x.Policy.TypedContent as PolicyRuleEventContent)!))
+ // .ToList();
+ // var hashPolicies = allPolicies
+ // .Where(x => x.TypedContent.IsHashedRule())
+ // .ToList();
+ // var wildcardPolicies = allPolicies
+ // .Except(hashPolicies) // hashed policies cannot be wildcards
+ // .Where(x => x.TypedContent.IsGlobRule() || x.TypedContent is ServerPolicyRuleEventContent)
+ // .ToList();
+ // var nonWildcardPolicies = allPolicies
+ // // .Except(wildcardPolicies)
+ // .Where(x => !x.TypedContent!.IsGlobRule() || x.TypedContent is ServerPolicyRuleEventContent)
+ // .ToList();
+ // Console.WriteLine($"Got {allPolicies.Count} total policies, {wildcardPolicies.Count} wildcard policies. Time spent: {scanSw.Elapsed}");
+ // int i = 0;
+ // int hits = 0;
+ // int redundant = 0;
+ // int duplicates = 0;
+
+ // foreach (var (policyInfo, policyContent) in allPolicies) {
+ // foreach (var (otherPolicyInfo, otherPolicyContent) in allPolicies) {
+ // if (policyInfo.Policy == otherPolicyInfo.Policy) continue; // same event
+ // if (StateEvent.TypeKeyPairMatches(policyInfo.Policy, otherPolicyInfo.Policy)) {
+ // logger.LogWarning("Sanity check failed: Found same type and state key for two different policies: {Policy1} and {Policy2}", policyInfo.Policy.RawContent.ToJson(), otherPolicyInfo.Policy.RawContent.ToJson());
+ // continue; // same type and state key
+ // }
+ // // if(!policyContent.IsHashedRule())
+ // }
+ //
+ // if (++i % 100 == 0) {
+ // Console.WriteLine($"Processed {i} policies in {scanSw.Elapsed}");
+ // await Task.Delay(1);
+ // }
+ // }
- if (hits % 5 == 0)
- StateHasChanged();
+ Console.WriteLine($"Scanning for redundant policies in {allPolicyInfos.Count} total policies... ({scanSw.Elapsed})");
+ List<Task<List<PolicyCollection.PolicyInfo>>> tasks = [];
+ // try to save some load...
+ var policiesJson = JsonSerializer.Serialize(allPolicyInfos.Select(x => x.Policy));
+ var ranges = Enumerable.Range(0, allPolicyInfos.Count).DistributeSequentially(WebWorkerService.MaxWorkerCount);
+ foreach (var range in ranges)
+ tasks.Add(WebWorkerService.TaskPool.Invoke(CheckDuplicatePoliciesAsync, policiesJson, range.First(), range.Last()));
+
+ // tasks.Add(CheckDuplicatePoliciesAsync(allPolicyInfos, range.First() .. range.Last()));
+
+ await foreach (var modifiedPolicyInfos in tasks.ToAsyncEnumerable()) {
+ Console.WriteLine($"Main: got {modifiedPolicyInfos.Count} modified policies from worker, time: {scanSw.Elapsed}");
+ foreach (var modifiedPolicyInfo in modifiedPolicyInfos) {
+ var original = allPolicyInfos.First(p => p.Policy.EventId == modifiedPolicyInfo.Policy.EventId);
+ original.DuplicatedBy = modifiedPolicyInfo.DuplicatedBy;
+ original.MadeRedundantBy = modifiedPolicyInfo.MadeRedundantBy;
}
- else logger.LogInformation("Sleeping...");
- await Task.Delay(1);
- i++;
+
+ Console.WriteLine($"Processed {modifiedPolicyInfos.Count} modified policies in {scanSw.Elapsed}");
}
- i = 0;
- foreach (var policy in allPolicies) {
- if (policy.Item2 is null) continue;
- var matchingPolicies = allPolicies
- .Where(x =>
- !StateEvent.TypeKeyPairMatches(policy.x.Policy, x.x.Policy)
- && x.Item2!.Entity == policy.Item2.Entity!
- )
- .ToList();
-
- if (matchingPolicies.Count > 0) {
- logger.LogInformation($"{i} Got {matchingPolicies.Count} duplicates for {policy.x.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.x.Policy.RawContent).ToJson()}");
- foreach (var match in matchingPolicies) {
- policy.x.MadeRedundantBy.Add(match.x.Policy);
- }
+ Console.WriteLine($"Processed {allPolicyInfos.Count} policies in {scanSw.Elapsed}");
+
+ // // scan for wildcard matches
+ // foreach (var policy in allPolicies) {
+ // var matchingPolicies = wildcardPolicies
+ // .Where(x =>
+ // !StateEvent.TypeKeyPairMatches(policy.PolicyInfo.Policy, x.PolicyInfo.Policy)
+ // && x.Item2.EntityMatches(policy.TypedContent.Entity!)
+ // )
+ // .ToList();
+ //
+ // if (matchingPolicies.Count > 0) {
+ // logger.LogInformation($"{i} Got {matchingPolicies.Count} hits for {policy.PolicyInfo.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.PolicyInfo.Policy.RawContent).ToJson()}");
+ // foreach (var match in matchingPolicies) {
+ // policy.PolicyInfo.MadeRedundantBy.Add(match.PolicyInfo.Policy);
+ // }
+ //
+ // hits++;
+ // redundant += matchingPolicies.Count;
+ //
+ // if (hits % 5 == 0)
+ // StateHasChanged();
+ // }
+ // else {
+ // //logger.LogInformation("Sleeping...");
+ // await Task.Delay(1);
+ // }
+ //
+ // i++;
+ // }
+ //
+ // i = 0;
+ // // scan for exact duplicates
+ // foreach (var policy in allPolicies) {
+ // var matchingPolicies = allPolicies
+ // .Where(x =>
+ // !StateEvent.TypeKeyPairMatches(policy.PolicyInfo.Policy, x.PolicyInfo.Policy)
+ // && (
+ // x.Item2.IsHashedRule()
+ // ? x.Item2.EntityMatches(policy.Item2.Entity)
+ // : x.Item2!.Entity == policy.Item2.Entity!
+ // )
+ // )
+ // .ToList();
+ //
+ // if (matchingPolicies.Count > 0) {
+ // logger.LogInformation($"{i} Got {matchingPolicies.Count} duplicates for {policy.PolicyInfo.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.PolicyInfo.Policy.RawContent).ToJson()}");
+ // foreach (var match in matchingPolicies) {
+ // policy.PolicyInfo.MadeRedundantBy.Add(match.PolicyInfo.Policy);
+ // }
+ //
+ // hits++;
+ // duplicates += matchingPolicies.Count;
+ //
+ // if (hits % 5 == 0)
+ // StateHasChanged();
+ // }
+ // else {
+ // //logger.LogInformation("Sleeping...");
+ // await Task.Delay(1);
+ // }
+ //
+ // i++;
+ // }
+ //
+ // logger.LogInformation($"LoadStatesAsync: Found {hits} ({redundant} redundant, {duplicates} duplicates) redundant policies in {sw.Elapsed}");
+ // StateHasChanged();
+ }
+
+ [return: WorkerTransfer]
+ private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(string policiesJson, int start, int end) {
+ Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.Length} bytes of JSON");
+ return await CheckDuplicatePoliciesAsync(JsonSerializer.Deserialize<List<StateEventResponse>>(policiesJson), start .. end);
+ }
+
+ [return: WorkerTransfer]
+ private static Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<StateEventResponse> policies, int start, int end)
+ => CheckDuplicatePoliciesAsync(policies, start .. end);
- hits++;
- redundant += matchingPolicies.Count;
+ [return: WorkerTransfer]
+ private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<StateEventResponse> policies, Range range) {
+ Console.WriteLine($"Processing policies in range {range} ({range.GetOffsetAndLength(policies.Count).Length}) with {policies.Count} total policies");
+ var allPolicies = policies
+ .Select(x => (Event: x, TypedContent: (x.TypedContent as PolicyRuleEventContent)!))
+ .ToList();
+ var toCheck = allPolicies[range];
+ var modifiedPolicies = new List<PolicyCollection.PolicyInfo>();
+
+ foreach (var (policyEvent, policyContent) in toCheck) {
+ List<StateEventResponse> duplicatedBy = [];
+ List<StateEventResponse> madeRedundantBy = [];
+
+ foreach (var (otherPolicyEvent, otherPolicyContent) in allPolicies) {
+ if (policyEvent == otherPolicyEvent) continue; // same event
+ if (StateEvent.TypeKeyPairMatches(policyEvent, otherPolicyEvent)) {
+ // logger.LogWarning("Sanity check failed: Found same type and state key for two different policies: {Policy1} and {Policy2}", policyInfo.Policy.RawContent.ToJson(), otherPolicyInfo.Policy.RawContent.ToJson());
+ Console.WriteLine($"Sanity check failed: Found same type and state key for two different policies: {policyEvent.RawContent.ToJson()} and {otherPolicyEvent.RawContent.ToJson()}");
+ continue; // same type and state key
+ }
+ // if(!policyContent.IsHashedRule())
+ }
- if (hits % 5 == 0)
- StateHasChanged();
+ if (duplicatedBy.Count > 0 || madeRedundantBy.Count > 0) {
+ modifiedPolicies.Add(new() {
+ Policy = policyEvent,
+ DuplicatedBy = duplicatedBy,
+ MadeRedundantBy = madeRedundantBy
+ });
}
- else logger.LogInformation("Sleeping...");
- await Task.Delay(1);
- i++;
+
+ // await Task.Delay(1);
}
- logger.LogInformation($"LoadStatesAsync: Found {hits} ({redundant} redundant, {duplicates} duplicates) redundant policies in {sw.Elapsed}");
- StateHasChanged();
+ return modifiedPolicies;
}
// the old one:
@@ -415,9 +532,10 @@ else {
public required Dictionary<(string Type, string StateKey), PolicyInfo> RemovedPolicies { get; set; }
public required FrozenDictionary<string, PropertyInfo> PropertiesToDisplay { get; set; }
- public struct PolicyInfo {
+ public class PolicyInfo {
public required StateEventResponse Policy { get; init; }
public required List<StateEventResponse> MadeRedundantBy { get; set; }
+ public required List<StateEventResponse> DuplicatedBy { get; set; }
}
}
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
index f818b62..b52e03f 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
@@ -18,7 +18,7 @@
</thead>
<tbody>
@foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue<string>())) {
- <PolicyListRowComponent PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent>
+ <PolicyListRowComponent RenderEventInfo="RenderEventInfo" PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent>
}
</tbody>
</table>
@@ -56,6 +56,9 @@
[Parameter]
public required GenericRoom Room { get; set; }
+
+ [Parameter]
+ public bool RenderEventInfo { get; set; }
protected override bool ShouldRender() {
// if (PolicyCollection is null) return false;
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
index 11de82c..9ac5077 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
@@ -10,9 +10,19 @@
if (prop.Name == "Entity") {
<td>
<span>@TruncateMxid(TypedContent.Entity)</span>
+ @foreach (var dup in PolicyInfo.DuplicatedBy) {
+ <br/>
+ <span>Duplicated by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span>
+ }
@foreach (var dup in PolicyInfo.MadeRedundantBy) {
<br/>
- <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId)">@TruncateMxid(dup.RawContent["entity"].GetValue<string>())</a></span>
+ <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span>
+ }
+ @if (RenderEventInfo) {
+ <br/>
+ <pre>
+ @PolicyInfo.Policy.Type/@PolicyInfo.Policy.StateKey by @PolicyInfo.Policy.Sender at @PolicyInfo.Policy.OriginServerTs
+ </pre>
}
</td>
}
@@ -25,9 +35,9 @@
@* @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, Policy.Type)) { *@
@if (true) {
<LinkButton OnClickAsync="@(() => {
- IsEditing = true;
- return Task.CompletedTask;
- })">Edit
+ IsEditing = true;
+ return Task.CompletedTask;
+ })">Edit
</LinkButton>
<LinkButton OnClickAsync="@RemovePolicyAsync">Remove</LinkButton>
@if (Policy.IsLegacyType) {
@@ -82,6 +92,9 @@
[Parameter]
public required PolicyList.PolicyCollection PolicyCollection { get; set; }
+ [Parameter]
+ public bool RenderEventInfo { get; set; }
+
private StateEventResponse Policy => PolicyInfo.Policy;
private bool IsEditing {
diff --git a/MatrixUtils.Web/Program.cs b/MatrixUtils.Web/Program.cs
index cad5393..bc047e8 100644
--- a/MatrixUtils.Web/Program.cs
+++ b/MatrixUtils.Web/Program.cs
@@ -20,13 +20,13 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
// builder.Logging.SetMinimumLevel(LogLevel.Trace);
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
-builder.Services.AddBlazorJSRuntime();
+builder.Services.AddBlazorJSRuntime(out var jsRuntime);
builder.Services.AddWebWorkerService(webWorkerService => {
// Optionally configure the WebWorkerService service before it is used
// Default WebWorkerService.TaskPool settings: PoolSize = 0, MaxPoolSize = 1, AutoGrow = true
// Below sets TaskPool max size to 2. By default the TaskPool size will grow as needed up to the max pool size.
// Setting max pool size to -1 will set it to the value of navigator.hardwareConcurrency
- webWorkerService.TaskPool.MaxPoolSize = 2;
+ webWorkerService.TaskPool.MaxPoolSize = -1;
// Below is telling the WebWorkerService TaskPool to set the initial size to 2 if running in a Window scope and 0 otherwise
// This starts up 2 WebWorkers to handle TaskPool tasks as needed
webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 0 : 0;
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
index 99dbbc3..bb4b672 100644
--- a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
@@ -175,8 +175,23 @@
}
}
- var tasks = entities.Select(x => ExecuteBan(Room, x)).ToList();
- await Task.WhenAll(tasks);
+ // var tasks = entities.Select(x => ExecuteBan(Room, x)).ToList();
+ // await Task.WhenAll(tasks);
+
+ var events = entities.Select(entity => {
+ var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent ?? throw new InvalidOperationException("Failed to create event content");
+ content.Recommendation = Recommendation;
+ content.Reason = Reason;
+ content.Entity = entity;
+ return new StateEvent() {
+ Type = MappedType,
+ TypedContent = content,
+ StateKey = content.GetDraupnir2StateKey()
+ };
+ });
+
+ foreach(var chunk in events.Chunk(50))
+ await Room.BulkSendEventsAsync(chunk);
OnSaved.Invoke();
}
|