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.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.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 UnsupportedOperationException("Method not implemented"); }); } 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 orElseThrowFuture(TdResult value, SynchronousSink> sink) { if (value.succeeded()) { sink.next(CompletableFuture.completedFuture(value.result())); } else { sink.next(CompletableFuture.failedFuture(new TdError(value.cause().code, value.cause().message))); } } 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)); } }); } public static Mono thenOrLogSkipError(Mono> optionalMono) { return optionalMono.handle((optional, sink) -> { if (optional.failed()) { logger.error("Received TDLib error: {}", optional.cause()); } sink.complete(); }); } public static Mono orElseLogSkipError(TdResult optional) { if (optional.failed()) { logger.error("Received TDLib error: {}", optional.cause()); return Mono.empty(); } return Mono.just(optional.result()); } public static Mono thenOrLogRepeatError(Supplier>> optionalMono) { return Mono.defer(() -> optionalMono.get().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(); } public static Mono toMono(Future future) { return Mono.create(sink -> future.onComplete(result -> { if (result.succeeded()) { sink.success(result.result()); } else { sink.error(result.cause()); } })); } @NotNull public static Mono toMono(Single single) { return Mono.from(single.toFlowable()); } @NotNull public static Mono toMono(Maybe single) { return Mono.from(single.toFlowable()); } @NotNull public static Mono toMono(Completable completable) { return Mono.from(completable.toFlowable()); } public static Completable toCompletable(Mono s) { return Completable.fromPublisher(s); } @SuppressWarnings({"unchecked", "rawtypes"}) public static Mono castVoid(Mono mono) { return (Mono) mono; } 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); return messageConsumer.toFlowable().to(RxJava2Adapter::flowableToFlux).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); } @FunctionalInterface public interface VoidCallable { void call() throws Exception; } public static Mono fromVoidCallable(VoidCallable callable) { return Mono.fromCallable(() -> { callable.call(); return null; }); } }