Improvements & fixes to AbstractSyncModule

This commit is contained in:
Vankka 2024-06-24 02:28:30 +03:00
parent 72758ff888
commit 654bfb5907
No known key found for this signature in database
GPG Key ID: 62E48025ED4E7EBB
18 changed files with 409 additions and 90 deletions

View File

@ -1,3 +1,26 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.discordsrv.api.punishment;
import org.jetbrains.annotations.Nullable;

View File

@ -70,6 +70,9 @@ public class BukkitBanModule extends AbstractModule<BukkitDiscordSRV> implements
}
return entryFuture.thenApply(ban -> {
if (ban == null) {
return null;
}
Date expiration = ban.getExpiration();
return new Punishment(expiration != null ? expiration.toInstant() : null, ban.getReason(), ban.getSource());
});

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.bansync;
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
@ -71,7 +89,7 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
}
@Override
public String logName() {
public String logFileName() {
return "bansync";
}
@ -91,8 +109,10 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
}
@Override
protected boolean isActive(Punishment state) {
return state != null;
protected @Nullable ISyncResult doesStateMatch(Punishment one, Punishment two) {
boolean oneActive = one != null;
boolean twoActive = two != null;
return (oneActive == twoActive) ? GenericSyncResults.both(oneActive) : null;
}
private PunishmentEvent upsertEvent(long userId, boolean newState) {
@ -211,7 +231,7 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
}
@Override
protected CompletableFuture<ISyncResult> applyDiscord(BanSyncConfig config, long userId, Punishment state) {
protected CompletableFuture<ISyncResult> applyDiscord(BanSyncConfig config, long userId, Punishment newState) {
if (config.direction == SyncDirection.DISCORD_TO_MINECRAFT) {
return CompletableFuture.completedFuture(GenericSyncResults.WRONG_DIRECTION);
}
@ -228,9 +248,10 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
}
UserSnowflake snowflake = UserSnowflake.fromId(userId);
if (state != null) {
if (newState != null) {
return guild.ban(snowflake, config.discordMessageHoursToDelete, TimeUnit.HOURS)
.reason(discordSRV.placeholderService().replacePlaceholders(config.discordBanReasonFormat, state))
.reason(discordSRV.placeholderService().replacePlaceholders(config.discordBanReasonFormat,
newState))
.submit()
.thenApply(v -> GenericSyncResults.ADD_DISCORD);
} else {
@ -242,7 +263,7 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
}
@Override
protected CompletableFuture<ISyncResult> applyGame(BanSyncConfig config, UUID playerUUID, Punishment state) {
protected CompletableFuture<ISyncResult> applyGame(BanSyncConfig config, UUID playerUUID, Punishment newState) {
if (config.direction == SyncDirection.MINECRAFT_TO_DISCORD) {
return CompletableFuture.completedFuture(GenericSyncResults.WRONG_DIRECTION);
}
@ -252,9 +273,11 @@ public class BanSyncModule extends AbstractSyncModule<DiscordSRV, BanSyncConfig,
return CompletableFuture.completedFuture(BanSyncResult.NO_PUNISHMENT_INTEGRATION);
}
if (state != null) {
String reason = discordSRV.placeholderService().replacePlaceholders(config.gameBanReasonFormat, state);
String punisher = discordSRV.placeholderService().replacePlaceholders(config.gamePunisherFormat, state);
if (newState != null) {
String reason = discordSRV.placeholderService().replacePlaceholders(config.gameBanReasonFormat,
newState);
String punisher = discordSRV.placeholderService().replacePlaceholders(config.gamePunisherFormat,
newState);
return bans.addBan(playerUUID, null, reason, punisher)
.thenApply(v -> GenericSyncResults.ADD_GAME);
} else {

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.bansync.enums;
import com.discordsrv.common.sync.cause.ISyncCause;

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.bansync.enums;
import com.discordsrv.common.sync.result.ISyncResult;

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.config.main;
import com.discordsrv.common.config.main.generic.AbstractSyncConfig;

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.config.main.generic;
import com.discordsrv.common.DiscordSRV;

View File

@ -66,7 +66,7 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
}
@Override
public String logName() {
public String logFileName() {
return "groupsync";
}
@ -86,8 +86,11 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
}
@Override
protected boolean isActive(Boolean state) {
return state;
protected @Nullable ISyncResult doesStateMatch(Boolean one, Boolean two) {
if (one == two) {
return GenericSyncResults.both(one);
}
return null;
}
@Override
@ -154,8 +157,8 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
groupChanged(player, groupName, serverContext, cause, false);
}
private void roleChanged(long userId, long roleId, boolean state) {
if (checkExpectation(expectedDiscordChanges, userId, roleId, state)) {
private void roleChanged(long userId, long roleId, boolean newState) {
if (checkExpectation(expectedDiscordChanges, userId, roleId, newState)) {
return;
}
@ -165,7 +168,7 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
return;
}
discordChanged(GroupSyncCause.DISCORD_ROLE_CHANGE, Someone.of(userId), roleId, state);
discordChanged(GroupSyncCause.DISCORD_ROLE_CHANGE, Someone.of(userId), roleId, newState);
}
private void groupChanged(
@ -173,7 +176,7 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
String groupName,
Set<String> serverContext,
GroupSyncCause cause,
Boolean state
boolean state
) {
if (cause.isDiscordSRVCanCause() && checkExpectation(expectedMinecraftChanges, playerUUID, groupName, state)) {
return;
@ -247,7 +250,7 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
}
@Override
public CompletableFuture<ISyncResult> applyDiscord(GroupSyncConfig.PairConfig config, long userId, Boolean state) {
public CompletableFuture<ISyncResult> applyDiscord(GroupSyncConfig.PairConfig config, long userId, Boolean newState) {
DiscordRole role = discordSRV.discordAPI().getRoleById(config.roleId);
if (role == null) {
return CompletableFutureUtil.failed(new SyncFail(GroupSyncResult.ROLE_DOESNT_EXIST));
@ -255,11 +258,11 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
Map<Long, Boolean> expected = expectedDiscordChanges.get(userId, key -> new ConcurrentHashMap<>());
if (expected != null) {
expected.put(config.roleId, state);
expected.put(config.roleId, newState);
}
return role.getGuild().retrieveMemberById(userId)
.thenCompose(member -> state
.thenCompose(member -> newState
? member.addRole(role).thenApply(v -> (ISyncResult) GenericSyncResults.ADD_DISCORD)
: member.removeRole(role).thenApply(v -> GenericSyncResults.REMOVE_DISCORD)
).whenComplete((r, t) -> {
@ -271,14 +274,14 @@ public class GroupSyncModule extends AbstractSyncModule<DiscordSRV, GroupSyncCon
}
@Override
public CompletableFuture<ISyncResult> applyGame(GroupSyncConfig.PairConfig config, UUID playerUUID, Boolean state) {
public CompletableFuture<ISyncResult> applyGame(GroupSyncConfig.PairConfig config, UUID playerUUID, Boolean newState) {
Map<String, Boolean> expected = expectedMinecraftChanges.get(playerUUID, key -> new ConcurrentHashMap<>());
if (expected != null) {
expected.put(config.groupName, state);
expected.put(config.groupName, newState);
}
CompletableFuture<ISyncResult> future =
state
newState
? addGroup(playerUUID, config).thenApply(v -> GenericSyncResults.ADD_GAME)
: removeGroup(playerUUID, config).thenApply(v -> GenericSyncResults.REMOVE_GAME);
return future.exceptionally(t -> {

View File

@ -178,8 +178,8 @@ public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implemen
InheritanceNode node = InheritanceNode.builder(group).context(contexts).build();
DataMutateResult result = function.apply(user.data(), node);
if (result != DataMutateResult.SUCCESS) {
return CompletableFutureUtil.failed(new MessageException(result.name()));
if (!result.wasSuccessful()) {
return CompletableFutureUtil.failed(new MessageException("Group mutate failed: " + result.name()));
}
return luckPerms.getUserManager().saveUser(user);

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.someone;
import com.discordsrv.api.discord.entity.DiscordUser;

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync;
import com.discordsrv.api.DiscordSRVApi;
@ -17,11 +35,12 @@ import com.discordsrv.common.sync.enums.SyncDirection;
import com.discordsrv.common.sync.result.GenericSyncResults;
import com.discordsrv.common.sync.enums.SyncSide;
import com.discordsrv.common.sync.result.ISyncResult;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.function.Consumer;
@ -51,17 +70,17 @@ public abstract class AbstractSyncModule<
super(discordSRV, new NamedLogger(discordSRV, loggerName));
}
public abstract String syncName();
public abstract String logName();
protected abstract String syncName();
protected abstract String logFileName();
public abstract String gameTerm();
public abstract String discordTerm();
protected abstract String gameTerm();
protected abstract String discordTerm();
/**
* Returns a list of all in use synchronizables.
* @return a list of configurations for synchronizables
*/
public abstract List<C> configs();
protected abstract List<C> configs();
@Override
public void reload(Consumer<DiscordSRVApi.ReloadResult> resultConsumer) {
@ -131,12 +150,11 @@ public abstract class AbstractSyncModule<
}
/**
* Check if the provided state is active or inactive, should this not match for the state of the two sides, synchronization will occur.
*
* @param state the state
* @return {@code true} indicating the provided state is "active"
* Checks if the given new and current state are the same, basically meaning that no update is necessary.
* @return the result stating the states are the same, otherwise {@code null} to state they are not
*/
protected abstract boolean isActive(S state);
@Nullable
protected abstract ISyncResult doesStateMatch(S one, S two);
/**
* Gets the current state of the provided config for the specified user on Discord.
@ -161,18 +179,18 @@ public abstract class AbstractSyncModule<
*
* @param config the configuration for the synchronizable
* @param userId the Discord user id
* @param state the state to apply
* @param newState the newState to apply
* @return a future with the result of the synchronization
*/
protected abstract CompletableFuture<ISyncResult> applyDiscord(C config, long userId, S state);
protected abstract CompletableFuture<ISyncResult> applyDiscord(C config, long userId, S newState);
protected CompletableFuture<ISyncResult> applyDiscordIfNot(C config, long userId, S state) {
return getDiscord(config, userId).thenCompose(value -> {
boolean actualValue;
if ((actualValue = isActive(state)) == isActive(value)) {
return CompletableFuture.completedFuture(actualValue ? GenericSyncResults.BOTH_TRUE : GenericSyncResults.BOTH_FALSE);
protected CompletableFuture<ISyncResult> applyDiscordIfDoesNotMatch(C config, long userId, S newState) {
return getDiscord(config, userId).thenCompose(currentState -> {
ISyncResult result = doesStateMatch(newState, currentState);
if (result != null) {
return CompletableFuture.completedFuture(result);
} else {
return applyDiscord(config, userId, state);
return applyDiscord(config, userId, newState);
}
});
}
@ -182,23 +200,23 @@ public abstract class AbstractSyncModule<
*
* @param config the configuration for the synchronizable
* @param playerUUID the Minecraft player {@link UUID}
* @param state the state to apply
* @param newState the newState to apply
* @return a future with the result of the synchronization
*/
protected abstract CompletableFuture<ISyncResult> applyGame(C config, UUID playerUUID, S state);
protected abstract CompletableFuture<ISyncResult> applyGame(C config, UUID playerUUID, S newState);
protected CompletableFuture<ISyncResult> applyGameIfNot(C config, UUID playerUUID, S state) {
return getGame(config, playerUUID).thenCompose(value -> {
boolean active;
if ((active = isActive(state)) == isActive(value)) {
return CompletableFuture.completedFuture(active ? GenericSyncResults.BOTH_TRUE : GenericSyncResults.BOTH_FALSE);
protected CompletableFuture<ISyncResult> applyGameIfDoesNotMatch(C config, UUID playerUUID, S newState) {
return getGame(config, playerUUID).thenCompose(currentState -> {
ISyncResult result = doesStateMatch(currentState, newState);
if (result != null) {
return CompletableFuture.completedFuture(result);
} else {
return applyGame(config, playerUUID, state);
return applyGame(config, playerUUID, newState);
}
});
}
protected CompletableFuture<SyncSummary<C>> discordChanged(ISyncCause cause, Someone someone, D discordId, S state) {
protected CompletableFuture<SyncSummary<C>> discordChanged(ISyncCause cause, Someone someone, D discordId, S newState) {
List<C> gameConfigs = configsForDiscord.get(discordId);
if (gameConfigs == null) {
return CompletableFuture.completedFuture(null);
@ -206,10 +224,10 @@ public abstract class AbstractSyncModule<
return someone.withLinkedAccounts(discordSRV).thenApply(resolved -> {
if (resolved == null) {
return new SyncSummary<C>(cause, someone).fail(GenericSyncResults.NOT_LINKED);
return new SyncSummary<>(this, cause, someone).fail(GenericSyncResults.NOT_LINKED);
}
SyncSummary<C> summary = new SyncSummary<>(cause, resolved);
SyncSummary<C> summary = new SyncSummary<>(this, cause, resolved);
for (C config : gameConfigs) {
SyncDirection direction = config.direction;
if (direction == SyncDirection.MINECRAFT_TO_DISCORD) {
@ -218,7 +236,7 @@ public abstract class AbstractSyncModule<
continue;
}
summary.appendResult(config, applyGameIfNot(config, resolved.playerUUID(), state));
summary.appendResult(config, applyGameIfDoesNotMatch(config, resolved.playerUUID(), newState));
// If the sync is bidirectional, also sync anything else linked to the same Minecraft id
if (direction == SyncDirection.DISCORD_TO_MINECRAFT) {
@ -231,11 +249,11 @@ public abstract class AbstractSyncModule<
}
for (C gameConfig : discordConfigs) {
if (gameConfig.discordId() == discordId) {
if (Objects.equals(gameConfig.discordId(), discordId)) {
continue;
}
summary.appendResult(gameConfig, applyDiscordIfNot(gameConfig, resolved.userId(), state));
summary.appendResult(gameConfig, applyDiscordIfDoesNotMatch(gameConfig, resolved.userId(), newState));
}
}
return summary;
@ -246,7 +264,7 @@ public abstract class AbstractSyncModule<
});
}
protected CompletableFuture<SyncSummary<C>> gameChanged(ISyncCause cause, Someone someone, G gameId, S state) {
protected CompletableFuture<SyncSummary<C>> gameChanged(ISyncCause cause, Someone someone, G gameId, S newState) {
List<C> discordConfigs = configsForGame.get(gameId);
if (discordConfigs == null) {
return CompletableFuture.completedFuture(null);
@ -254,10 +272,10 @@ public abstract class AbstractSyncModule<
return someone.withLinkedAccounts(discordSRV).thenApply(resolved -> {
if (resolved == null) {
return new SyncSummary<C>(cause, someone).fail(GenericSyncResults.NOT_LINKED);
return new SyncSummary<>(this, cause, someone).fail(GenericSyncResults.NOT_LINKED);
}
SyncSummary<C> summary = new SyncSummary<>(cause, resolved);
SyncSummary<C> summary = new SyncSummary<>(this, cause, resolved);
for (C config : discordConfigs) {
SyncDirection direction = config.direction;
if (direction == SyncDirection.DISCORD_TO_MINECRAFT) {
@ -266,7 +284,7 @@ public abstract class AbstractSyncModule<
continue;
}
summary.appendResult(config, applyDiscordIfNot(config, resolved.userId(), state));
summary.appendResult(config, applyDiscordIfDoesNotMatch(config, resolved.userId(), newState));
// If the sync is bidirectional, also sync anything else linked to the same Discord id
if (direction == SyncDirection.MINECRAFT_TO_DISCORD) {
@ -279,11 +297,11 @@ public abstract class AbstractSyncModule<
}
for (C gameConfig : gameConfigs) {
if (gameConfig.gameId() == gameId) {
if (Objects.equals(gameConfig.gameId(), gameId)) {
continue;
}
summary.appendResult(gameConfig, applyGameIfNot(gameConfig, resolved.playerUUID(), state));
summary.appendResult(gameConfig, applyGameIfDoesNotMatch(gameConfig, resolved.playerUUID(), newState));
}
}
return summary;
@ -297,11 +315,11 @@ public abstract class AbstractSyncModule<
public CompletableFuture<SyncSummary<C>> resyncAll(ISyncCause cause, Someone someone) {
return someone.withLinkedAccounts(discordSRV).thenApply(resolved -> {
if (resolved == null) {
return new SyncSummary<C>(cause, someone).fail(GenericSyncResults.NOT_LINKED);
return new SyncSummary<>(this, cause, someone).fail(GenericSyncResults.NOT_LINKED);
}
SyncSummary<C> summary = new SyncSummary<>(cause, resolved);
List<C> configs = configs();
SyncSummary<C> summary = new SyncSummary<>(this, cause, resolved);
Set<C> configs = syncs.keySet();
for (C config : configs) {
summary.appendResult(config, resync(config, resolved));
@ -317,10 +335,10 @@ public abstract class AbstractSyncModule<
protected CompletableFuture<SyncSummary<C>> resync(ISyncCause cause, C config, Someone someone) {
return someone.withLinkedAccounts(discordSRV).thenApply(resolved -> {
if (resolved == null) {
return new SyncSummary<C>(cause, someone).fail(GenericSyncResults.NOT_LINKED);
return new SyncSummary<>(this, cause, someone).fail(GenericSyncResults.NOT_LINKED);
}
return new SyncSummary<C>(cause, resolved)
return new SyncSummary<C>(this, cause, resolved)
.appendResult(config, resync(config, resolved));
}).whenComplete((summary, t) -> {
if (summary != null) {
@ -340,10 +358,9 @@ public abstract class AbstractSyncModule<
S gameState = gameGet.join();
S discordState = discordGet.join();
boolean bothState;
if ((bothState = (gameState != null)) == (discordState != null)) {
// Already in sync
return CompletableFuture.completedFuture((ISyncResult) (bothState ? GenericSyncResults.BOTH_TRUE : GenericSyncResults.BOTH_FALSE));
ISyncResult alreadyInSyncResult = doesStateMatch(gameState, discordState);
if (alreadyInSyncResult != null) {
return CompletableFuture.completedFuture(alreadyInSyncResult);
}
SyncSide side = config.tieBreaker;
@ -392,45 +409,71 @@ public abstract class AbstractSyncModule<
private String formatResults(SyncSummary<C> summary, List<String> results) {
int count = results.size();
return summary.who().toString()
return summary.who() + " (sync cause: " + summary.cause() + ")"
+ (count == 1 ? ": " : "\n")
+ String.join("\n", results);
}
private void logSummary(SyncSummary<C> summary) {
summary.resultFuture().whenComplete((results, t) -> {
Throwable throwableToLog = null;
if (t != null) {
logger().error("Failed to " + syncName() + " " + summary.who(), t);
return;
while (t instanceof CompletionException) {
t = t.getCause();
}
if (t instanceof SyncFail) {
SyncFail fail = (SyncFail) t;
summary.fail(fail.getResult());
throwableToLog = fail.getCause();
} else {
logger().error("Failed to " + syncName() + " " + summary.who() + " (sync cause: " + summary.cause() + ")", t);
return;
}
}
ISyncResult allFailReason = summary.allFailReason();
if (allFailReason != null) {
String reason = allFailReason.format(gameTerm(), discordTerm());
logger().debug("Failed to " + syncName() + " " + summary.who() + ": " + reason);
String message = "Failed to " + syncName() + " " + summary.who() + " (sync cause: " + summary.cause() + "): " + reason;
if (!allFailReason.isSuccess()) {
logger().error(message, throwableToLog);
} else {
logger().debug(message, throwableToLog);
}
return;
}
List<String> logResults = new ArrayList<>();
List<String> auditResults = new ArrayList<>();
Map<ISyncResult, List<String>> groupedResults = new LinkedHashMap<>();
for (Map.Entry<C, ISyncResult> entry : results.entrySet()) {
C config = entry.getKey();
ISyncResult result = entry.getValue();
String log = config.describe();
if (StringUtils.isEmpty(log)) {
log += ": ";
}
log += result.format(gameTerm(), discordTerm());
groupedResults.computeIfAbsent(result, key -> new ArrayList<>()).add(config.describe());
}
logResults.add(log);
List<String> successResults = new ArrayList<>();
List<String> failResults = new ArrayList<>();
for (Map.Entry<ISyncResult, List<String>> entry : groupedResults.entrySet()) {
ISyncResult result = entry.getKey();
String line = result.format(gameTerm(), discordTerm())
+ ": [" + String.join(", ", entry.getValue()) + "]";
if (result.isSuccess()) {
auditResults.add(log);
successResults.add(line);
} else {
failResults.add(line);
}
}
logger().debug(formatResults(summary, logResults));
discordSRV.logger().writeLogForCurrentDay(logName(), formatResults(summary, auditResults));
boolean anySuccess = !successResults.isEmpty();
boolean anyFail = !failResults.isEmpty();
String partially = anySuccess && anyFail ? " partially" : "";
if (anySuccess) {
logger().debug(syncName() + partially + " succeeded for " + formatResults(summary, successResults));
}
if (anyFail) {
logger().error(syncName() + partially + " failed for " + formatResults(summary, failResults));
}
discordSRV.logger().writeLogForCurrentDay(logFileName(), formatResults(summary, successResults));
});
}

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync;
import com.discordsrv.common.sync.result.ISyncResult;

View File

@ -1,5 +1,24 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.generic.AbstractSyncConfig;
import com.discordsrv.common.future.util.CompletableFutureUtil;
import com.discordsrv.common.someone.Someone;
@ -9,16 +28,19 @@ import com.discordsrv.common.sync.result.ISyncResult;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
public class SyncSummary<C extends AbstractSyncConfig<C, ?, ?>> {
private final AbstractSyncModule<? extends DiscordSRV, C, ?, ?, ?> syncModule;
private final ISyncCause cause;
private final Someone who;
private ISyncResult allFailReason;
private final Map<C, CompletableFuture<ISyncResult>> results = new ConcurrentHashMap<>();
public SyncSummary(ISyncCause cause, Someone who) {
public SyncSummary(AbstractSyncModule<? extends DiscordSRV, C, ?, ?, ?> syncModule, ISyncCause cause, Someone who) {
this.syncModule = syncModule;
this.cause = cause;
this.who = who;
}
@ -55,7 +77,25 @@ public class SyncSummary<C extends AbstractSyncConfig<C, ?, ?>> {
.thenApply((__) -> {
Map<C, ISyncResult> results = new HashMap<>();
for (Map.Entry<C, CompletableFuture<ISyncResult>> entry : this.results.entrySet()) {
results.put(entry.getKey(), entry.getValue().join());
results.put(entry.getKey(), entry.getValue().exceptionally(t -> {
while (t instanceof CompletionException) {
t = t.getCause();
}
Throwable throwableToLog = t;
ISyncResult result = null;
if (t instanceof SyncFail) {
throwableToLog = t.getCause();
result = ((SyncFail) t).getResult();
}
if (throwableToLog != null) {
syncModule.logger().error(
"Error in " + syncModule.syncName() + " "
+ entry.getKey().describe() + " for " + who()
+ " (sync cause: " + cause() + ")", throwableToLog);
}
return result;
}).join());
}
return results;
});

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync.cause;
public enum GenericSyncCauses implements ISyncCause {

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync.cause;
public interface ISyncCause {

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync.result;
public enum GenericSyncResults implements ISyncResult {
@ -18,6 +36,10 @@ public enum GenericSyncResults implements ISyncResult {
;
public static GenericSyncResults both(boolean value) {
return value ? BOTH_TRUE : BOTH_FALSE;
}
private final String message;
private final boolean success;

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 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.sync.result;
import com.discordsrv.api.placeholder.util.Placeholders;