This commit is contained in:
Andrea Cavalli 2020-10-13 01:31:32 +02:00
parent 4d51b63737
commit 0fd22c61f4
19 changed files with 548 additions and 585 deletions

@ -1 +1 @@
Subproject commit f5ecc4b89655ddfaa1e05b77d6740e8d9cdfcaea
Subproject commit 85bf271fb827a31b68d761a06a92972aa6548338

View File

@ -0,0 +1,10 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Object;
public interface ClientEventsHandler {
int getClientId();
void handleEvents(boolean isClosed, long[] eventIds, Object[] events);
}

View File

@ -0,0 +1,38 @@
package it.tdlight.common;
public abstract class CommonClient {
protected abstract String getImplementationName();
private InternalClientManager getClientManager() {
// ClientManager is singleton:
return InternalClientManager.get(getImplementationName());
}
public TelegramClient create(ResultHandler updateHandler,
ExceptionHandler updateExceptionHandler,
ExceptionHandler defaultExceptionHandler) {
InternalClient client = new InternalClient(getClientManager(),
updateHandler,
updateExceptionHandler,
defaultExceptionHandler
);
return create(client);
}
public TelegramClient create(UpdatesHandler updatesHandler,
ExceptionHandler updateExceptionHandler,
ExceptionHandler defaultExceptionHandler) {
InternalClient client = new InternalClient(getClientManager(),
updatesHandler,
updateExceptionHandler,
defaultExceptionHandler
);
return create(client);
}
private TelegramClient create(InternalClient internalClient) {
return internalClient;
}
}

View File

@ -0,0 +1,7 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Object;
public interface EventsHandler {
void handleClientEvents(int clientId, boolean isClosed, long[] clientEventIds, Object[] clientEvents);
}

View File

@ -0,0 +1,19 @@
package it.tdlight.common;
public class Handler {
private final ResultHandler resultHandler;
private final ExceptionHandler exceptionHandler;
public Handler(ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
this.resultHandler = resultHandler;
this.exceptionHandler = exceptionHandler;
}
public ResultHandler getResultHandler() {
return resultHandler;
}
public ExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
}

View File

@ -0,0 +1,21 @@
package it.tdlight.common;
import it.unimi.dsi.fastutil.Swapper;
class IntSwapper implements Swapper {
private final int[] array;
int tmp;
public IntSwapper(int[] array) {
this.array = array;
tmp = 0;
}
@Override
public void swap(int a, int b) {
tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
}

View File

@ -0,0 +1,162 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Error;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi.Update;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.concurrent.ConcurrentHashMap;
public class InternalClient implements ClientEventsHandler, TelegramClient {
private final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
private final int clientId;
private final InternalClientManager clientManager;
private final Handler updateHandler;
private final MultiHandler updatesHandler;
private final ExceptionHandler defaultExceptionHandler;
private volatile boolean isClosed;
public InternalClient(InternalClientManager clientManager,
ResultHandler updateHandler,
ExceptionHandler updateExceptionHandler,
ExceptionHandler defaultExceptionHandler) {
this.updateHandler = new Handler(updateHandler, updateExceptionHandler);
this.updatesHandler = null;
this.clientManager = clientManager;
this.clientId = NativeClientAccess.create();
this.defaultExceptionHandler = defaultExceptionHandler;
clientManager.registerClient(this);
}
public InternalClient(InternalClientManager clientManager,
UpdatesHandler updatesHandler,
ExceptionHandler updateExceptionHandler,
ExceptionHandler defaultExceptionHandler) {
this.updateHandler = null;
this.updatesHandler = new MultiHandler(updatesHandler, updateExceptionHandler);
this.clientManager = clientManager;
this.clientId = NativeClientAccess.create();
this.defaultExceptionHandler = defaultExceptionHandler;
clientManager.registerClient(this);
}
@Override
public int getClientId() {
return clientId;
}
@Override
public void handleEvents(boolean isClosed, long[] eventIds, Object[] events) {
if (updatesHandler != null) {
LongArrayList idsToFilter = new LongArrayList(eventIds);
ObjectArrayList<Object> eventsToFilter = new ObjectArrayList<>(events);
for (int i = eventIds.length - 1; i >= 0; i--) {
if (eventIds[i] != 0) {
idsToFilter.removeLong(i);
eventsToFilter.remove(i);
long eventId = eventIds[i];
Object event = events[i];
Handler handler = handlers.remove(eventId);
handleResponse(eventId, event, handler);
}
}
eventsToFilter.removeIf(event -> {
if (event instanceof Error) {
handleException(updatesHandler.getExceptionHandler(), new TDLibException((Error) event));
return true;
}
return false;
});
ObjectArrayList<Update> updates = new ObjectArrayList<>(eventsToFilter.size());
for (Object object : eventsToFilter) {
updates.add((Update) object);
}
try {
updatesHandler.getUpdatesHandler().onUpdates(updates);
} catch (Throwable cause) {
handleException(updatesHandler.getExceptionHandler(), cause);
}
} else {
for (int i = 0; i < eventIds.length; i++) {
handleEvent(eventIds[i], events[i]);
}
}
if (isClosed) {
this.isClosed = true;
}
}
/**
* Handles only a response (not an update!)
*/
private void handleResponse(long eventId, Object event, Handler handler) {
if (handler != null) {
try {
if (event instanceof Error) {
handleException(handler.getExceptionHandler(), new TDLibException((Error) event));
} else {
handler.getResultHandler().onResult(event);
}
} catch (Throwable cause) {
handleException(handler.getExceptionHandler(), cause);
}
} else {
System.err.println("Unknown event id " + eventId + ", the event has been dropped!");
}
}
/**
* Handles a response or an update
*/
private void handleEvent(long eventId, Object event) {
if (updatesHandler != null || updateHandler == null) throw new IllegalStateException();
Handler handler = eventId == 0 ? updateHandler : handlers.remove(eventId);
handleResponse(eventId, event, handler);
}
private void handleException(ExceptionHandler exceptionHandler, Throwable cause) {
if (exceptionHandler == null) {
exceptionHandler = defaultExceptionHandler;
}
if (exceptionHandler != null) {
try {
exceptionHandler.onException(cause);
} catch (Throwable ignored) {}
}
}
@Override
public void send(Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
ensureOpen();
long queryId = clientManager.getNextQueryId();
if (resultHandler != null) {
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
}
NativeClientAccess.send(clientId, queryId, query);
}
@Override
public Object execute(Function query) {
ensureOpen();
return NativeClientAccess.execute(query);
}
private void ensureOpen() {
if (isClosed) {
throw new IllegalStateException("The client is closed!");
}
}
}

View File

@ -0,0 +1,56 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Object;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
public class InternalClientManager implements AutoCloseable {
private static final AtomicReference<InternalClientManager> INSTANCE = new AtomicReference<>(null);
private final String implementationName;
private final ResponseReceiver responseReceiver = new ResponseReceiver(this::handleClientEvents);
private final Int2ObjectMap<ClientEventsHandler> clientEventsHandlerMap = new Int2ObjectOpenHashMap<>();
private final AtomicLong currentQueryId = new AtomicLong();
private InternalClientManager(String implementationName) {
this.implementationName = implementationName;
}
public static InternalClientManager get(String implementationName) {
return INSTANCE.updateAndGet(val -> val == null ? new InternalClientManager(implementationName) : val);
}
private void handleClientEvents(int clientId, boolean isClosed, long[] clientEventIds, Object[] clientEvents) {
ClientEventsHandler handler = clientEventsHandlerMap.get(clientId);
if (handler != null) {
handler.handleEvents(isClosed, clientEventIds, clientEvents);
} else {
System.err.println("Unknown client id " + clientId + ", " + clientEvents.length + " events have been dropped!");
}
if (isClosed) {
clientEventsHandlerMap.remove(clientId);
}
}
public void registerClient(ClientEventsHandler client) {
this.clientEventsHandlerMap.put(client.getClientId(), client);
}
public String getImplementationName() {
return implementationName;
}
public long getNextQueryId() {
return currentQueryId.getAndUpdate(value -> (value >= Long.MAX_VALUE ? 0 : value) + 1);
}
@Override
public void close() throws InterruptedException {
responseReceiver.close();
}
}

View File

@ -0,0 +1,19 @@
package it.tdlight.common;
public class MultiHandler {
private final UpdatesHandler updatesHandler;
private final ExceptionHandler exceptionHandler;
public MultiHandler(UpdatesHandler updatesHandler, ExceptionHandler exceptionHandler) {
this.updatesHandler = updatesHandler;
this.exceptionHandler = exceptionHandler;
}
public UpdatesHandler getUpdatesHandler() {
return updatesHandler;
}
public ExceptionHandler getExceptionHandler() {
return exceptionHandler;
}
}

View File

@ -0,0 +1,24 @@
package it.tdlight.common;
import it.tdlight.jni.NativeClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Function;
class NativeClientAccess extends NativeClient {
public static int create() {
return NativeClientAccess.createNativeClient();
}
public static TdApi.Object execute(Function function) {
return NativeClientAccess.nativeClientExecute(function);
}
public static void send(int nativeClientId, long eventId, TdApi.Function function) {
NativeClientAccess.nativeClientSend(nativeClientId, eventId, function);
}
public static int receive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout) {
return NativeClientAccess.nativeClientReceive(clientIds, eventIds, events, timeout);
}
}

View File

@ -0,0 +1,100 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
public class ResponseReceiver extends Thread implements AutoCloseable {
private static final int MAX_EVENTS = 1000;
private static final int[] originalSortingSource = new int[MAX_EVENTS];
static {
for (int i = 0; i < originalSortingSource.length; i++) {
originalSortingSource[i] = i;
}
}
private final EventsHandler eventsHandler;
private final int[] clientIds = new int[MAX_EVENTS];
private final long[] eventIds = new long[MAX_EVENTS];
private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS];
private final CountDownLatch closeWait = new CountDownLatch(1);
public ResponseReceiver(EventsHandler eventsHandler) {
super("TDLib thread");
this.eventsHandler = eventsHandler;
this.setDaemon(true);
this.start();
}
@SuppressWarnings({"UnnecessaryLocalVariable", "InfiniteLoopStatement"})
@Override
public void run() {
int[] sortIndex;
try {
while(true) {
int resultsCount = NativeClientAccess.receive(clientIds, eventIds, events, 2.0 /*seconds*/);
if (resultsCount <= 0)
continue;
// Generate a list of indices sorted by client id, from 0 to resultsCount
sortIndex = generateSortIndex(0, resultsCount, clientIds);
int lastClientId = clientIds[sortIndex[0]];
int lastClientIdEventsCount = 0;
boolean lastClientClosed = false;
for (int i = 0; i <= resultsCount; i++) {
if (i == resultsCount || (i != 0 && clientIds[sortIndex[i]] != lastClientId)) {
if (lastClientIdEventsCount > 0) {
int clientId = lastClientId;
long[] clientEventIds = new long[lastClientIdEventsCount];
TdApi.Object[] clientEvents = new TdApi.Object[lastClientIdEventsCount];
for (int j = 0; j < lastClientIdEventsCount; j++) {
clientEventIds[j] = eventIds[sortIndex[i - lastClientIdEventsCount + j]];
clientEvents[j] = events[sortIndex[i - lastClientIdEventsCount + j]];
if (clientEventIds[j] == 0 && clientEvents[j] instanceof TdApi.UpdateAuthorizationState) {
TdApi.AuthorizationState authorizationState = ((TdApi.UpdateAuthorizationState) clientEvents[j]).authorizationState;
if (authorizationState instanceof TdApi.AuthorizationStateClosed) {
lastClientClosed = true;
}
}
}
eventsHandler.handleClientEvents(clientId, lastClientClosed, clientEventIds, clientEvents);
}
lastClientId = clientIds[sortIndex[i]];
lastClientIdEventsCount = 0;
lastClientClosed = false;
}
lastClientIdEventsCount++;
}
Arrays.fill(events, null);
}
} finally {
this.closeWait.countDown();
}
}
@SuppressWarnings("SameParameterValue")
private int[] generateSortIndex(int from, int to, int[] data) {
int[] sortedIndices = Arrays.copyOfRange(originalSortingSource, from, to);
it.unimi.dsi.fastutil.Arrays.mergeSort(from, to, (o1, o2) -> {
return Integer.compare(data[sortedIndices[o1]], data[sortedIndices[o2]]);
}, new IntSwapper(sortedIndices));
return sortedIndices;
}
@Override
public void close() throws InterruptedException {
this.closeWait.await();
}
}

View File

@ -0,0 +1,21 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Error;
public class TDLibException extends RuntimeException {
private final Error event;
public TDLibException(Error event) {
super(event.code + ": " + event.message);
this.event = event;
}
public int getErrorCode() {
return event.code;
}
public String getErrorMessage() {
return event.message;
}
}

View File

@ -1,74 +1,42 @@
package it.tdlight.common;
import java.io.IOException;
import java.util.List;
import it.tdlight.jni.TdApi;
public interface TelegramClient {
/**
* Sends request to TDLib. May be called from any thread.
* @param request Request to TDLib.
* Sends a request to the TDLib.
*
* @param query Object representing a query to the TDLib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TdApi.Error as parameter. If it is null, nothing
* will be called.
* @param exceptionHandler Exception handler with onException method which will be called on
* exception thrown from resultHandler. If it is null, then
* defaultExceptionHandler will be called.
* @throws NullPointerException if query is null.
*/
void send(Request request);
void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler);
/**
* Receives incoming updates and request responses from TDLib. May be called from any thread, but shouldn't be called simultaneously from two different threads.
* @param timeout Maximum number of seconds allowed for this function to wait for new records.
* @param eventSize Maximum number of events allowed in list.
* @return An incoming update or request response list. The object returned in the response may be an empty list if the timeout expires.
* Sends a request to the TDLib with an empty ExceptionHandler.
*
* @param query Object representing a query to the TDLib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TdApi.Error as parameter. If it is null, then
* defaultExceptionHandler will be called.
* @throws NullPointerException if query is null.
*/
default List<Response> receive(double timeout, int eventSize) {
return receive(timeout, eventSize, true, true);
default void send(TdApi.Function query, ResultHandler resultHandler) {
send(query, resultHandler, null);
}
/**
* Receives incoming updates and/or request responses from TDLib. May be called from any thread, but shouldn't be called simultaneously from two different threads.
* @param timeout Maximum number of seconds allowed for this function to wait for new records.
* @param eventSize Maximum number of events allowed in list.
* @param receiveResponses True to receive responses.
* @param receiveUpdates True to receive updates from TDLib.
* @return An incoming update or request response list. The object returned in the response may be an empty list if the timeout expires.
*/
List<Response> receive(double timeout, int eventSize, boolean receiveResponses, boolean receiveUpdates);
/**
* Receives incoming updates and request responses from TDLib. May be called from any thread, but
* shouldn't be called simultaneously from two different threads.
* Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously.
*
* @param timeout Maximum number of seconds allowed for this function to wait for new records.
* @return An incoming update or request response. The object returned in the response may be a
* nullptr if the timeout expires.
* @param query Object representing a query to the TDLib.
* @return request result.
* @throws NullPointerException if query is null.
*/
default Response receive(double timeout) {
return receive(timeout, true, true);
}
/**
* Receives incoming updates and request responses from TDLib. May be called from any thread, but
* shouldn't be called simultaneously from two different threads.
*
* @param timeout Maximum number of seconds allowed for this function to wait for new records.
* @param receiveResponses True to receive responses.
* @param receiveUpdates True to receive updates from TDLib.
* @return An incoming update or request response. The object returned in the response may be a
* nullptr if the timeout expires.
*/
Response receive(double timeout, boolean receiveResponses, boolean receiveUpdates);
/**
* Synchronously executes TDLib requests. Only a few requests can be executed synchronously. May be called from any thread.
* @param request Request to the TDLib.
* @return The request response.
*/
Response execute(Request request);
/**
* Destroys the client and TDLib instance.
*/
void destroyClient();
/**
* Initializes the client and TDLib instance.
*/
void initializeClient() throws IOException;
TdApi.Object execute(TdApi.Function query);
}

View File

@ -0,0 +1,17 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Update;
import java.util.List;
/**
* Interface for handler for incoming updates from TDLib.
*/
public interface UpdatesHandler {
/**
* Callback called on incoming update from TDLib.
*
* @param object Updates of type TdApi.Update about new events.
*/
void onUpdates(List<Update> object);
}

View File

@ -8,7 +8,5 @@ public class NativeClient {
protected static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout);
protected static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout, boolean include_responses, boolean include_updates);
protected static native TdApi.Object nativeClientExecute(TdApi.Function function);
}

View File

@ -1,248 +1,14 @@
package it.tdlight.tdlib;
import it.cavallium.concurrentlocks.ReentrantReadWriteUpdateLock;
import it.tdlight.common.Init;
import it.tdlight.common.Request;
import it.tdlight.common.Response;
import it.tdlight.jni.NativeClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
import it.tdlight.jni.TdApi.AuthorizationStateClosing;
import it.tdlight.jni.TdApi.AuthorizationStateWaitTdlibParameters;
import it.tdlight.jni.TdApi.GetOption;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi.SetOption;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import it.tdlight.common.TelegramClient;
import it.tdlight.common.ClientState;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import it.tdlight.common.CommonClient;
/**
* Interface for interaction with TDLib.
*/
public class Client extends NativeClient implements TelegramClient {
private ClientState state = ClientState.of(false, 0, false, false, false);
private final ReentrantReadWriteUpdateLock stateLock = new ReentrantReadWriteUpdateLock();
/**
* Creates a new TDLib client.
*/
public Client() {
try {
Init.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
System.exit(1);
}
this.initializeClient();
}
public class Client extends CommonClient {
@Override
public void send(Request request) {
long clientId;
stateLock.readLock().lock();
try {
requireInitialized();
requireReadyToSend(request.getFunction().getConstructor());
clientId = state.getClientId();
} finally {
stateLock.readLock().unlock();
}
nativeClientSend(clientId, request.getId(), request.getFunction());
}
@Override
public List<Response> receive(double timeout, int eventsSize, boolean receiveResponses, boolean receiveUpdates) {
long clientId;
stateLock.updateLock().lock();
try {
if (!state.isInitialized()) {
sleep(timeout);
return Collections.emptyList();
}
requireInitialized();
if (!state.isReadyToReceive()) {
sleep(timeout);
return Collections.emptyList();
}
requireReadyToReceive();
clientId = state.getClientId();
return Arrays.asList(this.internalReceive(clientId, timeout, eventsSize, receiveResponses, receiveUpdates));
} finally {
stateLock.updateLock().unlock();
}
}
private void sleep(double timeout) {
long nanos = (long) (timeout * 1000000000d);
int nanosPart = (int) (nanos % 1000000L);
long millis = Duration.ofNanos(nanos - nanosPart).toMillis();
try {
Thread.sleep(millis, nanosPart);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public Response receive(double timeout, boolean receiveResponses, boolean receiveUpdates) {
long clientId;
stateLock.updateLock().lock();
try {
if (!state.isInitialized()) {
sleep(timeout);
return null;
}
requireInitialized();
if (!state.isReadyToReceive()) {
sleep(timeout);
return null;
}
requireReadyToReceive();
clientId = state.getClientId();
Response[] responses = this.internalReceive(clientId, timeout, 1, receiveResponses, receiveUpdates);
if (responses.length > 0) {
return responses[0];
}
return null;
} finally {
stateLock.updateLock().unlock();
}
}
private Response[] internalReceive(long clientId, double timeout, int eventsSize, boolean receiveResponses, boolean receiveUpdates) {
long[] eventIds = new long[eventsSize];
TdApi.Object[] events = new TdApi.Object[eventsSize];
if (!(receiveResponses && receiveUpdates)) {
throw new IllegalArgumentException("The variables receiveResponses and receiveUpdates must be both true, because you are using the original TDLib!");
}
int resultSize = nativeClientReceive(clientId, eventIds, events, timeout);
Response[] responses = new Response[resultSize];
for (int i = 0; i < resultSize; i++) {
responses[i] = new Response(eventIds[i], events[i]);
if (eventIds[i] == 0) {
handleStateEvent(events[i]);
}
}
return responses;
}
private void handleStateEvent(Object event) {
if (event == null) {
return;
}
if (event.getConstructor() != UpdateAuthorizationState.CONSTRUCTOR) {
return;
}
UpdateAuthorizationState updateAuthorizationState = (UpdateAuthorizationState) event;
switch (updateAuthorizationState.authorizationState.getConstructor()) {
case AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
stateLock.writeLock().lock();
try {
state.setReadyToSend(true);
} finally {
stateLock.writeLock().unlock();
}
break;
case AuthorizationStateClosing.CONSTRUCTOR:
stateLock.writeLock().lock();
try {
state.setReadyToSend(false);
} finally {
stateLock.writeLock().unlock();
}
break;
case AuthorizationStateClosed.CONSTRUCTOR:
stateLock.writeLock().lock();
try {
state.setReadyToSend(false).setReadyToReceive(false);
} finally {
stateLock.writeLock().unlock();
}
break;
}
}
@Override
public Response execute(Request request) {
stateLock.readLock().lock();
try {
requireInitialized();
requireReadyToSend(request.getFunction().getConstructor());
} finally {
stateLock.readLock().unlock();
}
Object object = nativeClientExecute(request.getFunction());
return new Response(0, object);
}
@Override
public void destroyClient() {
stateLock.writeLock().lock();
try {
if (state.isInitialized() && state.hasClientId()) {
if (state.isReadyToSend() || state.isReadyToReceive()) {
throw new IllegalStateException("You need to close the Client before destroying it!");
}
destroyNativeClient(this.state.getClientId());
state = ClientState.of(false, 0, false, false, false);
}
} finally {
stateLock.writeLock().unlock();
}
}
@Override
public void initializeClient() {
stateLock.writeLock().lock();
try {
if (!state.isInitialized() && !state.hasClientId()) {
long clientId = createNativeClient();
state = ClientState.of(true, clientId, true, true, false);
}
} finally {
stateLock.writeLock().unlock();
}
}
private void requireInitialized() {
if (!state.isInitialized() || !state.hasClientId()) {
throw new IllegalStateException("Client not initialized");
}
}
private void requireReadyToSend(int constructor) {
if (!state.isReadyToSend()) {
switch (constructor) {
case SetOption.CONSTRUCTOR:
case GetOption.CONSTRUCTOR:
case TdApi.SetTdlibParameters.CONSTRUCTOR:
return;
}
throw new IllegalStateException("Client not ready to send");
}
}
private void requireReadyToReceive() {
if (!state.isReadyToReceive()) {
throw new IllegalStateException("Client not ready to receive");
}
protected String getImplementationName() {
return "tdlib";
}
}

View File

@ -1,287 +1,14 @@
package it.tdlight.tdlight;
import it.tdlight.common.ClientState;
import it.tdlight.common.Init;
import it.tdlight.common.Request;
import it.tdlight.common.Response;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.NativeClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
import it.tdlight.jni.TdApi.AuthorizationStateClosing;
import it.tdlight.jni.TdApi.AuthorizationStateWaitTdlibParameters;
import it.tdlight.jni.TdApi.GetOption;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi.SetOption;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import it.tdlight.common.CommonClient;
/**
* Interface for interaction with TDLib.
* Interface for interaction with TDLight.
*/
public class Client extends NativeClient implements TelegramClient {
private ClientState state = ClientState.of(false, 0, false, false, false);
private final ReentrantReadWriteLock stateLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock updatesLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock responsesLock = new ReentrantReadWriteLock();
/**
* Creates a new TDLib client.
*/
public Client() {
try {
Init.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
System.exit(1);
}
this.initializeClient();
}
public class Client extends CommonClient {
@Override
public void send(Request request) {
long clientId;
stateLock.readLock().lock();
try {
requireInitialized();
requireReadyToSend(request.getFunction().getConstructor());
clientId = state.getClientId();
} finally {
stateLock.readLock().unlock();
}
nativeClientSend(clientId, request.getId(), request.getFunction());
}
@Override
public List<Response> receive(double timeout, int eventsSize, boolean receiveResponses, boolean receiveUpdates) {
if (receiveResponses) responsesLock.readLock().lock();
try {
if (receiveUpdates) updatesLock.readLock().lock();
try {
long clientId;
stateLock.readLock().lock();
try {
if (!state.isInitialized()) {
sleep(timeout);
return Collections.emptyList();
}
requireInitialized();
if (!state.isReadyToReceive()) {
sleep(timeout);
return Collections.emptyList();
}
requireReadyToReceive();
clientId = state.getClientId();
} finally {
stateLock.readLock().unlock();
}
return Arrays.asList(this.internalReceive(clientId, timeout, eventsSize, receiveResponses, receiveUpdates));
} finally {
if (receiveUpdates) updatesLock.readLock().unlock();
}
} finally {
if (receiveResponses) responsesLock.readLock().unlock();
}
}
private void sleep(double timeout) {
long nanos = (long) (timeout * 1000000000d);
int nanosPart = (int) (nanos % 1000000L);
long millis = Duration.ofNanos(nanos - nanosPart).toMillis();
try {
Thread.sleep(millis, nanosPart);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public Response receive(double timeout, boolean receiveResponses, boolean receiveUpdates) {
responsesLock.readLock().lock();
try {
updatesLock.readLock().lock();
try {
long clientId;
stateLock.readLock().lock();
try {
if (!state.isInitialized()) {
sleep(timeout);
return null;
}
requireInitialized();
if (!state.isReadyToReceive()) {
sleep(timeout);
return null;
}
requireReadyToReceive();
clientId = state.getClientId();
} finally {
stateLock.readLock().unlock();
}
Response[] responses = this.internalReceive(clientId, timeout, 1, receiveResponses, receiveUpdates);
if (responses.length > 0) {
return responses[0];
}
return null;
} finally {
updatesLock.readLock().unlock();
}
} finally {
responsesLock.readLock().unlock();
}
}
private Response[] internalReceive(long clientId, double timeout, int eventsSize, boolean receiveResponses, boolean receiveUpdates) {
long[] eventIds = new long[eventsSize];
TdApi.Object[] events = new TdApi.Object[eventsSize];
int resultSize = nativeClientReceive(clientId, eventIds, events, timeout, receiveResponses, receiveUpdates);
Response[] responses = new Response[resultSize];
for (int i = 0; i < resultSize; i++) {
responses[i] = new Response(eventIds[i], events[i]);
if (eventIds[i] == 0) {
handleStateEvent(events[i]);
}
}
return responses;
}
private void handleStateEvent(Object event) {
if (event == null) {
return;
}
if (event.getConstructor() != UpdateAuthorizationState.CONSTRUCTOR) {
return;
}
UpdateAuthorizationState updateAuthorizationState = (UpdateAuthorizationState) event;
switch (updateAuthorizationState.authorizationState.getConstructor()) {
case AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
stateLock.writeLock().lock();
try {
state.setReadyToSend(true);
} finally {
stateLock.writeLock().unlock();
}
break;
case AuthorizationStateClosing.CONSTRUCTOR:
stateLock.writeLock().lock();
try {
state.setReadyToSend(false);
} finally {
stateLock.writeLock().unlock();
}
break;
case AuthorizationStateClosed.CONSTRUCTOR:
stateLock.writeLock().lock();
try {
state.setReadyToSend(false).setReadyToReceive(false);
} finally {
stateLock.writeLock().unlock();
}
break;
}
}
@Override
public Response execute(Request request) {
stateLock.readLock().lock();
try {
requireInitialized();
requireReadyToSend(request.getFunction().getConstructor());
} finally {
stateLock.readLock().unlock();
}
Object object = nativeClientExecute(request.getFunction());
return new Response(0, object);
}
@Override
public void destroyClient() {
responsesLock.writeLock().lock();
try {
updatesLock.writeLock().lock();
try {
stateLock.writeLock().lock();
try {
if (state.isInitialized() && state.hasClientId()) {
if (state.isReadyToSend() || state.isReadyToReceive()) {
throw new IllegalStateException("You need to close the Client before destroying it!");
}
destroyNativeClient(this.state.getClientId());
state = ClientState.of(false, 0, false, false, false);
}
} finally {
stateLock.writeLock().unlock();
}
} finally {
updatesLock.writeLock().unlock();
}
} finally {
responsesLock.writeLock().unlock();
}
}
@Override
public void initializeClient() {
responsesLock.writeLock().lock();
try {
updatesLock.writeLock().lock();
try {
stateLock.writeLock().lock();
try {
if (!state.isInitialized() && !state.hasClientId()) {
long clientId = createNativeClient();
state = ClientState.of(true, clientId, true, true, false);
}
} finally {
stateLock.writeLock().unlock();
}
} finally {
updatesLock.writeLock().unlock();
}
} finally {
responsesLock.writeLock().unlock();
}
}
private void requireInitialized() {
if (!state.isInitialized() || !state.hasClientId()) {
throw new IllegalStateException("Client not initialized");
}
}
private void requireReadyToSend(int constructor) {
if (!state.isReadyToSend()) {
switch (constructor) {
case SetOption.CONSTRUCTOR:
case GetOption.CONSTRUCTOR:
case TdApi.SetTdlibParameters.CONSTRUCTOR:
return;
}
throw new IllegalStateException("Client not ready to send");
}
}
private void requireReadyToReceive() {
if (!state.isReadyToReceive()) {
throw new IllegalStateException("Client not ready to receive");
}
protected String getImplementationName() {
return "tdlight";
}
}

View File

@ -42,28 +42,33 @@
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-natives-linux-amd64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-natives-linux-aarch64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-natives-windows-amd64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-natives-osx-amd64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.cavallium</groupId>
<artifactId>concurrent-locks</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
<version>8.3.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>../src/main/java</sourceDirectory>

View File

@ -42,28 +42,33 @@
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-natives-linux-amd64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-natives-linux-aarch64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-natives-windows-amd64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-natives-osx-amd64</artifactId>
<version>3.0.84</version>
<version>3.0.92</version>
</dependency>
<dependency>
<groupId>it.cavallium</groupId>
<artifactId>concurrent-locks</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
<version>8.3.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>../src/main/java</sourceDirectory>