Convert resync to a combined command

This commit is contained in:
Vankka 2023-05-30 23:42:53 +03:00
parent 3e3438f0d2
commit 4613e821e2
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
9 changed files with 166 additions and 126 deletions

View File

@ -2,6 +2,7 @@ package com.discordsrv.common.command.combined.abstraction;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
public interface CommandExecution {
@ -12,7 +13,12 @@ public interface CommandExecution {
default void send(Text... texts) {
send(Arrays.asList(texts));
}
void send(Collection<Text> texts);
default void send(Collection<Text> texts) {
send(texts, Collections.emptyList());
}
void send(Collection<Text> texts, Collection<Text> extra);
void runAsync(Runnable runnable);
}

View File

@ -35,19 +35,23 @@ public class DiscordCommandExecution implements CommandExecution {
}
@Override
public void send(Collection<Text> texts) {
public void send(Collection<Text> texts, Collection<Text> extra) {
StringBuilder builder = new StringBuilder();
EnumMap<Text.Formatting, Boolean> formats = new EnumMap<>(Text.Formatting.class);
for (Text text : texts) {
if (StringUtils.isEmpty(text.content())) continue;
verifyStyle(builder, formats, text);
builder.append(text.content());
render(text, builder, formats);
}
verifyStyle(builder, formats, null);
if (!extra.isEmpty()) {
builder.append("\n\n");
for (Text text : extra) {
render(text, builder, formats);
}
verifyStyle(builder, formats, null);
}
InteractionHook interactionHook = hook.get();
boolean ephemeral = isEphemeral.get();
if (interactionHook != null) {
@ -57,6 +61,13 @@ public class DiscordCommandExecution implements CommandExecution {
}
}
private void render(Text text, StringBuilder builder, EnumMap<Text.Formatting, Boolean> formats) {
if (StringUtils.isEmpty(text.content())) return;
verifyStyle(builder, formats, text);
builder.append(text.content());
}
private void verifyStyle(StringBuilder builder, EnumMap<Text.Formatting, Boolean> formats, Text text) {
for (Text.Formatting format : Text.Formatting.values()) {
boolean is = formats.computeIfAbsent(format, key -> false);

View File

@ -5,6 +5,7 @@ import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.HoverEvent;
import java.util.Collection;
@ -31,7 +32,15 @@ public class GameCommandExecution implements CommandExecution {
}
@Override
public void send(Collection<Text> texts) {
public void send(Collection<Text> texts, Collection<Text> extra) {
TextComponent.Builder builder = render(texts);
if (!extra.isEmpty()) {
builder.hoverEvent(HoverEvent.showText(render(extra)));
}
sender.sendMessage(builder);
}
private TextComponent.Builder render(Collection<Text> texts) {
TextComponent.Builder builder = Component.text();
for (Text text : texts) {
builder.append(
@ -40,7 +49,7 @@ public class GameCommandExecution implements CommandExecution {
.decorations(text.gameFormatting(), true)
);
}
sender.sendMessage(builder);
return builder;
}
@Override

View File

@ -0,0 +1,118 @@
package com.discordsrv.common.command.combined.commands;
import com.discordsrv.api.discord.entity.interaction.command.Command;
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.combined.abstraction.CombinedCommand;
import com.discordsrv.common.command.combined.abstraction.CommandExecution;
import com.discordsrv.common.command.combined.abstraction.GameCommandExecution;
import com.discordsrv.common.command.combined.abstraction.Text;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.future.util.CompletableFutureUtil;
import com.discordsrv.common.groupsync.GroupSyncModule;
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
import com.discordsrv.common.groupsync.enums.GroupSyncResult;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class ResyncCommand extends CombinedCommand {
private static ResyncCommand INSTANCE;
private static GameCommand GAME;
private static Command DISCORD;
private static ResyncCommand getInstance(DiscordSRV discordSRV) {
return INSTANCE != null ? INSTANCE : (INSTANCE = new ResyncCommand(discordSRV));
}
public static GameCommand getGame(DiscordSRV discordSRV) {
if (GAME == null) {
ResyncCommand command = getInstance(discordSRV);
GAME = GameCommand.literal("resync")
.requiredPermission("discordsrv.admin.resync")
.executor(command);
}
return GAME;
}
public static Command getDiscord(DiscordSRV discordSRV) {
if (DISCORD == null) {
ResyncCommand command = getInstance(discordSRV);
DISCORD = Command.chatInput(ComponentIdentifier.of("DiscordSRV", "resync"), "resync", "Perform group resync for online players")
.setEventHandler(command)
.build();
}
return DISCORD;
}
public ResyncCommand(DiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public void execute(CommandExecution execution) {
GroupSyncModule module = discordSRV.getModule(GroupSyncModule.class);
if (module == null) {
execution.send(new Text("GroupSync module has not initialized correctly.").withGameColor(NamedTextColor.RED));
return;
}
if (module.noPermissionProvider()) {
execution.send(new Text("No permission provider available.").withGameColor(NamedTextColor.RED));
return;
}
if (execution instanceof GameCommandExecution) {
// Acknowledge for in-game runs
execution.send(new Text("Synchronizing online players").withGameColor(NamedTextColor.GRAY));
}
execution.runAsync(() -> {
long startTime = System.currentTimeMillis();
CompletableFutureUtil.combine(resyncOnlinePlayers(module))
.whenComplete((results, t) -> {
EnumMap<GroupSyncResult, AtomicInteger> resultCounts = new EnumMap<>(GroupSyncResult.class);
int total = 0;
for (List<GroupSyncResult> result : results) {
for (GroupSyncResult singleResult : result) {
total++;
resultCounts.computeIfAbsent(singleResult, key -> new AtomicInteger(0)).getAndIncrement();
}
}
String resultHover = resultCounts.entrySet().stream()
.map(entry -> entry.getKey().toString() + ": " + entry.getValue().get())
.collect(Collectors.joining("\n"));
long time = System.currentTimeMillis() - startTime;
execution.send(
Arrays.asList(
new Text("Synchronization completed in ").withGameColor(NamedTextColor.GRAY),
new Text(time + "ms").withGameColor(NamedTextColor.GREEN).withFormatting(Text.Formatting.BOLD),
new Text(" (").withGameColor(NamedTextColor.GRAY),
new Text(total + " result" + (total == 1 ? "" : "s"))
.withGameColor(NamedTextColor.GREEN)
.withDiscordFormatting(Text.Formatting.BOLD),
new Text(")").withGameColor(NamedTextColor.GRAY)
),
total > 0 ? Collections.singletonList(new Text(resultHover)) : Collections.emptyList()
);
});
});
}
private List<CompletableFuture<List<GroupSyncResult>>> resyncOnlinePlayers(GroupSyncModule module) {
List<CompletableFuture<List<GroupSyncResult>>> futures = new ArrayList<>();
for (IPlayer player : discordSRV.playerProvider().allPlayers()) {
futures.add(module.resync(player.uniqueId(), GroupSyncCause.COMMAND));
}
return futures;
}
}

View File

@ -4,6 +4,7 @@ import com.discordsrv.api.discord.entity.interaction.command.Command;
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.combined.commands.DebugCommand;
import com.discordsrv.common.command.combined.commands.ResyncCommand;
import com.discordsrv.common.command.combined.commands.VersionCommand;
public class DiscordSRVDiscordCommand {
@ -17,6 +18,7 @@ public class DiscordSRVDiscordCommand {
INSTANCE = Command.chatInput(IDENTIFIER, "discordsrv", "DiscordSRV related commands")
.addSubCommand(DebugCommand.getDiscord(discordSRV))
.addSubCommand(VersionCommand.getDiscord(discordSRV))
.addSubCommand(ResyncCommand.getDiscord(discordSRV))
.setGuildOnly(false)
.setDefaultPermission(Command.DefaultPermission.ADMINISTRATOR)
.build();

View File

@ -21,11 +21,13 @@ package com.discordsrv.common.command.game.commands;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.combined.commands.DebugCommand;
import com.discordsrv.common.command.combined.commands.ResyncCommand;
import com.discordsrv.common.command.combined.commands.VersionCommand;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.commands.subcommand.*;
import com.discordsrv.common.command.game.commands.subcommand.BroadcastCommand;
import com.discordsrv.common.command.game.commands.subcommand.LinkCommand;
import com.discordsrv.common.command.game.commands.subcommand.reload.ReloadCommand;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.component.util.ComponentUtil;
@ -52,7 +54,7 @@ public class DiscordSRVGameCommand implements GameCommandExecutor {
.then(DebugCommand.getGame(discordSRV))
.then(LinkCommand.get(discordSRV))
.then(ReloadCommand.get(discordSRV))
.then(ResyncCommand.get(discordSRV))
.then(ResyncCommand.getGame(discordSRV))
.then(VersionCommand.getGame(discordSRV))
);
}

View File

@ -1,112 +0,0 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2023 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.commands.subcommand;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.future.util.CompletableFutureUtil;
import com.discordsrv.common.groupsync.GroupSyncModule;
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
import com.discordsrv.common.groupsync.enums.GroupSyncResult;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class ResyncCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("resync")
.requiredPermission("discordsrv.admin.resync")
.executor(new ResyncCommand(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public ResyncCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
GroupSyncModule module = discordSRV.getModule(GroupSyncModule.class);
if (module == null) {
sender.sendMessage(Component.text("GroupSync module has not initialized correctly.", NamedTextColor.RED));
return;
}
sender.sendMessage(Component.text("Synchronizing online players", NamedTextColor.GRAY));
long startTime = System.currentTimeMillis();
CompletableFutureUtil.combine(resyncOnlinePlayers(module))
.whenComplete((results, t) -> {
EnumMap<GroupSyncResult, AtomicInteger> resultCounts = new EnumMap<>(GroupSyncResult.class);
int total = 0;
for (List<GroupSyncResult> result : results) {
for (GroupSyncResult singleResult : result) {
total++;
resultCounts.computeIfAbsent(singleResult, key -> new AtomicInteger(0)).getAndIncrement();
}
}
String resultHover;
if (total == 0) {
resultHover = "Nothing done";
} else {
resultHover = total + " result" + (total == 1 ? "" : "s") + ":\n\n" +
resultCounts.entrySet().stream()
.map(entry -> entry.getKey().toString() + ": " + entry.getValue().get())
.collect(Collectors.joining("\n"));
}
long time = System.currentTimeMillis() - startTime;
sender.sendMessage(
Component.text("Synchronization completed in ", NamedTextColor.GRAY)
.append(Component.text(time + "ms", NamedTextColor.GREEN))
.append(Component.text(" (", NamedTextColor.GRAY))
.append(Component.text(total, NamedTextColor.GREEN))
.append(Component.text(" result" + (total == 1 ? "" : "s") + ")", NamedTextColor.GRAY))
.hoverEvent(HoverEvent.showText(Component.text(resultHover)))
);
});
}
private List<CompletableFuture<List<GroupSyncResult>>> resyncOnlinePlayers(GroupSyncModule module) {
List<CompletableFuture<List<GroupSyncResult>>> futures = new ArrayList<>();
for (IPlayer player : discordSRV.playerProvider().allPlayers()) {
futures.add(module.resync(player.uniqueId(), GroupSyncCause.COMMAND));
}
return futures;
}
}

View File

@ -202,7 +202,7 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
return groupsContext == null ? discordSRV.getModule(PermissionDataProvider.Groups.class) : groupsContext;
}
private boolean noPermissionProvider() {
public boolean noPermissionProvider() {
PermissionDataProvider.Groups groups = getPermissionProvider();
return groups == null || !groups.isEnabled();
}
@ -276,8 +276,10 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
}
public Collection<CompletableFuture<GroupSyncResult>> resync(UUID player, long userId, GroupSyncCause cause) {
if (noPermissionProvider() || (discordSRV.playerProvider().player(player) == null && !supportsOffline())) {
return Collections.emptyList();
if (noPermissionProvider()) {
return Collections.singletonList(CompletableFuture.completedFuture(GroupSyncResult.NO_PERMISSION_PROVIDER));
} else if (discordSRV.playerProvider().player(player) == null && !supportsOffline()) {
return Collections.singletonList(CompletableFuture.completedFuture(GroupSyncResult.PERMISSION_PROVIDER_NO_OFFLINE_SUPPORT));
}
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();

View File

@ -37,6 +37,8 @@ public enum GroupSyncResult {
NOT_A_GUILD_MEMBER("User is not part of the server the role is in"),
PERMISSION_BACKEND_FAIL_CHECK("Failed to check group status, error printed"),
UPDATE_FAILED("Failed to modify role/group, error printed"),
NO_PERMISSION_PROVIDER("No permission provider"),
PERMISSION_PROVIDER_NO_OFFLINE_SUPPORT("Permission provider doesn't support offline players"),
;