about summary refs log tree commit diff
diff options
context:
space:
mode:
m---------LibMatrix0
-rw-r--r--MatrixUtils.RoomUpgradeCLI/Commands/DevCommands/DevDeleteAllRoomsCommand.cs32
-rw-r--r--MatrixUtils.RoomUpgradeCLI/Commands/ModifyCommand.cs35
-rw-r--r--MatrixUtils.RoomUpgradeCLI/Extensions/RoomBuilderExtensions.cs70
-rw-r--r--MatrixUtils.RoomUpgradeCLI/Program.cs1
-rw-r--r--MatrixUtils.RoomUpgradeCLI/appsettings.SynapseDev.json0
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor1
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyList.razor238
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor5
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor21
-rw-r--r--MatrixUtils.Web/Program.cs4
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor19
12 files changed, 315 insertions, 111 deletions
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(); }