This commit is contained in:
Andrea Cavalli 2021-11-09 15:54:28 +01:00
parent 0bb4856c7e
commit 251ee4951a
4 changed files with 208 additions and 183 deletions

View File

@ -45,6 +45,7 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.warp.commonutils.log.Logger; import org.warp.commonutils.log.Logger;
import org.warp.commonutils.log.LoggerFactory; import org.warp.commonutils.log.LoggerFactory;
@ -52,8 +53,11 @@ import org.warp.commonutils.error.InitializationException;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks; 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.Many;
import reactor.core.publisher.Sinks.One; import reactor.core.publisher.Sinks.One;
import reactor.core.publisher.SynchronousSink;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
@ -63,8 +67,11 @@ public class AsyncTdEasy {
private final Logger logger; private final Logger logger;
private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(1); private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(1);
private final Many<AuthorizationState> authState = Sinks.many().replay().latest(); private final Empty<Void> closed = Sinks.empty();
private final Many<AuthorizationState> authStateSink = Sinks.many().replay().latest();
private final AtomicReference<AuthorizationState> authState = new AtomicReference<>(new AuthorizationStateClosed());
private final AtomicBoolean requestedDefinitiveExit = new AtomicBoolean(); private final AtomicBoolean requestedDefinitiveExit = new AtomicBoolean();
private final AtomicBoolean canSendCloseRequest = new AtomicBoolean();
private final AtomicReference<TdEasySettings> settings = new AtomicReference<>(null); private final AtomicReference<TdEasySettings> settings = new AtomicReference<>(null);
private final Many<Error> globalErrors = Sinks.many().multicast().onBackpressureBuffer(); private final Many<Error> globalErrors = Sinks.many().multicast().onBackpressureBuffer();
private final One<FatalErrorType> fatalError = Sinks.one(); private final One<FatalErrorType> fatalError = Sinks.one();
@ -79,9 +86,28 @@ public class AsyncTdEasy {
this.logger = LoggerFactory.getLogger("AsyncTdEasy " + logName); this.logger = LoggerFactory.getLogger("AsyncTdEasy " + logName);
this.incomingUpdates = td.receive() 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) .flatMapSequential(this::preprocessUpdates)
.flatMapSequential(update -> Mono.from(this.getState()).single().map(state -> new AsyncTdUpdateObj(state, update))) .map(update -> {
var state = authState.get();
Objects.requireNonNull(state, "State is not set");
return new AsyncTdUpdateObj(state, update);
})
.map(upd -> (TdApi.Update) upd.getUpdate()) .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 -> { .doOnError(ex -> {
if (ex instanceof TdError) { if (ex instanceof TdError) {
var tdEx = (TdError) ex; var tdEx = (TdError) ex;
@ -97,35 +123,24 @@ public class AsyncTdEasy {
logger.error(ex.getLocalizedMessage(), ex); logger.error(ex.getLocalizedMessage(), ex);
} }
}) })
.doOnComplete(() -> authState.asFlux().take(1, true).single().subscribeOn(scheduler).subscribe(authState -> { .doFinally(s -> {
var state = authState.get();
onUpdatesTerminated(); onUpdatesTerminated();
if (authState.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) { if (state.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) {
logger.warn("Updates stream has closed while" logger.warn("Updates stream has closed while"
+ " the current authorization state is" + " the current authorization state is"
+ " still {}. Setting authorization state as closed!", authState.getClass().getSimpleName()); + " still {}. Setting authorization state as closed!", state.getClass().getSimpleName());
this.fatalError.tryEmitValue(FatalErrorType.CONNECTION_KILLED); this.fatalError.tryEmitValue(FatalErrorType.CONNECTION_KILLED);
this.authState.tryEmitNext(new AuthorizationStateClosed());
} }
})).doOnError(ex -> authState.asFlux() });
.take(1, true)
.single()
.subscribeOn(scheduler)
.subscribe(authState -> {
onUpdatesTerminated();
if (authState.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) {
logger.warn("Updates stream has terminated with an error while"
+ " the current authorization state is"
+ " still {}. Setting authorization state as closed!", authState.getClass().getSimpleName());
this.fatalError.tryEmitValue(FatalErrorType.CONNECTION_KILLED);
this.authState.tryEmitNext(new AuthorizationStateClosed());
}
})
);
} }
private void onUpdatesTerminated() { private void onUpdatesTerminated() {
logger.debug("Incoming updates flux terminated. Setting requestedDefinitiveExit: true"); logger.debug("Incoming updates flux terminated. Setting requestedDefinitiveExit: true");
requestedDefinitiveExit.set(true); requestedDefinitiveExit.set(true);
var newState = new AuthorizationStateClosed();
emitState(newState);
} }
public Mono<Void> create(TdEasySettings settings) { public Mono<Void> create(TdEasySettings settings) {
@ -156,8 +171,8 @@ public class AsyncTdEasy {
/** /**
* Get TDLib state * Get TDLib state
*/ */
public Flux<AuthorizationState> getState() { public Flux<AuthorizationState> state() {
return authState.asFlux().distinct(); return authStateSink.asFlux().distinct();
} }
/** /**
@ -199,7 +214,7 @@ public class AsyncTdEasy {
* @param i level * @param i level
*/ */
public Mono<Void> setVerbosityLevel(int i) { public Mono<Void> setVerbosityLevel(int i) {
return thenOrFatalError(sendDirectly(new TdApi.SetLogVerbosityLevel(i), true)); return sendDirectly(new TdApi.SetLogVerbosityLevel(i), true).transform(this::thenOrFatalError);
} }
/** /**
@ -207,8 +222,8 @@ public class AsyncTdEasy {
* @param name option name * @param name option name
*/ */
public Mono<Void> clearOption(String name) { public Mono<Void> clearOption(String name) {
return thenOrFatalError(sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueEmpty()), false return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueEmpty()), false)
)); .transform(this::thenOrFatalError);
} }
/** /**
@ -217,8 +232,8 @@ public class AsyncTdEasy {
* @param value option value * @param value option value
*/ */
public Mono<Void> setOptionString(String name, String value) { public Mono<Void> setOptionString(String name, String value) {
return thenOrFatalError(sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueString(value)), false return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueString(value)), false)
)); .transform(this::thenOrFatalError);
} }
/** /**
@ -227,8 +242,8 @@ public class AsyncTdEasy {
* @param value option value * @param value option value
*/ */
public Mono<Void> setOptionInteger(String name, long value) { public Mono<Void> setOptionInteger(String name, long value) {
return thenOrFatalError(sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueInteger(value)), false return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueInteger(value)), false)
)); .transform(this::thenOrFatalError);
} }
/** /**
@ -237,8 +252,8 @@ public class AsyncTdEasy {
* @param value option value * @param value option value
*/ */
public Mono<Void> setOptionBoolean(String name, boolean value) { public Mono<Void> setOptionBoolean(String name, boolean value) {
return thenOrFatalError(sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueBoolean(value)), false return sendDirectly(new TdApi.SetOption(name, new TdApi.OptionValueBoolean(value)), false)
)); .transform(this::thenOrFatalError);
} }
/** /**
@ -292,7 +307,7 @@ public class AsyncTdEasy {
*/ */
public Mono<Boolean> getOptionBoolean(String name) { public Mono<Boolean> getOptionBoolean(String name) {
return this return this
.<TdApi.OptionValue>sendDirectly(new TdApi.GetOption(name), false) .sendDirectly(new TdApi.GetOption(name), false)
.<TdApi.OptionValue>handle(MonoUtils::orElseThrow) .<TdApi.OptionValue>handle(MonoUtils::orElseThrow)
.flatMap(value -> { .flatMap(value -> {
switch (value.getConstructor()) { switch (value.getConstructor()) {
@ -323,7 +338,19 @@ public class AsyncTdEasy {
* Closes the client gracefully by sending {@link TdApi.Close}. * Closes the client gracefully by sending {@link TdApi.Close}.
*/ */
public Mono<Void> close() { public Mono<Void> close() {
return Mono.from(getState()) 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 -> { .filter(state -> {
switch (state.getConstructor()) { switch (state.getConstructor()) {
case AuthorizationStateClosing.CONSTRUCTOR: case AuthorizationStateClosing.CONSTRUCTOR:
@ -339,16 +366,23 @@ public class AsyncTdEasy {
logger.debug("Setting requestedDefinitiveExit: true"); logger.debug("Setting requestedDefinitiveExit: true");
requestedDefinitiveExit.set(true); requestedDefinitiveExit.set(true);
}) })
.doOnSuccess(s -> logger.debug("Sending TdApi.Close")) .then(td
.then(td.execute(new TdApi.Close(), DEFAULT_TIMEOUT, false)) .execute(new TdApi.Close(), Duration.ofSeconds(5), false)
.doOnNext(closeResponse -> logger.debug("TdApi.Close response is: \"{}\"", .doFirst(() -> logger.debug("Sending TdApi.Close"))
closeResponse.toString().replace('\n', ' ') .doOnNext(closeResponse -> logger.debug("TdApi.Close response is: \"{}\"",
)) closeResponse.toString().replace('\n', ' ')
.then(authState.asFlux() ))
.filter(authorizationState -> authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) .doOnSuccess(s -> logger.debug("Sent TdApi.Close"))
.take(1) .transformDeferred(closeMono -> {
.singleOrEmpty()) if (canSendCloseRequest.get()) {
.doOnNext(ok -> logger.debug("Received AuthorizationStateClosed after TdApi.Close")) return closeMono;
} else {
return Mono.empty();
}
})
)
.then(waitClosed)
.doOnSuccess(s -> logger.info("AsyncTdEasy closed successfully")) .doOnSuccess(s -> logger.info("AsyncTdEasy closed successfully"))
.then(); .then();
} }
@ -409,51 +443,61 @@ public class AsyncTdEasy {
.flatMap(obj -> { .flatMap(obj -> {
switch (obj.getConstructor()) { switch (obj.getConstructor()) {
case AuthorizationStateWaitTdlibParameters.CONSTRUCTOR: case AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
return thenOrFatalError(Mono.fromCallable(this.settings::get).single().map(settings -> { return Mono
var parameters = new TdlibParameters(); .fromCallable(this.settings::get)
parameters.useTestDc = settings.useTestDc; .single()
parameters.databaseDirectory = settings.databaseDirectory; .map(settings -> {
parameters.filesDirectory = settings.filesDirectory; var parameters = new TdlibParameters();
parameters.useFileDatabase = settings.useFileDatabase; parameters.useTestDc = settings.useTestDc;
parameters.useChatInfoDatabase = settings.useChatInfoDatabase; parameters.databaseDirectory = settings.databaseDirectory;
parameters.useMessageDatabase = settings.useMessageDatabase; parameters.filesDirectory = settings.filesDirectory;
parameters.useSecretChats = false; parameters.useFileDatabase = settings.useFileDatabase;
parameters.apiId = settings.apiId; parameters.useChatInfoDatabase = settings.useChatInfoDatabase;
parameters.apiHash = settings.apiHash; parameters.useMessageDatabase = settings.useMessageDatabase;
parameters.systemLanguageCode = settings.systemLanguageCode; parameters.useSecretChats = false;
parameters.deviceModel = settings.deviceModel; parameters.apiId = settings.apiId;
parameters.systemVersion = settings.systemVersion; parameters.apiHash = settings.apiHash;
parameters.applicationVersion = settings.applicationVersion; parameters.systemLanguageCode = settings.systemLanguageCode;
parameters.enableStorageOptimizer = settings.enableStorageOptimizer; parameters.deviceModel = settings.deviceModel;
parameters.ignoreFileNames = settings.ignoreFileNames; parameters.systemVersion = settings.systemVersion;
return new SetTdlibParameters(parameters); parameters.applicationVersion = settings.applicationVersion;
}).flatMap((SetTdlibParameters obj1) -> sendDirectly(obj1, false))); parameters.enableStorageOptimizer = settings.enableStorageOptimizer;
parameters.ignoreFileNames = settings.ignoreFileNames;
return new SetTdlibParameters(parameters);
})
.flatMap((SetTdlibParameters obj1) -> sendDirectly(obj1, false))
.transform(this::thenOrFatalError);
case AuthorizationStateWaitEncryptionKey.CONSTRUCTOR: case AuthorizationStateWaitEncryptionKey.CONSTRUCTOR:
return thenOrFatalError(sendDirectly(new CheckDatabaseEncryptionKey(), false)) return sendDirectly(new CheckDatabaseEncryptionKey(), false)
.transform(this::thenOrFatalError)
.onErrorResume((error) -> { .onErrorResume((error) -> {
logger.error("Error while checking TDLib encryption key", error); logger.error("Error while checking TDLib encryption key", error);
return sendDirectly(new TdApi.Close(), false).then(); return sendDirectly(new TdApi.Close(), false).then();
}); });
case AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: case AuthorizationStateWaitPhoneNumber.CONSTRUCTOR:
return thenOrFatalError(Mono.fromCallable(this.settings::get).single().flatMap(settings -> { return Mono
if (settings.isPhoneNumberSet()) { .fromCallable(this.settings::get).single().flatMap(settings -> {
return sendDirectly(new SetAuthenticationPhoneNumber(String.valueOf(settings.getPhoneNumber()), if (settings.isPhoneNumberSet()) {
new PhoneNumberAuthenticationSettings(false, false, false) return sendDirectly(new SetAuthenticationPhoneNumber(String.valueOf(settings.getPhoneNumber()),
), false); new PhoneNumberAuthenticationSettings(false, false, false)
} else if (settings.isBotTokenSet()) { ), false);
return sendDirectly(new CheckAuthenticationBotToken(settings.getBotToken()), false); } else if (settings.isBotTokenSet()) {
} else { return sendDirectly(new CheckAuthenticationBotToken(settings.getBotToken()), false);
return Mono.error(new IllegalArgumentException("A bot is neither an user or a bot")); } else {
} return Mono.error(new IllegalArgumentException("A bot is neither an user or a bot"));
})).onErrorResume((error) -> { }
logger.error("Error while waiting for phone number", error); })
return sendDirectly(new TdApi.Close(), false).then(); .transform(this::thenOrFatalError)
}); .onErrorResume((error) -> {
logger.error("Error while waiting for phone number", error);
return sendDirectly(new TdApi.Close(), false).then();
});
case AuthorizationStateWaitRegistration.CONSTRUCTOR: case AuthorizationStateWaitRegistration.CONSTRUCTOR:
var authorizationStateWaitRegistration = (AuthorizationStateWaitRegistration) obj; var authorizationStateWaitRegistration = (AuthorizationStateWaitRegistration) obj;
RegisterUser registerUser = new RegisterUser(); RegisterUser registerUser = new RegisterUser();
if (authorizationStateWaitRegistration.termsOfService != null if (authorizationStateWaitRegistration.termsOfService != null
&& authorizationStateWaitRegistration.termsOfService.text != null && !authorizationStateWaitRegistration.termsOfService.text.text.isBlank()) { && authorizationStateWaitRegistration.termsOfService.text != null
&& !authorizationStateWaitRegistration.termsOfService.text.text.isBlank()) {
logger.info("Telegram Terms of Service:\n" + authorizationStateWaitRegistration.termsOfService.text.text); logger.info("Telegram Terms of Service:\n" + authorizationStateWaitRegistration.termsOfService.text.text);
} }
@ -461,7 +505,7 @@ public class AsyncTdEasy {
.fromCallable(this.settings::get) .fromCallable(this.settings::get)
.single() .single()
.map(TdEasySettings::getParameterRequestHandler) .map(TdEasySettings::getParameterRequestHandler)
.flatMap(handler -> MonoUtils.thenOrLogRepeatError(() -> handler .flatMap(handler -> handler
.onParameterRequest(Parameter.ASK_FIRST_NAME, new ParameterInfoEmpty()) .onParameterRequest(Parameter.ASK_FIRST_NAME, new ParameterInfoEmpty())
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(String::trim) .map(String::trim)
@ -480,7 +524,8 @@ public class AsyncTdEasy {
.doOnNext(lastName -> registerUser.lastName = lastName) .doOnNext(lastName -> registerUser.lastName = lastName)
) )
.then(sendDirectly(registerUser, false)) .then(sendDirectly(registerUser, false))
)); .transform(this::thenOrLogRepeatError)
);
case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR: case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR:
var authorizationStateWaitOtherDeviceConfirmation = (AuthorizationStateWaitOtherDeviceConfirmation) obj; var authorizationStateWaitOtherDeviceConfirmation = (AuthorizationStateWaitOtherDeviceConfirmation) obj;
return Mono return Mono
@ -496,26 +541,29 @@ public class AsyncTdEasy {
.fromCallable(this.settings::get) .fromCallable(this.settings::get)
.single() .single()
.map(TdEasySettings::getParameterRequestHandler) .map(TdEasySettings::getParameterRequestHandler)
.flatMap(handler -> MonoUtils.thenOrLogRepeatError(() -> handler .flatMap(handler -> handler
.onParameterRequest(Parameter.ASK_CODE, new ParameterInfoCode(authorizationStateWaitCode.codeInfo.phoneNumber, .onParameterRequest(Parameter.ASK_CODE, new ParameterInfoCode(authorizationStateWaitCode.codeInfo.phoneNumber,
authorizationStateWaitCode.codeInfo.nextType, authorizationStateWaitCode.codeInfo.nextType,
authorizationStateWaitCode.codeInfo.timeout, authorizationStateWaitCode.codeInfo.timeout,
authorizationStateWaitCode.codeInfo.type)) authorizationStateWaitCode.codeInfo.type))
.flatMap(code -> sendDirectly(new CheckAuthenticationCode(code), false)) .flatMap(code -> sendDirectly(new CheckAuthenticationCode(code), false))
)); .transform(this::thenOrLogRepeatError)
);
case AuthorizationStateWaitPassword.CONSTRUCTOR: case AuthorizationStateWaitPassword.CONSTRUCTOR:
var authorizationStateWaitPassword = (AuthorizationStateWaitPassword) obj; var authorizationStateWaitPassword = (AuthorizationStateWaitPassword) obj;
return Mono return Mono
.fromCallable(this.settings::get) .fromCallable(this.settings::get)
.single() .single()
.map(TdEasySettings::getParameterRequestHandler) .map(TdEasySettings::getParameterRequestHandler)
.flatMap(handler -> MonoUtils.thenOrLogRepeatError(() -> handler .flatMap(handler -> handler
.onParameterRequest(Parameter.ASK_PASSWORD, new ParameterInfoPasswordHint( .onParameterRequest(Parameter.ASK_PASSWORD, new ParameterInfoPasswordHint(
authorizationStateWaitPassword.passwordHint)) authorizationStateWaitPassword.passwordHint))
.flatMap(password -> sendDirectly(new CheckAuthenticationPassword(password), false)) .flatMap(password -> sendDirectly(new CheckAuthenticationPassword(password), false))
)); )
.transform(this::thenOrLogRepeatError);
case AuthorizationStateReady.CONSTRUCTOR: { case AuthorizationStateReady.CONSTRUCTOR: {
this.authState.tryEmitNext(new AuthorizationStateReady()); var state = new AuthorizationStateReady();
emitState(state);
return Mono.empty(); return Mono.empty();
} }
case AuthorizationStateClosing.CONSTRUCTOR: case AuthorizationStateClosing.CONSTRUCTOR:
@ -530,7 +578,7 @@ public class AsyncTdEasy {
} else { } else {
logger.warn("td closed unexpectedly: {}", logName); logger.warn("td closed unexpectedly: {}", logName);
} }
authState.tryEmitNext(obj); emitState(obj);
return closeRequested; return closeRequested;
}).flatMap(closeRequested -> { }).flatMap(closeRequested -> {
if (closeRequested) { if (closeRequested) {
@ -574,11 +622,36 @@ public class AsyncTdEasy {
.then(Mono.justOrEmpty(updateObj.getConstructor() == Error.CONSTRUCTOR ? null : (Update) updateObj)); .then(Mono.justOrEmpty(updateObj.getConstructor() == Error.CONSTRUCTOR ? null : (Update) updateObj));
} }
public <T extends TdApi.Object> Mono<Void> thenOrFatalError(Mono<TdResult<T>> optionalMono) { private void emitState(AuthorizationState state) {
return MonoUtils.thenOrError(optionalMono.doOnNext(result -> { 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 <T extends TdApi.Object> Mono<Void> thenOrFatalError(Mono<TdResult<T>> mono) {
return mono.doOnNext(result -> {
if (result.failed()) { if (result.failed()) {
analyzeFatalErrors(result.cause()); analyzeFatalErrors(result.cause());
} }
})); }).transform(MonoUtils::thenOrError);
}
private <T extends TdApi.Object> Mono<Void> thenOrLogRepeatError(Mono<TdResult<T>> mono) {
return mono.handle((TdResult<T> optional, SynchronousSink<Void> 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();
} }
} }

View File

@ -66,10 +66,6 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
private final AtomicReference<Throwable> crash = new AtomicReference<>(); private final AtomicReference<Throwable> crash = new AtomicReference<>();
// This will only result in a successful completion, never completes in other ways // This will only result in a successful completion, never completes in other ways
private final Empty<Void> pingFail = Sinks.empty(); private final Empty<Void> pingFail = Sinks.empty();
// This will only result in a successful completion, never completes in other ways.
// It will be called when UpdateAuthorizationStateClosing is intercepted.
// If it's completed stop checking if the ping works or not
private final Empty<Void> authStateClosing = Sinks.empty();
private long botId; private long botId;
private String botAddress; private String botAddress;
@ -306,21 +302,25 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
logger.trace("Received update {}", update.getClass().getSimpleName()); logger.trace("Received update {}", update.getClass().getSimpleName());
if (update.getConstructor() == TdApi.UpdateAuthorizationState.CONSTRUCTOR) { if (update.getConstructor() == TdApi.UpdateAuthorizationState.CONSTRUCTOR) {
var updateAuthorizationState = (TdApi.UpdateAuthorizationState) update; var updateAuthorizationState = (TdApi.UpdateAuthorizationState) update;
switch (updateAuthorizationState.authorizationState.getConstructor()) { if (updateAuthorizationState.authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) {
case TdApi.AuthorizationStateClosing.CONSTRUCTOR: logger.info("Received AuthorizationStateClosed from tdlib");
authStateClosing.tryEmitEmpty();
break; var pinger = this.pinger.get();
case TdApi.AuthorizationStateClosed.CONSTRUCTOR: if (pinger != null) {
logger.info("Received AuthorizationStateClosed from tdlib"); pinger.dispose();
return cluster.getEventBus() }
.<EndSessionMessage>rxRequest(this.botAddress + ".read-binlog", EMPTY)
.to(RxJava2Adapter::singleToMono) return cluster.getEventBus()
.mapNotNull(Message::body) .<EndSessionMessage>rxRequest(this.botAddress + ".read-binlog", EMPTY)
.doOnNext(latestBinlog -> logger.info("Received binlog from server. Size: {}", .to(RxJava2Adapter::singleToMono)
BinlogUtils.humanReadableByteCountBin(latestBinlog.binlog().length()))) .mapNotNull(Message::body)
.flatMap(latestBinlog -> this.saveBinlog(latestBinlog.binlog())) .doOnNext(latestBinlog -> logger.info("Received binlog from server. Size: {}",
.doOnSuccess(s -> logger.info("Overwritten binlog from server")) BinlogUtils.humanReadableByteCountBin(latestBinlog.binlog().length())
.thenReturn(update); ))
.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); return Mono.just(update);

View File

@ -37,6 +37,7 @@ import java.net.ConnectException;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.warp.commonutils.log.Logger; import org.warp.commonutils.log.Logger;
import org.warp.commonutils.log.LoggerFactory; import org.warp.commonutils.log.LoggerFactory;
@ -82,6 +83,9 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
return Mono return Mono
.fromCallable(() -> { .fromCallable(() -> {
logger.trace("Stating verticle"); logger.trace("Stating verticle");
System.setProperty("it.tdlight.enableShutdownHooks", "false");
var botId = config().getInteger("botId"); var botId = config().getInteger("botId");
if (botId == null || botId <= 0) { if (botId == null || botId <= 0) {
throw new IllegalArgumentException("botId is not set!"); throw new IllegalArgumentException("botId is not set!");
@ -271,10 +275,12 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
if (pingConsumer != null) { if (pingConsumer != null) {
pingConsumer.dispose(); pingConsumer.dispose();
} }
var readBinlogConsumer = this.readBinlogConsumer.get(); var readBinlogConsumer = this.readBinlogConsumer.getAndSet(null);
if (readBinlogConsumer != null) { Schedulers.boundedElastic().schedule(() -> {
readBinlogConsumer.dispose(); if (readBinlogConsumer != null) {
} readBinlogConsumer.dispose();
}
}, 10, TimeUnit.MINUTES);
var readyToReceiveConsumer = this.readyToReceiveConsumer.get(); var readyToReceiveConsumer = this.readyToReceiveConsumer.get();
if (readyToReceiveConsumer != null) { if (readyToReceiveConsumer != null) {
readyToReceiveConsumer.dispose(); readyToReceiveConsumer.dispose();

View File

@ -9,6 +9,7 @@ import io.vertx.core.AsyncResult;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.reactivex.RxHelper;
import io.vertx.reactivex.core.eventbus.Message; import io.vertx.reactivex.core.eventbus.Message;
import io.vertx.reactivex.core.eventbus.MessageConsumer; import io.vertx.reactivex.core.eventbus.MessageConsumer;
import io.vertx.reactivex.core.streams.Pipe; import io.vertx.reactivex.core.streams.Pipe;
@ -27,6 +28,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
@ -59,7 +61,7 @@ public class MonoUtils {
public static <T> Mono<T> notImplemented() { public static <T> Mono<T> notImplemented() {
return Mono.fromCallable(() -> { return Mono.fromCallable(() -> {
throw new UnsupportedOperationException("Method not implemented"); throw new NotImplementedException();
}); });
} }
@ -78,14 +80,6 @@ public class MonoUtils {
return fromBlockingMaybe(callable).single(); return fromBlockingMaybe(callable).single();
} }
public static <R extends TdApi.Object> void orElseThrowFuture(TdResult<R> value, SynchronousSink<CompletableFuture<R>> 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 <T extends TdApi.Object> void orElseThrow(TdResult<T> value, SynchronousSink<T> sink) { public static <T extends TdApi.Object> void orElseThrow(TdResult<T> value, SynchronousSink<T> sink) {
if (value.succeeded()) { if (value.succeeded()) {
sink.next(value.result()); sink.next(value.result());
@ -104,66 +98,28 @@ public class MonoUtils {
}); });
} }
public static <T extends TdApi.Object> Mono<Void> thenOrLogSkipError(Mono<TdResult<T>> optionalMono) { @Deprecated
return optionalMono.handle((optional, sink) -> {
if (optional.failed()) {
logger.error("Received TDLib error: {}", optional.cause());
}
sink.complete();
});
}
public static <T extends TdApi.Object> Mono<T> orElseLogSkipError(TdResult<T> optional) {
if (optional.failed()) {
logger.error("Received TDLib error: {}", optional.cause());
return Mono.empty();
}
return Mono.just(optional.result());
}
public static <T extends TdApi.Object> Mono<Void> thenOrLogRepeatError(Supplier<? extends Mono<TdResult<T>>> optionalMono) {
return Mono.defer(() -> optionalMono.get().handle((TdResult<T> optional, SynchronousSink<Void> 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 <T> Mono<T> toMono(Future<T> future) { public static <T> Mono<T> toMono(Future<T> future) {
return Mono.create(sink -> future.onComplete(result -> { return Mono.fromCompletionStage(future.toCompletionStage());
if (result.succeeded()) {
sink.success(result.result());
} else {
sink.error(result.cause());
}
}));
} }
@Deprecated
@NotNull @NotNull
public static <T> Mono<T> toMono(Single<T> single) { public static <T> Mono<T> toMono(Single<T> single) {
return Mono.from(single.toFlowable()); return RxJava2Adapter.singleToMono(single);
} }
@Deprecated
@NotNull @NotNull
public static <T> Mono<T> toMono(Maybe<T> single) { public static <T> Mono<T> toMono(Maybe<T> maybe) {
return Mono.from(single.toFlowable()); return RxJava2Adapter.maybeToMono(maybe);
} }
@Deprecated
@NotNull @NotNull
public static <T> Mono<T> toMono(Completable completable) { public static <T> Mono<T> toMono(Completable completable) {
return Mono.from(completable.toFlowable()); //noinspection unchecked
} return (Mono<T>) RxJava2Adapter.completableToMono(completable);
public static <T> Completable toCompletable(Mono<T> s) {
return Completable.fromPublisher(s);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> Mono<T> castVoid(Mono<Void> mono) {
return (Mono) mono;
} }
public static <T> Flux<T> fromMessageConsumer(Mono<Void> onRegistered, MessageConsumer<T> messageConsumer) { public static <T> Flux<T> fromMessageConsumer(Mono<Void> onRegistered, MessageConsumer<T> messageConsumer) {
@ -177,7 +133,9 @@ public class MonoUtils {
.doFirst(() -> logger.trace("Waiting for consumer registration completion...")) .doFirst(() -> logger.trace("Waiting for consumer registration completion..."))
.doOnSuccess(s -> logger.trace("Consumer registered")) .doOnSuccess(s -> logger.trace("Consumer registered"))
.then(onRegistered); .then(onRegistered);
return messageConsumer.toFlowable().to(RxJava2Adapter::flowableToFlux).mergeWith(registration.then(Mono.empty())); var messages = messageConsumer.toFlowable().to(RxJava2Adapter::flowableToFlux);
return messages.mergeWith(registration.then(Mono.empty()));
} }
public static Scheduler newBoundedSingle(String name) { public static Scheduler newBoundedSingle(String name) {
@ -202,16 +160,4 @@ public class MonoUtils {
.map(res -> true) .map(res -> true)
.defaultIfEmpty(false); .defaultIfEmpty(false);
} }
@FunctionalInterface
public interface VoidCallable {
void call() throws Exception;
}
public static Mono<?> fromVoidCallable(VoidCallable callable) {
return Mono.fromCallable(() -> {
callable.call();
return null;
});
}
} }