diff --git a/dependencies/tdlight b/dependencies/tdlight index f5ecc4b..85bf271 160000 --- a/dependencies/tdlight +++ b/dependencies/tdlight @@ -1 +1 @@ -Subproject commit f5ecc4b89655ddfaa1e05b77d6740e8d9cdfcaea +Subproject commit 85bf271fb827a31b68d761a06a92972aa6548338 diff --git a/src/main/java/it/tdlight/common/ClientEventsHandler.java b/src/main/java/it/tdlight/common/ClientEventsHandler.java new file mode 100644 index 0000000..7e091e7 --- /dev/null +++ b/src/main/java/it/tdlight/common/ClientEventsHandler.java @@ -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); +} diff --git a/src/main/java/it/tdlight/common/CommonClient.java b/src/main/java/it/tdlight/common/CommonClient.java new file mode 100644 index 0000000..75d5d8c --- /dev/null +++ b/src/main/java/it/tdlight/common/CommonClient.java @@ -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; + } +} diff --git a/src/main/java/it/tdlight/common/EventsHandler.java b/src/main/java/it/tdlight/common/EventsHandler.java new file mode 100644 index 0000000..0c87f06 --- /dev/null +++ b/src/main/java/it/tdlight/common/EventsHandler.java @@ -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); +} diff --git a/src/main/java/it/tdlight/common/Handler.java b/src/main/java/it/tdlight/common/Handler.java new file mode 100644 index 0000000..e270056 --- /dev/null +++ b/src/main/java/it/tdlight/common/Handler.java @@ -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; + } +} diff --git a/src/main/java/it/tdlight/common/IntSwapper.java b/src/main/java/it/tdlight/common/IntSwapper.java new file mode 100644 index 0000000..9ab1a85 --- /dev/null +++ b/src/main/java/it/tdlight/common/IntSwapper.java @@ -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; + } +} diff --git a/src/main/java/it/tdlight/common/InternalClient.java b/src/main/java/it/tdlight/common/InternalClient.java new file mode 100644 index 0000000..f707e4e --- /dev/null +++ b/src/main/java/it/tdlight/common/InternalClient.java @@ -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 handlers = new ConcurrentHashMap(); + + 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 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 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!"); + } + } +} diff --git a/src/main/java/it/tdlight/common/InternalClientManager.java b/src/main/java/it/tdlight/common/InternalClientManager.java new file mode 100644 index 0000000..c31111b --- /dev/null +++ b/src/main/java/it/tdlight/common/InternalClientManager.java @@ -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 INSTANCE = new AtomicReference<>(null); + + private final String implementationName; + private final ResponseReceiver responseReceiver = new ResponseReceiver(this::handleClientEvents); + private final Int2ObjectMap 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(); + } +} diff --git a/src/main/java/it/tdlight/common/MultiHandler.java b/src/main/java/it/tdlight/common/MultiHandler.java new file mode 100644 index 0000000..f5016f1 --- /dev/null +++ b/src/main/java/it/tdlight/common/MultiHandler.java @@ -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; + } +} diff --git a/src/main/java/it/tdlight/common/NativeClientAccess.java b/src/main/java/it/tdlight/common/NativeClientAccess.java new file mode 100644 index 0000000..b960d3c --- /dev/null +++ b/src/main/java/it/tdlight/common/NativeClientAccess.java @@ -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); + } +} diff --git a/src/main/java/it/tdlight/common/ResponseReceiver.java b/src/main/java/it/tdlight/common/ResponseReceiver.java new file mode 100644 index 0000000..776b561 --- /dev/null +++ b/src/main/java/it/tdlight/common/ResponseReceiver.java @@ -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(); + } +} diff --git a/src/main/java/it/tdlight/common/TDLibException.java b/src/main/java/it/tdlight/common/TDLibException.java new file mode 100644 index 0000000..b807674 --- /dev/null +++ b/src/main/java/it/tdlight/common/TDLibException.java @@ -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; + } +} diff --git a/src/main/java/it/tdlight/common/TelegramClient.java b/src/main/java/it/tdlight/common/TelegramClient.java index 91b14e0..81e05b7 100644 --- a/src/main/java/it/tdlight/common/TelegramClient.java +++ b/src/main/java/it/tdlight/common/TelegramClient.java @@ -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 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 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); } diff --git a/src/main/java/it/tdlight/common/UpdatesHandler.java b/src/main/java/it/tdlight/common/UpdatesHandler.java new file mode 100644 index 0000000..55aebbb --- /dev/null +++ b/src/main/java/it/tdlight/common/UpdatesHandler.java @@ -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 object); +} \ No newline at end of file diff --git a/src/main/java/it/tdlight/jni/NativeClient.java b/src/main/java/it/tdlight/jni/NativeClient.java index e9b5ec8..9c6d3ab 100644 --- a/src/main/java/it/tdlight/jni/NativeClient.java +++ b/src/main/java/it/tdlight/jni/NativeClient.java @@ -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); } diff --git a/src/main/java/it/tdlight/tdlib/Client.java b/src/main/java/it/tdlight/tdlib/Client.java index 0e70ae1..30e608f 100644 --- a/src/main/java/it/tdlight/tdlib/Client.java +++ b/src/main/java/it/tdlight/tdlib/Client.java @@ -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 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"; } } diff --git a/src/main/java/it/tdlight/tdlight/Client.java b/src/main/java/it/tdlight/tdlight/Client.java index c917c59..b1b0b40 100644 --- a/src/main/java/it/tdlight/tdlight/Client.java +++ b/src/main/java/it/tdlight/tdlight/Client.java @@ -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 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"; } } diff --git a/tdlib/pom.xml b/tdlib/pom.xml index 04729ce..8add85e 100644 --- a/tdlib/pom.xml +++ b/tdlib/pom.xml @@ -42,28 +42,33 @@ it.tdlight tdlib-natives-linux-amd64 - 3.0.84 + 3.0.92 it.tdlight tdlib-natives-linux-aarch64 - 3.0.84 + 3.0.92 it.tdlight tdlib-natives-windows-amd64 - 3.0.84 + 3.0.92 it.tdlight tdlib-natives-osx-amd64 - 3.0.84 + 3.0.92 it.cavallium concurrent-locks 1.0.5 + + it.unimi.dsi + fastutil + 8.3.1 + ../src/main/java diff --git a/tdlight/pom.xml b/tdlight/pom.xml index ea03f43..3fdc169 100644 --- a/tdlight/pom.xml +++ b/tdlight/pom.xml @@ -42,28 +42,33 @@ it.tdlight tdlight-natives-linux-amd64 - 3.0.84 + 3.0.92 it.tdlight tdlight-natives-linux-aarch64 - 3.0.84 + 3.0.92 it.tdlight tdlight-natives-windows-amd64 - 3.0.84 + 3.0.92 it.tdlight tdlight-natives-osx-amd64 - 3.0.84 + 3.0.92 it.cavallium concurrent-locks 1.0.5 + + it.unimi.dsi + fastutil + 8.3.1 + ../src/main/java