Setup scheduler

This commit is contained in:
2026-01-14 01:18:39 +01:00
parent 9147479b00
commit 5f8691e1af
9 changed files with 170 additions and 0 deletions

View File

@@ -3,8 +3,10 @@ package dev.kruhlmann.imgfloat;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class ImgfloatApplication {

View File

@@ -0,0 +1,19 @@
package dev.kruhlmann.imgfloat.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler emoteSyncTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(1);
scheduler.setThreadNamePrefix("emote-sync-");
scheduler.initialize();
return scheduler;
}
}

View File

@@ -40,6 +40,9 @@ public class Settings {
@Column(nullable = false)
private int canvasFramesPerSecond;
@Column(nullable = false)
private int emoteSyncIntervalMinutes;
@Column(name = "created_at", nullable = false, updatable = false)
private Instant createdAt;
@@ -58,6 +61,7 @@ public class Settings {
s.setMaxAssetVolumeFraction(5.0);
s.setMaxCanvasSideLengthPixels(7680);
s.setCanvasFramesPerSecond(60);
s.setEmoteSyncIntervalMinutes(60);
return s;
}
@@ -133,6 +137,14 @@ public class Settings {
this.canvasFramesPerSecond = canvasFramesPerSecond;
}
public int getEmoteSyncIntervalMinutes() {
return emoteSyncIntervalMinutes;
}
public void setEmoteSyncIntervalMinutes(int emoteSyncIntervalMinutes) {
this.emoteSyncIntervalMinutes = emoteSyncIntervalMinutes;
}
@PrePersist
public void initializeTimestamps() {
Instant now = Instant.now();

View File

@@ -0,0 +1,81 @@
package dev.kruhlmann.imgfloat.service;
import dev.kruhlmann.imgfloat.model.Channel;
import dev.kruhlmann.imgfloat.model.Settings;
import dev.kruhlmann.imgfloat.repository.ChannelRepository;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Service;
@Service
public class EmoteSyncScheduler implements SchedulingConfigurer {
private static final Logger LOG = LoggerFactory.getLogger(EmoteSyncScheduler.class);
private static final int DEFAULT_INTERVAL_MINUTES = 60;
private final SettingsService settingsService;
private final ChannelRepository channelRepository;
private final TwitchEmoteService twitchEmoteService;
private final SevenTvEmoteService sevenTvEmoteService;
private final TaskScheduler taskScheduler;
public EmoteSyncScheduler(
SettingsService settingsService,
ChannelRepository channelRepository,
TwitchEmoteService twitchEmoteService,
SevenTvEmoteService sevenTvEmoteService,
@Qualifier("emoteSyncTaskScheduler") TaskScheduler taskScheduler
) {
this.settingsService = settingsService;
this.channelRepository = channelRepository;
this.twitchEmoteService = twitchEmoteService;
this.sevenTvEmoteService = sevenTvEmoteService;
this.taskScheduler = taskScheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler);
taskRegistrar.addTriggerTask(this::syncEmotes, buildTrigger());
}
private Trigger buildTrigger() {
return (TriggerContext triggerContext) -> {
Instant lastCompletion = triggerContext.lastCompletionTime() == null
? Instant.now()
: triggerContext.lastCompletionTime().toInstant();
return Date.from(lastCompletion.plus(Duration.ofMinutes(resolveIntervalMinutes())));
};
}
private int resolveIntervalMinutes() {
Settings settings = settingsService.get();
int interval = settings.getEmoteSyncIntervalMinutes();
return interval > 0 ? interval : DEFAULT_INTERVAL_MINUTES;
}
private void syncEmotes() {
int interval = resolveIntervalMinutes();
LOG.info("Synchronizing emotes (interval {} minutes)", interval);
twitchEmoteService.refreshGlobalEmotes();
List<Channel> channels = channelRepository.findAll();
for (Channel channel : channels) {
String broadcaster = channel.getBroadcaster();
twitchEmoteService.refreshChannelEmotes(broadcaster);
sevenTvEmoteService.refreshChannelEmotes(broadcaster);
}
LOG.info("Completed emote sync for {} channels", channels.size());
}
}

View File

@@ -70,6 +70,14 @@ public class SevenTvEmoteService {
return emotes.stream().map(CachedEmote::descriptor).toList();
}
public void refreshChannelEmotes(String channelLogin) {
if (channelLogin == null || channelLogin.isBlank()) {
return;
}
String normalized = channelLogin.toLowerCase(Locale.ROOT);
channelEmoteCache.put(normalized, fetchChannelEmotes(normalized));
}
public Optional<EmoteAsset> loadEmoteAsset(String emoteId) {
if (emoteId == null || emoteId.isBlank()) {
return Optional.empty();

View File

@@ -71,6 +71,10 @@ public class TwitchEmoteService {
return globalEmotes.stream().map(CachedEmote::descriptor).toList();
}
public void refreshGlobalEmotes() {
warmGlobalEmotes();
}
public List<EmoteDescriptor> getChannelEmotes(String channelLogin) {
if (channelLogin == null || channelLogin.isBlank()) {
return List.of();
@@ -80,6 +84,14 @@ public class TwitchEmoteService {
return emotes.stream().map(CachedEmote::descriptor).toList();
}
public void refreshChannelEmotes(String channelLogin) {
if (channelLogin == null || channelLogin.isBlank()) {
return;
}
String normalized = channelLogin.toLowerCase(Locale.ROOT);
channelEmoteCache.put(normalized, fetchChannelEmotes(normalized));
}
public Optional<EmoteAsset> loadEmoteAsset(String emoteId) {
if (emoteId == null || emoteId.isBlank()) {
return Optional.empty();