From 2dc4a35d9fe27678590268577ec8dd9bb05cb499 Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Sun, 5 Dec 2021 15:15:28 +0100 Subject: [PATCH] Rewrite using atomix --- pom.xml | 163 ++--- src/main/java/it/tdlight/reactiveapi/Cli.java | 104 +++ .../tdlight/reactiveapi/ClusterSettings.java | 21 + .../reactiveapi/CreateSessionRequest.java | 64 ++ .../reactiveapi/CreateSessionResponse.java | 11 + .../it/tdlight/reactiveapi/DiskSession.java | 33 + .../it/tdlight/reactiveapi/DiskSessions.java | 26 + .../reactiveapi/DiskSessionsManager.java | 27 + .../it/tdlight/reactiveapi/Entrypoint.java | 131 ++++ .../java/it/tdlight/reactiveapi/Event.java | 47 ++ .../tdlight/reactiveapi/InstanceSettings.java | 33 + .../it/tdlight/reactiveapi/NodeSettings.java | 18 + .../it/tdlight/reactiveapi/ReactiveApi.java | 199 ++++++ .../reactiveapi/ReactiveApiPublisher.java | 44 ++ .../reactiveapi/ReactiveApiSubscriber.java | 5 + .../reactiveapi/ReactiveApiUpdate.java | 8 + .../reactiveapi/SchedulerExecutor.java | 19 + .../it/tdlight/reactiveapi/TdSerializer.java | 63 ++ .../tdlight/tdlibsession/FatalErrorType.java | 5 - .../tdlight/tdlibsession/SignalMessage.java | 82 --- .../tdlibsession/SignalMessageCodec.java | 89 --- .../it/tdlight/tdlibsession/SignalType.java | 5 - .../tdlight/tdlibsession/VariableWrapper.java | 10 - .../remoteclient/BinlogManager.java | 3 - .../remoteclient/DeployClientResult.java | 7 - .../RemoteClientBotAddresses.java | 55 -- .../remoteclient/SecurityInfo.java | 76 -- .../remoteclient/TDLibRemoteClient.java | 233 ------- .../td/ReactorTelegramClient.java | 18 - .../tdlibsession/td/ResponseError.java | 148 ---- .../it/tdlight/tdlibsession/td/TdError.java | 34 - .../it/tdlight/tdlibsession/td/TdResult.java | 289 -------- .../td/WrappedReactorTelegramClient.java | 92 --- .../tdlibsession/td/direct/AsyncTdDirect.java | 32 - .../td/direct/AsyncTdDirectImpl.java | 109 --- .../td/direct/AsyncTdDirectOptions.java | 36 - .../td/direct/TelegramClientFactory.java | 29 - .../tdlibsession/td/direct/TestClient.java | 156 ----- .../tdlibsession/td/easy/AsyncTdEasy.java | 657 ------------------ .../td/easy/AsyncTdUpdateObj.java | 49 -- .../td/easy/FatalErrorHandler.java | 9 - .../tdlibsession/td/easy/Parameter.java | 9 - .../tdlibsession/td/easy/ParameterInfo.java | 5 - .../td/easy/ParameterInfoCode.java | 47 -- .../td/easy/ParameterInfoEmpty.java | 5 - .../td/easy/ParameterInfoNotifyLink.java | 13 - .../td/easy/ParameterInfoPasswordHint.java | 13 - .../td/easy/ParameterRequestHandler.java | 7 - .../easy/ScannerParameterRequestHandler.java | 47 -- .../tdlibsession/td/easy/TdEasySettings.java | 310 --------- .../tdlibsession/td/middle/AsyncTdMiddle.java | 28 - .../td/middle/EndSessionMessage.java | 49 -- .../td/middle/EndSessionMessageCodec.java | 46 -- .../tdlibsession/td/middle/ExecuteObject.java | 93 --- .../LazyTdExecuteObjectMessageCodec.java | 43 -- .../middle/LazyTdResultListMessageCodec.java | 44 -- .../td/middle/LazyTdResultMessageCodec.java | 44 -- .../td/middle/StartSessionMessage.java | 73 -- .../td/middle/StartSessionMessageCodec.java | 56 -- .../td/middle/TdClusterManager.java | 303 -------- .../middle/TdExecuteObjectMessageCodec.java | 53 -- .../td/middle/TdMessageCodec.java | 57 -- .../tdlibsession/td/middle/TdResultList.java | 93 --- .../td/middle/TdResultListMessageCodec.java | 64 -- .../td/middle/TdResultMessage.java | 57 -- .../td/middle/TdResultMessageCodec.java | 59 -- .../client/AsyncTdMiddleEventBusClient.java | 366 ---------- .../client/NoClustersAvailableException.java | 13 - .../td/middle/direct/AsyncTdMiddleDirect.java | 112 --- .../td/middle/direct/AsyncTdMiddleLocal.java | 100 --- .../server/AsyncTdMiddleEventBusServer.java | 402 ----------- .../td/middle/server/RequestId.java | 19 - .../server/RequestIdToReplyAddress.java | 30 - .../it/tdlight/utils/BinlogAsyncFile.java | 88 --- .../java/it/tdlight/utils/BinlogUtils.java | 128 ---- .../tdlight/utils/BufferTimeOutPublisher.java | 178 ----- .../java/it/tdlight/utils/BufferUtils.java | 94 --- .../java/it/tdlight/utils/EmptyCallable.java | 5 - src/main/java/it/tdlight/utils/MonoUtils.java | 163 ----- .../java/it/tdlight/utils/PromiseSink.java | 49 -- .../java/it/tdlight/utils/TdLightUtils.java | 40 -- .../it/tdlight/utils/TdMoshiPolymorphic.java | 49 -- .../tdlight/utils/VertxBufferInputStream.java | 132 ---- .../utils/VertxBufferOutputStream.java | 59 -- src/main/resources/log4j2.xml | 23 + .../tdlib-session-container-log4j2.xml | 44 -- 86 files changed, 951 insertions(+), 6000 deletions(-) create mode 100644 src/main/java/it/tdlight/reactiveapi/Cli.java create mode 100644 src/main/java/it/tdlight/reactiveapi/ClusterSettings.java create mode 100644 src/main/java/it/tdlight/reactiveapi/CreateSessionRequest.java create mode 100644 src/main/java/it/tdlight/reactiveapi/CreateSessionResponse.java create mode 100644 src/main/java/it/tdlight/reactiveapi/DiskSession.java create mode 100644 src/main/java/it/tdlight/reactiveapi/DiskSessions.java create mode 100644 src/main/java/it/tdlight/reactiveapi/DiskSessionsManager.java create mode 100644 src/main/java/it/tdlight/reactiveapi/Entrypoint.java create mode 100644 src/main/java/it/tdlight/reactiveapi/Event.java create mode 100644 src/main/java/it/tdlight/reactiveapi/InstanceSettings.java create mode 100644 src/main/java/it/tdlight/reactiveapi/NodeSettings.java create mode 100644 src/main/java/it/tdlight/reactiveapi/ReactiveApi.java create mode 100644 src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java create mode 100644 src/main/java/it/tdlight/reactiveapi/ReactiveApiSubscriber.java create mode 100644 src/main/java/it/tdlight/reactiveapi/ReactiveApiUpdate.java create mode 100644 src/main/java/it/tdlight/reactiveapi/SchedulerExecutor.java create mode 100644 src/main/java/it/tdlight/reactiveapi/TdSerializer.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/FatalErrorType.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/SignalMessage.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/SignalMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/SignalType.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/VariableWrapper.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/remoteclient/BinlogManager.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/remoteclient/DeployClientResult.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/remoteclient/RemoteClientBotAddresses.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/remoteclient/SecurityInfo.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/remoteclient/TDLibRemoteClient.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/ReactorTelegramClient.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/ResponseError.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/TdError.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/TdResult.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/WrappedReactorTelegramClient.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirect.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectImpl.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectOptions.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/direct/TelegramClientFactory.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/direct/TestClient.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdEasy.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdUpdateObj.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/FatalErrorHandler.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/Parameter.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfo.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoCode.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoEmpty.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoNotifyLink.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoPasswordHint.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ParameterRequestHandler.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/ScannerParameterRequestHandler.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/easy/TdEasySettings.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/AsyncTdMiddle.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessage.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/ExecuteObject.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdExecuteObjectMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultListMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessage.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdClusterManager.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdExecuteObjectMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdResultList.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdResultListMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessage.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessageCodec.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/client/AsyncTdMiddleEventBusClient.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/client/NoClustersAvailableException.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleDirect.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleLocal.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/server/AsyncTdMiddleEventBusServer.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestId.java delete mode 100644 src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestIdToReplyAddress.java delete mode 100644 src/main/java/it/tdlight/utils/BinlogAsyncFile.java delete mode 100644 src/main/java/it/tdlight/utils/BinlogUtils.java delete mode 100644 src/main/java/it/tdlight/utils/BufferTimeOutPublisher.java delete mode 100644 src/main/java/it/tdlight/utils/BufferUtils.java delete mode 100644 src/main/java/it/tdlight/utils/EmptyCallable.java delete mode 100644 src/main/java/it/tdlight/utils/MonoUtils.java delete mode 100644 src/main/java/it/tdlight/utils/PromiseSink.java delete mode 100644 src/main/java/it/tdlight/utils/TdLightUtils.java delete mode 100644 src/main/java/it/tdlight/utils/TdMoshiPolymorphic.java delete mode 100644 src/main/java/it/tdlight/utils/VertxBufferInputStream.java delete mode 100644 src/main/java/it/tdlight/utils/VertxBufferOutputStream.java create mode 100644 src/main/resources/log4j2.xml delete mode 100644 src/main/resources/tdlib-session-container-log4j2.xml diff --git a/pom.xml b/pom.xml index 7724b67..430f275 100644 --- a/pom.xml +++ b/pom.xml @@ -3,23 +3,16 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 it.tdlight - tdlib-session-container - 5.0.${revision} - TDLib Session Container + tdlib-reactive-api + 6.0.${revision} + TDLib Reactive API UTF-8 0-SNAPSHOT - 11 - 11 - 4.2.0 + 3.0.6 - - protoarch - protoarch - http://home.apache.org/~aajisaka/repository - mchv-release MCHV Release Apache Maven Packages @@ -51,51 +44,68 @@ - - io.vertx - vertx-stack-depchain - ${vertx.version} - pom - import - it.unimi.dsi fastutil 8.5.6 + + io.atomix + atomix + ${atomix.version} + + + io.atomix + atomix-raft + ${atomix.version} + + + io.atomix + atomix-primary-backup + ${atomix.version} + it.tdlight tdlight-java - 2.7.9.4 + 2.7.9.5 + + io.projectreactor + reactor-core + 3.4.12 + + + org.slf4j + slf4j-api + 1.7.32 + + + org.apache.logging.log4j + log4j-core + 2.14.1 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.14.1 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.13.0 + + + net.minecrell + terminalconsoleappender + 1.3.0 + - - io.vertx - vertx-hazelcast - ${vertx.version} - - - io.vertx - vertx-junit5 - ${vertx.version} - test - - - io.vertx - vertx-reactive-streams - ${vertx.version} - - - io.vertx - vertx-rx-java2 - ${vertx.version} - org.junit.jupiter junit-jupiter-api - 5.8.1 + 5.8.2 test @@ -107,48 +117,24 @@ org.junit.jupiter junit-jupiter-engine - 5.8.1 + 5.8.2 test - - io.vertx - vertx-circuit-breaker - ${vertx.version} - io.projectreactor reactor-core - 3.4.11 - - - io.projectreactor - reactor-tools - 3.4.11 - - - io.projectreactor.addons - reactor-adapter - 3.4.5 - - - com.akaita.java - rxjava2-debug - 1.4.0 org.slf4j slf4j-api - 1.7.32 org.apache.logging.log4j log4j-core - 2.14.1 org.apache.logging.log4j log4j-slf4j-impl - 2.14.1 org.warp @@ -158,38 +144,39 @@ io.netty netty-tcnative-boringssl-static - 2.0.45.Final + 2.0.46.Final it.tdlight tdlight-java - - it.cavallium - concurrent-locks - 1.0.8 - - - javax.annotation - javax.annotation-api - 1.3.2 - + + + io.atomix + atomix + + + io.atomix + atomix-raft + + + io.atomix + atomix-primary-backup + it.unimi.dsi fastutil - - com.squareup.moshi - moshi - 1.12.0 - - - dev.zacsweers.moshix - moshi-records-reflect - 0.14.1 - + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + net.minecrell + terminalconsoleappender + @@ -224,7 +211,7 @@ maven-compiler-plugin 3.8.1 - 11 + 17 false diff --git a/src/main/java/it/tdlight/reactiveapi/Cli.java b/src/main/java/it/tdlight/reactiveapi/Cli.java new file mode 100644 index 0000000..89aa951 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/Cli.java @@ -0,0 +1,104 @@ +package it.tdlight.reactiveapi; + +import io.atomix.core.Atomix; +import io.atomix.core.AtomixBuilder; +import it.tdlight.reactiveapi.CreateSessionRequest.CreateBotSessionRequest; +import it.tdlight.reactiveapi.CreateSessionRequest.CreateUserSessionRequest; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import net.minecrell.terminalconsole.SimpleTerminalConsole; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Cli { + + private static final Logger LOG = LoggerFactory.getLogger(Cli.class); + + public static void main(String[] args) throws IOException { + var validArgs = Entrypoint.parseArguments(args); + var atomixBuilder = Atomix.builder(); + var api = Entrypoint.start(validArgs, atomixBuilder); + + AtomicBoolean alreadyShutDown = new AtomicBoolean(false); + AtomicBoolean acceptInputs = new AtomicBoolean(true); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + acceptInputs.set(false); + if (alreadyShutDown.compareAndSet(false, true)) { + api.getAtomix().stop().join(); + } + })); + + var console = new SimpleTerminalConsole() { + + private static final Set commands = Set.of("exit", "stop", "createsession", "help", "man", "?"); + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + var reader = super.buildReader(builder); + reader.addCommandsInBuffer(commands); + return reader; + } + + @Override + protected boolean isRunning() { + return acceptInputs.get(); + } + + @Override + protected void runCommand(String command) { + var parts = command.split(" ", 2); + var commandName = parts[0].trim().toLowerCase(); + String commandArgs; + if (parts.length > 1) { + commandArgs = parts[1].trim(); + } else { + commandArgs = ""; + } + switch (commandName) { + case "exit", "stop" -> acceptInputs.set(false); + case "createsession" -> createSession(api, commandArgs); + case "help", "?", "man" -> LOG.info("Commands: {}", commands); + default -> LOG.info("Unknown command \"{}\"", command); + } + } + + @Override + protected void shutdown() { + acceptInputs.set(false); + if (alreadyShutDown.compareAndSet(false, true)) { + api.getAtomix().stop().join(); + } + } + }; + console.start(); + } + + private static void createSession(ReactiveApi api, String commandArgs) { + var parts = commandArgs.split(" "); + boolean invalid = false; + if (parts.length == 3) { + CreateSessionRequest request = switch (parts[0]) { + case "bot" -> new CreateBotSessionRequest(Long.parseLong(parts[1]), parts[2]); + case "user" -> new CreateUserSessionRequest(Long.parseLong(parts[1]), + Long.parseLong(parts[2])); + default -> { + invalid = true; + yield null; + } + }; + if (!invalid) { + CreateSessionResponse response = api.createSession(request).join(); + LOG.info("Created a session with session id \"{}\"", response.sessionId()); + } + } else { + invalid = true; + } + if (invalid) { + LOG.error("Syntax: CreateSession <\"bot\"|\"user\"> "); + } + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/ClusterSettings.java b/src/main/java/it/tdlight/reactiveapi/ClusterSettings.java new file mode 100644 index 0000000..58352a1 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/ClusterSettings.java @@ -0,0 +1,21 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Define the cluster structure + */ +public class ClusterSettings { + + public String id; + public List nodes; + + @JsonCreator + public ClusterSettings(@JsonProperty(required = true, value = "id") String id, + @JsonProperty(required = true, value = "nodes") List nodes) { + this.id = id; + this.nodes = nodes; + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/CreateSessionRequest.java b/src/main/java/it/tdlight/reactiveapi/CreateSessionRequest.java new file mode 100644 index 0000000..0c0621e --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/CreateSessionRequest.java @@ -0,0 +1,64 @@ +package it.tdlight.reactiveapi; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import it.tdlight.reactiveapi.CreateSessionRequest.CreateBotSessionRequest; +import it.tdlight.reactiveapi.CreateSessionRequest.CreateUserSessionRequest; +import it.tdlight.reactiveapi.CreateSessionRequest.LoadSessionFromDiskRequest; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import org.apache.commons.lang3.SerializationException; + +public sealed interface CreateSessionRequest permits CreateUserSessionRequest, CreateBotSessionRequest, + LoadSessionFromDiskRequest { + + long userId(); + + static CreateSessionRequest deserializeBytes(byte[] bytes) { + byte type = bytes[0]; + long userId = Longs.fromBytes(bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8]); + return switch (type) { + case 0 -> new CreateUserSessionRequest(userId, + Longs.fromBytes(bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16]) + ); + case 1 -> new CreateBotSessionRequest(userId, + new String(bytes, 1 + Long.BYTES + Integer.BYTES, Ints.fromBytes(bytes[9], bytes[10], bytes[11], bytes[12])) + ); + case 2 -> { + var dis = new DataInputStream(new ByteArrayInputStream(bytes, 1 + Long.BYTES, bytes.length - (1 + Long.BYTES))); + try { + var pathName = dis.readUTF(); + var isBot = dis.readBoolean(); + String token; + Long phoneNumber; + if (isBot) { + token = dis.readUTF(); + phoneNumber = null; + } else { + token = null; + phoneNumber = dis.readLong(); + } + yield new LoadSessionFromDiskRequest(userId, pathName, token, phoneNumber); + } catch (IOException e) { + throw new SerializationException(e); + } + } + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } + + final record CreateUserSessionRequest(long userId, long phoneNumber) implements CreateSessionRequest {} + + final record CreateBotSessionRequest(long userId, String token) implements CreateSessionRequest {} + + final record LoadSessionFromDiskRequest(long userId, String pathName, String token, Long phoneNumber) implements + CreateSessionRequest { + + public LoadSessionFromDiskRequest { + if ((token == null) == (phoneNumber == null)) { + throw new IllegalArgumentException("This must be either a bot or an user"); + } + } + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/CreateSessionResponse.java b/src/main/java/it/tdlight/reactiveapi/CreateSessionResponse.java new file mode 100644 index 0000000..1b9c67b --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/CreateSessionResponse.java @@ -0,0 +1,11 @@ +package it.tdlight.reactiveapi; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +public record CreateSessionResponse(long sessionId) { + + public static byte[] serializeBytes(CreateSessionResponse createSessionResponse) { + return Longs.toByteArray(createSessionResponse.sessionId); + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/DiskSession.java b/src/main/java/it/tdlight/reactiveapi/DiskSession.java new file mode 100644 index 0000000..16fd4ab --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/DiskSession.java @@ -0,0 +1,33 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.Nullable; + +@JsonInclude(Include.NON_NULL) +public class DiskSession { + + public long userId; + @Nullable + public String token; + @Nullable + public Long phoneNumber; + + @JsonCreator + public DiskSession(@JsonProperty(required = true, value = "userId") long userId, + @JsonProperty("token") @Nullable String token, + @JsonProperty("phoneNumber") @Nullable Long phoneNumber) { + this.userId = userId; + this.token = token; + this.phoneNumber = phoneNumber; + this.validate(); + } + + public void validate() { + if ((token == null) == (phoneNumber == null)) { + throw new UnsupportedOperationException("You must set either a token or a phone number"); + } + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/DiskSessions.java b/src/main/java/it/tdlight/reactiveapi/DiskSessions.java new file mode 100644 index 0000000..e97a280 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/DiskSessions.java @@ -0,0 +1,26 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public class DiskSessions { + + @NotNull + public String path; + + /** + * key: session folder name + */ + @NotNull + public Map sessions; + + @JsonCreator + public DiskSessions(@JsonProperty(required = true, value = "path") @NotNull String path, + @JsonProperty(required = true, value = "sessions") @NotNull Map sessions) { + this.path = path; + this.sessions = sessions; + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/DiskSessionsManager.java b/src/main/java/it/tdlight/reactiveapi/DiskSessionsManager.java new file mode 100644 index 0000000..55da714 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/DiskSessionsManager.java @@ -0,0 +1,27 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; + +public class DiskSessionsManager { + + private final ObjectMapper mapper; + private final DiskSessions diskSessionsSettings; + private final File file; + + public DiskSessionsManager(ObjectMapper mapper, String diskSessionsConfigPath) throws IOException { + this.mapper = mapper; + this.file = Paths.get(diskSessionsConfigPath).toFile(); + diskSessionsSettings = mapper.readValue(file, DiskSessions.class); + } + + public DiskSessions getSettings() { + return diskSessionsSettings; + } + + public synchronized void save() throws IOException { + mapper.writeValue(file, diskSessionsSettings); + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/Entrypoint.java b/src/main/java/it/tdlight/reactiveapi/Entrypoint.java new file mode 100644 index 0000000..b2f8117 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/Entrypoint.java @@ -0,0 +1,131 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.atomix.cluster.Node; +import io.atomix.cluster.discovery.BootstrapDiscoveryProvider; +import io.atomix.core.Atomix; +import io.atomix.core.AtomixBuilder; +import io.atomix.core.profile.Profile; +import io.atomix.protocols.backup.partition.PrimaryBackupPartitionGroup; +import io.atomix.protocols.raft.partition.RaftPartitionGroup; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Entrypoint { + + private static final Logger LOG = LoggerFactory.getLogger(Entrypoint.class); + + public static record ValidEntrypointArgs(String clusterPath, String instancePath, String diskSessionsPath) {} + + public static ValidEntrypointArgs parseArguments(String[] args) { + // Check arguments validity + if (args.length != 3 + || args[0].isBlank() + || args[1].isBlank() + || args[2].isBlank() + || !Files.isRegularFile(Paths.get(args[0])) + || !Files.isRegularFile(Paths.get(args[1])) + || !Files.isRegularFile(Paths.get(args[2]))) { + System.err.println("Syntax: executable node.id.equals(instanceSettings.id)) + .findAny(); + + // Check node settings presence + if (nodeSettingsOptional.isEmpty()) { + System.err.printf("Node id \"%s\" has not been described in cluster.yaml nodes list%n", instanceSettings.id); + System.exit(2); + } + + var nodeSettings = nodeSettingsOptional.get(); + + atomixBuilder.withMemberId(instanceSettings.id).withAddress(nodeSettings.address); + } + + var bootstrapDiscoveryProviderNodes = new ArrayList(); + List systemPartitionGroupMembers = new ArrayList<>(); + for (NodeSettings node : clusterSettings.nodes) { + bootstrapDiscoveryProviderNodes.add(Node.builder().withId(node.id).withAddress(node.address).build()); + systemPartitionGroupMembers.add(node.id); + } + + var bootstrapDiscoveryProviderBuilder = BootstrapDiscoveryProvider.builder(); + bootstrapDiscoveryProviderBuilder.withNodes(bootstrapDiscoveryProviderNodes).build(); + + atomixBuilder.withMembershipProvider(bootstrapDiscoveryProviderBuilder.build()); + + atomixBuilder.withManagementGroup(RaftPartitionGroup + .builder("system") + .withNumPartitions(1) + .withMembers(systemPartitionGroupMembers) + .build()); + + atomixBuilder.withPartitionGroups(PrimaryBackupPartitionGroup.builder("data").withNumPartitions(32).build()); + + atomixBuilder.withShutdownHook(false); + atomixBuilder.withTypeRegistrationRequired(); + + if (instanceSettings.client) { + atomixBuilder.addProfile(Profile.consensus(systemPartitionGroupMembers)); + atomixBuilder.addProfile(Profile.dataGrid(32)); + } else { + atomixBuilder.addProfile(Profile.client()); + } + + atomixBuilder.withCompatibleSerialization(false); + + var atomix = atomixBuilder.build(); + + TdSerializer.register(atomix.getSerializationService()); + + atomix.start().join(); + + var api = new ReactiveApi(atomix, diskSessions); + + LOG.info("Starting ReactiveApi..."); + + api.start(); + + return api; + } + + public static void main(String[] args) throws IOException { + var validArgs = parseArguments(args); + var atomixBuilder = Atomix.builder().withShutdownHookEnabled(); + start(validArgs, atomixBuilder); + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/Event.java b/src/main/java/it/tdlight/reactiveapi/Event.java new file mode 100644 index 0000000..dde248c --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/Event.java @@ -0,0 +1,47 @@ +package it.tdlight.reactiveapi; + +import it.tdlight.jni.TdApi; +import it.tdlight.reactiveapi.Event.AuthenticatedEvent; + +/** + * Any event received from a session + */ +public sealed interface Event permits AuthenticatedEvent { + + /** + * + * @return temporary unique identifier of the session + */ + long sessionId(); + + /** + * Event received after choosing the user id of the session + */ + sealed interface AuthenticatedEvent extends Event permits OnLoginCodeRequested, OnUpdate { + + /** + * + * @return telegram user id of the session + */ + long userId(); + } + + /** + * TDLib is asking for an authorization code + */ + sealed interface OnLoginCodeRequested extends AuthenticatedEvent + permits OnBotLoginCodeRequested, OnUserLoginCodeRequested {} + + final record OnUserLoginCodeRequested(long sessionId, long userId, long phoneNumber) implements OnLoginCodeRequested {} + + final record OnBotLoginCodeRequested(long sessionId, long userId, String token) implements OnLoginCodeRequested {} + + /** + * Event received from TDLib + */ + sealed interface OnUpdate extends AuthenticatedEvent permits OnUpdateData, OnUpdateError {} + + final record OnUpdateData(long sessionId, long userId, TdApi.Update update) implements OnUpdate {} + + final record OnUpdateError(long sessionId, long userId, TdApi.Error error) implements OnUpdate {} +} diff --git a/src/main/java/it/tdlight/reactiveapi/InstanceSettings.java b/src/main/java/it/tdlight/reactiveapi/InstanceSettings.java new file mode 100644 index 0000000..ac95702 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/InstanceSettings.java @@ -0,0 +1,33 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class InstanceSettings { + + @NotNull + public String id; + + /** + * True if this is just a client, false if this is a complete node + *

+ * A client is a lightweight node + */ + public boolean client; + + /** + * If {@link #client} is true, this will be the address of this client + */ + public @Nullable String clientAddress; + + @JsonCreator + public InstanceSettings(@JsonProperty(required = true, value = "id") @NotNull String id, + @JsonProperty(required = true, value = "client") boolean client, + @JsonProperty("clientAddress") @Nullable String clientAddress) { + this.id = id; + this.client = client; + this.clientAddress = clientAddress; + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/NodeSettings.java b/src/main/java/it/tdlight/reactiveapi/NodeSettings.java new file mode 100644 index 0000000..bc1dc67 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/NodeSettings.java @@ -0,0 +1,18 @@ +package it.tdlight.reactiveapi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import org.jetbrains.annotations.NotNull; + +public class NodeSettings { + + public @NotNull String id; + public @NotNull String address; + + public NodeSettings(@JsonProperty(required = true, value = "id") @NotNull String id, + @JsonProperty(required = true, value = "address") @NotNull String address) { + this.id = id; + this.address = address; + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/ReactiveApi.java b/src/main/java/it/tdlight/reactiveapi/ReactiveApi.java new file mode 100644 index 0000000..ee23a38 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/ReactiveApi.java @@ -0,0 +1,199 @@ +package it.tdlight.reactiveapi; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import io.atomix.core.Atomix; +import io.atomix.core.idgenerator.AsyncAtomicIdGenerator; +import io.atomix.core.lock.AsyncAtomicLock; +import io.atomix.core.map.AsyncAtomicMap; +import it.tdlight.reactiveapi.CreateSessionRequest.CreateBotSessionRequest; +import it.tdlight.reactiveapi.CreateSessionRequest.CreateUserSessionRequest; +import it.tdlight.reactiveapi.CreateSessionRequest.LoadSessionFromDiskRequest; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.scheduler.Schedulers; + +public class ReactiveApi { + + private static final Logger LOG = LoggerFactory.getLogger(ReactiveApi.class); + + private final Atomix atomix; + private static final SchedulerExecutor SCHEDULER_EXECUTOR = new SchedulerExecutor(Schedulers.parallel()); + private static final SchedulerExecutor BOUNDED_ELASTIC_EXECUTOR = new SchedulerExecutor(Schedulers.boundedElastic()); + private final AsyncAtomicIdGenerator nextSessionId; + + private final AsyncAtomicLock sessionModificationLock; + private final AsyncAtomicMap sessionIdToUserId; + private final ConcurrentMap localNodeSessions = new ConcurrentHashMap<>(); + private final DiskSessionsManager diskSessions; + + public ReactiveApi(Atomix atomix, DiskSessionsManager diskSessions) { + this.atomix = atomix; + this.nextSessionId = atomix.getAtomicIdGenerator("session-id").async(); + this.sessionIdToUserId = atomix.getAtomicMap("session-id-to-user-id").async(); + this.sessionModificationLock = atomix.getAtomicLock("session-modification").async(); + this.diskSessions = diskSessions; + } + + public void start() { + CompletableFuture.runAsync(() -> { + List> requests = new ArrayList<>(); + synchronized (diskSessions) { + for (Entry entry : diskSessions.getSettings().sessions.entrySet()) { + try { + entry.getValue().validate(); + } catch (Throwable ex) { + LOG.error("Failed to load disk session {}", entry.getKey(), ex); + } + var sessionFolderName = entry.getKey(); + var diskSession = entry.getValue(); + requests.add(createSession(new LoadSessionFromDiskRequest(diskSession.userId, + sessionFolderName, + diskSession.token, + diskSession.phoneNumber + ))); + } + } + CompletableFuture + .allOf(requests.toArray(CompletableFuture[]::new)) + .thenAccept(responses -> LOG.info("Loaded all saved sessions from disk")); + }, BOUNDED_ELASTIC_EXECUTOR); + + // Listen for create-session signals + atomix.getEventService().subscribe("create-session", CreateSessionRequest::deserializeBytes, req -> { + if (req instanceof LoadSessionFromDiskRequest) { + return failedFuture(new IllegalArgumentException("Can't pass a local request through the cluster")); + } else { + return createSession(req); + } + }, CreateSessionResponse::serializeBytes); + } + + public CompletableFuture createSession(CreateSessionRequest req) { + // Lock sessions creation + return sessionModificationLock.lock().thenCompose(lockVersion -> { + // Generate session id + return this.nextFreeId().thenCompose(sessionId -> { + // Create the session instance + ReactiveApiPublisher reactiveApiPublisher; + boolean loadedFromDisk; + long userId; + String botToken; + Long phoneNumber; + if (req instanceof CreateBotSessionRequest createBotSessionRequest) { + loadedFromDisk = false; + userId = createBotSessionRequest.userId(); + botToken = createBotSessionRequest.token(); + phoneNumber = null; + reactiveApiPublisher = ReactiveApiPublisher.fromToken(atomix, sessionId, userId, botToken); + } else if (req instanceof CreateUserSessionRequest createUserSessionRequest) { + loadedFromDisk = false; + userId = createUserSessionRequest.userId(); + botToken = null; + phoneNumber = createUserSessionRequest.phoneNumber(); + reactiveApiPublisher = ReactiveApiPublisher.fromPhoneNumber(atomix, sessionId, userId, phoneNumber); + } else if (req instanceof LoadSessionFromDiskRequest loadSessionFromDiskRequest) { + loadedFromDisk = true; + userId = loadSessionFromDiskRequest.userId(); + botToken = loadSessionFromDiskRequest.token(); + phoneNumber = loadSessionFromDiskRequest.phoneNumber(); + if (loadSessionFromDiskRequest.phoneNumber() != null) { + reactiveApiPublisher = ReactiveApiPublisher.fromPhoneNumber(atomix, sessionId, userId, phoneNumber); + } else { + reactiveApiPublisher = ReactiveApiPublisher.fromToken(atomix, sessionId, userId, botToken); + } + } else { + return failedFuture(new UnsupportedOperationException("Unexpected value: " + req)); + } + + // Register the session instance to the local nodes map + var prev = localNodeSessions.put(sessionId, reactiveApiPublisher); + if (prev != null) { + LOG.error("Session id \"{}\" was already registered locally!", sessionId); + } + + // Register the session instance to the distributed nodes map + return sessionIdToUserId.put(sessionId, req.userId()).thenComposeAsync(prevDistributed -> { + if (prevDistributed != null) { + LOG.error("Session id \"{}\" was already registered in the cluster!", sessionId); + } + + CompletableFuture saveToDiskFuture; + if (!loadedFromDisk) { + // Load existing session paths + HashSet alreadyExistingPaths = new HashSet<>(); + synchronized (diskSessions) { + for (var entry : diskSessions.getSettings().sessions.entrySet()) { + var path = entry.getKey(); + var diskSessionSettings = entry.getValue(); + if (diskSessionSettings.userId == userId) { + LOG.warn("User id \"{}\" session already exists in path: \"{}\"", userId, path); + } + alreadyExistingPaths.add(entry.getKey()); + } + } + + // Get a new disk session folder name + String diskSessionFolderName; + do { + diskSessionFolderName = UUID.randomUUID().toString(); + } while (alreadyExistingPaths.contains(diskSessionFolderName)); + + // Create the disk session configuration + var diskSession = new DiskSession(userId, botToken, phoneNumber); + Path path; + synchronized (diskSessions) { + diskSessions.getSettings().sessions.put(diskSessionFolderName, diskSession); + path = Paths.get(diskSessions.getSettings().path).resolve(diskSessionFolderName); + } + + // Start the session instance + reactiveApiPublisher.start(path); + + saveToDiskFuture = CompletableFuture.runAsync(() -> { + // Save updated sessions configuration to disk + try { + synchronized (diskSessions) { + diskSessions.save(); + } + } catch (IOException e) { + throw new CompletionException("Failed to save disk sessions configuration", e); + } + }, BOUNDED_ELASTIC_EXECUTOR); + } else { + saveToDiskFuture = completedFuture(null); + } + + return saveToDiskFuture.thenApply(ignored -> new CreateSessionResponse(sessionId)); + }, BOUNDED_ELASTIC_EXECUTOR); + }); + }); + } + + public CompletableFuture nextFreeId() { + return nextSessionId.nextId().thenCompose(id -> sessionIdToUserId.containsKey(id).thenCompose(exists -> { + if (exists) { + return nextFreeId(); + } else { + return completedFuture(id); + } + })); + } + + public Atomix getAtomix() { + return atomix; + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java b/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java new file mode 100644 index 0000000..c081cf9 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java @@ -0,0 +1,44 @@ +package it.tdlight.reactiveapi; + +import io.atomix.core.Atomix; +import it.tdlight.jni.TdApi; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.Executors; +import org.apache.commons.lang3.SerializationException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink.OverflowStrategy; +import reactor.core.scheduler.Schedulers; + +public class ReactiveApiPublisher { + + private static final SchedulerExecutor SCHEDULER_EXECUTOR = new SchedulerExecutor(Schedulers.boundedElastic()); + + private final Atomix atomix; + private final long userId; + private final long sessionId; + private final String botToken; + private final Long phoneNumber; + + private ReactiveApiPublisher(Atomix atomix, long sessionId, long userId, String botToken, Long phoneNumber) { + this.atomix = atomix; + this.userId = userId; + this.sessionId = sessionId; + this.botToken = botToken; + this.phoneNumber = phoneNumber; + } + + public static ReactiveApiPublisher fromToken(Atomix atomix, Long sessionId, long userId, String token) { + return new ReactiveApiPublisher(atomix, sessionId, userId, token, null); + } + + public static ReactiveApiPublisher fromPhoneNumber(Atomix atomix, Long sessionId, long userId, long phoneNumber) { + return new ReactiveApiPublisher(atomix, sessionId, userId, null, phoneNumber); + } + + public void start(Path path) { + + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/ReactiveApiSubscriber.java b/src/main/java/it/tdlight/reactiveapi/ReactiveApiSubscriber.java new file mode 100644 index 0000000..88045d9 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/ReactiveApiSubscriber.java @@ -0,0 +1,5 @@ +package it.tdlight.reactiveapi; + +public class ReactiveApiSubscriber { + +} diff --git a/src/main/java/it/tdlight/reactiveapi/ReactiveApiUpdate.java b/src/main/java/it/tdlight/reactiveapi/ReactiveApiUpdate.java new file mode 100644 index 0000000..914c1d4 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/ReactiveApiUpdate.java @@ -0,0 +1,8 @@ +package it.tdlight.reactiveapi; + +import it.tdlight.jni.TdApi; + +/** + * {@link #sessionUuid} changes every time a session is restarted + */ +public record ReactiveApiUpdate(long sessionUuid, TdApi.Object update) {} diff --git a/src/main/java/it/tdlight/reactiveapi/SchedulerExecutor.java b/src/main/java/it/tdlight/reactiveapi/SchedulerExecutor.java new file mode 100644 index 0000000..1bc8140 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/SchedulerExecutor.java @@ -0,0 +1,19 @@ +package it.tdlight.reactiveapi; + +import java.util.concurrent.Executor; +import org.jetbrains.annotations.NotNull; +import reactor.core.scheduler.Scheduler; + +public class SchedulerExecutor implements Executor { + + private final Scheduler scheduler; + + public SchedulerExecutor(Scheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public void execute(@NotNull Runnable command) { + scheduler.schedule(command); + } +} diff --git a/src/main/java/it/tdlight/reactiveapi/TdSerializer.java b/src/main/java/it/tdlight/reactiveapi/TdSerializer.java new file mode 100644 index 0000000..e3a5118 --- /dev/null +++ b/src/main/java/it/tdlight/reactiveapi/TdSerializer.java @@ -0,0 +1,63 @@ +package it.tdlight.reactiveapi; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.KryoDataInput; +import com.esotericsoftware.kryo.io.KryoDataOutput; +import com.esotericsoftware.kryo.io.Output; +import io.atomix.primitive.serialization.SerializationService; +import it.tdlight.common.ConstructorDetector; +import it.tdlight.jni.TdApi; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.stream.Stream; +import org.apache.commons.lang3.SerializationException; + +public class TdSerializer extends Serializer { + + private TdSerializer() { + + } + + public static void register(SerializationService serializationService) { + var serializerBuilder = serializationService.newBuilder("TdApi"); + var tdApiClasses = TdApi.class.getDeclaredClasses(); + // Add types + Class[] classes = Stream + .of(tdApiClasses) + .filter(clazz -> clazz.isAssignableFrom(TdApi.Object.class)) + .toArray(Class[]::new); + + serializerBuilder.addSerializer(new TdSerializer(), classes); + } + + @Override + public void write(Kryo kryo, Output output, TdApi.Object object) { + try { + object.serialize(new KryoDataOutput(output)); + } catch (IOException e) { + throw new SerializationException(e); + } + } + + @Override + public TdApi.Object read(Kryo kryo, Input input, Class type) { + try { + return TdApi.Deserializer.deserialize(new KryoDataInput(input)); + } catch (IOException e) { + throw new SerializationException(e); + } + } + + public static TdApi.Object deserializeBytes(byte[] bytes) { + var din = new DataInputStream(new ByteArrayInputStream(bytes)); + try { + return TdApi.Deserializer.deserialize(din); + } catch (IOException e) { + throw new SerializationException(e); + } + } +} diff --git a/src/main/java/it/tdlight/tdlibsession/FatalErrorType.java b/src/main/java/it/tdlight/tdlibsession/FatalErrorType.java deleted file mode 100644 index 9bb2bdf..0000000 --- a/src/main/java/it/tdlight/tdlibsession/FatalErrorType.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.tdlight.tdlibsession; - -public enum FatalErrorType { - ACCESS_TOKEN_INVALID, PHONE_NUMBER_INVALID, CONNECTION_KILLED, INVALID_UPDATE, PHONE_NUMBER_BANNED -} diff --git a/src/main/java/it/tdlight/tdlibsession/SignalMessage.java b/src/main/java/it/tdlight/tdlibsession/SignalMessage.java deleted file mode 100644 index 78d31b9..0000000 --- a/src/main/java/it/tdlight/tdlibsession/SignalMessage.java +++ /dev/null @@ -1,82 +0,0 @@ -package it.tdlight.tdlibsession; - -import java.util.Objects; -import java.util.StringJoiner; - -class SignalMessage { - - private final SignalType signalType; - private final T item; - private final String errorMessage; - - private SignalMessage(SignalType signalType, T item, String errorMessage) { - this.signalType = signalType; - this.item = item; - this.errorMessage = errorMessage; - } - - public static SignalMessage onNext(T item) { - return new SignalMessage<>(SignalType.ITEM, Objects.requireNonNull(item), null); - } - - public static SignalMessage onError(Throwable throwable) { - return new SignalMessage(SignalType.ERROR, null, Objects.requireNonNull(throwable.getMessage())); - } - - static SignalMessage onDecodedError(String throwable) { - return new SignalMessage(SignalType.ERROR, null, Objects.requireNonNull(throwable)); - } - - public static SignalMessage onComplete() { - return new SignalMessage(SignalType.COMPLETE, null, null); - } - - public SignalType getSignalType() { - return signalType; - } - - public String getErrorMessage() { - return Objects.requireNonNull(errorMessage); - } - - public T getItem() { - return Objects.requireNonNull(item); - } - - @Override - public String toString() { - return new StringJoiner(", ", SignalMessage.class.getSimpleName() + "[", "]") - .add("signalType=" + signalType) - .add("item=" + item) - .add("errorMessage='" + errorMessage + "'") - .toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - SignalMessage that = (SignalMessage) o; - - if (signalType != that.signalType) { - return false; - } - if (!Objects.equals(item, that.item)) { - return false; - } - return Objects.equals(errorMessage, that.errorMessage); - } - - @Override - public int hashCode() { - int result = signalType != null ? signalType.hashCode() : 0; - result = 31 * result + (item != null ? item.hashCode() : 0); - result = 31 * result + (errorMessage != null ? errorMessage.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/SignalMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/SignalMessageCodec.java deleted file mode 100644 index 7af66f6..0000000 --- a/src/main/java/it/tdlight/tdlibsession/SignalMessageCodec.java +++ /dev/null @@ -1,89 +0,0 @@ -package it.tdlight.tdlibsession; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.utils.VertxBufferInputStream; -import it.tdlight.utils.VertxBufferOutputStream; -import java.nio.charset.StandardCharsets; -import org.warp.commonutils.stream.SafeDataInputStream; -import org.warp.commonutils.stream.SafeDataOutputStream; - -public class SignalMessageCodec implements MessageCodec, SignalMessage> { - - private final String codecName; - private final MessageCodec typeCodec; - - public SignalMessageCodec(MessageCodec typeCodec) { - super(); - this.codecName = "SignalCodec-" + typeCodec.name(); - this.typeCodec = typeCodec; - } - - @Override - public void encodeToWire(Buffer buffer, SignalMessage t) { - try (var bos = new VertxBufferOutputStream(buffer)) { - try (var dos = new SafeDataOutputStream(bos)) { - switch (t.getSignalType()) { - case ITEM: - dos.writeByte(0x01); - break; - case ERROR: - dos.writeByte(0x02); - break; - case COMPLETE: - dos.writeByte(0x03); - break; - default: - throw new IllegalStateException("Unexpected value: " + t.getSignalType()); - } - } - switch (t.getSignalType()) { - case ITEM: - typeCodec.encodeToWire(buffer, t.getItem()); - break; - case ERROR: - var stringBytes = t.getErrorMessage().getBytes(StandardCharsets.UTF_8); - buffer.appendInt(stringBytes.length); - buffer.appendBytes(stringBytes); - break; - } - } - } - - @Override - public SignalMessage decodeFromWire(int pos, Buffer buffer) { - try (var fis = new VertxBufferInputStream(buffer, pos)) { - try (var dis = new SafeDataInputStream(fis)) { - switch (dis.readByte()) { - case 0x01: - return SignalMessage.onNext(typeCodec.decodeFromWire(pos + 1, buffer)); - case 0x02: - var size = dis.readInt(); - return SignalMessage.onDecodedError(new String(dis.readNBytes(size), StandardCharsets.UTF_8)); - case 0x03: - return SignalMessage.onComplete(); - default: - throw new IllegalStateException("Unexpected value: " + dis.readByte()); - } - } - } - } - - @Override - public SignalMessage transform(SignalMessage t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/SignalType.java b/src/main/java/it/tdlight/tdlibsession/SignalType.java deleted file mode 100644 index 3492115..0000000 --- a/src/main/java/it/tdlight/tdlibsession/SignalType.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.tdlight.tdlibsession; - -enum SignalType { - COMPLETE, ERROR, ITEM -} diff --git a/src/main/java/it/tdlight/tdlibsession/VariableWrapper.java b/src/main/java/it/tdlight/tdlibsession/VariableWrapper.java deleted file mode 100644 index 1d510c1..0000000 --- a/src/main/java/it/tdlight/tdlibsession/VariableWrapper.java +++ /dev/null @@ -1,10 +0,0 @@ -package it.tdlight.tdlibsession; - -public class VariableWrapper { - - public volatile T var; - - public VariableWrapper(T value) { - this.var = value; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/remoteclient/BinlogManager.java b/src/main/java/it/tdlight/tdlibsession/remoteclient/BinlogManager.java deleted file mode 100644 index ca23615..0000000 --- a/src/main/java/it/tdlight/tdlibsession/remoteclient/BinlogManager.java +++ /dev/null @@ -1,3 +0,0 @@ -package it.tdlight.tdlibsession.remoteclient; - -public class BinlogManager {} diff --git a/src/main/java/it/tdlight/tdlibsession/remoteclient/DeployClientResult.java b/src/main/java/it/tdlight/tdlibsession/remoteclient/DeployClientResult.java deleted file mode 100644 index b4fd4d7..0000000 --- a/src/main/java/it/tdlight/tdlibsession/remoteclient/DeployClientResult.java +++ /dev/null @@ -1,7 +0,0 @@ -package it.tdlight.tdlibsession.remoteclient; - -public enum DeployClientResult { - DEPLOYED, - IGNORED, - FAILED -} diff --git a/src/main/java/it/tdlight/tdlibsession/remoteclient/RemoteClientBotAddresses.java b/src/main/java/it/tdlight/tdlibsession/remoteclient/RemoteClientBotAddresses.java deleted file mode 100644 index af1ded3..0000000 --- a/src/main/java/it/tdlight/tdlibsession/remoteclient/RemoteClientBotAddresses.java +++ /dev/null @@ -1,55 +0,0 @@ -package it.tdlight.tdlibsession.remoteclient; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.stream.Collectors; - -public class RemoteClientBotAddresses { - - private final LinkedHashSet addresses; - private final LinkedHashSet tempAddresses; - private final Path addressesFilePath; - - public RemoteClientBotAddresses(Path addressesFilePath) throws IOException { - this.addressesFilePath = addressesFilePath; - if (Files.notExists(addressesFilePath)) { - Files.createFile(addressesFilePath); - } - tempAddresses = new LinkedHashSet<>(); - addresses = Files - .readAllLines(addressesFilePath, StandardCharsets.UTF_8) - .stream() - .filter(address -> !address.isBlank()) - .collect(Collectors.toCollection(LinkedHashSet::new)); - } - - public synchronized void putAddress(String address) throws IOException { - tempAddresses.remove(address); - addresses.add(address); - Files.write(addressesFilePath, addresses, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.SYNC); - } - - public synchronized void putTempAddress(String address) { - tempAddresses.add(address); - } - - public synchronized void removeAddress(String address) throws IOException { - tempAddresses.remove(address); - addresses.remove(address); - Files.write(addressesFilePath, addresses, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.SYNC); - } - - public synchronized boolean has(String botAddress) { - return addresses.contains(botAddress) || tempAddresses.contains(botAddress); - } - - public synchronized Set values() { - return new HashSet<>(addresses); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/remoteclient/SecurityInfo.java b/src/main/java/it/tdlight/tdlibsession/remoteclient/SecurityInfo.java deleted file mode 100644 index bc5a4ea..0000000 --- a/src/main/java/it/tdlight/tdlibsession/remoteclient/SecurityInfo.java +++ /dev/null @@ -1,76 +0,0 @@ -package it.tdlight.tdlibsession.remoteclient; - -import io.vertx.core.file.FileSystemException; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.NoSuchElementException; -import java.util.StringJoiner; - -public class SecurityInfo { - - private final Path keyStorePath; - private final Path keyStorePasswordPath; - private final Path trustStorePath; - private final Path trustStorePasswordPath; - - public SecurityInfo(Path keyStorePath, Path keyStorePasswordPath, Path trustStorePath, Path trustStorePasswordPath) { - this.keyStorePath = keyStorePath; - this.keyStorePasswordPath = keyStorePasswordPath; - this.trustStorePath = trustStorePath; - this.trustStorePasswordPath = trustStorePasswordPath; - } - - public Path getKeyStorePath() { - return keyStorePath; - } - - public Path getKeyStorePasswordPath() { - return keyStorePasswordPath; - } - - public String getKeyStorePassword(boolean required) { - try { - if (Files.isReadable(keyStorePasswordPath) && Files.size(keyStorePasswordPath) >= 6) { - return Files.readString(keyStorePasswordPath, StandardCharsets.UTF_8).split("\n")[0]; - } else if (required) { - throw new NoSuchElementException("No keystore password is set on '" + keyStorePasswordPath.toString() + "'"); - } - } catch (IOException ex) { - throw new FileSystemException(ex); - } - return null; - } - - public Path getTrustStorePath() { - return trustStorePath; - } - - public Path getTrustStorePasswordPath() { - return trustStorePasswordPath; - } - - public String getTrustStorePassword(boolean required) { - try { - if (Files.isReadable(trustStorePasswordPath) && Files.size(trustStorePasswordPath) >= 6) { - return Files.readString(trustStorePasswordPath, StandardCharsets.UTF_8).split("\n")[0]; - } else if (required) { - throw new NoSuchElementException("No truststore password is set on '" + trustStorePasswordPath.toString() + "'"); - } - } catch (IOException ex) { - throw new FileSystemException(ex); - } - return null; - } - - @Override - public String toString() { - return new StringJoiner(", ", SecurityInfo.class.getSimpleName() + "[", "]") - .add("keyStorePath=" + keyStorePath) - .add("keyStorePasswordPath=" + keyStorePasswordPath) - .add("trustStorePath=" + trustStorePath) - .add("trustStorePasswordPath=" + trustStorePasswordPath) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/remoteclient/TDLibRemoteClient.java b/src/main/java/it/tdlight/tdlibsession/remoteclient/TDLibRemoteClient.java deleted file mode 100644 index cedd4c4..0000000 --- a/src/main/java/it/tdlight/tdlibsession/remoteclient/TDLibRemoteClient.java +++ /dev/null @@ -1,233 +0,0 @@ -package it.tdlight.tdlibsession.remoteclient; - -import com.akaita.java.rxjava2debug.RxJava2Debug; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.json.JsonObject; -import io.vertx.core.net.JksOptions; -import io.vertx.reactivex.core.eventbus.Message; -import io.vertx.reactivex.core.eventbus.MessageConsumer; -import it.tdlight.common.Init; -import it.tdlight.common.Log; -import it.tdlight.common.utils.CantLoadLibrary; -import it.tdlight.tdlibsession.td.middle.StartSessionMessage; -import it.tdlight.tdlibsession.td.middle.TdClusterManager; -import it.tdlight.tdlibsession.td.middle.server.AsyncTdMiddleEventBusServer; -import it.tdlight.tdnative.NativeLog; -import it.tdlight.utils.BinlogUtils; -import it.tdlight.utils.MonoUtils; -import java.net.URISyntaxException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.logging.log4j.LogManager; -import org.jetbrains.annotations.Nullable; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.One; -import reactor.core.scheduler.Schedulers; -import reactor.tools.agent.ReactorDebugAgent; - -public class TDLibRemoteClient implements AutoCloseable { - - private static final Logger logger = LoggerFactory.getLogger(TDLibRemoteClient.class); - - @Nullable - private final SecurityInfo securityInfo; - private final String masterHostname; - private final String netInterface; - private final int port; - private final Set membersAddresses; - private final AtomicReference clusterManager = new AtomicReference<>(); - - public TDLibRemoteClient(@Nullable SecurityInfo securityInfo, - String masterHostname, - String netInterface, - int port, - Set membersAddresses, - boolean enableAsyncStacktraces, - boolean enableFullAsyncStacktraces) { - this.securityInfo = securityInfo; - this.masterHostname = masterHostname; - this.netInterface = netInterface; - this.port = port; - this.membersAddresses = membersAddresses; - - if (enableAsyncStacktraces && enableFullAsyncStacktraces) { - RxJava2Debug.enableRxJava2AssemblyTracking(new String[]{"it.tdlight.utils", "it.tdlight.tdlibsession"}); - } - - try { - Init.start(); - //noinspection deprecation - Log.setVerbosityLevel(2); - } catch (CantLoadLibrary ex) { - throw new RuntimeException(ex); - } - } - - public static void main(String[] args) throws URISyntaxException { - if (args.length < 1) { - return; - } - - String masterHostname = args[0]; - - String[] interfaceAndPort = args[1].split(":", 2); - - String netInterface = interfaceAndPort[0]; - - int port = Integer.parseInt(interfaceAndPort[1]); - - Set membersAddresses = Set.of(args[2].split(",")); - - Path keyStorePath = Paths.get(args[3]); - Path keyStorePasswordPath = Paths.get(args[4]); - Path trustStorePath = Paths.get(args[5]); - Path trustStorePasswordPath = Paths.get(args[6]); - boolean enableAsyncStacktraces = Boolean.parseBoolean(args[7]); - boolean enableFullAsyncStacktraces = Boolean.parseBoolean(args[8]); - - var loggerContext = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false); - loggerContext.setConfigLocation(Objects - .requireNonNull(TDLibRemoteClient.class.getResource("/tdlib-session-container-log4j2.xml"), - "tdlib-session-container-log4j2.xml doesn't exist") - .toURI()); - - var securityInfo = new SecurityInfo(keyStorePath, keyStorePasswordPath, trustStorePath, trustStorePasswordPath); - - var client = new TDLibRemoteClient(securityInfo, - masterHostname, - netInterface, - port, - membersAddresses, - enableAsyncStacktraces, - enableFullAsyncStacktraces - ); - - client - .start() - .block(); - - // Close vert.x on shutdown - var vertxMono = Mono.fromCallable(() -> client.clusterManager.get().getVertx()); - Runtime - .getRuntime() - .addShutdownHook(new Thread(() -> vertxMono - .flatMap(vertx -> MonoUtils.toMono(vertx.rxClose())) - .blockOptional()) - ); - } - - public Mono start() { - var ksp = securityInfo == null ? null : securityInfo.getKeyStorePassword(false); - var keyStoreOptions = securityInfo == null || ksp == null ? null : new JksOptions() - .setPath(securityInfo.getKeyStorePath().toAbsolutePath().toString()) - .setPassword(ksp); - - var tsp = securityInfo == null ? null : securityInfo.getTrustStorePassword(false); - var trustStoreOptions = securityInfo == null || tsp == null ? null : new JksOptions() - .setPath(securityInfo.getTrustStorePath().toAbsolutePath().toString()) - .setPassword(tsp); - - return MonoUtils - .fromBlockingEmpty(() -> { - // Set verbosity level here, before creating the bots - if (Files.notExists(Paths.get("logs"))) { - try { - Files.createDirectory(Paths.get("logs")); - } catch (FileAlreadyExistsException ignored) { - } - } - - logger.info( - "TDLib remote client is being hosted on" + netInterface + ":" + port + ". Master: " + masterHostname); - logger.info( - "TDLib remote client SSL enabled: " + (keyStoreOptions != null && trustStoreOptions != null)); - }) - .then(TdClusterManager.ofNodes(keyStoreOptions, - trustStoreOptions, - false, - masterHostname, - netInterface, - port, - membersAddresses - )) - .doOnSuccess(clusterManager::set) - .single() - .doOnError(ex -> logger.error("Failed to set cluster manager", ex)) - .flatMap(clusterManager -> { - MessageConsumer startBotConsumer - = clusterManager.getEventBus().consumer("bots.start-bot"); - - return this.listenForStartBotsCommand( - clusterManager, - MonoUtils.fromReplyableMessageConsumer(Mono.empty(), startBotConsumer) - ); - }) - .then(); - } - - private Mono listenForStartBotsCommand(TdClusterManager clusterManager, - Flux> messages) { - return MonoUtils - .fromBlockingEmpty(() -> messages - .flatMapSequential(msg -> { - StartSessionMessage req = msg.body(); - DeploymentOptions deploymentOptions = clusterManager - .newDeploymentOpts() - .setConfig(new JsonObject() - .put("botId", req.id()) - .put("botAlias", req.alias()) - .put("local", false) - .put("implementationDetails", req.implementationDetails())); - var verticle = new AsyncTdMiddleEventBusServer(); - - // Binlog path - var sessPath = getSessionDirectory(req.id()); - var mediaPath = getMediaDirectory(req.id()); - var blPath = getSessionBinlogDirectory(req.id()); - - return BinlogUtils - .chooseBinlog(clusterManager.getVertx().fileSystem(), blPath, req.binlog(), req.binlogDate()) - .then(BinlogUtils.cleanSessionPath(clusterManager.getVertx().fileSystem(), blPath, sessPath, mediaPath)) - .then(clusterManager.getVertx().rxDeployVerticle(verticle, deploymentOptions).as(MonoUtils::toMono)) - .then(MonoUtils.fromBlockingEmpty(() -> msg.reply(new byte[0]))) - .onErrorResume(ex -> { - msg.fail(500, "Failed to deploy bot verticle: " + ex.getMessage()); - logger.error("Failed to deploy bot verticle", ex); - return Mono.empty(); - }); - }) - .subscribeOn(Schedulers.parallel()) - .subscribe( - v -> {}, - ex -> logger.error("Bots starter activity crashed. From now on, no new bots can be started anymore", ex) - ) - ); - } - - public static Path getSessionDirectory(long botId) { - return Paths.get(".sessions-cache").resolve("id" + botId); - } - - public static Path getMediaDirectory(long botId) { - return Paths.get(".cache").resolve("media").resolve("id" + botId); - } - - public static Path getSessionBinlogDirectory(long botId) { - return getSessionDirectory(botId).resolve("td.binlog"); - } - - @Override - public void close() { - this.clusterManager.get().getVertx().rxClose().blockingAwait(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/ReactorTelegramClient.java b/src/main/java/it/tdlight/tdlibsession/td/ReactorTelegramClient.java deleted file mode 100644 index a4f1612..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/ReactorTelegramClient.java +++ /dev/null @@ -1,18 +0,0 @@ -package it.tdlight.tdlibsession.td; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Object; -import java.time.Duration; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public interface ReactorTelegramClient { - - Mono initialize(); - - Flux receive(); - - Mono send(TdApi.Function query, Duration timeout); - - TdApi.Object execute(TdApi.Function query); -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/ResponseError.java b/src/main/java/it/tdlight/tdlibsession/td/ResponseError.java deleted file mode 100644 index 8f73d16..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/ResponseError.java +++ /dev/null @@ -1,148 +0,0 @@ -package it.tdlight.tdlibsession.td; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Function; -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ResponseError extends IOException { - - @NotNull - private final String botName; - @NotNull - private final String tag; - private final int code; - @NotNull - private final String message; - - private ResponseError(@NotNull Function function, @NotNull String botName, @NotNull TdApi.Error tdError, @Nullable Throwable cause) { - super("Bot '" + botName + "' failed the request '" + functionToInlineString(function) + "': " + tdError.code + " " + tdError.message, cause); - this.botName = botName; - this.tag = functionToInlineString(function); - this.code = tdError.code; - this.message = tdError.message; - } - - private ResponseError(@NotNull String tag, @NotNull String botName, @NotNull TdApi.Error tdError, @Nullable Throwable cause) { - super("Bot '" + botName + "' failed the request '" + tag + "': " + tdError.code + " " + tdError.message, cause); - this.botName = botName; - this.tag = tag; - this.code = tdError.code; - this.message = tdError.message; - } - - private ResponseError(@NotNull Function function, @NotNull String botName, @Nullable Throwable cause) { - super("Bot '" + botName + "' failed the request '" + functionToInlineString(function) + "': " + (cause == null ? null : cause.getMessage()), cause); - this.botName = botName; - this.tag = functionToInlineString(function); - this.code = 500; - this.message = (cause == null ? "" : (cause.getMessage() == null ? "" : cause.getMessage())); - } - - private ResponseError(@NotNull String tag, @NotNull String botName, @Nullable Throwable cause) { - super("Bot '" + botName + "' failed the request '" + tag + "': " + (cause == null ? null : cause.getMessage()), cause); - this.botName = botName; - this.tag = tag; - this.code = 500; - this.message = (cause == null ? "" : (cause.getMessage() == null ? "" : cause.getMessage())); - } - - public static ResponseError newResponseError(@NotNull Function function, - @NotNull String botName, - @NotNull TdApi.Error tdError, - @Nullable Throwable cause) { - return new ResponseError(function, botName, tdError, cause); - } - - public static ResponseError newResponseError(@NotNull String tag, - @NotNull String botName, - @NotNull TdApi.Error tdError, - @Nullable Throwable cause) { - return new ResponseError(tag, botName, tdError, cause); - } - - public static ResponseError newResponseError(@NotNull String tag, - @NotNull String botName, - @NotNull TdApi.Error tdError, - @Nullable TdError cause) { - return new ResponseError(tag, botName, tdError, cause); - } - - public static ResponseError newResponseError(@NotNull Function function, - @NotNull String botName, - @Nullable Throwable cause) { - return new ResponseError(function, botName, cause); - } - - public static ResponseError newResponseError(@NotNull String tag, - @NotNull String botName, - @Nullable Throwable cause) { - return new ResponseError(tag, botName, cause); - } - - @Nullable - public static T get(@NotNull Function function, @NotNull String botName, CompletableFuture action) throws ResponseError { - try { - return action.get(); - } catch (InterruptedException e) { - throw ResponseError.newResponseError(function, botName, e); - } catch (ExecutionException executionException) { - if (executionException.getCause() instanceof ResponseError) { - throw (ResponseError) executionException.getCause(); - } else { - throw ResponseError.newResponseError(function, botName, executionException); - } - } - } - - @Nullable - public static T get(@NotNull String tag, @NotNull String botName, CompletableFuture action) throws ResponseError { - try { - return action.get(); - } catch (InterruptedException e) { - throw ResponseError.newResponseError(tag, botName, e); - } catch (ExecutionException executionException) { - if (executionException.getCause() instanceof ResponseError) { - throw (ResponseError) executionException.getCause(); - } else { - throw ResponseError.newResponseError(tag, botName, executionException); - } - } - } - - @NotNull - public String getBotName() { - return botName; - } - - public int getErrorCode() { - return code; - } - - public String getTag() { - return tag; - } - - @NotNull - public String getErrorMessage() { - return message; - } - - private static String functionToInlineString(Function function) { - return function - .toString() - .replace("\n", " ") - .replace("\t", "") - .replace(" ", "") - .replace(" = ", "=") - .trim(); - } - - @Override - public String toString() { - return getMessage(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/TdError.java b/src/main/java/it/tdlight/tdlibsession/td/TdError.java deleted file mode 100644 index 1a14e08..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/TdError.java +++ /dev/null @@ -1,34 +0,0 @@ -package it.tdlight.tdlibsession.td; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Error; - -public class TdError extends RuntimeException { - - private final int code; - private final String message; - - public TdError(int code, String message) { - super(code + " " + message); - this.code = code; - this.message = message; - } - - public TdError(int code, String message, Throwable cause) { - super(code + " " + message, cause); - this.code = code; - this.message = message; - } - - public int getTdCode() { - return code; - } - - public String getTdMessage() { - return message; - } - - public TdApi.Error getTdError() { - return new Error(code, message); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/TdResult.java b/src/main/java/it/tdlight/tdlibsession/td/TdResult.java deleted file mode 100644 index 74f6e87..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/TdResult.java +++ /dev/null @@ -1,289 +0,0 @@ -package it.tdlight.tdlibsession.td; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Error; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.concurrent.CompletionException; -import java.util.function.Function; -import org.jetbrains.annotations.NotNull; - -/** - * Encapsulates the result of an asynchronous operation. - *

- * Many operations in Vert.x APIs provide results back by passing an instance of this in a {@link io.vertx.core.Handler}. - *

- * The result can either have failed or succeeded. - *

- * If it failed then the cause of the failure is available with {@link #cause}. - *

- * If it succeeded then the actual result is available with {@link #result} - * - * @author Tim Fox - */ -public interface TdResult { - - /** - * The result of the operation. This will be null if the operation failed. - * - * @return the result or null if the operation failed. - */ - T result(); - - /** - * The result of the operation. This will throw CompletionException if the operation failed. - * - * @return the result. - */ - T orElseThrow() throws CompletionException; - - /** - * A TdApi.Error describing failure. This will be null if the operation succeeded. - * - * @return the cause or null if the operation succeeded. - */ - TdApi.Error cause(); - - /** - * Did it succeed? - * - * @return true if it succeded or false otherwise - */ - boolean succeeded(); - - /** - * Did it fail? - * - * @return true if it failed or false otherwise - */ - boolean failed(); - - /** - * Apply a {@code mapper} function on this async result.

- * - * The {@code mapper} is called with the completed value and this mapper returns a value. This value will complete the result returned by this method call.

- * - * When this async result is failed, the failure will be propagated to the returned async result and the {@code mapper} will not be called. - * - * @param mapper the mapper function - * @return the mapped async result - */ - default TdResult map(Function mapper) { - if (mapper == null) { - throw new NullPointerException(); - } - return new TdResult() { - @Override - public U result() { - if (succeeded()) { - return mapper.apply(TdResult.this.result()); - } else { - return null; - } - } - - @Override - public U orElseThrow() throws CompletionException { - if (succeeded()) { - return mapper.apply(TdResult.this.orElseThrow()); - } else { - return null; - } - } - - @Override - public TdApi.Error cause() { - return TdResult.this.cause(); - } - - @Override - public boolean succeeded() { - return TdResult.this.succeeded(); - } - - @Override - public boolean failed() { - return TdResult.this.failed(); - } - }; - } - - /** - * Map the result of this async result to a specific {@code value}.

- * - * When this async result succeeds, this {@code value} will succeeed the async result returned by this method call.

- * - * When this async result fails, the failure will be propagated to the returned async result. - * - * @param value the value that eventually completes the mapped async result - * @return the mapped async result - */ - default TdResult map(V value) { - return map((Function) t -> value); - } - - /** - * Map the result of this async result to {@code null}.

- * - * This is a convenience for {@code TdResult.map((T) null)} or {@code TdResult.map((Void) null)}.

- * - * When this async result succeeds, {@code null} will succeeed the async result returned by this method call.

- * - * When this async result fails, the failure will be propagated to the returned async result. - * - * @return the mapped async result - */ - default TdResult mapEmpty() { - return map((V)null); - } - - /** - * Apply a {@code mapper} function on this async result.

- * - * The {@code mapper} is called with the failure and this mapper returns a value. This value will complete the result returned by this method call.

- * - * When this async result is succeeded, the value will be propagated to the returned async result and the {@code mapper} will not be called. - * - * @param mapper the mapper function - * @return the mapped async result - */ - default TdResult otherwise(Function mapper) { - if (mapper == null) { - throw new NullPointerException(); - } - return new TdResult() { - @Override - public T result() { - if (TdResult.this.succeeded()) { - return TdResult.this.result(); - } else if (TdResult.this.failed()) { - return mapper.apply(TdResult.this.cause()); - } else { - return null; - } - } - @Override - public T orElseThrow() { - if (TdResult.this.succeeded()) { - return TdResult.this.orElseThrow(); - } else if (TdResult.this.failed()) { - return mapper.apply(TdResult.this.cause()); - } else { - return null; - } - } - - @Override - public TdApi.Error cause() { - return null; - } - - @Override - public boolean succeeded() { - return TdResult.this.succeeded() || TdResult.this.failed(); - } - - @Override - public boolean failed() { - return false; - } - }; - } - - /** - * Map the failure of this async result to a specific {@code value}.

- * - * When this async result fails, this {@code value} will succeeed the async result returned by this method call.

- * - * When this async succeeds, the result will be propagated to the returned async result. - * - * @param value the value that eventually completes the mapped async result - * @return the mapped async result - */ - default TdResult otherwise(T value) { - return otherwise(err -> value); - } - - /** - * Map the failure of this async result to {@code null}.

- * - * This is a convenience for {@code TdResult.otherwise((T) null)}.

- * - * When this async result fails, the {@code null} will succeeed the async result returned by this method call.

- * - * When this async succeeds, the result will be propagated to the returned async result. - * - * @return the mapped async result - */ - default TdResult otherwiseEmpty() { - return otherwise(err -> null); - } - - static TdResult succeeded(@NotNull T value) { - Objects.requireNonNull(value); - return new TdResultImpl(value, null); - } - - static TdResult failed(@NotNull TdApi.Error error) { - Objects.requireNonNull(error); - return new TdResultImpl(null, error); - } - - static TdResult of(@NotNull TdApi.Object resultOrError) { - if (resultOrError.getConstructor() == TdApi.Error.CONSTRUCTOR) { - return failed((TdApi.Error) resultOrError); - } else { - //noinspection unchecked - return succeeded((T) resultOrError); - } - } - - class TdResultImpl implements TdResult { - - private final U value; - private final Error error; - - public TdResultImpl(U value, Error error) { - this.value = value; - this.error = error; - - assert (value == null) != (error == null); - } - - @Override - public U result() { - return value; - } - - @Override - public U orElseThrow() { - if (error != null) { - throw new TdError(error.code, error.message); - } - return value; - } - - @Override - public Error cause() { - return error; - } - - @Override - public boolean succeeded() { - return value != null; - } - - @Override - public boolean failed() { - return error != null; - } - - @Override - public String toString() { - return new StringJoiner(", ", TdResultImpl.class.getSimpleName() + "[", "]") - .add("value=" + value) - .add("error=" + error) - .toString(); - } - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/WrappedReactorTelegramClient.java b/src/main/java/it/tdlight/tdlibsession/td/WrappedReactorTelegramClient.java deleted file mode 100644 index f1ef877..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/WrappedReactorTelegramClient.java +++ /dev/null @@ -1,92 +0,0 @@ -package it.tdlight.tdlibsession.td; - -import it.tdlight.common.ReactiveTelegramClient; -import it.tdlight.common.Signal; -import it.tdlight.common.SignalListener; -import it.tdlight.common.UpdatesHandler; -import it.tdlight.jni.TdApi; -import it.tdlight.utils.MonoUtils; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicReference; -import org.jetbrains.annotations.NotNull; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.publisher.FluxSink.OverflowStrategy; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -public class WrappedReactorTelegramClient implements ReactorTelegramClient { - - private final ReactiveTelegramClient reactiveTelegramClient; - private final AtomicReference> multicastSignals = new AtomicReference<>(null); - - public WrappedReactorTelegramClient(ReactiveTelegramClient reactiveTelegramClient) { - this.reactiveTelegramClient = reactiveTelegramClient; - } - - public Mono initialize() { - return MonoUtils - .fromBlockingEmpty(() -> { - reactiveTelegramClient.createAndRegisterClient(); - Flux signalsFlux = Flux - .create(sink -> { - reactiveTelegramClient.setListener(sink::next); - sink.onCancel(reactiveTelegramClient::cancel); - sink.onDispose(reactiveTelegramClient::dispose); - }, OverflowStrategy.BUFFER) - .subscribeOn(Schedulers.boundedElastic()) - .takeWhile(Signal::isNotClosed); - Flux refCountedSharedSignalsFlux = signalsFlux.publish().refCount(); - multicastSignals.set(refCountedSharedSignalsFlux); - }); - } - - @Override - public Flux receive() { - return Flux - .defer(() -> { - Flux flux = multicastSignals.get(); - if (flux == null) { - return Flux.error(new IllegalStateException("TDLib session not started")); - } else { - return flux; - } - }) - .handle((item, sink) -> { - if (item.isUpdate()) { - sink.next(item.getUpdate()); - } else if (item.isException()) { - sink.error(item.getException()); - } else { - sink.error(new IllegalStateException("This shouldn't happen. Received unknown ReactiveItem type")); - } - }); - } - - /** - * Sends a request to the TDLib. - * - * @param query Object representing a query to the TDLib. - * @param timeout Response timeout. - * @return a publisher that will emit exactly one item, or an error - * @throws NullPointerException if query is null. - */ - @Override - public Mono send(TdApi.Function query, Duration timeout) { - return Mono.from(reactiveTelegramClient.send(query, timeout)).single(); - } - - /** - * Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously. - * - * @param query Object representing a query to the TDLib. - * @return request result or {@link TdApi.Error}. - * @throws NullPointerException if query is null. - */ - @Override - public TdApi.Object execute(TdApi.Function query) { - return reactiveTelegramClient.execute(query); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirect.java b/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirect.java deleted file mode 100644 index 7d12ab5..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirect.java +++ /dev/null @@ -1,32 +0,0 @@ -package it.tdlight.tdlibsession.td.direct; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.tdlibsession.td.TdResult; -import java.time.Duration; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public interface AsyncTdDirect { - - Mono initialize(); - - /** - * Receives incoming updates and request responses from TDLib. - * Can be called only once. - * - */ - Flux receive(AsyncTdDirectOptions options); - - /** - * Sends request to TDLib. - * Should be called after receive. - * - * @param request Request to TDLib. - * @param timeout Response timeout. - * @param synchronous Execute synchronously. - * @return The request response or {@link it.tdlight.jni.TdApi.Error}. - */ - Mono> execute(Function request, Duration timeout, boolean synchronous); - -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectImpl.java b/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectImpl.java deleted file mode 100644 index 51cba2c..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectImpl.java +++ /dev/null @@ -1,109 +0,0 @@ -package it.tdlight.tdlibsession.td.direct; - -import io.vertx.core.json.JsonObject; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.AuthorizationStateClosed; -import it.tdlight.jni.TdApi.Close; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.Ok; -import it.tdlight.jni.TdApi.UpdateAuthorizationState; -import it.tdlight.tdlibsession.td.ReactorTelegramClient; -import it.tdlight.tdlibsession.td.TdError; -import it.tdlight.tdlibsession.td.TdResult; -import it.tdlight.utils.MonoUtils; -import java.time.Duration; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.One; -import reactor.core.scheduler.Schedulers; - -public class AsyncTdDirectImpl implements AsyncTdDirect { - - private static final Logger logger = LoggerFactory.getLogger(AsyncTdDirect.class); - - private final TelegramClientFactory telegramClientFactory; - private final JsonObject implementationDetails; - private final String botAlias; - - private final AtomicReference td = new AtomicReference<>(null); - - public AsyncTdDirectImpl(TelegramClientFactory telegramClientFactory, - JsonObject implementationDetails, - String botAlias) { - this.telegramClientFactory = telegramClientFactory; - this.implementationDetails = implementationDetails; - this.botAlias = botAlias; - } - - @Override - public Mono> execute(Function request, Duration timeout, boolean synchronous) { - if (synchronous) { - return MonoUtils.fromBlockingSingle(() -> { - var td = this.td.get(); - logger.trace("Sending execute to TDLib {}", request); - Objects.requireNonNull(td, "td is null"); - TdResult result = TdResult.of(td.execute(request)); - logger.trace("Received execute response from TDLib. Request was {}", request); - return result; - }) - .single(); - } else { - return Mono.defer(() -> { - var td = this.td.get(); - - if (td != null) { - return Mono - .fromRunnable(() -> logger.trace("Sending request to TDLib {}", request)) - .then(td.send(request, timeout)) - .single() - .>map(TdResult::of) - .doOnSuccess(s -> logger.trace("Sent request to TDLib {}", request)); - } else { - return Mono.fromCallable(() -> { - if (request.getConstructor() == Close.CONSTRUCTOR) { - logger.trace("Sending close success to request {}", request); - return TdResult.of(new Ok()); - } else { - logger.trace("Sending close error to request {} ", request); - throw new IllegalStateException("TDLib client is destroyed"); - } - }); - } - }); - } - } - - @Override - public Mono initialize() { - return Mono - .fromRunnable(() -> logger.trace("Initializing")) - .then(telegramClientFactory.create(implementationDetails)) - .flatMap(reactorTelegramClient -> reactorTelegramClient.initialize().thenReturn(reactorTelegramClient)) - .doOnNext(td::set) - .doOnNext(client -> client.execute(new TdApi.SetLogVerbosityLevel(1))) - .doOnSuccess(s -> logger.trace("Initialized")) - .then(); - } - - @Override - public Flux receive(AsyncTdDirectOptions options) { - return Mono - .fromCallable(td::get) - .single() - .flatMapMany(ReactorTelegramClient::receive) - .doOnNext(update -> { - // Close the emitter if receive closed state - if (update.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR - && ((UpdateAuthorizationState) update).authorizationState.getConstructor() - == AuthorizationStateClosed.CONSTRUCTOR) { - logger.debug("Received closed status from tdlib"); - } - }) - .subscribeOn(Schedulers.boundedElastic()); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectOptions.java b/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectOptions.java deleted file mode 100644 index a82cc83..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/direct/AsyncTdDirectOptions.java +++ /dev/null @@ -1,36 +0,0 @@ -package it.tdlight.tdlibsession.td.direct; - -import java.time.Duration; -import java.util.StringJoiner; - -public class AsyncTdDirectOptions { - - private final Duration receiveDuration; - private final int eventsSize; - - /** - * - * @param receiveDuration Maximum number of seconds allowed for this function to wait for new records. Default: 1 sec - * @param eventsSize Maximum number of events allowed in list. Default: 350 events - */ - public AsyncTdDirectOptions(Duration receiveDuration, int eventsSize) { - this.receiveDuration = receiveDuration; - this.eventsSize = eventsSize; - } - - public Duration getReceiveDuration() { - return receiveDuration; - } - - public int getEventsSize() { - return eventsSize; - } - - @Override - public String toString() { - return new StringJoiner(", ", AsyncTdDirectOptions.class.getSimpleName() + "[", "]") - .add("receiveDuration=" + receiveDuration) - .add("eventsSize=" + eventsSize) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/direct/TelegramClientFactory.java b/src/main/java/it/tdlight/tdlibsession/td/direct/TelegramClientFactory.java deleted file mode 100644 index 0c2a95a..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/direct/TelegramClientFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -package it.tdlight.tdlibsession.td.direct; - -import io.vertx.core.json.JsonObject; -import it.tdlight.tdlibsession.td.ReactorTelegramClient; -import it.tdlight.tdlibsession.td.WrappedReactorTelegramClient; -import it.tdlight.tdlight.ClientManager; -import it.tdlight.utils.MonoUtils; -import reactor.core.publisher.Mono; - -public class TelegramClientFactory { - - public TelegramClientFactory() { - - } - - public Mono create(JsonObject implementationDetails) { - return MonoUtils.fromBlockingSingle(() -> { - var implementationName = implementationDetails.getString("name", "native-client"); - switch (implementationName) { - case "native-client": - return new WrappedReactorTelegramClient(ClientManager.createReactive()); - case "test-client": - return new TestClient(implementationDetails.getJsonObject("test-client-settings")); - default: - return null; - } - }); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/direct/TestClient.java b/src/main/java/it/tdlight/tdlibsession/td/direct/TestClient.java deleted file mode 100644 index d836283..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/direct/TestClient.java +++ /dev/null @@ -1,156 +0,0 @@ -package it.tdlight.tdlibsession.td.direct; - -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.AuthorizationStateClosed; -import it.tdlight.jni.TdApi.AuthorizationStateClosing; -import it.tdlight.jni.TdApi.AuthorizationStateReady; -import it.tdlight.jni.TdApi.Close; -import it.tdlight.jni.TdApi.ConnectionStateReady; -import it.tdlight.jni.TdApi.FormattedText; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.GetMe; -import it.tdlight.jni.TdApi.Message; -import it.tdlight.jni.TdApi.MessageSenderUser; -import it.tdlight.jni.TdApi.MessageText; -import it.tdlight.jni.TdApi.Ok; -import it.tdlight.jni.TdApi.SetLogTagVerbosityLevel; -import it.tdlight.jni.TdApi.SetLogVerbosityLevel; -import it.tdlight.jni.TdApi.SetOption; -import it.tdlight.jni.TdApi.SetTdlibParameters; -import it.tdlight.jni.TdApi.TextEntity; -import it.tdlight.jni.TdApi.UpdateAuthorizationState; -import it.tdlight.jni.TdApi.UpdateConnectionState; -import it.tdlight.jni.TdApi.UpdateNewMessage; -import it.tdlight.jni.TdApi.User; -import it.tdlight.tdlibsession.td.ReactorTelegramClient; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import org.jetbrains.annotations.Nullable; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.Empty; - -public class TestClient implements ReactorTelegramClient { - - private static final Logger logger = LoggerFactory.getLogger(TestClient.class); - - private static final AtomicLong incrementalMessageId = new AtomicLong(1); - private final List features; - private final Empty closedSink = Sinks.empty(); - - public TestClient(JsonObject testClientSettings) { - JsonArray features = testClientSettings.getJsonArray("features", new JsonArray()); - this.features = new ArrayList<>(); - for (java.lang.Object feature : features) { - var featureName = (String) feature; - this.features.add(featureName); - } - } - - private static Message generateRandomMessage(boolean randomSender, boolean randomChat, boolean randomText) { - var msg = new Message(); - msg.sender = new MessageSenderUser(312042); - msg.chatId = 240213; - msg.id = incrementalMessageId.getAndIncrement(); - var content = new MessageText(); - content.text = new FormattedText("Text", new TextEntity[0]); - msg.content = content; - msg.date = (int) System.currentTimeMillis() / 1000; - return msg; - } - - @Override - public Mono initialize() { - return Mono.empty(); - } - - @Override - public Flux receive() { - return Flux.fromIterable(features).flatMap(featureName -> { - switch (featureName) { - case "status-update": - return Flux - .just( - new UpdateAuthorizationState(new AuthorizationStateReady()), - new UpdateConnectionState(new ConnectionStateReady()) - ) - .mergeWith(closedSink - .asMono() - .thenMany(Flux.just(new UpdateAuthorizationState(new AuthorizationStateClosing()), - new UpdateAuthorizationState(new AuthorizationStateClosed()) - )) - ); - case "infinite-messages": - var randomSenders = features.contains("random-senders"); - var randomChats = features.contains("random-chats"); - var randomTexts = features.contains("random-text"); - return Flux - .fromIterable(() -> new Iterator<>() { - @Override - public boolean hasNext() { - return true; - } - - @Override - public TdApi.Object next() { - return new UpdateNewMessage(generateRandomMessage(randomSenders, randomChats, randomTexts)); - } - }).takeUntilOther(this.closedSink.asMono()); - default: - return Mono.fromCallable(() -> { - throw new IllegalArgumentException("Unknown feature name: " + featureName); - }); - } - }); - } - - @Override - public Mono send(Function query, Duration timeout) { - return Mono.fromCallable(() -> { - TdApi.Object result = executeCommon(query); - if (result != null) { - return result; - } - return new TdApi.Error(500, "Unsupported"); - }); - } - - @Override - public TdApi.Object execute(Function query) { - TdApi.Object result = executeCommon(query); - if (result != null) { - return result; - } - return new TdApi.Error(500, "Unsupported"); - } - - @Nullable - public TdApi.Object executeCommon(Function query) { - switch (query.getConstructor()) { - case SetLogVerbosityLevel.CONSTRUCTOR: - case SetLogTagVerbosityLevel.CONSTRUCTOR: - case SetTdlibParameters.CONSTRUCTOR: - case SetOption.CONSTRUCTOR: - return new Ok(); - case GetMe.CONSTRUCTOR: - var user = new User(); - user.id = 420; - user.firstName = "Test"; - user.lastName = "Test"; - user.phoneNumber = "+77"; - return user; - case Close.CONSTRUCTOR: - closedSink.tryEmitEmpty(); - return new Ok(); - } - return null; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdEasy.java b/src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdEasy.java deleted file mode 100644 index 40c2b90..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdEasy.java +++ /dev/null @@ -1,657 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.AuthorizationState; -import it.tdlight.jni.TdApi.AuthorizationStateClosed; -import it.tdlight.jni.TdApi.AuthorizationStateClosing; -import it.tdlight.jni.TdApi.AuthorizationStateReady; -import it.tdlight.jni.TdApi.AuthorizationStateWaitCode; -import it.tdlight.jni.TdApi.AuthorizationStateWaitEncryptionKey; -import it.tdlight.jni.TdApi.AuthorizationStateWaitOtherDeviceConfirmation; -import it.tdlight.jni.TdApi.AuthorizationStateWaitPassword; -import it.tdlight.jni.TdApi.AuthorizationStateWaitPhoneNumber; -import it.tdlight.jni.TdApi.AuthorizationStateWaitRegistration; -import it.tdlight.jni.TdApi.AuthorizationStateWaitTdlibParameters; -import it.tdlight.jni.TdApi.CheckAuthenticationBotToken; -import it.tdlight.jni.TdApi.CheckAuthenticationCode; -import it.tdlight.jni.TdApi.CheckAuthenticationPassword; -import it.tdlight.jni.TdApi.CheckDatabaseEncryptionKey; -import it.tdlight.jni.TdApi.Error; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.Object; -import it.tdlight.jni.TdApi.OptionValueBoolean; -import it.tdlight.jni.TdApi.OptionValueEmpty; -import it.tdlight.jni.TdApi.OptionValueInteger; -import it.tdlight.jni.TdApi.OptionValueString; -import it.tdlight.jni.TdApi.PhoneNumberAuthenticationSettings; -import it.tdlight.jni.TdApi.RegisterUser; -import it.tdlight.jni.TdApi.SetAuthenticationPhoneNumber; -import it.tdlight.jni.TdApi.SetTdlibParameters; -import it.tdlight.jni.TdApi.TdlibParameters; -import it.tdlight.jni.TdApi.Update; -import it.tdlight.jni.TdApi.UpdateAuthorizationState; -import it.tdlight.tdlibsession.FatalErrorType; -import it.tdlight.tdlibsession.td.TdError; -import it.tdlight.tdlibsession.td.TdResult; -import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle; -import it.tdlight.utils.MonoUtils; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Comparator; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; -import org.reactivestreams.Publisher; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import org.warp.commonutils.error.InitializationException; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.EmitResult; -import reactor.core.publisher.Sinks.Empty; -import reactor.core.publisher.Sinks.Many; -import reactor.core.publisher.Sinks.One; -import reactor.core.publisher.SynchronousSink; -import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; - -@SuppressWarnings("unused") -public class AsyncTdEasy { - - private final Logger logger; - private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(1); - - private final Empty closed = Sinks.empty(); - private final Many authStateSink = Sinks.many().replay().latest(); - private final AtomicReference authState = new AtomicReference<>(new AuthorizationStateClosed()); - private final AtomicBoolean requestedDefinitiveExit = new AtomicBoolean(); - private final AtomicBoolean canSendCloseRequest = new AtomicBoolean(); - private final AtomicReference settings = new AtomicReference<>(null); - private final Many globalErrors = Sinks.many().multicast().onBackpressureBuffer(); - private final One fatalError = Sinks.one(); - private final AsyncTdMiddle td; - private final String logName; - private final Flux incomingUpdates; - private final Scheduler scheduler = Schedulers.parallel(); - - public AsyncTdEasy(AsyncTdMiddle td, String logName) { - this.td = td; - this.logName = logName; - this.logger = LoggerFactory.getLogger("AsyncTdEasy " + logName); - - this.incomingUpdates = td.receive() - .doFirst(() -> { - canSendCloseRequest.set(true); - logger.debug("From now onwards TdApi.Close cannot be called"); - }) - .doOnTerminate(() -> { - canSendCloseRequest.set(false); - logger.debug("From now onwards TdApi.Close can be called"); - }) - .flatMapSequential(this::preprocessUpdates) - .map(update -> { - var state = authState.get(); - Objects.requireNonNull(state, "State is not set"); - return new AsyncTdUpdateObj(state, update); - }) - .map(upd -> (TdApi.Update) upd.getUpdate()) - .takeUntil(update -> { - if (update.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) { - var state = ((UpdateAuthorizationState) update).authorizationState; - return state.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR; - } - return false; - }) - .doOnError(ex -> { - if (ex instanceof TdError) { - var tdEx = (TdError) ex; - logger.error("Received an error update from telegram: " + tdEx.getTdCode() + " " + tdEx.getTdMessage()); - FatalErrorType fatalErrorType; - try { - fatalErrorType = FatalErrorType.valueOf(tdEx.getTdMessage()); - } catch (IllegalArgumentException ignored) { - fatalErrorType = FatalErrorType.INVALID_UPDATE; - } - this.fatalError.tryEmitValue(fatalErrorType); - } else { - logger.error(ex.getLocalizedMessage(), ex); - } - }) - .doFinally(s -> { - var state = authState.get(); - onUpdatesTerminated(); - if (state.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) { - logger.warn("Updates stream has closed while" - + " the current authorization state is" - + " still {}. Setting authorization state as closed!", state.getClass().getSimpleName()); - this.fatalError.tryEmitValue(FatalErrorType.CONNECTION_KILLED); - } - }); - } - - private void onUpdatesTerminated() { - logger.debug("Incoming updates flux terminated. Setting requestedDefinitiveExit: true"); - requestedDefinitiveExit.set(true); - - var newState = new AuthorizationStateClosed(); - emitState(newState); - } - - public Mono create(TdEasySettings settings) { - return Mono - .fromCallable(() -> { - // Create session directories - if (Files.notExists(Path.of(settings.databaseDirectory))) { - try { - Files.createDirectories(Path.of(settings.databaseDirectory)); - } catch (IOException ex) { - throw new InitializationException(ex); - } - } - - // Register fatal error handler - fatalError.asMono().flatMap(settings.getFatalErrorHandler()::onFatalError).subscribeOn(scheduler).subscribe(); - - return true; - }) - .subscribeOn(Schedulers.boundedElastic()) - .flatMap(_v -> { - this.settings.set(settings); - return Mono.empty(); - }) - .then(td.initialize()); - } - - /** - * Get TDLib state - */ - public Flux state() { - return authStateSink.asFlux().distinct(); - } - - /** - * Get incoming updates from TDLib. - */ - public Flux getIncomingUpdates() { - return incomingUpdates; - } - - /** - * Get generic error updates from TDLib (When they are not linked to a precise request). - */ - public Flux getIncomingErrors() { - return Flux.from(globalErrors.asFlux()); - } - - /** - * Receives fatal errors from TDLib. - */ - public Mono getFatalErrors() { - return Mono.from(fatalError.asMono()); - } - - /** - * Sends request to TDLib. - * @param timeout Timeout duration. - * @return The response or {@link TdApi.Error}. - */ - public Mono> send(TdApi.Function request, Duration timeout) { - return td.execute(request, timeout, false); - } - - private Mono> sendDirectly(Function obj, boolean synchronous) { - return td.execute(obj, AsyncTdEasy.DEFAULT_TIMEOUT, synchronous); - } - - /** - * Set verbosity level - * @param i level - */ - public Mono setVerbosityLevel(int i) { - return sendDirectly(new TdApi.SetLogVerbosityLevel(i), true).transform(this::thenOrFatalError); - } - - /** - * Clear option on TDLib - * @param name option name - */ - public Mono clearOption(String name) { - return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueEmpty()), false) - .transform(this::thenOrFatalError); - } - - /** - * Set option on TDLib - * @param name option name - * @param value option value - */ - public Mono setOptionString(String name, String value) { - return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueString(value)), false) - .transform(this::thenOrFatalError); - } - - /** - * Set option on TDLib - * @param name option name - * @param value option value - */ - public Mono setOptionInteger(String name, long value) { - return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueInteger(value)), false) - .transform(this::thenOrFatalError); - } - - /** - * Set option on TDLib - * @param name option name - * @param value option value - */ - public Mono setOptionBoolean(String name, boolean value) { - return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueBoolean(value)), false) - .transform(this::thenOrFatalError); - } - - /** - * Get option from TDLib - * @param name option name - * @return The value or nothing - */ - public Mono getOptionString(String name) { - return this - .sendDirectly(new TdApi.GetOption(name), false) - .handle(MonoUtils::orElseThrow) - .flatMap(value -> { - switch (value.getConstructor()) { - case OptionValueString.CONSTRUCTOR: - return Mono.just(((OptionValueString) value).value); - case OptionValueEmpty.CONSTRUCTOR: - return Mono.empty(); - default: - return Mono.error(new UnsupportedOperationException("The option " + name + " is of type " - + value.getClass().getSimpleName())); - } - }); - } - - /** - * Get option from TDLib - * @param name option name - * @return The value or nothing - */ - public Mono getOptionInteger(String name) { - return this - .sendDirectly(new TdApi.GetOption(name), false) - .handle(MonoUtils::orElseThrow) - .flatMap(value -> { - switch (value.getConstructor()) { - case OptionValueInteger.CONSTRUCTOR: - return Mono.just(((OptionValueInteger) value).value); - case OptionValueEmpty.CONSTRUCTOR: - return Mono.empty(); - default: - return Mono.error(new UnsupportedOperationException( - "The option " + name + " is of type " + value.getClass().getSimpleName())); - } - }); - } - - /** - * Get option from TDLib - * @param name option name - * @return The value or nothing - */ - public Mono getOptionBoolean(String name) { - return this - .sendDirectly(new TdApi.GetOption(name), false) - .handle(MonoUtils::orElseThrow) - .flatMap(value -> { - switch (value.getConstructor()) { - case OptionValueBoolean.CONSTRUCTOR: - return Mono.just(((OptionValueBoolean) value).value); - case OptionValueEmpty.CONSTRUCTOR: - return Mono.empty(); - default: - return Mono.error(new UnsupportedOperationException( - "The option " + name + " is of type " + value.getClass().getSimpleName())); - } - }); - } - - /** - * Synchronously executes TDLib requests. Only a few requests can be executed synchronously. May - * be called from any thread. - * - * @param request Request to the TDLib. - * @param timeout Timeout. - * @return The request response. - */ - public Mono> execute(TdApi.Function request, Duration timeout) { - return td.execute(request, timeout, true); - } - - /** - * Closes the client gracefully by sending {@link TdApi.Close}. - */ - public Mono close() { - var waitClosed = closed.asMono() - .doFirst(() -> logger.debug("Waiting for AuthorizationStateClosed...")) - .doOnSuccess(s -> logger.debug("Received AuthorizationStateClosed after TdApi.Close")) - .transformDeferred(mono -> { - if (canSendCloseRequest.get()) { - return mono; - } else { - return Mono.fromRunnable(() -> emitState(new AuthorizationStateClosed())); - } - }); - - return Mono - .fromSupplier(authState::get) - .filter(state -> { - switch (state.getConstructor()) { - case AuthorizationStateClosing.CONSTRUCTOR: - case AuthorizationStateClosed.CONSTRUCTOR: - return false; - default: - return true; - } - }) - .then(Mono.fromCallable(requestedDefinitiveExit::get).single()) - .filter(closeRequested -> !closeRequested) - .doOnSuccess(s -> { - logger.debug("Setting requestedDefinitiveExit: true"); - requestedDefinitiveExit.set(true); - }) - .then(td - .execute(new TdApi.Close(), Duration.ofSeconds(5), false) - .doFirst(() -> logger.debug("Sending TdApi.Close")) - .doOnNext(closeResponse -> logger.debug("TdApi.Close response is: \"{}\"", - closeResponse.toString().replace('\n', ' ') - )) - .doOnSuccess(s -> logger.debug("Sent TdApi.Close")) - .transformDeferred(closeMono -> { - if (canSendCloseRequest.get()) { - return closeMono; - } else { - return Mono.empty(); - } - }) - ) - - .then(waitClosed) - .doOnSuccess(s -> logger.info("AsyncTdEasy closed successfully")) - .then(); - } - - private Mono catchErrors(Object obj) { - return Mono.fromCallable(() -> { - if (obj.getConstructor() == Error.CONSTRUCTOR) { - var error = (Error) obj; - - switch (error.message) { - case "PHONE_CODE_INVALID": - globalErrors.tryEmitNext(error); - return new UpdateAuthorizationState(new AuthorizationStateWaitCode()); - case "PASSWORD_HASH_INVALID": - globalErrors.tryEmitNext(error); - return new UpdateAuthorizationState(new AuthorizationStateWaitPassword()); - default: - globalErrors.tryEmitNext(error); - break; - } - analyzeFatalErrors(error); - return null; - } else { - return (Update) obj; - } - }); - } - - private void analyzeFatalErrors(Object obj) { - if (obj != null && obj.getConstructor() == Error.CONSTRUCTOR) { - var error = (Error) obj; - switch (error.message) { - case "PHONE_NUMBER_INVALID": - fatalError.tryEmitValue(FatalErrorType.PHONE_NUMBER_INVALID); - break; - case "ACCESS_TOKEN_INVALID": - fatalError.tryEmitValue(FatalErrorType.ACCESS_TOKEN_INVALID); - break; - case "CONNECTION_KILLED": - fatalError.tryEmitValue(FatalErrorType.CONNECTION_KILLED); - break; - case "INVALID_UPDATE": - fatalError.tryEmitValue(FatalErrorType.INVALID_UPDATE); - break; - case "PHONE_NUMBER_BANNED": - fatalError.tryEmitValue(FatalErrorType.PHONE_NUMBER_BANNED); - break; - } - } - } - - private Publisher preprocessUpdates(TdApi.Object updateObj) { - return Mono - .just(updateObj) - .flatMap(this::catchErrors) - .filter(obj -> obj.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) - .map(obj -> ((UpdateAuthorizationState) obj).authorizationState) - .flatMap(obj -> { - switch (obj.getConstructor()) { - case AuthorizationStateWaitTdlibParameters.CONSTRUCTOR: - return Mono - .fromCallable(this.settings::get) - .single() - .map(settings -> { - var parameters = new TdlibParameters(); - parameters.useTestDc = settings.useTestDc; - parameters.databaseDirectory = settings.databaseDirectory; - parameters.filesDirectory = settings.filesDirectory; - parameters.useFileDatabase = settings.useFileDatabase; - parameters.useChatInfoDatabase = settings.useChatInfoDatabase; - parameters.useMessageDatabase = settings.useMessageDatabase; - parameters.useSecretChats = false; - parameters.apiId = settings.apiId; - parameters.apiHash = settings.apiHash; - parameters.systemLanguageCode = settings.systemLanguageCode; - parameters.deviceModel = settings.deviceModel; - parameters.systemVersion = settings.systemVersion; - parameters.applicationVersion = settings.applicationVersion; - parameters.enableStorageOptimizer = settings.enableStorageOptimizer; - parameters.ignoreFileNames = settings.ignoreFileNames; - return new SetTdlibParameters(parameters); - }) - .flatMap((SetTdlibParameters obj1) -> sendDirectly(obj1, false)) - .transform(this::thenOrFatalError); - case AuthorizationStateWaitEncryptionKey.CONSTRUCTOR: - return sendDirectly(new CheckDatabaseEncryptionKey(), false) - .transform(this::thenOrFatalError) - .onErrorResume((error) -> { - logger.error("Error while checking TDLib encryption key", error); - return sendDirectly(new TdApi.Close(), false).then(); - }); - case AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: - return Mono - .fromCallable(this.settings::get).single().flatMap(settings -> { - if (settings.isPhoneNumberSet()) { - return sendDirectly(new SetAuthenticationPhoneNumber(String.valueOf(settings.getPhoneNumber()), - new PhoneNumberAuthenticationSettings(false, false, false) - ), false); - } else if (settings.isBotTokenSet()) { - return sendDirectly(new CheckAuthenticationBotToken(settings.getBotToken()), false); - } else { - return Mono.error(new IllegalArgumentException("A bot is neither an user or a bot")); - } - }) - .transform(this::thenOrFatalError) - .onErrorResume((error) -> { - logger.error("Error while waiting for phone number", error); - return sendDirectly(new TdApi.Close(), false).then(); - }); - case AuthorizationStateWaitRegistration.CONSTRUCTOR: - var authorizationStateWaitRegistration = (AuthorizationStateWaitRegistration) obj; - RegisterUser registerUser = new RegisterUser(); - if (authorizationStateWaitRegistration.termsOfService != null - && authorizationStateWaitRegistration.termsOfService.text != null - && !authorizationStateWaitRegistration.termsOfService.text.text.isBlank()) { - logger.info("Telegram Terms of Service:\n" + authorizationStateWaitRegistration.termsOfService.text.text); - } - - return Mono - .fromCallable(this.settings::get) - .single() - .map(TdEasySettings::getParameterRequestHandler) - .flatMap(handler -> handler - .onParameterRequest(Parameter.ASK_FIRST_NAME, new ParameterInfoEmpty()) - .filter(Objects::nonNull) - .map(String::trim) - .filter(firstName -> !firstName.isBlank() && firstName.length() <= 64 && firstName.length() > 0) - .repeatWhen(s -> s.takeWhile(n -> n == 0)) - .last() - .doOnNext(firstName -> registerUser.firstName = firstName) - .then(handler - .onParameterRequest(Parameter.ASK_LAST_NAME, new ParameterInfoEmpty()) - .filter(Objects::nonNull) - .map(String::trim) - .filter(lastName -> lastName.length() <= 64) - .repeatWhen(s -> s.takeWhile(n -> n == 0)) - .last() - .defaultIfEmpty("") - .doOnNext(lastName -> registerUser.lastName = lastName) - ) - .then(sendDirectly(registerUser, false)) - .transform(this::thenOrLogRepeatError) - ); - case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR: - var authorizationStateWaitOtherDeviceConfirmation = (AuthorizationStateWaitOtherDeviceConfirmation) obj; - return Mono - .fromCallable(this.settings::get) - .single() - .map(TdEasySettings::getParameterRequestHandler) - .flatMap(handler -> handler.onParameterRequest(Parameter.NOTIFY_LINK, - new ParameterInfoNotifyLink(authorizationStateWaitOtherDeviceConfirmation.link) - )); - case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: - var authorizationStateWaitCode = (AuthorizationStateWaitCode) obj; - return Mono - .fromCallable(this.settings::get) - .single() - .map(TdEasySettings::getParameterRequestHandler) - .flatMap(handler -> handler - .onParameterRequest(Parameter.ASK_CODE, new ParameterInfoCode(authorizationStateWaitCode.codeInfo.phoneNumber, - authorizationStateWaitCode.codeInfo.nextType, - authorizationStateWaitCode.codeInfo.timeout, - authorizationStateWaitCode.codeInfo.type)) - .flatMap(code -> sendDirectly(new CheckAuthenticationCode(code), false)) - .transform(this::thenOrLogRepeatError) - ); - case AuthorizationStateWaitPassword.CONSTRUCTOR: - var authorizationStateWaitPassword = (AuthorizationStateWaitPassword) obj; - return Mono - .fromCallable(this.settings::get) - .single() - .map(TdEasySettings::getParameterRequestHandler) - .flatMap(handler -> handler - .onParameterRequest(Parameter.ASK_PASSWORD, new ParameterInfoPasswordHint( - authorizationStateWaitPassword.passwordHint)) - .flatMap(password -> sendDirectly(new CheckAuthenticationPassword(password), false)) - ) - .transform(this::thenOrLogRepeatError); - case AuthorizationStateReady.CONSTRUCTOR: { - var state = new AuthorizationStateReady(); - emitState(state); - return Mono.empty(); - } - case AuthorizationStateClosing.CONSTRUCTOR: - logger.debug("Received AuthorizationStateClosing from td"); - return Mono.empty(); - case AuthorizationStateClosed.CONSTRUCTOR: - logger.debug("Received AuthorizationStateClosed from td"); - return Mono.fromCallable(() -> { - var closeRequested = this.requestedDefinitiveExit.get(); - if (closeRequested) { - logger.debug("td closed successfully"); - } else { - logger.warn("td closed unexpectedly: {}", logName); - } - emitState(obj); - return closeRequested; - }).flatMap(closeRequested -> { - if (closeRequested) { - return Mono - .fromCallable(settings::get) - .single() - .map(settings -> settings.databaseDirectory) - .map(Path::of) - .flatMapIterable(sessionPath -> Set.of(sessionPath.resolve("media"), - sessionPath.resolve("passport"), - sessionPath.resolve("profile_photos"), - sessionPath.resolve("stickers"), - sessionPath.resolve("temp"), - sessionPath.resolve("thumbnails"), - sessionPath.resolve("wallpapers") - )) - .filterWhen(file -> Mono - .fromCallable(() -> Files.exists(file)) - .subscribeOn(Schedulers.boundedElastic())) - .doOnNext(directory -> { - try { - if (!Files.walk(directory) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .allMatch(File::delete)) { - throw new IOException("Can't delete a file!"); - } - } catch (IOException e) { - logger.error("Can't delete temporary session subdirectory", e); - } - }) - .then(Mono.just(true)); - } else { - return Mono.just(false); - } - }).then(); - default: - return Mono.empty(); - } - }) - .then(Mono.justOrEmpty(updateObj.getConstructor() == Error.CONSTRUCTOR ? null : (Update) updateObj)); - } - - private void emitState(AuthorizationState state) { - if (state.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) { - this.closed.tryEmitEmpty(); - } - this.authState.set(state); - EmitResult emitResult; - while ((emitResult = this.authStateSink.tryEmitNext(state)) == EmitResult.FAIL_NON_SERIALIZED) { - // Wait 10ms - LockSupport.parkNanos(10L * 1000000L); - } - emitResult.orThrow(); - } - - private Mono thenOrFatalError(Mono> mono) { - return mono.doOnNext(result -> { - if (result.failed()) { - analyzeFatalErrors(result.cause()); - } - }).transform(MonoUtils::thenOrError); - } - - - private Mono thenOrLogRepeatError(Mono> mono) { - return mono.handle((TdResult optional, SynchronousSink sink) -> { - if (optional.succeeded()) { - sink.complete(); - } else { - logger.error("Received TDLib error: {}", optional.cause()); - sink.error(new TdError(optional.cause().code, optional.cause().message)); - } - }).retry(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdUpdateObj.java b/src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdUpdateObj.java deleted file mode 100644 index dab815e..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/AsyncTdUpdateObj.java +++ /dev/null @@ -1,49 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.AuthorizationState; -import java.util.Objects; -import java.util.StringJoiner; - -public class AsyncTdUpdateObj { - private final AuthorizationState state; - private final TdApi.Object update; - - public AsyncTdUpdateObj(AuthorizationState state, TdApi.Object update) { - this.state = state; - this.update = update; - } - - public AuthorizationState getState() { - return state; - } - - public TdApi.Object getUpdate() { - return update; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - AsyncTdUpdateObj that = (AsyncTdUpdateObj) o; - return Objects.equals(state, that.state) && Objects.equals(update, that.update); - } - - @Override - public int hashCode() { - return Objects.hash(state, update); - } - - @Override - public String toString() { - return new StringJoiner(", ", AsyncTdUpdateObj.class.getSimpleName() + "[", "]") - .add("state=" + state) - .add("update=" + update) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/FatalErrorHandler.java b/src/main/java/it/tdlight/tdlibsession/td/easy/FatalErrorHandler.java deleted file mode 100644 index 3ba8a3b..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/FatalErrorHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import it.tdlight.tdlibsession.FatalErrorType; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -public interface FatalErrorHandler { - @NotNull Mono onFatalError(FatalErrorType error); -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/Parameter.java b/src/main/java/it/tdlight/tdlibsession/td/easy/Parameter.java deleted file mode 100644 index 8c8be84..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/Parameter.java +++ /dev/null @@ -1,9 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -public enum Parameter { - ASK_FIRST_NAME, - ASK_LAST_NAME, - ASK_CODE, - ASK_PASSWORD, - NOTIFY_LINK -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfo.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfo.java deleted file mode 100644 index e0a22b5..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfo.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -public interface ParameterInfo { - -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoCode.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoCode.java deleted file mode 100644 index 08e1a40..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoCode.java +++ /dev/null @@ -1,47 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import it.tdlight.jni.TdApi.AuthenticationCodeType; -import java.util.StringJoiner; - -public class ParameterInfoCode implements ParameterInfo { - private final String phoneNumber; - private final AuthenticationCodeType nextType; - private final int timeout; - private final AuthenticationCodeType type; - - public ParameterInfoCode(String phoneNumber, - AuthenticationCodeType nextType, - int timeout, - AuthenticationCodeType type) { - this.phoneNumber = phoneNumber; - this.nextType = nextType; - this.timeout = timeout; - this.type = type; - } - - public String getPhoneNumber() { - return phoneNumber; - } - - public AuthenticationCodeType getNextType() { - return nextType; - } - - public int getTimeout() { - return timeout; - } - - public AuthenticationCodeType getType() { - return type; - } - - @Override - public String toString() { - return new StringJoiner(", ", ParameterInfoCode.class.getSimpleName() + "[", "]") - .add("phoneNumber='" + phoneNumber + "'") - .add("nextType=" + nextType) - .add("timeout=" + timeout) - .add("type=" + type) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoEmpty.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoEmpty.java deleted file mode 100644 index 13f0315..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoEmpty.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -public class ParameterInfoEmpty implements ParameterInfo { - -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoNotifyLink.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoNotifyLink.java deleted file mode 100644 index 17c586e..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoNotifyLink.java +++ /dev/null @@ -1,13 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -public class ParameterInfoNotifyLink implements ParameterInfo { - private final String link; - - public ParameterInfoNotifyLink(String link) { - this.link = link; - } - - public String getLink() { - return link; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoPasswordHint.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoPasswordHint.java deleted file mode 100644 index cea17f0..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterInfoPasswordHint.java +++ /dev/null @@ -1,13 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -public class ParameterInfoPasswordHint implements ParameterInfo { - private final String hint; - - public ParameterInfoPasswordHint(String hint) { - this.hint = hint; - } - - public String getHint() { - return hint; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterRequestHandler.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterRequestHandler.java deleted file mode 100644 index 7d6193f..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ParameterRequestHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import reactor.core.publisher.Mono; - -public interface ParameterRequestHandler { - Mono onParameterRequest(Parameter parameter, ParameterInfo parameterInfo); -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/ScannerParameterRequestHandler.java b/src/main/java/it/tdlight/tdlibsession/td/easy/ScannerParameterRequestHandler.java deleted file mode 100644 index b8f0f8a..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/ScannerParameterRequestHandler.java +++ /dev/null @@ -1,47 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import it.tdlight.common.utils.ScannerUtils; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -public class ScannerParameterRequestHandler implements ParameterRequestHandler { - - private final String botName; - - public ScannerParameterRequestHandler(String botName) { - this.botName = botName; - } - - @Override - public Mono onParameterRequest(Parameter parameter, ParameterInfo parameterInfo) { - return Mono.fromCallable(() -> { - String question; - boolean trim = false; - switch (parameter) { - case ASK_FIRST_NAME: question = "Enter first name"; trim = true; break; - case ASK_LAST_NAME: question = "Enter last name"; trim = true; break; - case ASK_CODE: question = "Enter authentication code"; trim = true; break; - case ASK_PASSWORD: - question = "Enter your password"; - String passwordMessage = "Password authorization of '" + this.botName + "':"; - String hint = ((ParameterInfoPasswordHint) parameterInfo).getHint(); - if (hint != null && !hint.isBlank()) { - passwordMessage += "\n\tHint: " + hint; - } - System.out.println(passwordMessage); - break; - case NOTIFY_LINK: - System.out.println("Please confirm this login link on another device: " - + ((ParameterInfoNotifyLink) parameterInfo).getLink()); - return ""; - default: question = parameter.toString(); break; - } - var result = ScannerUtils.askParameter(this.botName, question); - if (trim) { - return result.trim(); - } else { - return result; - } - }).subscribeOn(Schedulers.boundedElastic()); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/easy/TdEasySettings.java b/src/main/java/it/tdlight/tdlibsession/td/easy/TdEasySettings.java deleted file mode 100644 index cdc54d7..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/easy/TdEasySettings.java +++ /dev/null @@ -1,310 +0,0 @@ -package it.tdlight.tdlibsession.td.easy; - -import java.util.Objects; -import org.jetbrains.annotations.Nullable; -import reactor.core.publisher.Mono; - -public class TdEasySettings { - public final boolean useTestDc; - public final String databaseDirectory; - public final String filesDirectory; - public final boolean useFileDatabase; - public final boolean useChatInfoDatabase; - public final boolean useMessageDatabase; - public final int apiId; - public final String apiHash; - public final String systemLanguageCode; - public final String deviceModel; - public final String systemVersion; - public final String applicationVersion; - public final boolean enableStorageOptimizer; - public final boolean ignoreFileNames; - private final Long phoneNumber; - private final String botToken; - private final ParameterRequestHandler parameterRequestHandler; - private final FatalErrorHandler fatalErrorHandler; - - public TdEasySettings(boolean useTestDc, - String databaseDirectory, - String filesDirectory, - boolean useFileDatabase, - boolean useChatInfoDatabase, - boolean useMessageDatabase, - int apiId, - String apiHash, - String systemLanguageCode, - String deviceModel, - String systemVersion, - String applicationVersion, - boolean enableStorageOptimizer, - boolean ignoreFileNames, - @Nullable Long phoneNumber, - @Nullable String botToken, - @Nullable ParameterRequestHandler parameterRequestHandler, - @Nullable FatalErrorHandler fatalErrorHandler) { - this.useTestDc = useTestDc; - this.databaseDirectory = databaseDirectory; - this.filesDirectory = filesDirectory; - this.useFileDatabase = useFileDatabase; - this.useChatInfoDatabase = useChatInfoDatabase; - this.useMessageDatabase = useMessageDatabase; - this.apiId = apiId; - this.apiHash = apiHash; - this.systemLanguageCode = systemLanguageCode; - this.deviceModel = deviceModel; - this.systemVersion = systemVersion; - this.applicationVersion = applicationVersion; - this.enableStorageOptimizer = enableStorageOptimizer; - this.ignoreFileNames = ignoreFileNames; - this.phoneNumber = phoneNumber; - this.botToken = botToken; - if ((phoneNumber == null) == (botToken == null)) { - throw new IllegalArgumentException("You must set a phone number or a bot token"); - } - if (parameterRequestHandler == null) { - if (botToken != null) { - parameterRequestHandler = new ScannerParameterRequestHandler("bot_" + botToken.split(":")[0]); - } else { - parameterRequestHandler = new ScannerParameterRequestHandler("+" + phoneNumber); - } - } - this.parameterRequestHandler = parameterRequestHandler; - if (fatalErrorHandler == null) { - fatalErrorHandler = error -> Mono.empty(); - } - this.fatalErrorHandler = fatalErrorHandler; - } - - public boolean isPhoneNumberSet() { - return phoneNumber != null; - } - - public long getPhoneNumber() { - return Objects.requireNonNull(phoneNumber, "You must set a phone number"); - } - - public boolean isBotTokenSet() { - return botToken != null; - } - - public String getBotToken() { - return Objects.requireNonNull(botToken, "You must set a bot token"); - } - - public ParameterRequestHandler getParameterRequestHandler() { - return Objects.requireNonNull(parameterRequestHandler, "You must set a parameter request handler"); - } - - public FatalErrorHandler getFatalErrorHandler() { - return Objects.requireNonNull(fatalErrorHandler, "You must set a fatal error handler"); - } - - public static Builder newBuilder() { - return new Builder(); - } - - - public static class Builder { - private boolean useTestDc = false; - private String databaseDirectory = "jtdlib-database"; - private String filesDirectory = "jtdlib-files"; - private boolean useFileDatabase = true; - private boolean useChatInfoDatabase = true; - private boolean useMessageDatabase = true; - private int apiId = 376588; - private String apiHash = "2143fdfc2bbba3ec723228d2f81336c9"; - private String systemLanguageCode = "en"; - private String deviceModel = "JTDLib"; - private String systemVersion = "JTDLib"; - private String applicationVersion = "1.0"; - private boolean enableStorageOptimizer = false; - private boolean ignoreFileNames = false; - @Nullable - private Long phoneNumber = null; - @Nullable - private String botToken = null; - private ParameterRequestHandler parameterRequestHandler; - @Nullable - private FatalErrorHandler fatalErrorHandler; - - private Builder() { - - } - - public boolean isUseTestDc() { - return useTestDc; - } - - public Builder setUseTestDc(boolean useTestDc) { - this.useTestDc = useTestDc; - return this; - } - - public String getDatabaseDirectory() { - return databaseDirectory; - } - - public Builder setDatabaseDirectory(String databaseDirectory) { - this.databaseDirectory = databaseDirectory; - return this; - } - - public String getFilesDirectory() { - return filesDirectory; - } - - public Builder setFilesDirectory(String filesDirectory) { - this.filesDirectory = filesDirectory; - return this; - } - - public boolean isUseFileDatabase() { - return useFileDatabase; - } - - public Builder setUseFileDatabase(boolean useFileDatabase) { - this.useFileDatabase = useFileDatabase; - return this; - } - - public boolean isUseChatInfoDatabase() { - return useChatInfoDatabase; - } - - public Builder setUseChatInfoDatabase(boolean useChatInfoDatabase) { - this.useChatInfoDatabase = useChatInfoDatabase; - return this; - } - - public boolean isUseMessageDatabase() { - return useMessageDatabase; - } - - public Builder setUseMessageDatabase(boolean useMessageDatabase) { - this.useMessageDatabase = useMessageDatabase; - return this; - } - - public int getApiId() { - return apiId; - } - - public Builder setApiId(int apiId) { - this.apiId = apiId; - return this; - } - - public String getApiHash() { - return apiHash; - } - - public Builder setApiHash(String apiHash) { - this.apiHash = apiHash; - return this; - } - - public String getSystemLanguageCode() { - return systemLanguageCode; - } - - public Builder setSystemLanguageCode(String systemLanguageCode) { - this.systemLanguageCode = systemLanguageCode; - return this; - } - - public String getDeviceModel() { - return deviceModel; - } - - public Builder setDeviceModel(String deviceModel) { - this.deviceModel = deviceModel; - return this; - } - - public String getSystemVersion() { - return systemVersion; - } - - public Builder setSystemVersion(String systemVersion) { - this.systemVersion = systemVersion; - return this; - } - - public String getApplicationVersion() { - return applicationVersion; - } - - public Builder setApplicationVersion(String applicationVersion) { - this.applicationVersion = applicationVersion; - return this; - } - - public boolean isEnableStorageOptimizer() { - return enableStorageOptimizer; - } - - public Builder setEnableStorageOptimizer(boolean enableStorageOptimizer) { - this.enableStorageOptimizer = enableStorageOptimizer; - return this; - } - - public boolean isIgnoreFileNames() { - return ignoreFileNames; - } - - public Builder setIgnoreFileNames(boolean ignoreFileNames) { - this.ignoreFileNames = ignoreFileNames; - return this; - } - - public Builder setPhoneNumber(long phoneNumber) { - this.phoneNumber = phoneNumber; - return this; - } - - public Builder setBotToken(String botToken) { - this.botToken = botToken; - return this; - } - - public Builder setParameterRequestHandler(ParameterRequestHandler parameterRequestHandler) { - this.parameterRequestHandler = parameterRequestHandler; - return this; - } - - public ParameterRequestHandler getParameterRequestHandler() { - return parameterRequestHandler; - } - - public Builder setFatalErrorHandler(FatalErrorHandler fatalErrorHandler) { - this.fatalErrorHandler = fatalErrorHandler; - return this; - } - - public @Nullable FatalErrorHandler getFatalErrorHandler() { - return fatalErrorHandler; - } - - public TdEasySettings build() { - return new TdEasySettings(useTestDc, - databaseDirectory, - filesDirectory, - useFileDatabase, - useChatInfoDatabase, - useMessageDatabase, - apiId, - apiHash, - systemLanguageCode, - deviceModel, - systemVersion, - applicationVersion, - enableStorageOptimizer, - ignoreFileNames, - phoneNumber, - botToken, - parameterRequestHandler, - fatalErrorHandler - ); - } - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/AsyncTdMiddle.java b/src/main/java/it/tdlight/tdlibsession/td/middle/AsyncTdMiddle.java deleted file mode 100644 index 0ff2cc7..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/AsyncTdMiddle.java +++ /dev/null @@ -1,28 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import it.tdlight.jni.TdApi; -import it.tdlight.tdlibsession.td.TdResult; -import java.time.Duration; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public interface AsyncTdMiddle { - - Mono initialize(); - - /** - * Receives incoming updates from TDLib. - * - * @return Updates (or Error if received a fatal error. A fatal error means that the client is no longer working) - */ - Flux receive(); - - /** - * Sends request to TDLib. May be called from any thread. - * - * @param request Request to TDLib. - * @param timeout Timeout. - * @param executeSync Execute the function synchronously. - */ - Mono> execute(TdApi.Function request, Duration timeout, boolean executeSync); -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessage.java b/src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessage.java deleted file mode 100644 index 281c349..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessage.java +++ /dev/null @@ -1,49 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.reactivex.core.buffer.Buffer; -import java.util.Objects; -import java.util.StringJoiner; - -public final class EndSessionMessage { - - private final int id; - private final Buffer binlog; - - public EndSessionMessage(int id, Buffer binlog) { - this.id = id; - this.binlog = binlog; - } - - public int id() { - return id; - } - - public Buffer binlog() { - return binlog; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EndSessionMessage that = (EndSessionMessage) o; - return id == that.id && Objects.equals(binlog, that.binlog); - } - - @Override - public int hashCode() { - return Objects.hash(id, binlog); - } - - @Override - public String toString() { - return new StringJoiner(", ", EndSessionMessage.class.getSimpleName() + "[", "]") - .add("id=" + id) - .add("binlog=" + binlog) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessageCodec.java deleted file mode 100644 index 1f72ab1..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/EndSessionMessageCodec.java +++ /dev/null @@ -1,46 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.utils.BufferUtils; - -public class EndSessionMessageCodec implements MessageCodec { - - private final String codecName; - - public EndSessionMessageCodec() { - super(); - this.codecName = "EndSessionMessageCodec"; - } - - @Override - public void encodeToWire(Buffer buffer, EndSessionMessage t) { - BufferUtils.encode(buffer, os -> { - os.writeInt(t.id()); - BufferUtils.writeBuf(os, t.binlog()); - }); - } - - @Override - public EndSessionMessage decodeFromWire(int pos, Buffer buffer) { - return BufferUtils.decode(pos, buffer, is -> new EndSessionMessage(is.readInt(), BufferUtils.rxReadBuf(is))); - } - - @Override - public EndSessionMessage transform(EndSessionMessage t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/ExecuteObject.java b/src/main/java/it/tdlight/tdlibsession/td/middle/ExecuteObject.java deleted file mode 100644 index 140db39..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/ExecuteObject.java +++ /dev/null @@ -1,93 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Function; -import java.time.Duration; -import java.util.Objects; -import java.util.StringJoiner; - -public class ExecuteObject { - - private static final TdExecuteObjectMessageCodec realCodec = new TdExecuteObjectMessageCodec<>(); - - private boolean executeDirectly; - private TdApi.Function request; - private Duration timeout; - private int pos; - private Buffer buffer; - - public ExecuteObject(boolean executeDirectly, Function request, Duration timeout) { - if (request == null) throw new NullPointerException(); - - this.executeDirectly = executeDirectly; - this.request = request; - this.timeout = timeout; - } - - public ExecuteObject(int pos, Buffer buffer) { - this.pos = pos; - this.buffer = buffer; - } - - private void tryDecode() { - if (request == null) { - @SuppressWarnings("unchecked") - ExecuteObject data = (ExecuteObject) realCodec.decodeFromWire(pos, buffer); - this.executeDirectly = data.executeDirectly; - this.request = data.request; - this.buffer = null; - this.timeout = data.timeout; - } - } - - public boolean isExecuteDirectly() { - tryDecode(); - return executeDirectly; - } - - public TdApi.Function getRequest() { - tryDecode(); - return request; - } - - public Duration getTimeout() { - tryDecode(); - return timeout; - } - - @Override - public boolean equals(Object o) { - tryDecode(); - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ExecuteObject that = (ExecuteObject) o; - - if (executeDirectly != that.executeDirectly) { - return false; - } - return Objects.equals(request, that.request); - } - - @Override - public int hashCode() { - tryDecode(); - int result = (executeDirectly ? 1 : 0); - result = 31 * result + (request != null ? request.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return new StringJoiner(", ", ExecuteObject.class.getSimpleName() + "[", "]") - .add("executeDirectly=" + executeDirectly) - .add("request=" + request) - .add("timeout=" + timeout) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdExecuteObjectMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdExecuteObjectMessageCodec.java deleted file mode 100644 index 78d8a6a..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdExecuteObjectMessageCodec.java +++ /dev/null @@ -1,43 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.jni.TdApi; - -public class LazyTdExecuteObjectMessageCodec - implements MessageCodec, ExecuteObject> { - - private static final TdExecuteObjectMessageCodec realCodec = new TdExecuteObjectMessageCodec<>(); - - public LazyTdExecuteObjectMessageCodec() { - super(); - } - - @Override - public void encodeToWire(Buffer buffer, ExecuteObject t) { - realCodec.encodeToWire(buffer, t); - } - - @Override - public ExecuteObject decodeFromWire(int pos, Buffer buffer) { - return new ExecuteObject<>(pos, buffer); - } - - @Override - public ExecuteObject transform(ExecuteObject t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return "ExecuteObjectCodec"; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultListMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultListMessageCodec.java deleted file mode 100644 index 400000e..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultListMessageCodec.java +++ /dev/null @@ -1,44 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; - -@SuppressWarnings("rawtypes") -public class LazyTdResultListMessageCodec implements MessageCodec { - - private final String codecName; - private static final TdResultListMessageCodec realCodec = new TdResultListMessageCodec(); - - public LazyTdResultListMessageCodec() { - super(); - this.codecName = "TdOptListCodec"; - } - - @Override - public void encodeToWire(Buffer buffer, TdResultList t) { - realCodec.encodeToWire(buffer, t); - } - - @Override - public TdResultList decodeFromWire(int pos, Buffer buffer) { - return new TdResultList(pos, buffer); - } - - @Override - public TdResultList transform(TdResultList t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultMessageCodec.java deleted file mode 100644 index c48b191..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/LazyTdResultMessageCodec.java +++ /dev/null @@ -1,44 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; - -@SuppressWarnings("rawtypes") -public class LazyTdResultMessageCodec implements MessageCodec { - - private final String codecName; - private static final TdResultMessageCodec realCodec = new TdResultMessageCodec(); - - public LazyTdResultMessageCodec() { - super(); - this.codecName = "TdResultCodec"; - } - - @Override - public void encodeToWire(Buffer buffer, TdResultMessage t) { - realCodec.encodeToWire(buffer, t); - } - - @Override - public TdResultMessage decodeFromWire(int pos, Buffer buffer) { - return new TdResultMessage(pos, buffer); - } - - @Override - public TdResultMessage transform(TdResultMessage t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessage.java b/src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessage.java deleted file mode 100644 index 1330b84..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessage.java +++ /dev/null @@ -1,73 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.core.buffer.Buffer; -import java.util.Objects; -import java.util.StringJoiner; - -public final class StartSessionMessage { - - private final long id; - private final String alias; - private final Buffer binlog; - private final long binlogDate; - private final JsonObject implementationDetails; - - public StartSessionMessage(long id, String alias, Buffer binlog, long binlogDate, JsonObject implementationDetails) { - this.id = id; - this.alias = alias; - this.binlog = binlog; - this.binlogDate = binlogDate; - this.implementationDetails = implementationDetails; - } - - public long id() { - return id; - } - - public String alias() { - return alias; - } - - public Buffer binlog() { - return binlog; - } - - public long binlogDate() { - return binlogDate; - } - - public JsonObject implementationDetails() { - return implementationDetails; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - StartSessionMessage that = (StartSessionMessage) o; - return id == that.id && binlogDate == that.binlogDate && Objects.equals(alias, that.alias) && Objects.equals(binlog, - that.binlog - ) && Objects.equals(implementationDetails, that.implementationDetails); - } - - @Override - public int hashCode() { - return Objects.hash(id, alias, binlog, binlogDate, implementationDetails); - } - - @Override - public String toString() { - return new StringJoiner(", ", StartSessionMessage.class.getSimpleName() + "[", "]") - .add("id=" + id) - .add("alias='" + alias + "'") - .add("binlog=" + binlog) - .add("binlogDate=" + binlogDate) - .add("implementationDetails=" + implementationDetails) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessageCodec.java deleted file mode 100644 index c54264d..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/StartSessionMessageCodec.java +++ /dev/null @@ -1,56 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import io.vertx.core.json.JsonObject; -import it.tdlight.utils.BufferUtils; -import org.warp.commonutils.serialization.UTFUtils; - -public class StartSessionMessageCodec implements MessageCodec { - - private final String codecName; - - public StartSessionMessageCodec() { - super(); - this.codecName = "StartSessionMessageCodec"; - } - - @Override - public void encodeToWire(Buffer buffer, StartSessionMessage t) { - BufferUtils.encode(buffer, os -> { - os.writeLong(t.id()); - UTFUtils.writeUTF(os, t.alias()); - BufferUtils.writeBuf(os, t.binlog()); - os.writeLong(t.binlogDate()); - UTFUtils.writeUTF(os, t.implementationDetails().toString()); - }); - } - - @Override - public StartSessionMessage decodeFromWire(int pos, Buffer buffer) { - return BufferUtils.decode(pos, buffer, is -> new StartSessionMessage(is.readLong(), - UTFUtils.readUTF(is), - BufferUtils.rxReadBuf(is), - is.readLong(), - new JsonObject(UTFUtils.readUTF(is)) - )); - } - - @Override - public StartSessionMessage transform(StartSessionMessage t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdClusterManager.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdClusterManager.java deleted file mode 100644 index ef8bbd4..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdClusterManager.java +++ /dev/null @@ -1,303 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import com.hazelcast.config.Config; -import com.hazelcast.config.MapConfig; -import com.hazelcast.config.MultiMapConfig; -import com.hazelcast.config.cp.SemaphoreConfig; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Handler; -import io.vertx.core.VertxOptions; -import io.vertx.core.eventbus.DeliveryOptions; -import io.vertx.core.eventbus.MessageCodec; -import io.vertx.core.http.ClientAuth; -import io.vertx.core.metrics.MetricsOptions; -import io.vertx.core.net.JksOptions; -import io.vertx.core.spi.cluster.ClusterManager; -import io.vertx.reactivex.core.Vertx; -import io.vertx.reactivex.core.eventbus.EventBus; -import io.vertx.reactivex.core.eventbus.Message; -import io.vertx.reactivex.core.eventbus.MessageConsumer; -import io.vertx.reactivex.core.shareddata.SharedData; -import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager; -import it.tdlight.common.ConstructorDetector; -import it.tdlight.jni.TdApi; -import it.tdlight.utils.MonoUtils; -import java.nio.channels.AlreadyBoundException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.Nullable; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -public class TdClusterManager { - - private static final AtomicBoolean definedMasterCluster = new AtomicBoolean(false); - private static final AtomicBoolean definedNodesCluster = new AtomicBoolean(false); - private final ClusterManager mgr; - private final VertxOptions vertxOptions; - private final Vertx vertx; - private final boolean isMaster; - - @SuppressWarnings({"unchecked", "rawtypes"}) - public TdClusterManager(ClusterManager mgr, VertxOptions vertxOptions, Vertx vertx, boolean isMaster) { - this.isMaster = isMaster; - this.mgr = mgr; - this.vertxOptions = vertxOptions; - this.vertx = vertx; - - if (vertx != null && vertx.eventBus() != null) { - vertx - .eventBus() - .getDelegate() - .registerDefaultCodec(TdResultList.class, new TdResultListMessageCodec()) - .registerDefaultCodec(ExecuteObject.class, new TdExecuteObjectMessageCodec()) - .registerDefaultCodec(TdResultMessage.class, new TdResultMessageCodec()) - .registerDefaultCodec(StartSessionMessage.class, new StartSessionMessageCodec()) - .registerDefaultCodec(EndSessionMessage.class, new EndSessionMessageCodec()); - for (Class declaredClass : TdApi.class.getDeclaredClasses()) { - if (declaredClass.isAssignableFrom(declaredClass)) { - vertx.eventBus().getDelegate().registerDefaultCodec(declaredClass, new TdMessageCodec(declaredClass)); - } - } - } - } - - public static Mono ofMaster(@Nullable JksOptions keyStoreOptions, - @Nullable JksOptions trustStoreOptions, - boolean onlyLocal, - String masterHostname, - String netInterface, - int port, - Set nodesAddresses) { - if (definedMasterCluster.compareAndSet(false, true)) { - var vertxOptions = new VertxOptions(); - netInterface = onlyLocal ? "127.0.0.1" : netInterface; - Config cfg; - if (!onlyLocal) { - cfg = new Config(); - cfg.setInstanceName("Master"); - } else { - cfg = null; - } - return of(cfg, - vertxOptions, - keyStoreOptions, trustStoreOptions, masterHostname, netInterface, port, nodesAddresses, true); - } else { - return Mono.error(new AlreadyBoundException()); - } - } - - public static Mono ofNodes(@Nullable JksOptions keyStoreOptions, - @Nullable JksOptions trustStoreOptions, - boolean onlyLocal, - String masterHostname, - String netInterface, - int port, - Set nodesAddresses) { - return Mono.defer(() -> { - if (definedNodesCluster.compareAndSet(false, true)) { - var vertxOptions = new VertxOptions(); - var netInterfaceF = onlyLocal ? "127.0.0.1" : netInterface; - Config cfg; - if (!onlyLocal) { - cfg = new Config(); - cfg.setInstanceName("Node-" + new Random().nextLong()); - } else { - cfg = null; - } - return of(cfg, vertxOptions, keyStoreOptions, trustStoreOptions, masterHostname, netInterfaceF, port, - nodesAddresses, false); - } else { - return Mono.error(new AlreadyBoundException()); - } - }); - } - - public static Mono of(@Nullable Config cfg, - VertxOptions vertxOptions, - @Nullable JksOptions keyStoreOptions, - @Nullable JksOptions trustStoreOptions, - String masterHostname, - String netInterface, - int port, - Set nodesAddresses, - boolean isMaster) { - ClusterManager mgr; - if (cfg != null) { - cfg.getNetworkConfig().setPortCount(1); - cfg.getNetworkConfig().setPort(port); - cfg.getNetworkConfig().setPortAutoIncrement(false); - cfg.getPartitionGroupConfig().setEnabled(false); - cfg.addMapConfig(new MapConfig().setName("__vertx.haInfo").setBackupCount(1)); - cfg.addMapConfig(new MapConfig().setName("__vertx.nodeInfo").setBackupCount(1)); - cfg - .getCPSubsystemConfig() - .setCPMemberCount(0) - .setSemaphoreConfigs(Map.of("__vertx.*", new SemaphoreConfig().setInitialPermits(1).setJDKCompatible(false))); - cfg.addMultiMapConfig(new MultiMapConfig().setName("__vertx.subs").setBackupCount(1).setValueCollectionType("SET")); - cfg.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false); - cfg.getNetworkConfig().getJoin().getAwsConfig().setEnabled(false); - cfg.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true); - var addresses = new ArrayList<>(nodesAddresses); - cfg.getNetworkConfig().getJoin().getTcpIpConfig().setMembers(addresses); - cfg.getNetworkConfig().getInterfaces().clear(); - cfg.getNetworkConfig().getInterfaces().setInterfaces(Collections.singleton(netInterface)).setEnabled(true); - cfg.getNetworkConfig().setOutboundPorts(Collections.singleton(0)); - - cfg.setProperty("hazelcast.logging.type", "slf4j"); - cfg.setProperty("hazelcast.wait.seconds.before.join", "0"); - cfg.setProperty("hazelcast.tcp.join.port.try.count", "5"); - cfg.setProperty("hazelcast.socket.bind.any", "false"); - cfg.setProperty("hazelcast.health.monitoring.level", "OFF"); - cfg.setClusterName("tdlib-session-container"); - mgr = new HazelcastClusterManager(cfg); - vertxOptions.setClusterManager(mgr); - vertxOptions.getEventBusOptions().setConnectTimeout(120000); - //vertxOptions.getEventBusOptions().setIdleTimeout(60); - //vertxOptions.getEventBusOptions().setSsl(false); - - vertxOptions.getEventBusOptions().setSslHandshakeTimeout(120000).setSslHandshakeTimeoutUnit(TimeUnit.MILLISECONDS); - if (keyStoreOptions != null && trustStoreOptions != null) { - vertxOptions.getEventBusOptions().setKeyStoreOptions(keyStoreOptions); - vertxOptions.getEventBusOptions().setTrustStoreOptions(trustStoreOptions); - vertxOptions - .getEventBusOptions() - .setUseAlpn(true) - .setSsl(true) - .setEnabledSecureTransportProtocols(Set.of("TLSv1.3", "TLSv1.2")); - } else { - vertxOptions - .getEventBusOptions() - .setSsl(false); - } - vertxOptions.getEventBusOptions().setHost(masterHostname); - vertxOptions.getEventBusOptions().setPort(port + 1); - vertxOptions.getEventBusOptions().setClientAuth(ClientAuth.REQUIRED); - } else { - mgr = null; - vertxOptions.setClusterManager(null); - } - - vertxOptions.setPreferNativeTransport(true); - vertxOptions.setMetricsOptions(new MetricsOptions().setEnabled(false)); - // check for blocked threads every 5s - vertxOptions.setBlockedThreadCheckInterval(5); - vertxOptions.setBlockedThreadCheckIntervalUnit(TimeUnit.SECONDS); - // warn if an event loop thread handler took more than 10s to execute - vertxOptions.setMaxEventLoopExecuteTime(10); - vertxOptions.setMaxEventLoopExecuteTimeUnit(TimeUnit.SECONDS); - // warn if an worker thread handler took more than 10s to execute - vertxOptions.setMaxWorkerExecuteTime(10); - vertxOptions.setMaxWorkerExecuteTimeUnit(TimeUnit.SECONDS); - // log the stack trace if an event loop or worker handler took more than 20s to execute - vertxOptions.setWarningExceptionTime(100); - vertxOptions.setWarningExceptionTimeUnit(TimeUnit.MILLISECONDS); - - return Mono - .defer(() -> { - if (mgr != null) { - return Vertx.rxClusteredVertx(vertxOptions).as(MonoUtils::toMono).subscribeOn(Schedulers.boundedElastic()); - } else { - return Mono.just(Vertx.vertx(vertxOptions)); - } - }) - .flatMap(vertx -> Mono - .fromCallable(() -> new TdClusterManager(mgr, vertxOptions, vertx, isMaster)) - .subscribeOn(Schedulers.boundedElastic()) - ); - } - - public Vertx getVertx() { - return vertx; - } - - public EventBus getEventBus() { - return vertx.eventBus(); - } - - public VertxOptions getVertxOptions() { - return vertxOptions; - } - - public DeliveryOptions newDeliveryOpts() { - return new DeliveryOptions().setSendTimeout(120000); - } - - /** - * - * @param messageCodec - * @param - * @return true if registered, false if already registered - */ - public boolean registerCodec(MessageCodec messageCodec) { - try { - vertx.eventBus().registerCodec(messageCodec); - return true; - } catch (IllegalStateException ex) { - if (ex.getMessage().startsWith("Already a default codec registered for class")) { - return false; - } - if (ex.getMessage().startsWith("Already a codec registered with name")) { - return false; - } - throw ex; - } - } - - /** - * Create a message consumer against the specified address. - *

- * The returned consumer is not yet registered - * at the address, registration will be effective when {@link MessageConsumer#handler(io.vertx.core.Handler)} - * is called. - * - * @param address the address that it will register it at - * @param localOnly if you want to receive only local messages - * @return the event bus message consumer - */ - public MessageConsumer consumer(String address, boolean localOnly) { - if (localOnly) { - return vertx.eventBus().localConsumer(address); - } else { - return vertx.eventBus().consumer(address); - } - } - - /** - * Create a consumer and register it against the specified address. - * - * @param address the address that will register it at - * @param localOnly if you want to receive only local messages - * @param handler the handler that will process the received messages - * - * @return the event bus message consumer - */ - public MessageConsumer consumer(String address, boolean localOnly, Handler> handler) { - if (localOnly) { - return vertx.eventBus().localConsumer(address, handler); - } else { - return vertx.eventBus().consumer(address, handler); - } - } - - public DeploymentOptions newDeploymentOpts() { - return new DeploymentOptions().setWorkerPoolName("td-main-pool"); - } - - public SharedData getSharedData() { - return vertx.sharedData(); - } - - public Mono close() { - return Mono.from(vertx.rxClose().toFlowable()).then(Mono.fromRunnable(() -> { - if (isMaster) { - definedMasterCluster.set(false); - } - })); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdExecuteObjectMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdExecuteObjectMessageCodec.java deleted file mode 100644 index 6227594..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdExecuteObjectMessageCodec.java +++ /dev/null @@ -1,53 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.utils.BufferUtils; -import java.time.Duration; - -public class TdExecuteObjectMessageCodec - implements MessageCodec, ExecuteObject> { - - public TdExecuteObjectMessageCodec() { - super(); - } - - @Override - public void encodeToWire(Buffer buffer, ExecuteObject t) { - BufferUtils.encode(buffer, os -> { - os.writeBoolean(t.isExecuteDirectly()); - t.getRequest().serialize(os); - os.writeLong(t.getTimeout().toMillis()); - }); - } - - @SuppressWarnings("unchecked") - @Override - public ExecuteObject decodeFromWire(int pos, Buffer buffer) { - return BufferUtils.decode(pos, buffer, is -> new ExecuteObject( - is.readBoolean(), - (Function) TdApi.Deserializer.deserialize(is), - Duration.ofMillis(is.readLong()) - )); - } - - @Override - public ExecuteObject transform(ExecuteObject t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return "ExecuteObjectCodec"; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdMessageCodec.java deleted file mode 100644 index 01fffce..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdMessageCodec.java +++ /dev/null @@ -1,57 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.jni.TdApi; -import java.io.IOException; - -public class TdMessageCodec implements MessageCodec { - - private final Class clazz; - private final String codecName; - - public TdMessageCodec(Class clazz) { - super(); - this.clazz = clazz; - this.codecName = clazz.getSimpleName() + "TdCodec"; - } - - @Override - public void encodeToWire(Buffer buffer, T t) { - try (var os = new ByteBufOutputStream(buffer.getByteBuf())) { - t.serialize(os); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public T decodeFromWire(int pos, Buffer buffer) { - try (var is = new ByteBufInputStream(buffer.getByteBuf(), pos)) { - //noinspection unchecked - return (T) TdApi.Deserializer.deserialize(is); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public T transform(T t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultList.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultList.java deleted file mode 100644 index 2ab589d..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultList.java +++ /dev/null @@ -1,93 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Error; -import java.util.List; -import java.util.Objects; -import java.util.StringJoiner; - -public class TdResultList { - - private static final TdResultListMessageCodec realCodec = new TdResultListMessageCodec(); - - private List values; - private Error error; - private int pos; - private Buffer buffer; - - public TdResultList(List values) { - this.values = values; - this.error = null; - if (values == null) throw new NullPointerException("Null message"); - } - - public TdResultList(TdApi.Error error) { - this.values = null; - this.error = error; - if (error == null) throw new NullPointerException("Null message"); - } - - public TdResultList(int pos, Buffer buffer) { - this.pos = pos; - this.buffer = buffer; - } - - private void tryDecode() { - if (error == null && values == null) { - var value = realCodec.decodeFromWire(pos, buffer); - this.values = value.values; - this.error = value.error; - this.buffer = null; - } - } - - public List value() { - tryDecode(); - return values; - } - - public TdApi.Error error() { - tryDecode(); - return error; - } - - public boolean succeeded() { - tryDecode(); - return error == null && values != null; - } - - @Override - public boolean equals(Object o) { - tryDecode(); - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - TdResultList that = (TdResultList) o; - - if (!Objects.equals(values, that.values)) { - return false; - } - return Objects.equals(error, that.error); - } - - @Override - public int hashCode() { - tryDecode(); - int result = values != null ? values.hashCode() : 0; - result = 31 * result + (error != null ? error.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return new StringJoiner(", ", TdResultList.class.getSimpleName() + "[", "]") - .add("values=" + values) - .add("error=" + error) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultListMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultListMessageCodec.java deleted file mode 100644 index ea27041..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultListMessageCodec.java +++ /dev/null @@ -1,64 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Error; -import it.tdlight.utils.BufferUtils; -import java.util.ArrayList; - -public class TdResultListMessageCodec implements MessageCodec { - - public TdResultListMessageCodec() { - super(); - } - - @Override - public void encodeToWire(Buffer buffer, TdResultList ts) { - BufferUtils.encode(buffer, os -> { - if (ts.succeeded()) { - os.writeBoolean(true); - var t = ts.value(); - os.writeInt(t.size()); - for (TdApi.Object t1 : t) { - t1.serialize(os); - } - } else { - os.writeBoolean(false); - ts.error().serialize(os); - } - }); - } - - @Override - public TdResultList decodeFromWire(int pos, Buffer buffer) { - return BufferUtils.decode(pos, buffer, is -> { - if (is.readBoolean()) { - var size = is.readInt(); - ArrayList list = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - list.add((TdApi.Object) TdApi.Deserializer.deserialize(is)); - } - return new TdResultList(list); - } else { - return new TdResultList((Error) TdApi.Deserializer.deserialize(is)); - } - }); - } - - @Override - public TdResultList transform(TdResultList ts) { - return ts; - } - - @Override - public String name() { - return "TdOptListCodec"; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessage.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessage.java deleted file mode 100644 index 8d6147c..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessage.java +++ /dev/null @@ -1,57 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Error; -import it.tdlight.jni.TdApi.Object; -import it.tdlight.tdlibsession.td.TdResult; -import java.util.StringJoiner; - -public class TdResultMessage { - - private static final TdResultMessageCodec realCodec = new TdResultMessageCodec(); - - public TdApi.Object value; - public TdApi.Error cause; - private int pos; - private Buffer buffer; - - public TdResultMessage(Object value, Error cause) { - this.value = value; - this.cause = cause; - if (value == null && cause == null) throw new NullPointerException("Null message"); - } - - public TdResultMessage(int pos, Buffer buffer) { - this.pos = pos; - this.buffer = buffer; - } - - public TdResult toTdResult() { - if (value != null) { - //noinspection unchecked - return TdResult.succeeded((T) value); - } else if (cause != null) { - return TdResult.failed(cause); - } else { - var data = realCodec.decodeFromWire(pos, buffer); - this.value = data.value; - this.cause = data.cause; - this.buffer = null; - if (value != null) { - //noinspection unchecked - return TdResult.succeeded((T) value); - } else { - return TdResult.failed(cause); - } - } - } - - @Override - public String toString() { - return new StringJoiner(", ", TdResultMessage.class.getSimpleName() + "[", "]") - .add("value=" + value) - .add("cause=" + cause) - .toString(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessageCodec.java b/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessageCodec.java deleted file mode 100644 index de9457d..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/TdResultMessageCodec.java +++ /dev/null @@ -1,59 +0,0 @@ -package it.tdlight.tdlibsession.td.middle; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.eventbus.MessageCodec; -import it.tdlight.jni.TdApi; -import it.tdlight.utils.BufferUtils; - -@SuppressWarnings("rawtypes") -public class TdResultMessageCodec implements MessageCodec { - - private final String codecName; - - public TdResultMessageCodec() { - super(); - this.codecName = "TdResultCodec"; - } - - @Override - public void encodeToWire(Buffer buffer, TdResultMessage t) { - BufferUtils.encode(buffer, os -> { - if (t.value != null) { - os.writeBoolean(true); - t.value.serialize(os); - } else { - os.writeBoolean(false); - t.cause.serialize(os); - } - }); - } - - @Override - public TdResultMessage decodeFromWire(int pos, Buffer buffer) { - return BufferUtils.decode(pos, buffer, is -> { - if (is.readBoolean()) { - return new TdResultMessage(TdApi.Deserializer.deserialize(is), null); - } else { - return new TdResultMessage(null, (TdApi.Error) TdApi.Deserializer.deserialize(is)); - } - }); - } - - @Override - public TdResultMessage transform(TdResultMessage t) { - // If a message is sent *locally* across the event bus. - // This sends message just as is - return t; - } - - @Override - public String name() { - return codecName; - } - - @Override - public byte systemCodecID() { - // Always -1 - return -1; - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/client/AsyncTdMiddleEventBusClient.java b/src/main/java/it/tdlight/tdlibsession/td/middle/client/AsyncTdMiddleEventBusClient.java deleted file mode 100644 index dddce3a..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/client/AsyncTdMiddleEventBusClient.java +++ /dev/null @@ -1,366 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.client; - -import io.vertx.core.eventbus.DeliveryOptions; -import io.vertx.core.eventbus.ReplyException; -import io.vertx.core.eventbus.ReplyFailure; -import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.core.Vertx; -import io.vertx.reactivex.core.buffer.Buffer; -import io.vertx.reactivex.core.eventbus.Message; -import io.vertx.reactivex.core.eventbus.MessageConsumer; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.AuthorizationStateClosed; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.Object; -import it.tdlight.jni.TdApi.UpdateAuthorizationState; -import it.tdlight.tdlibsession.td.ResponseError; -import it.tdlight.tdlibsession.td.TdError; -import it.tdlight.tdlibsession.td.TdResult; -import it.tdlight.tdlibsession.td.middle.TdResultMessage; -import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle; -import it.tdlight.tdlibsession.td.middle.EndSessionMessage; -import it.tdlight.tdlibsession.td.middle.ExecuteObject; -import it.tdlight.tdlibsession.td.middle.StartSessionMessage; -import it.tdlight.tdlibsession.td.middle.TdClusterManager; -import it.tdlight.tdlibsession.td.middle.TdResultList; -import it.tdlight.utils.BinlogAsyncFile; -import it.tdlight.utils.BinlogUtils; -import it.tdlight.utils.MonoUtils; -import java.net.ConnectException; -import java.nio.file.Path; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; -import org.warp.commonutils.locks.LockUtils; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.adapter.rxjava.RxJava2Adapter; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.EmitResult; -import reactor.core.publisher.Sinks.Empty; -import reactor.core.publisher.Sinks.One; -import reactor.core.scheduler.Schedulers; - -public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle { - - private Logger logger; - - public static final byte[] EMPTY = new byte[0]; - - private final TdClusterManager cluster; - private final DeliveryOptions deliveryOptions; - private final DeliveryOptions deliveryOptionsWithTimeout; - private final DeliveryOptions pingDeliveryOptions; - - private final AtomicReference binlog = new AtomicReference<>(); - - private final AtomicReference pinger = new AtomicReference<>(); - - private final AtomicReference> updates = new AtomicReference<>(); - // This will only result in a successful completion, never completes in other ways - private final Empty updatesStreamEnd = Sinks.empty(); - // This will only result in a crash, never completes in other ways - private final AtomicReference crash = new AtomicReference<>(); - // This will only result in a successful completion, never completes in other ways - private final Empty pingFail = Sinks.empty(); - - private long botId; - private String botAddress; - private String botAlias; - private boolean local; - - public AsyncTdMiddleEventBusClient(TdClusterManager clusterManager) { - this.logger = LoggerFactory.getLogger(AsyncTdMiddleEventBusClient.class); - this.cluster = clusterManager; - this.deliveryOptions = cluster.newDeliveryOpts().setLocalOnly(local); - this.deliveryOptionsWithTimeout = cluster.newDeliveryOpts().setLocalOnly(local).setSendTimeout(30000); - this.pingDeliveryOptions = cluster.newDeliveryOpts().setLocalOnly(local).setSendTimeout(60000); - } - - private Mono initializeEb() { - return Mono.just(this); - } - - @Override - public Mono initialize() { - // Do nothing here. - return Mono.empty(); - } - - public static Mono getAndDeployInstance(TdClusterManager clusterManager, - long botId, - String botAlias, - boolean local, - JsonObject implementationDetails, - Path binlogsArchiveDirectory) { - return new AsyncTdMiddleEventBusClient(clusterManager) - .initializeEb() - .flatMap(instance -> retrieveBinlog(clusterManager.getVertx(), binlogsArchiveDirectory, botId) - .flatMap(binlog -> binlog - .getLastModifiedTime() - .filter(modTime -> modTime == 0) - .doOnNext(v -> LoggerFactory - .getLogger(AsyncTdMiddleEventBusClient.class) - .error("Can't retrieve binlog of bot " + botId + " " + botAlias + ". Creating a new one...")) - .thenReturn(binlog)).flatMap(binlog -> instance - .start(botId, botAlias, local, implementationDetails, binlog) - .thenReturn(instance) - ) - .single() - ) - .single(); - } - - /** - * - * @return optional result - */ - public static Mono retrieveBinlog(Vertx vertx, Path binlogsArchiveDirectory, long botId) { - return BinlogUtils.retrieveBinlog(vertx.fileSystem(), binlogsArchiveDirectory.resolve(botId + ".binlog")); - } - - private Mono saveBinlog(Buffer data) { - return Mono.fromSupplier(this.binlog::get).flatMap(binlog -> BinlogUtils.saveBinlog(binlog, data)); - } - - public Mono start(long botId, - String botAlias, - boolean local, - JsonObject implementationDetails, - BinlogAsyncFile binlog) { - this.botId = botId; - this.botAlias = botAlias; - this.botAddress = "bots.bot." + this.botId; - this.local = local; - this.logger = LoggerFactory.getLogger(this.botId + " " + botAlias); - - return Mono - .fromRunnable(() -> this.binlog.set(binlog)) - .then(binlog.getLastModifiedTime()) - .zipWith(binlog.readFully().map(Buffer::getDelegate)) - .single() - .flatMap(tuple -> { - var binlogLastModifiedTime = tuple.getT1(); - var binlogData = tuple.getT2(); - - var msg = new StartSessionMessage(this.botId, - this.botAlias, - Buffer.newInstance(binlogData), - binlogLastModifiedTime, - implementationDetails - ); - - Mono startBotRequest; - - if (local) { - startBotRequest = Mono.empty(); - } else { - startBotRequest = cluster - .getEventBus() - .rxRequest("bots.start-bot", msg) - .to(RxJava2Adapter::singleToMono) - .doOnSuccess(s -> logger.trace("bots.start-bot returned successfully")) - .doFirst(() -> logger.trace("Requesting bots.start-bot")) - .onErrorMap(ex -> { - if (ex instanceof ReplyException) { - if (((ReplyException) ex).failureType() == ReplyFailure.NO_HANDLERS) { - return new NoClustersAvailableException("Can't start bot " - + botId + " " + botAlias); - } - } - return ex; - }) - .then() - .subscribeOn(Schedulers.boundedElastic()); - } - - return setupUpdatesListener().then(startBotRequest).then(setupPing()); - }); - } - - private Mono setupPing() { - // Disable ping on local servers - if (local) { - return Mono.empty(); - } - - var pingRequest = cluster.getEventBus() - .rxRequest(botAddress + ".ping", EMPTY, pingDeliveryOptions) - .to(RxJava2Adapter::singleToMono) - .doFirst(() -> logger.trace("Requesting ping...")); - - return Mono - .fromRunnable(() -> pinger.set(pingRequest - .flatMap(msg -> Mono.fromCallable(msg::body).subscribeOn(Schedulers.boundedElastic())) - .repeatWhen(l -> l.delayElements(Duration.ofSeconds(10)).takeWhile(x -> true)) - .doOnNext(s -> logger.trace("PING")) - .then() - .onErrorResume(ex -> { - logger.warn("Ping failed: {}", ex.getMessage()); - return Mono.empty(); - }) - .doOnNext(s -> logger.debug("END PING")) - .then(Mono.fromRunnable(() -> { - while (this.pingFail.tryEmitEmpty() == EmitResult.FAIL_NON_SERIALIZED) { - // 10ms - LockSupport.parkNanos(10000000); - } - }).subscribeOn(Schedulers.boundedElastic())) - .subscribeOn(Schedulers.parallel()) - .subscribe()) - ) - .then() - .doFirst(() -> logger.trace("Setting up ping")) - .doOnSuccess(s -> logger.trace("Ping setup success")) - .subscribeOn(Schedulers.boundedElastic()); - } - - private Mono setupUpdatesListener() { - return Mono - .fromRunnable(() -> logger.trace("Setting up updates listener...")) - .then(Mono.>fromSupplier(() -> MessageConsumer - .newInstance(cluster.getEventBus().consumer(botAddress + ".updates") - .setMaxBufferedMessages(5000) - .getDelegate() - )) - ) - .flatMap(updateConsumer -> { - // Return when the registration of all the consumers has been done across the cluster - return Mono - .fromRunnable(() -> logger.trace("Emitting updates flux to sink")) - .then(Mono.fromRunnable(() -> { - var previous = this.updates.getAndSet(updateConsumer); - if (previous != null) { - logger.error("Already subscribed a consumer to the updates"); - } - })) - .doOnSuccess(s -> logger.trace("Emitted updates flux to sink")) - .doOnSuccess(s -> logger.trace("Waiting to register update consumer across the cluster")) - .doOnSuccess(s -> logger.trace("Registered update consumer across the cluster")); - }) - .doOnSuccess(s ->logger.trace("Set up updates listener")) - .then(); - } - - @SuppressWarnings("Convert2MethodRef") - @Override - public Flux receive() { - // Here the updates will be received - - return Mono - .fromRunnable(() -> logger.trace("Called receive() from parent")) - .then(Mono.fromCallable(() -> updates.get())) - .doOnSuccess(s -> logger.trace("Registering updates flux")) - .flatMapMany(updatesMessageConsumer -> MonoUtils.fromMessageConsumer(Mono - .empty() - .doOnSuccess(s -> logger.trace("Sending ready-to-receive")) - .then(cluster.getEventBus().rxRequest(botAddress + ".ready-to-receive", - EMPTY, - deliveryOptionsWithTimeout - ).to(RxJava2Adapter::singleToMono)) - .doOnSuccess(s -> logger.trace("Sent ready-to-receive, received reply")) - .doOnSuccess(s -> logger.trace("About to read updates flux")) - .then(), updatesMessageConsumer) - ) - .takeUntilOther(pingFail.asMono()) - .takeUntil(a -> a.succeeded() && a.value().stream().anyMatch(item -> { - if (item.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) { - return ((UpdateAuthorizationState) item).authorizationState.getConstructor() - == AuthorizationStateClosed.CONSTRUCTOR; - } - return false; - })) - .flatMapSequential(updates -> { - if (updates.succeeded()) { - return Flux.fromIterable(updates.value()); - } else { - return Mono.fromCallable(() -> TdResult.failed(updates.error()).orElseThrow()); - } - }) - .concatMap(update -> interceptUpdate(update)) - // Redirect errors to crash sink - .doOnError(error -> crash.compareAndSet(null, error)) - .onErrorResume(ex -> { - logger.trace("Absorbing the error, the error has been published using the crash sink", ex); - return Mono.empty(); - }) - .doOnCancel(() -> { - }) - .doFinally(s -> { - var pinger = this.pinger.get(); - if (pinger != null) { - pinger.dispose(); - } - updatesStreamEnd.tryEmitEmpty(); - }); - } - - private Mono interceptUpdate(Object update) { - logger.trace("Received update {}", update.getClass().getSimpleName()); - if (update.getConstructor() == TdApi.UpdateAuthorizationState.CONSTRUCTOR) { - var updateAuthorizationState = (TdApi.UpdateAuthorizationState) update; - if (updateAuthorizationState.authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) { - logger.info("Received AuthorizationStateClosed from tdlib"); - - var pinger = this.pinger.get(); - if (pinger != null) { - pinger.dispose(); - } - - return cluster.getEventBus() - .rxRequest(this.botAddress + ".read-binlog", EMPTY) - .to(RxJava2Adapter::singleToMono) - .mapNotNull(Message::body) - .doOnNext(latestBinlog -> logger.info("Received binlog from server. Size: {}", - BinlogUtils.humanReadableByteCountBin(latestBinlog.binlog().length()) - )) - .flatMap(latestBinlog -> this.saveBinlog(latestBinlog.binlog())) - .doOnSuccess(s -> logger.info("Overwritten binlog from server")) - .doFirst(() -> logger.info("Asking binlog to server")) - .thenReturn(update); - } - } - return Mono.just(update); - } - - @Override - public Mono> execute(Function request, Duration timeout, - boolean executeSync) { - var req = new ExecuteObject<>(executeSync, request, timeout); - var deliveryOptions = new DeliveryOptions(this.deliveryOptions) - // Timeout + 5s (5 seconds extra are used to wait the graceful server-side timeout response) - .setSendTimeout(timeout.toMillis() + 5000); - - var executionMono = cluster.getEventBus() - .rxRequest(botAddress + ".execute", req, deliveryOptions) - .to(RxJava2Adapter::singleToMono) - .onErrorMap(ex -> ResponseError.newResponseError(request, botAlias, ex)) - .>handle((resp, sink) -> { - if (resp.body() == null) { - var tdError = new TdError(500, "Response is empty"); - sink.error(ResponseError.newResponseError(request, botAlias, tdError)); - } else { - sink.next(resp.body().toTdResult()); - } - }) - .doFirst(() -> logger.trace("Executing request {}", request)) - .doOnSuccess(s -> logger.trace("Executed request {}", request)) - .doOnError(ex -> logger.debug("Failed request {}: {}", req, ex)); - - return executionMono - .transformDeferred(mono -> { - var crash = this.crash.get(); - if (crash != null) { - logger.debug("Failed request {} because the TDLib session was already crashed", request); - return Mono.empty(); - } else { - return mono; - } - }) - .switchIfEmpty(Mono.error(() -> ResponseError.newResponseError(request, botAlias, - new TdError(500, "The client is closed or the response is empty")))); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/client/NoClustersAvailableException.java b/src/main/java/it/tdlight/tdlibsession/td/middle/client/NoClustersAvailableException.java deleted file mode 100644 index 36d0201..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/client/NoClustersAvailableException.java +++ /dev/null @@ -1,13 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.client; - -public class NoClustersAvailableException extends Throwable { - - public NoClustersAvailableException(String error) { - super(error); - } - - @Override - public String toString() { - return "No clusters are available. " + this.getMessage(); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleDirect.java b/src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleDirect.java deleted file mode 100644 index bcabdbc..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleDirect.java +++ /dev/null @@ -1,112 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.direct; - -import static it.tdlight.tdlibsession.td.middle.server.AsyncTdMiddleEventBusServer.WAIT_DURATION; - -import io.reactivex.Completable; -import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.core.AbstractVerticle; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.Object; -import it.tdlight.tdlibsession.td.ResponseError; -import it.tdlight.tdlibsession.td.TdResult; -import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl; -import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions; -import it.tdlight.tdlibsession.td.direct.TelegramClientFactory; -import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle; -import it.tdlight.tdlibsession.td.middle.TdClusterManager; -import it.tdlight.utils.MonoUtils; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicReference; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.Empty; -import reactor.core.scheduler.Schedulers; - -public class AsyncTdMiddleDirect extends AbstractVerticle implements AsyncTdMiddle { - - private static final Logger logger = LoggerFactory.getLogger(AsyncTdMiddleDirect.class); - - private final TelegramClientFactory clientFactory; - - protected AsyncTdDirectImpl td; - private String botAddress; - private String botAlias; - private final Empty closeRequest = Sinks.empty(); - - public AsyncTdMiddleDirect() { - this.clientFactory = new TelegramClientFactory(); - } - - public static Mono getAndDeployInstance(TdClusterManager clusterManager, - String botAlias, - String botAddress, - JsonObject implementationDetails) { - var instance = new AsyncTdMiddleDirect(); - var options = clusterManager.newDeploymentOpts().setConfig(new JsonObject() - .put("botAlias", botAlias) - .put("botAddress", botAddress) - .put("implementationDetails", implementationDetails)); - return clusterManager.getVertx() - .rxDeployVerticle(instance, options) - .as(MonoUtils::toMono) - .doOnNext(_v -> logger.trace("Deployed verticle for bot " + botAlias + ", address: " + botAddress)) - .thenReturn(instance); - } - - @Override - public Completable rxStart() { - var botAddress = config().getString("botAddress"); - if (botAddress == null || botAddress.isEmpty()) { - throw new IllegalArgumentException("botAddress is not set!"); - } - this.botAddress = botAddress; - var botAlias = config().getString("botAlias"); - if (botAlias == null || botAlias.isEmpty()) { - throw new IllegalArgumentException("botAlias is not set!"); - } - this.botAlias = botAlias; - var implementationDetails = config().getJsonObject("implementationDetails"); - if (implementationDetails == null) { - throw new IllegalArgumentException("implementationDetails is not set!"); - } - - this.td = new AsyncTdDirectImpl(clientFactory, implementationDetails, botAlias); - - return Completable.complete(); - } - - @Override - public Completable rxStop() { - return Completable.fromRunnable(closeRequest::tryEmitEmpty); - } - - @Override - public Mono initialize() { - return td.initialize(); - } - - @Override - public Flux receive() { - return td - .receive(new AsyncTdDirectOptions(WAIT_DURATION, 100)) - .takeUntilOther(closeRequest.asMono()) - .doOnNext(s -> logger.trace("Received update from tdlib: {}", s.getClass().getSimpleName())) - .doOnError(ex -> logger.info("TdMiddle verticle error", ex)) - .doOnTerminate(() -> logger.debug("TdMiddle verticle stopped")) - .subscribeOn(Schedulers.boundedElastic()); - } - - @Override - public Mono> execute(Function requestFunction, - Duration timeout, - boolean executeDirectly) { - return td - .execute(requestFunction, timeout, executeDirectly) - .onErrorMap(error -> ResponseError.newResponseError(requestFunction, botAlias, error)); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleLocal.java b/src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleLocal.java deleted file mode 100644 index 5a9ca2d..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/direct/AsyncTdMiddleLocal.java +++ /dev/null @@ -1,100 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.direct; - -import io.vertx.core.DeploymentOptions; -import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.core.Vertx; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.Object; -import it.tdlight.tdlibsession.td.TdResult; -import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle; -import it.tdlight.tdlibsession.td.middle.TdClusterManager; -import it.tdlight.tdlibsession.td.middle.client.AsyncTdMiddleEventBusClient; -import it.tdlight.tdlibsession.td.middle.server.AsyncTdMiddleEventBusServer; -import it.tdlight.utils.MonoUtils; -import java.nio.file.Path; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicReference; -import org.warp.commonutils.error.InitializationException; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.One; - -public class AsyncTdMiddleLocal implements AsyncTdMiddle { - - private final TdClusterManager masterClusterManager; - private final String botAlias; - private final int botId; - private final DeploymentOptions deploymentOptions; - private final Vertx vertx; - - private final AsyncTdMiddleEventBusServer srv; - private final AtomicReference cli = new AtomicReference<>(null); - private final AtomicReference startError = new AtomicReference<>(null); - private final JsonObject implementationDetails; - - - public AsyncTdMiddleLocal(TdClusterManager masterClusterManager, - String botAlias, - int botId, - JsonObject implementationDetails) { - this.masterClusterManager = masterClusterManager; - this.botAlias = botAlias; - this.botId = botId; - this.implementationDetails = implementationDetails; - this.vertx = masterClusterManager.getVertx(); - this.deploymentOptions = masterClusterManager - .newDeploymentOpts() - .setConfig(new JsonObject() - .put("botId", botId) - .put("botAlias", botAlias) - .put("local", true) - .put("implementationDetails", implementationDetails) - ); - this.srv = new AsyncTdMiddleEventBusServer(); - } - - public Mono start() { - return vertx - .rxDeployVerticle(srv, deploymentOptions).as(MonoUtils::toMono) - .single() - .then(Mono.fromSupplier(() -> new AsyncTdMiddleEventBusClient(masterClusterManager))) - .zipWith(AsyncTdMiddleEventBusClient.retrieveBinlog(vertx, Path.of("binlogs"), botId)) - .flatMap(tuple -> tuple - .getT1() - .start(botId, botAlias, true, implementationDetails, tuple.getT2()) - .thenReturn(tuple.getT1())) - .onErrorMap(InitializationException::new) - .doOnNext(this.cli::set) - .doOnError(this.startError::set) - .thenReturn(this); - } - - @Override - public Mono initialize() { - var startError = this.startError.get(); - if (startError != null) { - return Mono.error(startError); - } - return Mono.fromCallable(cli::get).single().flatMap(AsyncTdMiddle::initialize); - } - - @Override - public Flux receive() { - var startError = this.startError.get(); - if (startError != null) { - return Flux.error(startError); - } - return Mono.fromCallable(cli::get).single().flatMapMany(AsyncTdMiddle::receive); - } - - @Override - public Mono> execute(Function request, Duration timeout, boolean executeDirectly) { - var startError = this.startError.get(); - if (startError != null) { - return Mono.error(startError); - } - return Mono.fromCallable(cli::get).single().flatMap(c -> c.execute(request, timeout, executeDirectly)); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/server/AsyncTdMiddleEventBusServer.java b/src/main/java/it/tdlight/tdlibsession/td/middle/server/AsyncTdMiddleEventBusServer.java deleted file mode 100644 index b281a4f..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/server/AsyncTdMiddleEventBusServer.java +++ /dev/null @@ -1,402 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.server; - -import io.reactivex.Completable; -import io.reactivex.processors.BehaviorProcessor; -import io.vertx.core.eventbus.DeliveryOptions; -import io.vertx.core.eventbus.ReplyException; -import io.vertx.core.eventbus.ReplyFailure; -import io.vertx.core.streams.Pipe; -import io.vertx.core.streams.Pump; -import io.vertx.ext.reactivestreams.impl.ReactiveWriteStreamImpl; -import io.vertx.reactivex.RxHelper; -import io.vertx.reactivex.WriteStreamObserver; -import io.vertx.reactivex.core.AbstractVerticle; -import io.vertx.reactivex.core.eventbus.Message; -import io.vertx.reactivex.core.eventbus.MessageConsumer; -import io.vertx.reactivex.core.eventbus.MessageProducer; -import io.vertx.reactivex.core.streams.WriteStream; -import io.vertx.reactivex.impl.FlowableReadStream; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.AuthorizationStateClosed; -import it.tdlight.jni.TdApi.Error; -import it.tdlight.jni.TdApi.Function; -import it.tdlight.jni.TdApi.SetTdlibParameters; -import it.tdlight.jni.TdApi.Update; -import it.tdlight.jni.TdApi.UpdateAuthorizationState; -import it.tdlight.tdlibsession.remoteclient.TDLibRemoteClient; -import it.tdlight.tdlibsession.td.TdError; -import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl; -import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions; -import it.tdlight.tdlibsession.td.direct.TelegramClientFactory; -import it.tdlight.tdlibsession.td.middle.ExecuteObject; -import it.tdlight.tdlibsession.td.middle.TdResultList; -import it.tdlight.tdlibsession.td.middle.TdResultListMessageCodec; -import it.tdlight.tdlibsession.td.middle.TdResultMessage; -import it.tdlight.utils.BinlogUtils; -import java.net.ConnectException; -import java.time.Duration; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.adapter.rxjava.RxJava2Adapter; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; -import reactor.util.function.Tuples; - -public class AsyncTdMiddleEventBusServer extends AbstractVerticle { - - // Static values - protected static final Logger logger = LoggerFactory.getLogger("TdMiddleServer"); - public static final byte[] EMPTY = new byte[0]; - public static final Duration WAIT_DURATION = Duration.ofSeconds(1); - - // Values configured from constructor - private final AsyncTdDirectOptions tdOptions; - private final TelegramClientFactory clientFactory; - - // Variables configured by the user at startup - private final AtomicReference botId = new AtomicReference<>(); - private final AtomicReference botAddress = new AtomicReference<>(); - private final AtomicReference botAlias = new AtomicReference<>(); - - // Variables configured at startup - private final AtomicReference td = new AtomicReference<>(); - private final AtomicReference executeConsumer = new AtomicReference<>(); - private final AtomicReference readBinlogConsumer = new AtomicReference<>(); - private final AtomicReference readyToReceiveConsumer = new AtomicReference<>(); - private final AtomicReference pingConsumer = new AtomicReference<>(); - private final AtomicReference clusterPropagationWaiter = new AtomicReference<>(); - private final AtomicReference> pipeFlux = new AtomicReference<>(); - - public AsyncTdMiddleEventBusServer() { - this.tdOptions = new AsyncTdDirectOptions(WAIT_DURATION, 100); - this.clientFactory = new TelegramClientFactory(); - } - - @Override - public Completable rxStart() { - return Mono - .fromCallable(() -> { - logger.trace("Stating verticle"); - - System.setProperty("it.tdlight.enableShutdownHooks", "false"); - - var botId = config().getInteger("botId"); - if (botId == null || botId <= 0) { - throw new IllegalArgumentException("botId is not set!"); - } - this.botId.set(botId); - var botAddress = "bots.bot." + botId; - this.botAddress.set(botAddress); - var botAlias = config().getString("botAlias"); - if (botAlias == null || botAlias.isEmpty()) { - throw new IllegalArgumentException("botAlias is not set!"); - } - this.botAlias.set(botAlias); - var local = config().getBoolean("local"); - if (local == null) { - throw new IllegalArgumentException("local is not set!"); - } - var implementationDetails = config().getJsonObject("implementationDetails"); - if (implementationDetails == null) { - throw new IllegalArgumentException("implementationDetails is not set!"); - } - - var td = new AsyncTdDirectImpl(clientFactory, implementationDetails, botAlias); - this.td.set(td); - return new OnSuccessfulStartRequestInfo(td, botAddress, botAlias, botId, local); - }) - .flatMap(r -> onSuccessfulStartRequest(r.td, r.botAddress, r.botAlias, r.botId, r.local)) - .doOnSuccess(s -> logger.trace("Started verticle")) - .as(RxJava2Adapter::monoToCompletable); - } - - private static class OnSuccessfulStartRequestInfo { - - public final AsyncTdDirectImpl td; - public final String botAddress; - public final String botAlias; - public final int botId; - public final boolean local; - - public OnSuccessfulStartRequestInfo(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, - boolean local) { - this.td = td; - this.botAddress = botAddress; - this.botAlias = botAlias; - this.botId = botId; - this.local = local; - } - } - - private Mono onSuccessfulStartRequest(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, - boolean local) { - return td - .initialize() - .then(this.pipe(td, botAddress, local)) - .then(this.listen(td, botAddress, botId, local)) - .doOnSuccess(s -> logger.info("Deploy and start of bot \"" + botAlias + "\": ✅ Succeeded")) - .doOnError(ex -> logger.error("Deploy and start of bot \"" + botAlias + "\": ❌ Failed", ex)); - } - - private Mono listen(AsyncTdDirectImpl td, String botAddress, int botId, boolean local) { - return Mono.create(registrationSink -> { - logger.trace("Preparing listeners"); - - MessageConsumer> executeConsumer = vertx.eventBus().consumer(botAddress + ".execute"); - this.executeConsumer.set(executeConsumer - .toFlowable() - .to(RxJava2Adapter::flowableToFlux) - .flatMap(msg -> { - var body = msg.body(); - var request = overrideRequest(body.getRequest(), botId); - var timeout = body.getTimeout(); - if (logger.isTraceEnabled()) { - logger.trace("Received execute request {}", request); - } - return td - .execute(request, timeout, body.isExecuteDirectly()) - .single() - .doOnSuccess(s -> logger.trace("Executed successfully. Request was {}", request)) - .onErrorResume(ex -> Mono.fromRunnable(() -> msg.fail(500, ex.getLocalizedMessage()))) - .map(response -> { - var replyOpts = new DeliveryOptions().setLocalOnly(local); - var replyValue = new TdResultMessage(response.result(), response.cause()); - try { - logger.trace("Replying with success response. Request was {}", request); - msg.reply(replyValue, replyOpts); - return response; - } catch (Exception ex) { - logger.debug("Replying with error response: {}. Request was {}", ex.getLocalizedMessage(), request); - msg.fail(500, ex.getLocalizedMessage()); - throw ex; - } - }); - }) - .then() - .subscribeOn(Schedulers.boundedElastic()) - .subscribe(v -> {}, - ex -> logger.error("Fatal error when processing an execute request." - + " Can't process further requests since the subscription has been broken", ex), - () -> logger.trace("Finished handling execute requests") - )); - - MessageConsumer readBinlogConsumer = vertx.eventBus().consumer(botAddress + ".read-binlog"); - this.readBinlogConsumer.set(BinlogUtils - .readBinlogConsumer(vertx, readBinlogConsumer, botId, local) - .subscribeOn(Schedulers.boundedElastic()) - .subscribe(v -> {}, ex -> logger.error("Error when processing a read-binlog request", ex))); - - MessageConsumer readyToReceiveConsumer = vertx.eventBus().consumer(botAddress + ".ready-to-receive"); - - // Pipe the data - this.readyToReceiveConsumer.set(readyToReceiveConsumer - .toFlowable() - .to(RxJava2Adapter::flowableToFlux) - .take(1, true) - .single() - .doOnNext(s -> logger.trace("Received ready-to-receive request from client")) - .map(msg -> Tuples.of(msg, Objects.requireNonNull(pipeFlux.get(), "PipeFlux is empty"))) - .doOnError(ex -> logger.error("Error when processing a ready-to-receive request", ex)) - .doOnNext(s -> logger.trace("Replying to ready-to-receive request")) - .flatMapMany(tuple -> { - var opts = new DeliveryOptions().setLocalOnly(local).setSendTimeout(Duration.ofSeconds(10).toMillis()); - - tuple.getT1().reply(EMPTY, opts); - logger.trace("Replied to ready-to-receive"); - - logger.trace("Start piping data"); - - // Start piping the data - return tuple.getT2().doOnSubscribe(s -> logger.trace("Subscribed to updates pipe")); - }) - .then() - .doOnSuccess(s -> logger.trace("Finished handling ready-to-receive requests (updates pipe ended)")) - .subscribeOn(Schedulers.boundedElastic()) - // Don't handle errors here. Handle them in pipeFlux - .subscribe(v -> {})); - - MessageConsumer pingConsumer = vertx.eventBus().consumer(botAddress + ".ping"); - - this.pingConsumer.set(pingConsumer - .toFlowable() - .to(RxJava2Adapter::flowableToFlux) - .doOnNext(msg -> { - var opts = new DeliveryOptions().setLocalOnly(local).setSendTimeout(Duration.ofSeconds(10).toMillis()); - msg.reply(EMPTY, opts); - }) - .subscribeOn(Schedulers.boundedElastic()) - .subscribe(unused -> logger.trace("Finished handling ping requests"), - ex -> logger.error("Error when processing a ping request", ex) - )); - - var executorPropagated = executeConsumer.rxCompletionHandler().to(RxJava2Adapter::completableToMono); - var readyToReceivePropagated = executeConsumer.rxCompletionHandler().to(RxJava2Adapter::completableToMono); - var readBinLogPropagated = executeConsumer.rxCompletionHandler().to(RxJava2Adapter::completableToMono); - var pingPropagated = executeConsumer.rxCompletionHandler().to(RxJava2Adapter::completableToMono); - - var allPropagated = Mono.when(executorPropagated, readyToReceivePropagated, readBinLogPropagated, pingPropagated); - this.clusterPropagationWaiter.set(allPropagated - .doOnSuccess(s -> logger.trace("Finished preparing listeners")) - .subscribeOn(Schedulers.boundedElastic()) - .subscribe(v -> {}, registrationSink::error, registrationSink::success)); - }); - } - - /** - * Override some requests - */ - private Function overrideRequest(Function request, int botId) { - if (request.getConstructor() == SetTdlibParameters.CONSTRUCTOR) { - // Fix session directory locations - var setTdlibParamsObj = (SetTdlibParameters) request; - setTdlibParamsObj.parameters.databaseDirectory = TDLibRemoteClient.getSessionDirectory(botId).toString(); - setTdlibParamsObj.parameters.filesDirectory = TDLibRemoteClient.getMediaDirectory(botId).toString(); - } - return request; - } - - @Override - public Completable rxStop() { - return Mono - .fromRunnable(() -> { - logger.info("Undeploy of bot \"" + botAlias.get() + "\": stopping"); - var executeConsumer = this.executeConsumer.get(); - if (executeConsumer != null) { - executeConsumer.dispose(); - logger.trace("Unregistered execute consumer"); - } - var pingConsumer = this.pingConsumer.get(); - if (pingConsumer != null) { - pingConsumer.dispose(); - } - var readBinlogConsumer = this.readBinlogConsumer.getAndSet(null); - Schedulers.boundedElastic().schedule(() -> { - if (readBinlogConsumer != null) { - readBinlogConsumer.dispose(); - } - }, 10, TimeUnit.MINUTES); - var readyToReceiveConsumer = this.readyToReceiveConsumer.get(); - if (readyToReceiveConsumer != null) { - readyToReceiveConsumer.dispose(); - } - var clusterPropagationWaiter = this.clusterPropagationWaiter.get(); - if (clusterPropagationWaiter != null) { - clusterPropagationWaiter.dispose(); - } - }) - .doOnError(ex -> logger.error("Undeploy of bot \"" + botAlias.get() + "\": stop failed", ex)) - .doOnTerminate(() -> logger.info("Undeploy of bot \"" + botAlias.get() + "\": stopped")) - .as(RxJava2Adapter::monoToCompletable); - } - - private Mono pipe(AsyncTdDirectImpl td, String botAddress, boolean local) { - logger.trace("Preparing to pipe requests"); - Flux updatesFlux = td - .receive(tdOptions) - .takeUntil(item -> { - if (item instanceof Update) { - var tdUpdate = (Update) item; - if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) { - var updateAuthorizationState = (UpdateAuthorizationState) tdUpdate; - return updateAuthorizationState.authorizationState.getConstructor() - == AuthorizationStateClosed.CONSTRUCTOR; - } - } else - return item instanceof Error; - return false; - }) - .flatMap(update -> Mono.fromCallable(() -> { - if (update.getConstructor() == TdApi.Error.CONSTRUCTOR) { - var error = (Error) update; - throw new TdError(error.code, error.message); - } else { - return update; - } - })) - .limitRate(Math.max(1, tdOptions.getEventsSize())) - //.transform(normal -> new BufferTimeOutPublisher<>(normal,Math.max(1, tdOptions.getEventsSize()), - // local ? Duration.ofMillis(1) : Duration.ofMillis(100), false)) - //.bufferTimeout(Math.max(1, tdOptions.getEventsSize()), local ? Duration.ofMillis(1) : Duration.ofMillis(100)) - .map(List::of) - .limitRate(Math.max(1, tdOptions.getEventsSize())) - .map(TdResultList::new); - - var fluxCodec = new TdResultListMessageCodec(); - var opts = new DeliveryOptions() - .setLocalOnly(local) - .setSendTimeout(Duration.ofSeconds(30).toMillis()) - .setCodecName(fluxCodec.name()); - - MessageProducer updatesSender = vertx - .eventBus() - .sender(botAddress + ".updates", opts); - - var pipeFlux = updatesFlux - .concatMap(updatesList -> updatesSender - .rxWrite(updatesList) - .to(RxJava2Adapter::completableToMono) - .thenReturn(updatesList) - ) - .concatMap(updatesList -> Flux - .fromIterable(updatesList.value()) - .concatMap(item -> { - if (item instanceof Update) { - var tdUpdate = (Update) item; - if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) { - var tdUpdateAuthorizationState = (UpdateAuthorizationState) tdUpdate; - if (tdUpdateAuthorizationState.authorizationState.getConstructor() - == AuthorizationStateClosed.CONSTRUCTOR) { - logger.info("Undeploying after receiving AuthorizationStateClosed"); - return rxStop().to(RxJava2Adapter::completableToMono).thenReturn(item); - } - } - } else if (item instanceof Error) { - // An error in updates means that a fatal error occurred - logger.info("Undeploying after receiving a fatal error"); - return rxStop().to(RxJava2Adapter::completableToMono).thenReturn(item); - } - return Mono.just(item); - }) - .then() - ) - .doOnTerminate(() -> updatesSender.close(h -> { - if (h.failed()) { - logger.error("Failed to close \"updates\" message sender"); - } - })) - .onErrorResume(ex -> { - boolean printDefaultException = true; - if (ex instanceof ReplyException) { - ReplyException replyException = (ReplyException) ex; - if (replyException.failureCode() == -1 && replyException.failureType() == ReplyFailure.NO_HANDLERS) { - logger.warn("Undeploying, the flux has been terminated because no more" - + " handlers are available on the event bus. {}", replyException.getMessage()); - printDefaultException = false; - } - } else if (ex instanceof ConnectException || ex instanceof java.nio.channels.ClosedChannelException) { - logger.warn("Undeploying, the flux has been terminated because the consumer" - + " disconnected from the event bus. {}", ex.getMessage()); - printDefaultException = false; - } - if (printDefaultException) { - logger.warn("Undeploying after a fatal error in a served flux", ex); - } - return td - .execute(new TdApi.Close(), Duration.ofDays(1), false) - .doOnError(ex2 -> logger.error("Unexpected error", ex2)) - .doOnSuccess(s -> logger.debug("Emergency Close() signal has been sent successfully")) - .then(rxStop().to(RxJava2Adapter::completableToMono)); - }); - - return Mono.fromRunnable(() -> { - this.pipeFlux.set(pipeFlux); - logger.trace("Prepared piping requests successfully"); - }); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestId.java b/src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestId.java deleted file mode 100644 index 33a0d34..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestId.java +++ /dev/null @@ -1,19 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.server; - -import java.util.concurrent.atomic.AtomicLong; -import reactor.core.publisher.Mono; - -public class RequestId { - - public static Mono create() { - AtomicLong _requestId = new AtomicLong(1); - - return Mono.fromCallable(() -> _requestId.updateAndGet(n -> { - if (n > Long.MAX_VALUE - 100) { - return 1; - } else { - return n + 1; - } - })); - } -} diff --git a/src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestIdToReplyAddress.java b/src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestIdToReplyAddress.java deleted file mode 100644 index 57abb7c..0000000 --- a/src/main/java/it/tdlight/tdlibsession/td/middle/server/RequestIdToReplyAddress.java +++ /dev/null @@ -1,30 +0,0 @@ -package it.tdlight.tdlibsession.td.middle.server; - -import io.vertx.core.Promise; -import it.tdlight.jni.TdApi.Object; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import org.jetbrains.annotations.Async.Execute; -import org.jetbrains.annotations.Async.Schedule; - -public class RequestIdToReplyAddress { - private final ConcurrentHashMap> reqIdToReplyAddress = new ConcurrentHashMap<>();; - - public RequestIdToReplyAddress() { - - } - - public void schedule(@Schedule Long requestId, Promise replyPromise) { - reqIdToReplyAddress.put(requestId, replyPromise); - } - - public void failed(@Execute Long requestId, Promise replyPromise) { - reqIdToReplyAddress.remove(requestId, replyPromise); - } - - public void complete(@Execute Long id, Object item) { - var replyPromise = reqIdToReplyAddress.remove(id); - Objects.requireNonNull(replyPromise, () -> "Reply promise must be not empty"); - replyPromise.complete(item); - } -} diff --git a/src/main/java/it/tdlight/utils/BinlogAsyncFile.java b/src/main/java/it/tdlight/utils/BinlogAsyncFile.java deleted file mode 100644 index e56cb78..0000000 --- a/src/main/java/it/tdlight/utils/BinlogAsyncFile.java +++ /dev/null @@ -1,88 +0,0 @@ -package it.tdlight.utils; - -import io.vertx.core.file.OpenOptions; -import io.vertx.reactivex.core.buffer.Buffer; -import io.vertx.reactivex.core.file.AsyncFile; -import io.vertx.reactivex.core.file.FileProps; -import io.vertx.reactivex.core.file.FileSystem; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.core.publisher.Mono; - -public class BinlogAsyncFile { - - private static final Logger logger = LoggerFactory.getLogger(BinlogAsyncFile.class); - - private final FileSystem filesystem; - private final String path; - private final OpenOptions openOptions; - - public BinlogAsyncFile(FileSystem fileSystem, String path) { - this.filesystem = fileSystem; - this.path = path; - this.openOptions = new OpenOptions().setWrite(true).setRead(true).setCreate(false).setDsync(true); - } - - private Mono openRW() { - return filesystem.rxOpen(path, openOptions).as(MonoUtils::toMono); - } - - public Mono readFully() { - return openRW() - .flatMap(asyncFile -> filesystem - .rxProps(path) - .map(props -> (int) props.size()) - .as(MonoUtils::toMono) - .flatMap(size -> { - var buf = Buffer.buffer(size); - logger.debug("Reading binlog from disk. Size: " + BinlogUtils.humanReadableByteCountBin(size)); - return asyncFile.rxRead(buf, 0, 0, size).as(MonoUtils::toMono).thenReturn(buf); - }) - ); - } - - public Mono readFullyBytes() { - return this.readFully().map(Buffer::getBytes); - } - - public Mono overwrite(Buffer newData) { - return openRW().flatMap(asyncFile -> this - .getSize() - .doOnNext(size -> logger.debug("Preparing to overwrite binlog. Initial size: " + BinlogUtils.humanReadableByteCountBin(size))) - .then(asyncFile.rxWrite(newData, 0) - .andThen(asyncFile.rxFlush()) - .andThen(filesystem.rxTruncate(path, newData.length())) - .as(MonoUtils::toMono) - ) - .then(getSize()) - .doOnNext(size -> logger.debug("Overwritten binlog. Final size: " + BinlogUtils.humanReadableByteCountBin(size))) - .then() - ); - } - - public Mono overwrite(byte[] newData) { - return this.overwrite(Buffer.buffer(newData)); - } - - public FileSystem getFilesystem() { - return filesystem; - } - - public String getPath() { - return path; - } - - public Mono getLastModifiedTime() { - return filesystem - .rxProps(path) - .map(fileProps -> fileProps.size() == 0 ? 0 : fileProps.lastModifiedTime()) - .as(MonoUtils::toMono); - } - - public Mono getSize() { - return filesystem - .rxProps(path) - .map(FileProps::size) - .as(MonoUtils::toMono); - } -} diff --git a/src/main/java/it/tdlight/utils/BinlogUtils.java b/src/main/java/it/tdlight/utils/BinlogUtils.java deleted file mode 100644 index f8006d0..0000000 --- a/src/main/java/it/tdlight/utils/BinlogUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -package it.tdlight.utils; - -import io.vertx.core.eventbus.DeliveryOptions; -import io.vertx.core.file.OpenOptions; -import io.vertx.reactivex.core.Vertx; -import io.vertx.reactivex.core.buffer.Buffer; -import io.vertx.reactivex.core.eventbus.Message; -import io.vertx.reactivex.core.eventbus.MessageConsumer; -import io.vertx.reactivex.core.file.FileSystem; -import it.tdlight.tdlibsession.remoteclient.TDLibRemoteClient; -import it.tdlight.tdlibsession.td.middle.EndSessionMessage; -import java.nio.file.Path; -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneOffset; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; -import reactor.util.function.Tuple2; -import reactor.util.function.Tuples; - -public class BinlogUtils { - - private static final Logger logger = LoggerFactory.getLogger(BinlogUtils.class); - - public static Mono retrieveBinlog(FileSystem vertxFilesystem, Path binlogPath) { - var path = binlogPath.toString(); - return vertxFilesystem - // Create file if not exist to avoid errors - .rxExists(path).filter(exists -> exists).as(MonoUtils::toMono) - .switchIfEmpty(Mono.defer(() -> vertxFilesystem.rxMkdirs(binlogPath.getParent().toString()).as(MonoUtils::toMono)) - .then(vertxFilesystem.rxCreateFile(path).as(MonoUtils::toMono)) - .thenReturn(true) - ) - // Open file - .map(x -> new BinlogAsyncFile(vertxFilesystem, path)) - .single(); - } - - public static Mono saveBinlog(BinlogAsyncFile binlog, Buffer data) { - return binlog.overwrite(data); - } - - public static Mono chooseBinlog(FileSystem vertxFilesystem, - Path binlogPath, - Buffer remoteBinlog, - long remoteBinlogDate) { - var path = binlogPath.toString(); - return retrieveBinlog(vertxFilesystem, binlogPath) - .flatMap(binlog -> Mono - .just(binlog) - .zipWith(binlog.getLastModifiedTime()) - ) - .doOnSuccess(s -> logger.info("Local binlog: " + binlogPath + ". Local date: " + Instant.ofEpochMilli(s == null ? 0 : s.getT2()).atZone(ZoneOffset.UTC).toString() + " Remote date: " + Instant.ofEpochMilli(remoteBinlogDate).atZone(ZoneOffset.UTC).toString())) - // Files older than the remote file will be overwritten - .filter(tuple -> tuple.getT2() >= remoteBinlogDate) - .doOnNext(v -> logger.info("Using local binlog: " + binlogPath)) - .map(Tuple2::getT1) - .switchIfEmpty(Mono.defer(() -> Mono.fromRunnable(() -> logger.info("Using remote binlog. Overwriting " + binlogPath))) - .then(vertxFilesystem.rxWriteFile(path, remoteBinlog.copy()).as(MonoUtils::toMono)) - .then(retrieveBinlog(vertxFilesystem, binlogPath)) - ) - .single() - .then(); - } - - public static Mono cleanSessionPath(FileSystem vertxFilesystem, - Path binlogPath, - Path sessionPath, - Path mediaPath) { - return vertxFilesystem - .rxReadFile(binlogPath.toString()).as(MonoUtils::toMono) - .flatMap(buffer -> vertxFilesystem - .rxReadDir(sessionPath.toString(), "^(?!td.binlog$).*").as(MonoUtils::toMono) - .flatMapIterable(list -> list) - .doOnNext(file -> logger.debug("Deleting session file {}", file)) - .flatMap(file -> vertxFilesystem.rxDeleteRecursive(file, true).as(MonoUtils::toMono)) - .then(vertxFilesystem.rxReadDir(mediaPath.toString(), "^(?!td.binlog$).*").as(MonoUtils::toMono)) - .flatMapIterable(list -> list) - .doOnNext(file -> logger.debug("Deleting media file {}", file)) - .flatMap(file -> vertxFilesystem.rxDeleteRecursive(file, true).as(MonoUtils::toMono)) - .onErrorResume(ex -> Mono.empty()) - .then() - ); - } - - public static String humanReadableByteCountBin(long bytes) { - long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); - if (absB < 1024) { - return bytes + " B"; - } - long value = absB; - CharacterIterator ci = new StringCharacterIterator("KMGTPE"); - for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { - value >>= 10; - ci.next(); - } - value *= Long.signum(bytes); - return String.format("%.1f %ciB", value / 1024.0, ci.current()); - } - - public static Mono readBinlogConsumer(Vertx vertx, - MessageConsumer readBinlogConsumer, - int botId, - boolean local) { - return Flux - .>create(sink -> { - readBinlogConsumer.handler(sink::next); - readBinlogConsumer.endHandler(h -> sink.complete()); - }) - .flatMapSequential(req -> BinlogUtils - .retrieveBinlog(vertx.fileSystem(), TDLibRemoteClient.getSessionBinlogDirectory(botId)) - .flatMap(BinlogAsyncFile::readFully) - .map(Buffer::copy) - .single() - .map(binlog -> Tuples.of(req, binlog)) - ) - .doOnNext(tuple -> { - var opts = new DeliveryOptions().setLocalOnly(local).setSendTimeout(Duration.ofSeconds(10).toMillis()); - tuple.getT1().reply(new EndSessionMessage(botId, tuple.getT2()), opts); - }) - .then(); - } -} diff --git a/src/main/java/it/tdlight/utils/BufferTimeOutPublisher.java b/src/main/java/it/tdlight/utils/BufferTimeOutPublisher.java deleted file mode 100644 index 90597bb..0000000 --- a/src/main/java/it/tdlight/utils/BufferTimeOutPublisher.java +++ /dev/null @@ -1,178 +0,0 @@ -package it.tdlight.utils; - -/** Based on: - * https://gist.github.com/glandais-sparklane/e38834aa9df0c56f23e2d8d2e6899c78 - */ - -import java.time.Duration; -import org.jetbrains.annotations.NotNull; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; -import reactor.core.CoreSubscriber; - -@SuppressWarnings("ReactiveStreamsPublisherImplementation") -public class BufferTimeOutPublisher implements Publisher> { - - private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); - - private final Publisher source; - private final int size; - private final long duration; - private final boolean discardOnError; - - public BufferTimeOutPublisher(Publisher source, int size, Duration duration, boolean discardOnError) { - this.source = source; - this.size = size; - this.duration = duration.toMillis(); - this.discardOnError = discardOnError; - } - - @Override - public void subscribe(Subscriber> subscriber) { - subscriber.onSubscribe(new BufferTimeOutSubscription(source, subscriber, size, duration, discardOnError)); - } - - protected static class BufferTimeOutSubscription implements Subscription, CoreSubscriber { - - private final Subscriber> subscriber; - private final int size; - private final long duration; - private final boolean discardOnError; - private Subscription subscription; - - private final ReentrantLock lock = new ReentrantLock(); - - private List buffer; - private ScheduledFuture scheduledFuture; - - private long downstreamRequests = 0; - private long downstreamTransmit = 0; - - private long upstreamRequests = 0; - private long upstreamTransmit = 0; - private boolean upstreamCompleted = false; - - public BufferTimeOutSubscription(Publisher source, - Subscriber> subscriber, - int size, - long duration, - boolean discardOnError) { - this.subscriber = subscriber; - this.size = size; - this.duration = duration; - this.discardOnError = discardOnError; - this.buffer = new ArrayList<>(size); - source.subscribe(this); - } - - // downstream - @Override - public void request(long n) { - lock.lock(); - downstreamRequests = downstreamRequests + n; - - checkSend(); - - long downstreamMax = (downstreamRequests - downstreamTransmit) * size; - long upstreamRequested = upstreamRequests - upstreamTransmit; - long toRequest = downstreamMax - upstreamRequested; - - if (toRequest > 0) { - subscription.request(toRequest); - upstreamRequests = upstreamRequests + toRequest; - } - lock.unlock(); - } - - @Override - public void cancel() { - subscription.cancel(); - } - - // upstream - @Override - public void onSubscribe(@NotNull Subscription s) { - this.subscription = s; - scheduledFuture = EXECUTOR.scheduleAtFixedRate(this::timeout, 0, this.duration, TimeUnit.MILLISECONDS); - } - - private void timeout() { - checkSend(); - } - - private void checkSend() { - lock.lock(); - if (!this.buffer.isEmpty() && downstreamRequests > downstreamTransmit) { - List output = prepareOutput(); - subscriber.onNext(output); - downstreamTransmit++; - if (!this.buffer.isEmpty()) { - checkSend(); - } - } - if (upstreamCompleted && downstreamRequests > downstreamTransmit) { - scheduledFuture.cancel(false); - subscriber.onComplete(); - } - lock.unlock(); - } - - private List prepareOutput() { - if (this.buffer.size() > size) { - List output = new ArrayList<>(this.buffer.subList(0, size)); - this.buffer = new ArrayList<>(this.buffer.subList(size, this.buffer.size())); - return output; - } else { - List output = this.buffer; - this.buffer = new ArrayList<>(size); - return output; - } - } - - @Override - public void onNext(T t) { - lock.lock(); - this.buffer.add(t); - upstreamTransmit++; - if (this.buffer.size() == size) { - checkSend(); - } - lock.unlock(); - } - - @Override - public void onError(Throwable t) { - if (discardOnError) { - scheduledFuture.cancel(false); - subscriber.onError(t); - } else { - lock.lock(); - try { - checkSend(); - scheduledFuture.cancel(false); - subscriber.onError(t); - } finally { - lock.unlock(); - } - } - } - - @Override - public void onComplete() { - lock.lock(); - upstreamCompleted = true; - checkSend(); - lock.unlock(); - } - - } -} diff --git a/src/main/java/it/tdlight/utils/BufferUtils.java b/src/main/java/it/tdlight/utils/BufferUtils.java deleted file mode 100644 index d0bdd16..0000000 --- a/src/main/java/it/tdlight/utils/BufferUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -package it.tdlight.utils; - -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.buffer.impl.BufferImpl; -import java.io.IOException; -import org.apache.commons.lang3.SerializationException; - -public class BufferUtils { - - private static final int CHUNK_SIZE = 8192; - - public static void writeBuf(ByteBufOutputStream os, io.vertx.reactivex.core.buffer.Buffer dataToWrite) - throws IOException { - var len = dataToWrite.length(); - os.writeInt(len); - byte[] part = new byte[CHUNK_SIZE]; - for (int i = 0; i < len; i += CHUNK_SIZE) { - var end = Math.min(i + CHUNK_SIZE, len); - dataToWrite.getBytes(i, end, part, 0); - os.write(part, 0, end - i); - } - } - - public static void writeBuf(ByteBufOutputStream os, io.vertx.core.buffer.Buffer dataToWrite) throws IOException { - var len = dataToWrite.length(); - os.writeInt(len); - byte[] part = new byte[CHUNK_SIZE]; - for (int i = 0; i < len; i += CHUNK_SIZE) { - var end = Math.min(i + CHUNK_SIZE, len); - dataToWrite.getBytes(i, end, part, 0); - os.write(part, 0, end - i); - } - } - - public static io.vertx.core.buffer.Buffer readBuf(ByteBufInputStream is) throws IOException { - int len = is.readInt(); - Buffer buf = Buffer.buffer(len); - byte[] part = new byte[1024]; - int readPart = 0; - for (int i = 0; i < len; i += 1024) { - var lenx = (Math.min(i + 1024, len)) - i; - if (lenx > 0) { - readPart = is.readNBytes(part, 0, lenx); - buf.appendBytes(part, 0, readPart); - } - } - return buf; - } - - public static io.vertx.reactivex.core.buffer.Buffer rxReadBuf(ByteBufInputStream is) throws IOException { - int len = is.readInt(); - io.vertx.reactivex.core.buffer.Buffer buf = io.vertx.reactivex.core.buffer.Buffer.buffer(len); - byte[] part = new byte[1024]; - int readPart = 0; - for (int i = 0; i < len; i += 1024) { - var lenx = (Math.min(i + 1024, len)) - i; - if (lenx > 0) { - readPart = is.readNBytes(part, 0, lenx); - buf.appendBytes(part, 0, readPart); - } - } - return buf; - } - - public interface Writer { - - void write(ByteBufOutputStream os) throws IOException; - } - - public interface Reader { - - T read(ByteBufInputStream is) throws IOException; - } - - public static void encode(Buffer buffer, Writer writer) { - try (var os = new ByteBufOutputStream(((BufferImpl) buffer).byteBuf())) { - writer.write(os); - } catch (IOException ex) { - throw new SerializationException(ex); - } - } - - - public static T decode(int pos, Buffer buffer, Reader reader) { - try (var is = new ByteBufInputStream(buffer.slice(pos, buffer.length()).getByteBuf())) { - return reader.read(is); - } catch (IOException ex) { - throw new SerializationException(ex); - } - } - -} diff --git a/src/main/java/it/tdlight/utils/EmptyCallable.java b/src/main/java/it/tdlight/utils/EmptyCallable.java deleted file mode 100644 index c6bf519..0000000 --- a/src/main/java/it/tdlight/utils/EmptyCallable.java +++ /dev/null @@ -1,5 +0,0 @@ -package it.tdlight.utils; - -public interface EmptyCallable { - void call() throws Exception; -} diff --git a/src/main/java/it/tdlight/utils/MonoUtils.java b/src/main/java/it/tdlight/utils/MonoUtils.java deleted file mode 100644 index 053b82d..0000000 --- a/src/main/java/it/tdlight/utils/MonoUtils.java +++ /dev/null @@ -1,163 +0,0 @@ -package it.tdlight.utils; - -import io.reactivex.Completable; -import io.reactivex.Flowable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.vertx.core.AsyncResult; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Promise; -import io.vertx.reactivex.RxHelper; -import io.vertx.reactivex.core.eventbus.Message; -import io.vertx.reactivex.core.eventbus.MessageConsumer; -import io.vertx.reactivex.core.streams.Pipe; -import io.vertx.reactivex.core.streams.ReadStream; -import io.vertx.reactivex.core.streams.WriteStream; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Chat; -import it.tdlight.tdlibsession.td.TdError; -import it.tdlight.tdlibsession.td.TdResult; -import java.time.Duration; -import java.util.Objects; -import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Supplier; -import org.apache.commons.lang3.NotImplementedException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscription; -import org.warp.commonutils.concurrency.future.CompletableFutureUtils; -import org.warp.commonutils.functional.IOConsumer; -import org.warp.commonutils.log.Logger; -import org.warp.commonutils.log.LoggerFactory; -import reactor.adapter.rxjava.RxJava2Adapter; -import reactor.core.CoreSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.publisher.FluxSink.OverflowStrategy; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoSink; -import reactor.core.publisher.Sinks; -import reactor.core.publisher.Sinks.EmissionException; -import reactor.core.publisher.Sinks.EmitResult; -import reactor.core.publisher.Sinks.Empty; -import reactor.core.publisher.Sinks.Many; -import reactor.core.publisher.Sinks.One; -import reactor.core.publisher.SynchronousSink; -import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; -import reactor.util.concurrent.Queues; -import reactor.util.context.Context; - -public class MonoUtils { - - private static final Logger logger = LoggerFactory.getLogger(MonoUtils.class); - - public static Mono notImplemented() { - return Mono.fromCallable(() -> { - throw new NotImplementedException(); - }); - } - - public static Mono fromBlockingMaybe(Callable callable) { - return Mono.fromCallable(callable).subscribeOn(Schedulers.boundedElastic()); - } - - public static Mono fromBlockingEmpty(EmptyCallable callable) { - return Mono.fromCallable(() -> { - callable.call(); - return null; - }).subscribeOn(Schedulers.boundedElastic()); - } - - public static Mono fromBlockingSingle(Callable callable) { - return fromBlockingMaybe(callable).single(); - } - - public static void orElseThrow(TdResult value, SynchronousSink sink) { - if (value.succeeded()) { - sink.next(value.result()); - } else { - sink.error(new TdError(value.cause().code, value.cause().message)); - } - } - - public static Mono thenOrError(Mono> optionalMono) { - return optionalMono.handle((optional, sink) -> { - if (optional.succeeded()) { - sink.complete(); - } else { - sink.error(new TdError(optional.cause().code, optional.cause().message)); - } - }); - } - - @Deprecated - public static Mono toMono(Future future) { - return Mono.fromCompletionStage(future.toCompletionStage()); - } - - @Deprecated - @NotNull - public static Mono toMono(Single single) { - return RxJava2Adapter.singleToMono(single); - } - - @Deprecated - @NotNull - public static Mono toMono(Maybe maybe) { - return RxJava2Adapter.maybeToMono(maybe); - } - - @Deprecated - @NotNull - public static Mono toMono(Completable completable) { - //noinspection unchecked - return (Mono) RxJava2Adapter.completableToMono(completable); - } - - public static Flux fromMessageConsumer(Mono onRegistered, MessageConsumer messageConsumer) { - return fromReplyableMessageConsumer(onRegistered, messageConsumer).map(Message::body); - } - - public static Flux> fromReplyableMessageConsumer(Mono onRegistered, - MessageConsumer messageConsumer) { - var registration = messageConsumer - .rxCompletionHandler().to(RxJava2Adapter::completableToMono) - .doFirst(() -> logger.trace("Waiting for consumer registration completion...")) - .doOnSuccess(s -> logger.trace("Consumer registered")) - .then(onRegistered); - var messages = messageConsumer.toFlowable().to(RxJava2Adapter::flowableToFlux); - - return messages.mergeWith(registration.then(Mono.empty())); - } - - public static Scheduler newBoundedSingle(String name) { - return newBoundedSingle(name, false); - } - - public static Scheduler newBoundedSingle(String name, boolean daemon) { - return Schedulers.newBoundedElastic(1, - Schedulers.DEFAULT_BOUNDED_ELASTIC_QUEUESIZE, - name, - Integer.MAX_VALUE, - daemon - ); - } - - public static Mono> toOptional(Mono mono) { - return mono.map(Optional::of).defaultIfEmpty(Optional.empty()); - } - - public static Mono isSet(Mono mono) { - return mono - .map(res -> true) - .defaultIfEmpty(false); - } -} diff --git a/src/main/java/it/tdlight/utils/PromiseSink.java b/src/main/java/it/tdlight/utils/PromiseSink.java deleted file mode 100644 index 4aabd7a..0000000 --- a/src/main/java/it/tdlight/utils/PromiseSink.java +++ /dev/null @@ -1,49 +0,0 @@ -package it.tdlight.utils; - -import io.vertx.core.Promise; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.SynchronousSink; -import reactor.util.context.Context; - -public abstract class PromiseSink implements SynchronousSink { - - private final Promise promise; - - private PromiseSink(Promise promise) { - this.promise = promise; - } - - public static PromiseSink of(Context context, Promise promise) { - return new PromiseSinkImpl<>(promise, context); - } - - @Override - public void complete() { - promise.complete(); - } - - @Override - public void error(@NotNull Throwable error) { - promise.fail(error); - } - - @Override - public void next(@NotNull T value) { - promise.complete(value); - } - - private static class PromiseSinkImpl extends PromiseSink { - - private final Context context; - - public PromiseSinkImpl(Promise promise, Context context) { - super(promise); - this.context = context; - } - - @Override - public @NotNull Context currentContext() { - return context; - } - } -} diff --git a/src/main/java/it/tdlight/utils/TdLightUtils.java b/src/main/java/it/tdlight/utils/TdLightUtils.java deleted file mode 100644 index c95ea67..0000000 --- a/src/main/java/it/tdlight/utils/TdLightUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package it.tdlight.utils; - -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Error; -import it.tdlight.tdlibsession.td.ResponseError; -import it.tdlight.tdlibsession.td.TdError; -import org.jetbrains.annotations.Nullable; - -public class TdLightUtils { - - @SuppressWarnings("RedundantIfStatement") - public static boolean errorEquals(Throwable ex, @Nullable Integer errorCode, @Nullable String errorText) { - while (ex != null) { - TdApi.Error error = null; - if (ex instanceof TdError) { - error = ((TdError) ex).getTdError(); - } - if (ex instanceof ResponseError) { - error = new Error(((ResponseError) ex).getErrorCode(), ((ResponseError) ex).getErrorMessage()); - } - - if (error != null) { - if (errorCode != null) { - if (error.code != errorCode) { - return false; - } - } - if (errorText != null) { - if (error.message == null || !error.message.contains(errorText)) { - return false; - } - } - return true; - } - - ex = ex.getCause(); - } - return false; - } -} diff --git a/src/main/java/it/tdlight/utils/TdMoshiPolymorphic.java b/src/main/java/it/tdlight/utils/TdMoshiPolymorphic.java deleted file mode 100644 index ca83fcf..0000000 --- a/src/main/java/it/tdlight/utils/TdMoshiPolymorphic.java +++ /dev/null @@ -1,49 +0,0 @@ -package it.tdlight.utils; - -import com.squareup.moshi.JsonAdapter; -import it.tdlight.jni.TdApi; -import it.tdlight.jni.TdApi.Object; -import java.lang.reflect.Modifier; -import java.util.HashSet; -import java.util.Set; -import org.warp.commonutils.moshi.MoshiPolymorphic; - -public class TdMoshiPolymorphic extends MoshiPolymorphic { - - - private final Set> abstractClasses = new HashSet<>(); - private final Set> concreteClasses = new HashSet<>(); - - public TdMoshiPolymorphic() { - super(); - var declaredClasses = TdApi.class.getDeclaredClasses(); - for (Class declaredClass : declaredClasses) { - var modifiers = declaredClass.getModifiers(); - if (Modifier.isPublic(modifiers) && Modifier - .isStatic(modifiers)) { - if (Modifier.isAbstract(modifiers)) { - //noinspection unchecked - this.abstractClasses.add((Class) declaredClass); - } else { - //noinspection unchecked - this.concreteClasses.add((Class) declaredClass); - } - } - } - } - - @Override - public Set> getAbstractClasses() { - return abstractClasses; - } - - @Override - public Set> getConcreteClasses() { - return concreteClasses; - } - - @Override - protected boolean shouldIgnoreField(String fieldName) { - return fieldName.equals("CONSTRUCTOR"); - } -} diff --git a/src/main/java/it/tdlight/utils/VertxBufferInputStream.java b/src/main/java/it/tdlight/utils/VertxBufferInputStream.java deleted file mode 100644 index 846775b..0000000 --- a/src/main/java/it/tdlight/utils/VertxBufferInputStream.java +++ /dev/null @@ -1,132 +0,0 @@ -package it.tdlight.utils; - -import io.vertx.core.buffer.Buffer; -import org.warp.commonutils.stream.SafeMeasurableInputStream; -import org.warp.commonutils.stream.SafeRepositionableStream; - -public class VertxBufferInputStream extends SafeMeasurableInputStream implements SafeRepositionableStream { - - private final Buffer buffer; - - /** The first valid entry. */ - public int offset; - - /** The number of valid bytes in {@link #buffer} starting from {@link #offset}. */ - public int length; - - /** The current position as a distance from {@link #offset}. */ - private int position; - - /** The current mark as a position, or -1 if no mark exists. */ - private int mark; - - /** Creates a new buffer input stream using a given buffer fragment. - * - * @param buffer the backing buffer. - * @param offset the first valid entry of the buffer. - * @param length the number of valid bytes. - */ - public VertxBufferInputStream(final Buffer buffer, final int offset, final int length) { - this.buffer = buffer; - this.offset = offset; - this.length = length; - } - - /** Creates a new buffer input stream using a given buffer fragment. - * - * @param buffer the backing buffer. - * @param offset the first valid entry of the buffer. - */ - public VertxBufferInputStream(final Buffer buffer, final int offset) { - this.buffer = buffer; - this.offset = offset; - this.length = buffer.length(); - } - - /** Creates a new buffer input stream using a given buffer. - * - * @param buffer the backing buffer. - */ - public VertxBufferInputStream(final Buffer buffer) { - this(buffer, 0, buffer.length()); - } - - /** Creates a new buffer input stream using a given buffer. - * - * @param in the backing buffer. - */ - public VertxBufferInputStream(final VertxBufferInputStream in) { - this(in.buffer, in.offset + in.position, in.buffer.length()); - } - - @Override - public boolean markSupported() { - return true; - } - - @Override - public void reset() { - position = mark; - } - - /** Closing a fast byte buffer input stream has no effect. */ - @Override - public void close() {} - - @Override - public void mark(final int dummy) { - mark = position; - } - - @Override - public int available() { - return length - position; - } - - @Override - public long skip(long n) { - if (n <= length - position) { - position += (int)n; - return n; - } - n = length - position; - position = length; - return n; - } - - @Override - public int read() { - if (length == position) return -1; - return buffer.getByte(offset + position++) & 0xFF; - } - - /** Reads bytes from this byte-buffer input stream as - * specified in {@link java.io.InputStream#read(byte[], int, int)}. - * Note that the implementation given in {@link java.io.ByteArrayInputStream#read(byte[], int, int)} - * will return -1 on a zero-length read at EOF, contrarily to the specification. We won't. - */ - - @Override - public int read(final byte b[], final int offset, final int length) { - if (this.length == this.position) return length == 0 ? 0 : -1; - final int n = Math.min(length, this.length - this.position); - buffer.getBytes(this.offset + this.position, this.offset + this.position + n, b, offset); - this.position += n; - return n; - } - - @Override - public long position() { - return position; - } - - @Override - public void position(final long newPosition) { - position = (int)Math.min(newPosition, length); - } - - @Override - public long length() { - return length; - } -} diff --git a/src/main/java/it/tdlight/utils/VertxBufferOutputStream.java b/src/main/java/it/tdlight/utils/VertxBufferOutputStream.java deleted file mode 100644 index 62cbcb3..0000000 --- a/src/main/java/it/tdlight/utils/VertxBufferOutputStream.java +++ /dev/null @@ -1,59 +0,0 @@ -package it.tdlight.utils; - -import io.vertx.core.buffer.Buffer; -import it.unimi.dsi.fastutil.bytes.ByteArrays; -import org.warp.commonutils.stream.SafeMeasurableOutputStream; -import org.warp.commonutils.stream.SafeRepositionableStream; - -public class VertxBufferOutputStream extends SafeMeasurableOutputStream implements SafeRepositionableStream { - - /** The buffer backing the output stream. */ - public Buffer buffer; - - /** Creates a new buffer output stream with an initial capacity of 0 bytes. */ - public VertxBufferOutputStream() { - this(0); - } - - /** Creates a new buffer output stream with a given initial capacity. - * - * @param initialCapacity the initial length of the backing buffer. - */ - public VertxBufferOutputStream(final int initialCapacity) { - buffer = Buffer.buffer(initialCapacity); - } - - /** Creates a new buffer output stream wrapping a given byte buffer. - * - * @param a the byte buffer to wrap. - */ - public VertxBufferOutputStream(final Buffer a) { - buffer = a; - } - - @Override - public void write(final int b) { - buffer.appendByte((byte) b); - } - - @Override - public void write(final byte[] b, final int off, final int len) { - ByteArrays.ensureOffsetLength(b, off, len); - buffer.appendBytes(b, off, len); - } - - @Override - public void position(long newPosition) { - throw new UnsupportedOperationException("Can't change position of a vertx buffer output stream"); - } - - @Override - public long position() { - return this.length(); - } - - @Override - public long length() { - return buffer.length(); - } -} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..307d194 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/tdlib-session-container-log4j2.xml b/src/main/resources/tdlib-session-container-log4j2.xml deleted file mode 100644 index b2b173a..0000000 --- a/src/main/resources/tdlib-session-container-log4j2.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file