diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/ApplicationLifecycleLogger.java b/src/main/java/dev/kruhlmann/imgfloat/config/ApplicationLifecycleLogger.java new file mode 100644 index 0000000..d8682e9 --- /dev/null +++ b/src/main/java/dev/kruhlmann/imgfloat/config/ApplicationLifecycleLogger.java @@ -0,0 +1,25 @@ +package dev.kruhlmann.imgfloat.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationLifecycleLogger { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationLifecycleLogger.class); + + private final String serverPort; + + public ApplicationLifecycleLogger(@Value("${server.port:8080}") String serverPort) { + this.serverPort = serverPort; + } + + @EventListener(ApplicationReadyEvent.class) + public void logReady() { + LOG.info("Imgfloat ready to accept connections on port {}", serverPort); + } +} diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/TwitchEmoteService.java b/src/main/java/dev/kruhlmann/imgfloat/service/TwitchEmoteService.java index 852ec95..31956a7 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/TwitchEmoteService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/TwitchEmoteService.java @@ -12,7 +12,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -22,6 +24,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; @@ -41,6 +45,7 @@ public class TwitchEmoteService { private final Map emoteCache = new ConcurrentHashMap<>(); private final Map> channelEmoteCache = new ConcurrentHashMap<>(); private volatile List globalEmotes = List.of(); + private final AtomicBoolean initialGlobalSyncScheduled = new AtomicBoolean(); public TwitchEmoteService( RestTemplateBuilder builder, @@ -61,12 +66,11 @@ public class TwitchEmoteService { } catch (IOException ex) { throw new IllegalStateException("Failed to create Twitch emote cache directory", ex); } - warmGlobalEmotes(); } public List getGlobalEmotes() { if (globalEmotes.isEmpty()) { - warmGlobalEmotes(); + ensureInitialGlobalSyncScheduled(); } return globalEmotes.stream().map(CachedEmote::descriptor).toList(); } @@ -127,6 +131,31 @@ public class TwitchEmoteService { LOG.info("Loaded {} global Twitch emotes", cached.size()); } + private void ensureInitialGlobalSyncScheduled() { + if (initialGlobalSyncScheduled.compareAndSet(false, true)) { + LOG.info("Scheduling initial global Twitch emote sync in the background"); + CompletableFuture.runAsync(this::safeWarmGlobalEmotes); + } + } + + private void safeWarmGlobalEmotes() { + LOG.info("Initial global Twitch emote sync started"); + try { + warmGlobalEmotes(); + LOG.info( + "Initial global Twitch emote sync completed (cached {} emotes)", + globalEmotes.size() + ); + } catch (Exception ex) { + LOG.warn("Initial global Twitch emote sync failed", ex); + } + } + + @EventListener(ApplicationReadyEvent.class) + public void startInitialGlobalEmoteSync() { + ensureInitialGlobalSyncScheduled(); + } + private List fetchChannelEmotes(String channelLogin) { String broadcasterId = fetchBroadcasterId(channelLogin).orElse(null); if (broadcasterId == null) {