Initial for console rotation

This commit is contained in:
Vankka 2024-11-10 19:50:24 +02:00
parent 51cd79e700
commit 9d702ef8d9
No known key found for this signature in database
GPG Key ID: 62E48025ED4E7EBB
9 changed files with 154 additions and 23 deletions

View File

@ -81,6 +81,7 @@ import com.discordsrv.common.feature.profile.ProfileManager;
import com.discordsrv.common.feature.update.UpdateChecker;
import com.discordsrv.common.helper.ChannelConfigHelper;
import com.discordsrv.common.helper.DestinationLookupHelper;
import com.discordsrv.common.helper.TemporaryLocalData;
import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
import com.discordsrv.common.util.ApiInstanceUtil;
import com.discordsrv.common.util.UUIDUtil;
@ -154,6 +155,7 @@ public abstract class AbstractDiscordSRV<
private JDAConnectionManager discordConnectionManager;
private ChannelConfigHelper channelConfig;
private DestinationLookupHelper destinationLookupHelper;
private TemporaryLocalData temporaryLocalData;
private Storage storage;
private LinkProvider linkProvider;
@ -191,6 +193,7 @@ public abstract class AbstractDiscordSRV<
this.discordConnectionManager = new JDAConnectionManager(this);
this.channelConfig = new ChannelConfigHelper(this);
this.destinationLookupHelper = new DestinationLookupHelper(this);
this.temporaryLocalData = new TemporaryLocalData(this);
this.updateChecker = new UpdateChecker(this);
readManifest();
@ -363,6 +366,11 @@ public abstract class AbstractDiscordSRV<
return storage;
}
@Override
public TemporaryLocalData temporaryLocalData() {
return temporaryLocalData;
}
@Override
public final LinkProvider linkProvider() {
return linkProvider;
@ -707,6 +715,7 @@ public abstract class AbstractDiscordSRV<
} catch (Throwable t) {
logger().error("Failed to close storage connection", t);
}
temporaryLocalData.save();
this.status.set(Status.SHUTDOWN);
}

View File

@ -53,6 +53,7 @@ import com.discordsrv.common.feature.linking.LinkProvider;
import com.discordsrv.common.feature.profile.ProfileManager;
import com.discordsrv.common.helper.ChannelConfigHelper;
import com.discordsrv.common.helper.DestinationLookupHelper;
import com.discordsrv.common.helper.TemporaryLocalData;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import okhttp3.OkHttpClient;
@ -110,6 +111,7 @@ public interface DiscordSRV extends DiscordSRVApi {
// Storage
Storage storage();
TemporaryLocalData temporaryLocalData();
// Link Provider
LinkProvider linkProvider();

View File

@ -32,7 +32,10 @@ import java.util.List;
public class ConsoleConfig {
@Comment("The console channel or thread")
public DestinationConfig.Single channel = new DestinationConfig.Single();
public DestinationConfig.Single channel = new DestinationConfig.Single("DiscordSRV Console #%date:'w'%", true);
@Comment("The amount of threads to keep. Rotation interval is based on placeholders in the thread name")
public int threadsToKeepInRotation = 3;
public Appender appender = new Appender();

View File

@ -51,6 +51,14 @@ public class DestinationConfig {
@Setting(nodeFromParent = true)
public ThreadConfig thread = new ThreadConfig("");
@SuppressWarnings("unused") // Configurate
public Single() {}
public Single(String threadName, boolean privateThread) {
this.thread.threadName = threadName;
this.thread.privateThread = privateThread;
}
public DestinationConfig asDestination() {
DestinationConfig config = new DestinationConfig();
if (thread == null || StringUtils.isEmpty(thread.threadName)) {

View File

@ -94,12 +94,12 @@ public class JDAConnectionManager implements DiscordConnectionManager {
private final Set<DiscordMemberCachePolicy> memberCachePolicies = new HashSet<>();
// Bot owner details
private final Timeout botOwnerTimeout = new Timeout(5, TimeUnit.MINUTES);
private final Timeout botOwnerTimeout = new Timeout(Duration.ofMinutes(5));
private final AtomicReference<CompletableFuture<DiscordUser>> botOwnerRequest = new AtomicReference<>();
// Logging timeouts
private final Timeout mfaTimeout = new Timeout(30, TimeUnit.SECONDS);
private final Timeout serverErrorTimeout = new Timeout(20, TimeUnit.SECONDS);
private final Timeout mfaTimeout = new Timeout(Duration.ofSeconds(30));
private final Timeout serverErrorTimeout = new Timeout(Duration.ofSeconds(20));
public JDAConnectionManager(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
@ -293,7 +293,9 @@ public class JDAConnectionManager implements DiscordConnectionManager {
throw new IllegalStateException("Cannot reconnect, still active");
}
return connectionFuture = discordSRV.scheduler().execute(this::connectInternal);
this.connectInternal();
return CompletableFuture.completedFuture(null);
//return connectionFuture = discordSRV.scheduler().execute(this::connectInternal);
}
private void connectInternal() {

View File

@ -45,6 +45,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
@ -511,7 +512,12 @@ public class SingleConsoleHandler {
}
sendFuture = sendFuture
.thenCompose(__ -> discordSRV.destinations().lookupDestination(config.channel.asDestination(), true, true))
.thenCompose(__ -> discordSRV.destinations().lookupDestination(
config.channel.asDestination(),
true,
true,
OffsetDateTime.now())
)
.thenCompose(channels -> {
if (channels.isEmpty()) {
// Nowhere to send to

View File

@ -48,7 +48,8 @@ public class DestinationLookupHelper {
public CompletableFuture<List<DiscordGuildMessageChannel>> lookupDestination(
DestinationConfig config,
boolean allowRequests,
boolean logFailures
boolean logFailures,
Object... threadNameContext
) {
List<CompletableFuture<? extends DiscordGuildMessageChannel>> futures = new ArrayList<>();
@ -90,7 +91,9 @@ public class DestinationLookupHelper {
continue;
}
DiscordThreadChannel existingThread = findThread(threadContainer.getActiveThreads(), threadConfig);
String threadName = discordSRV.placeholderService().replacePlaceholders(threadConfig.threadName, threadNameContext);
DiscordThreadChannel existingThread = findThread(threadContainer.getActiveThreads(), threadName);
if (existingThread != null && !existingThread.isArchived()) {
futures.add(CompletableFuture.completedFuture(existingThread));
continue;
@ -100,7 +103,7 @@ public class DestinationLookupHelper {
continue;
}
String threadKey = Long.toUnsignedString(channelId) + ":" + threadConfig.threadName + "/" + threadConfig.privateThread;
String threadKey = Long.toUnsignedString(channelId) + ":" + threadName + "/" + threadConfig.privateThread;
CompletableFuture<DiscordThreadChannel> future;
synchronized (threadActions) {
@ -110,7 +113,7 @@ public class DestinationLookupHelper {
future = existingFuture;
} else if (!threadConfig.unarchiveExisting) {
// Unarchiving not allowed, create new
future = createThread(threadContainer, threadConfig, logFailures);
future = createThread(threadContainer, threadName, threadConfig.privateThread, logFailures);
} else if (existingThread != null) {
// Unarchive existing thread
future = unarchiveThread(existingThread, logFailures);
@ -122,14 +125,14 @@ public class DestinationLookupHelper {
: threadContainer.retrieveArchivedPublicThreads();
future = threads.thenCompose(archivedThreads -> {
DiscordThreadChannel archivedThread = findThread(archivedThreads, threadConfig);
DiscordThreadChannel archivedThread = findThread(archivedThreads, threadName);
if (archivedThread != null) {
// Unarchive existing thread
return unarchiveThread(archivedThread, logFailures);
}
// Create thread
return createThread(threadContainer, threadConfig, logFailures);
return createThread(threadContainer, threadName, threadConfig.privateThread, logFailures);
}).exceptionally(t -> {
if (logFailures) {
logger.error("Failed to lookup threads in channel #" + threadContainer.getName(), t);
@ -164,9 +167,9 @@ public class DestinationLookupHelper {
});
}
private DiscordThreadChannel findThread(Collection<DiscordThreadChannel> threads, ThreadConfig config) {
private DiscordThreadChannel findThread(Collection<DiscordThreadChannel> threads, String threadName) {
for (DiscordThreadChannel thread : threads) {
if (thread.getName().equals(config.threadName)) {
if (thread.getName().equals(threadName)) {
return thread;
}
}
@ -175,11 +178,12 @@ public class DestinationLookupHelper {
private CompletableFuture<DiscordThreadChannel> createThread(
DiscordThreadContainer threadContainer,
ThreadConfig threadConfig,
String threadName,
boolean privateThread,
boolean logFailures
) {
boolean forum = threadContainer instanceof DiscordForumChannel;
boolean privateThread = !forum && threadConfig.privateThread;
if (forum) privateThread = false;
Permission createPermission;
if (forum) {
@ -196,7 +200,7 @@ public class DestinationLookupHelper {
);
if (missingPermissions != null) {
if (logFailures) {
logger.error("Failed to create thread \"" + threadConfig.threadName + "\" "
logger.error("Failed to create thread \"" + threadName + "\" "
+ "in channel #" + threadContainer.getName() + ": " + missingPermissions);
}
return CompletableFuture.completedFuture(null);
@ -205,15 +209,15 @@ public class DestinationLookupHelper {
CompletableFuture<DiscordThreadChannel> future;
if (forum) {
future = ((DiscordForumChannel) threadContainer).createPost(
threadConfig.threadName,
threadName,
SendableDiscordMessage.builder().setContent("\u200B").build() // zero-width-space
);
} else {
future = threadContainer.createThread(threadConfig.threadName, privateThread);
future = threadContainer.createThread(threadName, privateThread);
}
return future.exceptionally(t -> {
if (logFailures) {
logger.error("Failed to create thread \"" + threadConfig.threadName + "\" "
logger.error("Failed to create thread \"" + threadName + "\" "
+ "in channel #" + threadContainer.getName(), t);
}
return null;

View File

@ -0,0 +1,97 @@
package com.discordsrv.common.helper;
import com.discordsrv.common.DiscordSRV;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.NonBlocking;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
/**
* Data that may or may not actually ever persist.
* @see Model
* @see #get()
* @see #save()
*/
public class TemporaryLocalData {
private final DiscordSRV discordSRV;
private final Path file;
private Model model;
private Future<?> saveFuture;
public TemporaryLocalData(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.file = discordSRV.dataDirectory().resolve(".temporary-local-data.json");
}
@Blocking
public Model get() {
if (model != null) {
return model;
}
synchronized (this) {
return model = resolve();
}
}
@NonBlocking
public void saveLater() {
synchronized (this) {
if (saveFuture != null && !saveFuture.isDone()) {
return;
}
saveFuture = discordSRV.scheduler().runLater(this::save, Duration.ofSeconds(30));
}
}
@Blocking
public void save() {
synchronized (this) {
try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(file))) {
discordSRV.json().writeValue(outputStream, model);
} catch (IOException e) {
discordSRV.logger().error("Failed to save temporary local data", e);
}
}
}
@Blocking
private Model resolve() {
synchronized (this) {
if (model != null) {
return model;
}
if (!Files.exists(file)) {
return new Model();
}
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(file))) {
return discordSRV.json().readValue(inputStream, Model.class);
} catch (IOException e) {
discordSRV.logger().error("Failed to load temporary local data, resetting", e);
return new Model();
}
}
}
/**
* Saved/loaded via {@link DiscordSRV#json()} ({@link com.fasterxml.jackson.databind.ObjectMapper}).
*/
public static class Model {
/**
* {@link com.discordsrv.common.feature.console.SingleConsoleHandler} thread rotation.
*/
public Map<String, List<Long>> consoleThreadRotationIds;
}
}

View File

@ -20,7 +20,7 @@ package com.discordsrv.common.helper;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicLong;
/**
@ -31,8 +31,8 @@ public class Timeout {
private final AtomicLong last = new AtomicLong(0);
private final long timeoutMS;
public Timeout(long time, @NotNull TimeUnit unit) {
this(unit.toMillis(time));
public Timeout(@NotNull Duration duration) {
this(duration.toMillis());
}
public Timeout(long timeoutMS) {