mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-01 08:39:31 +01:00
Add channel auto locking, implement thread archiving. More fixes to shutdown not working properly/ errors when the server is stopped before DiscordSRV starts
This commit is contained in:
parent
51fd9ef6d8
commit
bbc722e689
@ -39,6 +39,10 @@ public interface Module {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default int shutdownOrder() {
|
||||
return priority(getClass());
|
||||
}
|
||||
|
||||
default void enable() {
|
||||
reload();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
||||
import com.discordsrv.api.module.type.Module;
|
||||
import com.discordsrv.common.api.util.ApiInstanceUtil;
|
||||
import com.discordsrv.common.channel.ChannelConfigHelper;
|
||||
import com.discordsrv.common.channel.ChannelShutdownBehaviourModule;
|
||||
import com.discordsrv.common.channel.ChannelUpdaterModule;
|
||||
import com.discordsrv.common.channel.GlobalChannelLookupModule;
|
||||
import com.discordsrv.common.command.game.GameCommandModule;
|
||||
@ -388,7 +389,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
// Lifecycle
|
||||
|
||||
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable) {
|
||||
return invoke(() -> {
|
||||
return invokeLifecycle(() -> {
|
||||
try {
|
||||
lifecycleLock.lock();
|
||||
runnable.run();
|
||||
@ -398,11 +399,19 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
}, "Failed to enable", true);
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> invoke(CheckedRunnable runnable, String message, boolean enable) {
|
||||
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable, String message, boolean enable) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
if (status().isShutdown()) {
|
||||
// Already shutdown/shutting down, don't bother
|
||||
return;
|
||||
}
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable t) {
|
||||
if (status().isShutdown() && t instanceof NoClassDefFoundError) {
|
||||
// Already shutdown, ignore errors for classes that already got unloaded
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
setStatus(Status.FAILED_TO_START);
|
||||
disable();
|
||||
@ -424,6 +433,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
@Override
|
||||
public final CompletableFuture<Void> invokeDisable() {
|
||||
if (enableFuture != null && !enableFuture.isDone()) {
|
||||
logger().warning("Start cancelled");
|
||||
enableFuture.cancel(true);
|
||||
}
|
||||
return CompletableFuture.runAsync(this::disable, scheduler().executorService());
|
||||
@ -431,7 +441,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
|
||||
@Override
|
||||
public final CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent) {
|
||||
return invoke(() -> reload(flags, silent), "Failed to reload", false);
|
||||
return invokeLifecycle(() -> reload(flags, silent), "Failed to reload", false);
|
||||
}
|
||||
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
@ -453,6 +463,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
|
||||
|
||||
// Modules
|
||||
registerModule(ChannelShutdownBehaviourModule::new);
|
||||
registerModule(ChannelUpdaterModule::new);
|
||||
registerModule(GameCommandModule::new);
|
||||
registerModule(GlobalChannelLookupModule::new);
|
||||
@ -588,6 +599,16 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
}
|
||||
if (!initial) {
|
||||
waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS);
|
||||
} else {
|
||||
JDA jda = jda().orElse(null);
|
||||
if (jda != null) {
|
||||
try {
|
||||
jda.awaitReady();
|
||||
} catch (IllegalStateException ignored) {
|
||||
// JDA shutdown -> don't continue
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
throw e.getCause();
|
||||
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 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.channel;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.ShutdownBehaviourConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.function.OrDefault;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.IPermissionHolder;
|
||||
import net.dv8tion.jda.api.entities.PermissionOverride;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.TextChannel;
|
||||
import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ChannelShutdownBehaviourModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
public ChannelShutdownBehaviourModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int shutdownOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
doForAllChannels((config, channelConfig) -> {
|
||||
OrDefault<ShutdownBehaviourConfig> shutdownConfig = config.map(cfg -> cfg.shutdownBehaviour);
|
||||
OrDefault<ShutdownBehaviourConfig.Channels> channels = shutdownConfig.map(cfg -> cfg.channels);
|
||||
OrDefault<ShutdownBehaviourConfig.Threads> threads = shutdownConfig.map(cfg -> cfg.threads);
|
||||
|
||||
if (threads.get(cfg -> cfg.unarchive, true)) {
|
||||
discordSRV.discordAPI().findOrCreateThreads(config, channelConfig, __ -> {}, new ArrayList<>(), false);
|
||||
}
|
||||
channelPermissions(channelConfig, channels, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
doForAllChannels((config, channelConfig) -> {
|
||||
OrDefault<ShutdownBehaviourConfig> shutdownConfig = config.map(cfg -> cfg.shutdownBehaviour);
|
||||
OrDefault<ShutdownBehaviourConfig.Channels> channels = shutdownConfig.map(cfg -> cfg.channels);
|
||||
OrDefault<ShutdownBehaviourConfig.Threads> threads = shutdownConfig.map(cfg -> cfg.threads);
|
||||
|
||||
if (threads.get(cfg -> cfg.archive, true)) {
|
||||
for (DiscordThreadChannel thread : discordSRV.discordAPI().findThreads(config, channelConfig)) {
|
||||
thread.getAsJDAThreadChannel().getManager()
|
||||
.setArchived(true)
|
||||
.reason("DiscordSRV shutdown behaviour")
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
channelPermissions(channelConfig, channels, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void channelPermissions(
|
||||
IChannelConfig channelConfig,
|
||||
OrDefault<ShutdownBehaviourConfig.Channels> shutdownConfig,
|
||||
boolean state
|
||||
) {
|
||||
JDA jda = discordSRV.jda().orElse(null);
|
||||
if (jda == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean everyone = shutdownConfig.get(cfg -> cfg.everyone, false);
|
||||
List<Long> roleIds = shutdownConfig.get(cfg -> cfg.roleIds, Collections.emptyList());
|
||||
if (!everyone && roleIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Permission> permissions = new ArrayList<>();
|
||||
if (shutdownConfig.get(cfg -> cfg.read, false)) {
|
||||
permissions.add(Permission.VIEW_CHANNEL);
|
||||
}
|
||||
if (shutdownConfig.get(cfg -> cfg.write, true)) {
|
||||
permissions.add(Permission.MESSAGE_SEND);
|
||||
}
|
||||
if (shutdownConfig.get(cfg -> cfg.addReactions, true)) {
|
||||
permissions.add(Permission.MESSAGE_ADD_REACTION);
|
||||
}
|
||||
|
||||
for (Long channelId : channelConfig.channelIds()) {
|
||||
TextChannel channel = jda.getTextChannelById(channelId);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (everyone) {
|
||||
setPermission(channel, channel.getGuild().getPublicRole(), permissions, state);
|
||||
}
|
||||
for (Long roleId : roleIds) {
|
||||
Role role = channel.getGuild().getRoleById(roleId);
|
||||
if (role == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setPermission(channel, role, permissions, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPermission(TextChannel channel, IPermissionHolder holder, List<Permission> permissions, boolean state) {
|
||||
PermissionOverride override = channel.getPermissionOverride(holder);
|
||||
if (override != null && (state ? override.getAllowed() : override.getDenied()).containsAll(permissions)) {
|
||||
// Already correct
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionOverrideAction action = override != null
|
||||
? override.getManager()
|
||||
: channel.putPermissionOverride(holder);
|
||||
|
||||
if (state) {
|
||||
action = action.grant(permissions);
|
||||
} else {
|
||||
action = action.deny(permissions);
|
||||
}
|
||||
action.reason("DiscordSRV shutdown behaviour").queue();
|
||||
}
|
||||
|
||||
private void doForAllChannels(BiConsumer<OrDefault<BaseChannelConfig>, IChannelConfig> channelConsumer) {
|
||||
for (OrDefault<BaseChannelConfig> config : discordSRV.channelConfig().getAllChannels()) {
|
||||
IChannelConfig channelConfig = config.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null);
|
||||
if (channelConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
channelConsumer.accept(config, channelConfig);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2022 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.channels;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ShutdownBehaviourConfig {
|
||||
|
||||
public Channels channels = new Channels();
|
||||
public Threads threads = new Threads();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Channels {
|
||||
|
||||
@Comment("If the permissions should be taken from @everyone while the server is offline")
|
||||
public boolean everyone = false;
|
||||
|
||||
@Comment("Role ids for roles that should have the permissions taken while the server is offline")
|
||||
public List<Long> roleIds = new ArrayList<>();
|
||||
|
||||
public boolean read = false;
|
||||
public boolean write = true;
|
||||
public boolean addReactions = true;
|
||||
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Threads {
|
||||
|
||||
@Comment("If threads should be archived while the server is shutdown")
|
||||
public boolean archive = true;
|
||||
|
||||
@Comment("If the bot will attempt to unarchive threads rather than make new threads")
|
||||
public boolean unarchive = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -31,14 +31,20 @@ public class BaseChannelConfig {
|
||||
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
|
||||
public JoinMessageConfig joinMessages = new JoinMessageConfig();
|
||||
public LeaveMessageConfig leaveMessages = new LeaveMessageConfig();
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
@Order(10)
|
||||
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
|
||||
|
||||
@Order(20)
|
||||
public StartMessageConfig startMessage = new StartMessageConfig();
|
||||
@Order(20)
|
||||
public StopMessageConfig stopMessage = new StopMessageConfig();
|
||||
|
||||
@Order(10)
|
||||
@Order(30)
|
||||
@Comment("Settings for synchronizing messages between the defined Discord channels and threads")
|
||||
public MirroringConfig mirroring = new MirroringConfig();
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
@Order(50)
|
||||
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
|
||||
public ShutdownBehaviourConfig shutdownBehaviour = new ShutdownBehaviourConfig();
|
||||
}
|
||||
|
@ -28,6 +28,4 @@ public class ThreadConfig {
|
||||
public String threadName = "Minecraft Server chat bridge";
|
||||
public boolean privateThread = false;
|
||||
|
||||
public boolean archiveOnShutdown = true;
|
||||
public boolean unarchive = true;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.discordsrv.common.discord.api.entity.channel.DiscordThreadChannelImpl
|
||||
import com.discordsrv.common.discord.api.entity.guild.DiscordGuildImpl;
|
||||
import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl;
|
||||
import com.discordsrv.common.function.CheckedSupplier;
|
||||
import com.discordsrv.common.function.OrDefault;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
@ -98,9 +99,9 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
* @param config the config that specified the threads
|
||||
* @return the list of active threads
|
||||
*/
|
||||
public List<DiscordThreadChannel> findThreads(IChannelConfig config) {
|
||||
public List<DiscordThreadChannel> findThreads(OrDefault<BaseChannelConfig> config, IChannelConfig channelConfig) {
|
||||
List<DiscordThreadChannel> channels = new ArrayList<>();
|
||||
findOrCreateThreads(config, channels::add, null);
|
||||
findOrCreateThreads(config, channelConfig, channels::add, null, false);
|
||||
return channels;
|
||||
}
|
||||
|
||||
@ -111,11 +112,13 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
* @param futures a possibly null list of {@link CompletableFuture} for tasks that need to be completed to get all threads
|
||||
*/
|
||||
public void findOrCreateThreads(
|
||||
IChannelConfig config,
|
||||
OrDefault<BaseChannelConfig> config,
|
||||
IChannelConfig channelConfig,
|
||||
Consumer<DiscordThreadChannel> channelConsumer,
|
||||
@Nullable List<CompletableFuture<DiscordThreadChannel>> futures
|
||||
@Nullable List<CompletableFuture<DiscordThreadChannel>> futures,
|
||||
boolean log
|
||||
) {
|
||||
List<ThreadConfig> threads = config.threads();
|
||||
List<ThreadConfig> threads = channelConfig.threads();
|
||||
if (threads == null) {
|
||||
return;
|
||||
}
|
||||
@ -124,10 +127,8 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
long channelId = threadConfig.channelId;
|
||||
DiscordTextChannel channel = getTextChannelById(channelId).orElse(null);
|
||||
if (channel == null) {
|
||||
if (channelId > 0) {
|
||||
discordSRV.logger().error("Unable to find channel with ID "
|
||||
+ Long.toUnsignedString(channelId)
|
||||
+ ", unable to forward message to Discord");
|
||||
if (channelId > 0 && log) {
|
||||
discordSRV.logger().error("Unable to find channel with ID " + Long.toUnsignedString(channelId));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -136,7 +137,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
DiscordThreadChannel thread = findThread(threadConfig, channel.getActiveThreads());
|
||||
if (thread != null) {
|
||||
ThreadChannel jdaChannel = thread.getAsJDAThreadChannel();
|
||||
if (!jdaChannel.isLocked() && !jdaChannel.isArchived()) {
|
||||
if (!jdaChannel.isArchived()) {
|
||||
channelConsumer.accept(thread);
|
||||
continue;
|
||||
}
|
||||
@ -154,7 +155,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
unarchiveOrCreateThread(threadConfig, channel, thread, future);
|
||||
} else {
|
||||
// Find or create the thread
|
||||
future = findOrCreateThread(threadConfig, channel);
|
||||
future = findOrCreateThread(config, threadConfig, channel);
|
||||
}
|
||||
|
||||
futures.add(future.handle((threadChannel, t) -> {
|
||||
@ -182,16 +183,20 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompletableFuture<DiscordThreadChannel> findOrCreateThread(ThreadConfig config, DiscordTextChannel textChannel) {
|
||||
if (!config.unarchive) {
|
||||
return textChannel.createThread(config.threadName, config.privateThread);
|
||||
private CompletableFuture<DiscordThreadChannel> findOrCreateThread(
|
||||
OrDefault<BaseChannelConfig> config,
|
||||
ThreadConfig threadConfig,
|
||||
DiscordTextChannel textChannel
|
||||
) {
|
||||
if (!config.map(cfg -> cfg.shutdownBehaviour).map(cfg -> cfg.threads).get(cfg -> cfg.unarchive, true)) {
|
||||
return textChannel.createThread(threadConfig.threadName, threadConfig.privateThread);
|
||||
}
|
||||
|
||||
CompletableFuture<DiscordThreadChannel> completableFuture = new CompletableFuture<>();
|
||||
lookupThreads(
|
||||
textChannel,
|
||||
config.privateThread,
|
||||
lookup -> findOrCreateThread(config, textChannel, lookup, completableFuture),
|
||||
threadConfig.privateThread,
|
||||
lookup -> findOrCreateThread(threadConfig, textChannel, lookup, completableFuture),
|
||||
(thread, throwable) -> {
|
||||
if (throwable != null) {
|
||||
completableFuture.completeExceptionally(throwable);
|
||||
@ -244,10 +249,10 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
ThreadChannel channel = thread.getAsJDAThreadChannel();
|
||||
if (channel.isLocked() || channel.isArchived()) {
|
||||
try {
|
||||
channel.getManager().setArchived(false).queue(
|
||||
v -> future.complete(thread),
|
||||
future::completeExceptionally
|
||||
);
|
||||
channel.getManager()
|
||||
.setArchived(false)
|
||||
.reason("DiscordSRV Auto Unarchive")
|
||||
.queue(v -> future.complete(thread), future::completeExceptionally);
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
// Our own (named) threads
|
||||
jdaBuilder.setCallbackPool(discordSRV.scheduler().forkJoinPool());
|
||||
jdaBuilder.setGatewayPool(gatewayPool);
|
||||
jdaBuilder.setRateLimitPool(rateLimitPool);
|
||||
jdaBuilder.setRateLimitPool(rateLimitPool, true);
|
||||
|
||||
OkHttpClient.Builder httpBuilder = IOUtil.newHttpClientBuilder();
|
||||
// These 3 are 10 seconds by default
|
||||
@ -304,17 +304,20 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
instance.shutdown();
|
||||
|
||||
try {
|
||||
discordSRV.logger().info("Waiting up to " + TimeUnit.MILLISECONDS.toSeconds(timeoutMillis) + " seconds for JDA to shutdown...");
|
||||
discordSRV.scheduler().run(() -> {
|
||||
try {
|
||||
while (instance != null && instance.getStatus() != JDA.Status.SHUTDOWN) {
|
||||
while (instance != null && !rateLimitPool.isShutdown()) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
} catch (InterruptedException ignored) {}
|
||||
}).get(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||
instance = null;
|
||||
shutdownExecutors();
|
||||
discordSRV.logger().info("JDA shutdown completed.");
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
try {
|
||||
discordSRV.logger().info("JDA failed to shutdown within the timeout, cancelling any remaining requests");
|
||||
shutdownNow();
|
||||
} catch (Throwable t) {
|
||||
if (e instanceof ExecutionException) {
|
||||
@ -332,13 +335,14 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
instance = null;
|
||||
}
|
||||
shutdownExecutors();
|
||||
discordSRV.logger().info("JDA shutdown completed.");
|
||||
}
|
||||
|
||||
private void shutdownExecutors() {
|
||||
if (gatewayPool != null) {
|
||||
gatewayPool.shutdownNow();
|
||||
}
|
||||
if (rateLimitPool != null) {
|
||||
if (rateLimitPool != null && !rateLimitPool.isShutdown()) {
|
||||
rateLimitPool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,13 @@
|
||||
package com.discordsrv.common.logging.impl;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.logging.LogLevel;
|
||||
import com.discordsrv.common.logging.LogAppender;
|
||||
import com.discordsrv.common.logging.LogLevel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
public class DependencyLoggingHandler implements LogAppender {
|
||||
|
||||
@ -89,6 +90,14 @@ public class DependencyLoggingHandler implements LogAppender {
|
||||
}
|
||||
}
|
||||
|
||||
if (name.equals("JDA") && message != null
|
||||
&& message.contains("Got an unexpected error. Please redirect the following message to the devs:")
|
||||
&& throwable instanceof RejectedExecutionException
|
||||
&& discordSRV.status().isShutdown()) {
|
||||
// Might happen if the server shuts down while JDA is starting
|
||||
return;
|
||||
}
|
||||
|
||||
discordSRV.logger().log(null, logLevel, "[" + name + "]" + (message != null ? " " + message : ""), throwable);
|
||||
}
|
||||
}
|
||||
|
@ -162,8 +162,13 @@ public class DiscordSRVLogger implements Logger {
|
||||
|
||||
Files.write(path, line.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
|
||||
} catch (Throwable e) {
|
||||
// Prevent infinite loop
|
||||
discordSRV.platformLogger().error("Failed to write to debug log", e);
|
||||
try {
|
||||
// Prevent infinite loop
|
||||
if (discordSRV.status() == DiscordSRV.Status.SHUTDOWN) {
|
||||
return;
|
||||
}
|
||||
discordSRV.platformLogger().error("Failed to write to debug log", e);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,11 +104,11 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
}
|
||||
}
|
||||
|
||||
discordSRV.discordAPI().findOrCreateThreads(iChannelConfig, threadChannel -> {
|
||||
discordSRV.discordAPI().findOrCreateThreads(channelConfig, iChannelConfig, threadChannel -> {
|
||||
if (threadChannel.getId() != channel.getId()) {
|
||||
mirrorChannels.add(Pair.of(threadChannel, config));
|
||||
}
|
||||
}, futures);
|
||||
}, futures, false);
|
||||
}
|
||||
|
||||
CompletableFutureUtil.combine(futures).whenComplete((v, t) -> {
|
||||
|
@ -78,24 +78,24 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig> extend
|
||||
private CompletableFuture<Void> forwardToChannel(
|
||||
@Nullable AbstractGameMessageReceiveEvent event,
|
||||
@Nullable DiscordSRVPlayer player,
|
||||
@NotNull OrDefault<BaseChannelConfig> channelConfig
|
||||
@NotNull OrDefault<BaseChannelConfig> config
|
||||
) {
|
||||
OrDefault<T> config = mapConfig(channelConfig);
|
||||
if (!config.get(IMessageConfig::enabled, true)) {
|
||||
OrDefault<T> moduleConfig = mapConfig(config);
|
||||
if (!moduleConfig.get(IMessageConfig::enabled, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IChannelConfig iChannelConfig = channelConfig.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null);
|
||||
if (iChannelConfig == null) {
|
||||
IChannelConfig channelConfig = config.get(c -> c instanceof IChannelConfig ? (IChannelConfig) c : null);
|
||||
if (channelConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<DiscordMessageChannel> messageChannels = new CopyOnWriteArrayList<>();
|
||||
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
||||
|
||||
List<Long> channelIds = iChannelConfig.channelIds();
|
||||
List<Long> channelIds = channelConfig.channelIds();
|
||||
if (channelIds != null) {
|
||||
for (Long channelId : iChannelConfig.channelIds()) {
|
||||
for (Long channelId : channelConfig.channelIds()) {
|
||||
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null);
|
||||
if (textChannel != null) {
|
||||
messageChannels.add(textChannel);
|
||||
@ -107,24 +107,24 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig> extend
|
||||
}
|
||||
}
|
||||
|
||||
discordSRV.discordAPI().findOrCreateThreads(iChannelConfig, messageChannels::add, futures);
|
||||
discordSRV.discordAPI().findOrCreateThreads(config, channelConfig, messageChannels::add, futures, true);
|
||||
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).whenComplete((v, t1) -> {
|
||||
SendableDiscordMessage.Builder format = config.get(IMessageConfig::format);
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenCompose((v) -> {
|
||||
SendableDiscordMessage.Builder format = moduleConfig.get(IMessageConfig::format);
|
||||
if (format == null) {
|
||||
return;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
Component component = event != null ? ComponentUtil.fromAPI(event.getMessage()) : null;
|
||||
String message = component != null ? convertMessage(config, component) : null;
|
||||
String message = component != null ? convertMessage(moduleConfig, component) : null;
|
||||
Map<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> messageFutures;
|
||||
messageFutures = sendMessageToChannels(
|
||||
config, format, messageChannels, message,
|
||||
moduleConfig, format, messageChannels, message,
|
||||
// Context
|
||||
channelConfig, player
|
||||
);
|
||||
|
||||
CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0]))
|
||||
return CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0]))
|
||||
.whenComplete((vo, t2) -> {
|
||||
Set<ReceivedDiscordMessage> messages = new LinkedHashSet<>();
|
||||
for (Map.Entry<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> entry : messageFutures.entrySet()) {
|
||||
|
@ -142,9 +142,9 @@ public class ModuleManager {
|
||||
|
||||
@Subscribe(priority = EventPriority.EARLY)
|
||||
public void onShuttingDown(DiscordSRVShuttingDownEvent event) {
|
||||
for (Module module : modules) {
|
||||
unregister(module);
|
||||
}
|
||||
modules.stream()
|
||||
.sorted((m1, m2) -> Integer.compare(m2.shutdownOrder(), m1.shutdownOrder()))
|
||||
.forEachOrdered(Module::disable);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
|
Loading…
Reference in New Issue
Block a user