tdlib-session-container/src/main/java/it/tdlight/reactiveapi/BaseAtomixReactiveApiClient.java

154 lines
6.3 KiB
Java
Raw Normal View History

2022-01-21 22:25:47 +01:00
package it.tdlight.reactiveapi;
2022-05-05 20:25:00 +02:00
import static it.tdlight.reactiveapi.Event.SERIAL_VERSION;
2022-01-21 22:25:47 +01:00
import it.tdlight.jni.TdApi;
2022-06-27 00:06:53 +02:00
import it.tdlight.jni.TdApi.Error;
2022-09-13 22:15:18 +02:00
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Object;
2022-01-21 22:25:47 +01:00
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
2022-05-05 20:25:00 +02:00
import it.tdlight.reactiveapi.Event.Ignored;
2022-01-21 22:25:47 +01:00
import it.tdlight.reactiveapi.Event.OnBotLoginCodeRequested;
import it.tdlight.reactiveapi.Event.OnOtherDeviceLoginRequested;
import it.tdlight.reactiveapi.Event.OnPasswordRequested;
2022-06-27 00:06:53 +02:00
import it.tdlight.reactiveapi.Event.OnRequest;
2022-05-05 20:25:00 +02:00
import it.tdlight.reactiveapi.Event.OnRequest.Request;
2022-06-27 00:06:53 +02:00
import it.tdlight.reactiveapi.Event.OnResponse;
import it.tdlight.reactiveapi.Event.OnResponse.Response;
2022-01-21 22:25:47 +01:00
import it.tdlight.reactiveapi.Event.OnUpdateData;
import it.tdlight.reactiveapi.Event.OnUpdateError;
import it.tdlight.reactiveapi.Event.OnUserLoginCodeRequested;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
2022-01-21 22:25:47 +01:00
import java.io.DataInputStream;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
2022-06-27 00:06:53 +02:00
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
2022-10-07 00:48:10 +02:00
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
2022-05-05 20:25:00 +02:00
import org.jetbrains.annotations.NotNull;
2022-01-22 17:45:56 +01:00
import reactor.core.Disposable;
2022-01-21 22:25:47 +01:00
import reactor.core.publisher.Mono;
2022-10-08 13:44:32 +02:00
import reactor.core.publisher.MonoSink;
import reactor.core.publisher.Sinks;
2022-06-27 00:06:53 +02:00
import reactor.core.publisher.Sinks.EmitFailureHandler;
import reactor.core.publisher.Sinks.Many;
2022-01-21 22:25:47 +01:00
import reactor.core.scheduler.Schedulers;
2022-09-13 22:15:18 +02:00
abstract class BaseAtomixReactiveApiClient implements ReactiveApiMultiClient {
2022-01-22 17:45:56 +01:00
private static final Logger LOG = LogManager.getLogger(BaseAtomixReactiveApiClient.class);
2022-09-13 22:15:18 +02:00
private static final long EMPTY_USER_ID = 0;
2022-01-21 22:25:47 +01:00
2022-06-27 00:06:53 +02:00
// Temporary id used to make requests
private final long clientId;
2022-10-07 00:48:10 +02:00
private final Consumer<OnRequest<?>> requests;
2022-10-08 13:44:32 +02:00
private final Map<Long, MonoSink<Timestamped<OnResponse<Object>>>> responses
2022-06-27 00:06:53 +02:00
= new ConcurrentHashMap<>();
private final AtomicLong requestId = new AtomicLong(0);
2022-06-27 00:32:02 +02:00
private final Disposable subscription;
2022-06-27 00:06:53 +02:00
2022-10-06 00:36:00 +02:00
public BaseAtomixReactiveApiClient(TdlibChannelsSharedReceive sharedTdlibClients) {
2022-06-27 00:06:53 +02:00
this.clientId = System.nanoTime();
2022-10-07 00:48:10 +02:00
this.requests = sharedTdlibClients::emitRequest;
2022-06-27 00:06:53 +02:00
2022-10-05 02:26:30 +02:00
this.subscription = sharedTdlibClients.responses().doOnNext(response -> {
2022-09-13 22:15:18 +02:00
var responseSink = responses.get(response.data().requestId());
if (responseSink == null) {
LOG.debug("Bot received a response for an unknown request id: {}", response.data().requestId());
return;
}
2022-10-08 13:44:32 +02:00
responseSink.success(response);
2022-10-07 00:48:10 +02:00
}).subscribeOn(Schedulers.parallel())
.subscribe(v -> {}, ex -> LOG.error("Reactive api client responses flux has failed unexpectedly!", ex));
2022-01-22 17:45:56 +01:00
}
2022-01-21 22:25:47 +01:00
@Override
2022-09-13 22:15:18 +02:00
public <T extends Object> Mono<T> request(long userId, Function<T> request, Instant timeout) {
2022-06-27 00:06:53 +02:00
return Mono.defer(() -> {
var requestId = this.requestId.getAndIncrement();
var timeoutError = new TdError(408, "Request Timeout");
2022-06-28 00:11:34 +02:00
var requestTimestamp = Instant.now();
var timeoutDuration = Duration.between(requestTimestamp, timeout);
2022-06-27 00:06:53 +02:00
if (timeoutDuration.isNegative() || timeoutDuration.isZero()) {
2022-06-28 00:11:34 +02:00
return Mono.error(timeoutError);
2022-06-27 00:06:53 +02:00
}
2022-10-08 13:44:32 +02:00
Mono<T> response = Mono
.<Timestamped<OnResponse<TdApi.Object>>>create(sink -> {
sink.onDispose(() -> this.responses.remove(requestId, sink));
var prev = this.responses.putIfAbsent(requestId, sink);
if (prev != null) {
sink.error(new IllegalStateException("Can't call the same request twice: " + requestId));
}
})
2022-06-28 00:11:34 +02:00
.timeout(timeoutDuration, Mono.fromSupplier(() -> new Timestamped<>(requestTimestamp.toEpochMilli(),
new Response<>(clientId, requestId, userId, new TdApi.Error(408, "Request Timeout")))))
2022-06-27 00:06:53 +02:00
.<T>handle((responseObj, sink) -> {
if (Instant.ofEpochMilli(responseObj.timestamp()).compareTo(timeout) > 0) {
sink.error(new TdError(408, "Request Timeout"));
} else if (responseObj.data() instanceof OnResponse.InvalidResponse<?>) {
sink.error(new TdError(400, "Conflicting protocol version"));
} else if (responseObj.data() instanceof OnResponse.Response<?> onResponse) {
if (onResponse.response().getConstructor() == Error.CONSTRUCTOR) {
var tdError = (TdApi.Error) onResponse.response();
sink.error(new TdError(tdError.code, tdError.message));
2022-01-21 22:25:47 +01:00
} else {
2022-06-27 00:06:53 +02:00
//noinspection unchecked
var tdResponse = (T) onResponse.response();
sink.next(tdResponse);
2022-01-21 22:25:47 +01:00
}
2022-06-27 00:06:53 +02:00
} else {
sink.error(new UnsupportedOperationException("Unknown response type: " + responseObj.data().getClass()));
}
2022-10-08 13:44:32 +02:00
});
2022-10-07 00:48:10 +02:00
requests.accept(new Request<>(userId, clientId, requestId, request, timeout));
2022-06-27 00:06:53 +02:00
return response;
});
2022-01-21 22:25:47 +01:00
}
static ClientBoundEvent deserializeEvent(byte[] bytes) {
try (var byteArrayInputStream = new ByteArrayInputStream(bytes)) {
try (var is = new DataInputStream(byteArrayInputStream)) {
return deserializeEvent(is);
}
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
static @NotNull ClientBoundEvent deserializeEvent(DataInput is) throws IOException {
2022-01-21 22:25:47 +01:00
var userId = is.readLong();
2022-05-05 20:25:00 +02:00
var dataVersion = is.readInt();
if (dataVersion != SERIAL_VERSION) {
2022-06-27 00:06:53 +02:00
return new Ignored(userId);
2022-05-05 20:25:00 +02:00
}
2022-01-21 22:25:47 +01:00
return switch (is.readByte()) {
2022-06-27 00:06:53 +02:00
case 0x01 -> new OnUpdateData(userId, (TdApi.Update) TdApi.Deserializer.deserialize(is));
case 0x02 -> new OnUpdateError(userId, (TdApi.Error) TdApi.Deserializer.deserialize(is));
case 0x03 -> new OnUserLoginCodeRequested(userId, is.readLong());
case 0x04 -> new OnBotLoginCodeRequested(userId, is.readUTF());
case 0x05 -> new OnOtherDeviceLoginRequested(userId, is.readUTF());
case 0x06 -> new OnPasswordRequested(userId, is.readUTF(), is.readBoolean(), is.readUTF());
2022-10-13 12:40:30 +02:00
case 0x07 -> new Ignored(userId);
2022-01-21 22:25:47 +01:00
default -> throw new IllegalStateException("Unexpected value: " + is.readByte());
};
}
2022-01-22 17:45:56 +01:00
@Override
2022-09-13 22:15:18 +02:00
public Mono<Void> close() {
return Mono.fromRunnable(() -> {
subscription.dispose();
long now = System.currentTimeMillis();
2022-10-08 13:44:32 +02:00
responses.forEach((requestId, cf) -> cf.success(new Timestamped<>(now,
2022-09-13 22:15:18 +02:00
new Response<>(clientId, requestId, EMPTY_USER_ID, new Error(408, "Request Timeout"))
)));
responses.clear();
});
2022-01-22 17:45:56 +01:00
}
2022-01-21 22:25:47 +01:00
}