com.github.twitch4j.eventsub.socket.TwitchSingleUserEventSocketPool Maven / Gradle / Ivy
package com.github.twitch4j.eventsub.socket;
import com.github.philippheuer.credentialmanager.domain.OAuth2Credential;
import com.github.twitch4j.common.pool.TwitchModuleConnectionPool;
import com.github.twitch4j.eventsub.EventSubSubscription;
import com.github.twitch4j.helix.TwitchHelix;
import com.github.twitch4j.helix.TwitchHelixBuilder;
import io.github.xanthic.cache.api.Cache;
import io.github.xanthic.cache.api.domain.ExpiryType;
import io.github.xanthic.cache.core.CacheApi;
import lombok.Builder;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.Synchronized;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.RandomStringUtils;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
/**
* A pool for a single user id to subscriptions spread over multiple EventSub websockets.
*/
@SuperBuilder
public final class TwitchSingleUserEventSocketPool extends TwitchModuleConnectionPool implements IEventSubSocket {
private final String threadPrefix = "twitch4j-unitary-pool-" + RandomStringUtils.random(4, true, true) + "-eventsub-ws-";
/**
* The base url for websocket connections.
*
* @see TwitchEventSocket#WEB_SOCKET_SERVER
*/
@Builder.Default
private String baseUrl = TwitchEventSocket.WEB_SOCKET_SERVER;
/**
* The {@link TwitchHelix} instance for creating eventsub subscriptions in the official API.
*/
@Nullable
@Builder.Default
private TwitchHelix helix = TwitchHelixBuilder.builder().build();
/**
* The default credential (representing the single user) to create eventsub subscriptions with.
*/
@Getter
@Nullable
private OAuth2Credential defaultToken;
/**
* A temporary cache of what credential is used for which eventsub subscription registration request,
* so it can be delivered to the individual underlying socket to be used.
*/
private final Cache credentials = CacheApi.create(spec -> {
spec.maxSize(Runtime.getRuntime().availableProcessors() * 4L);
spec.expiryType(ExpiryType.POST_WRITE);
spec.expiryTime(Duration.ofMinutes(5L));
});
@Override
protected TwitchEventSocket createConnection() {
if (closed.get()) throw new IllegalStateException("EventSocket cannot be created after pool was closed!");
return advancedConfiguration.apply(
TwitchEventSocket.builder()
.api(helix)
.baseUrl(baseUrl)
.defaultToken(defaultToken)
.eventManager(getConnectionEventManager())
.proxyConfig(proxyConfig.get())
.taskExecutor(getExecutor(threadPrefix + RandomStringUtils.random(4, true, true), TwitchEventSocket.REQUIRED_THREAD_COUNT))
).build();
}
@Override
@SneakyThrows
protected void disposeConnection(TwitchEventSocket connection) {
connection.close();
}
@Override
protected EventSubSubscription handleSubscription(TwitchEventSocket twitchEventSocket, EventSubSubscription eventSubSubscription) {
SubscriptionWrapper wrapped = SubscriptionWrapper.wrap(eventSubSubscription);
OAuth2Credential cred = credentials.remove(wrapped);
boolean success = twitchEventSocket.register(cred != null ? cred : defaultToken, wrapped);
if (success) {
return twitchEventSocket.getSubscriptions().stream()
.filter(sub -> sub.equals(wrapped))
.findAny()
.orElse(wrapped);
}
return null;
}
@Override
protected EventSubSubscription handleDuplicateSubscription(TwitchEventSocket twitchEventSocket, TwitchEventSocket old, EventSubSubscription eventSubSubscription) {
return twitchEventSocket != null && twitchEventSocket != old && twitchEventSocket.unregister(eventSubSubscription) ? eventSubSubscription : null;
}
@Override
protected Boolean handleUnsubscription(TwitchEventSocket twitchEventSocket, EventSubSubscription eventSubSubscription) {
return twitchEventSocket != null && twitchEventSocket.unregister(eventSubSubscription);
}
@Override
protected EventSubSubscription getRequestFromSubscription(EventSubSubscription eventSubSubscription) {
return eventSubSubscription;
}
@Override
protected int getSubscriptionSize(EventSubSubscription eventSubSubscription) {
return 1;
}
@Override
@Synchronized
public void connect() {
if (saturatedConnections.isEmpty() && unsaturatedConnections.isEmpty()) {
unsaturatedConnections.put(createConnection(), 0);
}
}
@Override
public void disconnect() {
getConnections().forEach(TwitchEventSocket::disconnect);
}
@Override
public void reconnect() {
getConnections().forEach(TwitchEventSocket::reconnect);
}
@Override
public Collection getSubscriptions() {
return Collections.unmodifiableSet(subscriptions.keySet());
}
@Override
public boolean register(OAuth2Credential token, EventSubSubscription sub) {
SubscriptionWrapper wrapped = SubscriptionWrapper.wrap(sub);
credentials.put(wrapped, token != null ? token : Objects.requireNonNull(defaultToken));
return subscribe(wrapped) != null;
}
@Override
public boolean unregister(EventSubSubscription sub) {
return this.unsubscribe(SubscriptionWrapper.wrap(sub));
}
@Override
public long getLatency() {
long sum = 0;
int count = 0;
for (TwitchEventSocket ws : getConnections()) {
long latency = ws.getLatency();
if (latency >= 0) {
sum += latency;
count++;
}
}
return count > 0 ? sum / count : -1L;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy