diff options
author | George Hazan <ghazan@miranda.im> | 2022-11-30 17:48:47 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-11-30 17:48:47 +0300 |
commit | 0ece30dc7c0e34b4c5911969b8fa99c33c6d023c (patch) | |
tree | 671325d3fec09b999411e4e3ab84ef8259261818 /protocols/Telegram/tdlib/td/example/java | |
parent | 46c53ffc6809c67e4607e99951a2846c382b63b2 (diff) |
Telegram: update for TDLIB
Diffstat (limited to 'protocols/Telegram/tdlib/td/example/java')
6 files changed, 527 insertions, 409 deletions
diff --git a/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt b/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt index e8313a68b7..576eee2cfc 100644 --- a/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt +++ b/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt @@ -1,7 +1,26 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) + +if (POLICY CMP0065) + # do not export symbols from executables + # affects compiler checks in project(), so must be set before it + cmake_policy(SET CMP0065 NEW) +endif() project(TdJavaExample VERSION 1.0 LANGUAGES CXX) +if (POLICY CMP0054) + # do not expand quoted arguments + cmake_policy(SET CMP0054 NEW) +endif() +if (POLICY CMP0060) + # link libraries by full path + cmake_policy(SET CMP0060 NEW) +endif() +if (POLICY CMP0074) + # use environment variables to find libraries + cmake_policy(SET CMP0074 NEW) +endif() + find_package(Td REQUIRED) if (NOT JNI_FOUND) @@ -10,12 +29,15 @@ endif() message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}") if (NOT Java_FOUND) - find_package(Java 1.6 REQUIRED) + find_package(Java REQUIRED) endif() message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}") # Generating TdApi.java find_program(PHP_EXECUTABLE php) +if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD")) + set(PHP_EXECUTABLE "PHP_EXECUTABLE-NOTFOUND") +endif() set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib") set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR}) @@ -37,14 +59,13 @@ set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}") get_filename_component(JAVA_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin REALPATH BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") file(MAKE_DIRECTORY ${JAVA_OUTPUT_DIRECTORY}) add_custom_target(build_java - COMMAND ${Java_JAVAC_EXECUTABLE} -d ${JAVA_OUTPUT_DIRECTORY} ${JAVA_SOURCE_PATH}/example/Example.java ${JAVA_SOURCE_PATH}/Client.java ${JAVA_SOURCE_PATH}/Log.java ${JAVA_SOURCE_PATH}/TdApi.java + COMMAND ${Java_JAVAC_EXECUTABLE} -encoding UTF-8 -d ${JAVA_OUTPUT_DIRECTORY} ${JAVA_SOURCE_PATH}/example/Example.java ${JAVA_SOURCE_PATH}/Client.java ${JAVA_SOURCE_PATH}/TdApi.java COMMENT "Building Java code" DEPENDS td_generate_java_api ) -set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}") add_custom_target(generate_javadoc - COMMAND ${Java_JAVADOC_EXECUTABLE} -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib + COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib WORKING_DIRECTORY ${TD_API_JAVA_PATH} COMMENT "Generating Javadoc documentation" DEPENDS td_generate_java_api @@ -58,7 +79,39 @@ target_include_directories(tdjni PRIVATE ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PAT target_link_libraries(tdjni PRIVATE Td::TdStatic ${JAVA_JVM_LIBRARY}) target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}") -set_property(TARGET tdjni PROPERTY CXX_STANDARD 14) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(GCC 1) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CLANG 1) +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") + set(INTEL 1) +elseif (NOT MSVC) + message(FATAL_ERROR "Compiler isn't supported") +endif() + +include(CheckCXXCompilerFlag) + +if (GCC OR CLANG OR INTEL) + if (WIN32 AND INTEL) + set(STD14_FLAG /Qstd=c++14) + else() + set(STD14_FLAG -std=c++14) + endif() + check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14) + if (NOT HAVE_STD14) + string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}") + check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y) + set(HAVE_STD14 ${HAVE_STD1Y}) + endif() + + target_compile_options(tdjni PRIVATE "${STD14_FLAG}") +elseif (MSVC) + set(HAVE_STD14 MSVC_VERSION>=1900) +endif() + +if (NOT HAVE_STD14) + message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.") +endif() add_dependencies(tdjni td_generate_java_api build_java generate_javadoc) @@ -66,3 +119,6 @@ install(TARGETS tdjni LIBRARY DESTINATION bin RUNTIME DESTINATION bin ) +if (MSVC AND VCPKG_TOOLCHAIN) + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "*.pdb") +endif() diff --git a/protocols/Telegram/tdlib/td/example/java/README.md b/protocols/Telegram/tdlib/td/example/java/README.md index c2b5c43171..4c7df1916d 100644 --- a/protocols/Telegram/tdlib/td/example/java/README.md +++ b/protocols/Telegram/tdlib/td/example/java/README.md @@ -3,7 +3,11 @@ To run this example, you will need installed JDK >= 1.6. For Javadoc documentation generation PHP is needed. -TDLib should be prebuilt for using with Java and installed to local subdirectory `td/` as follows: +You can find complete build instructions for your operating system at https://tdlib.github.io/td/build.html?language=Java. + +In general, the build process looks as follows. + +TDLib should be prebuilt with JNI bindings and installed to local subdirectory `td/` as follows: ``` cd <path to TDLib sources> mkdir jnibuild @@ -11,9 +15,9 @@ cd jnibuild cmake -DCMAKE_BUILD_TYPE=Release -DTD_ENABLE_JNI=ON -DCMAKE_INSTALL_PREFIX:PATH=../example/java/td .. cmake --build . --target install ``` -If you want to compile TDLib for 64-bit Java on Windows using MSVC, you will also need to add `-A x64` option to CMake. +If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you will also need to add `-A Win32`/`-A x64` option to CMake. -In Windows, use Vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake +In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake Then you can build this example: ``` @@ -32,8 +36,8 @@ cd <path to TDLib sources>/example/java/bin java '-Djava.library.path=.' org/drinkless/tdlib/example/Example ``` -If you get "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/". +If you receive "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/". -If you get java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared libraries to `bin/`. +If you receive java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared OpenSSL and zlib libraries to `bin/`. -In case you compiled the example as 32-bit version, you may need to give -d32 parameter to Java. +Make sure that you compiled the example for the same architecture as your JVM. diff --git a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java index efb38e9c5a..0a58bb45a7 100644 --- a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java +++ b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,14 +8,19 @@ package org.drinkless.tdlib; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Main class for interaction with the TDLib. */ -public final class Client implements Runnable { +public final class Client { + static { + try { + System.loadLibrary("tdjni"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } + } + /** * Interface for handler for results of queries to TDLib and incoming updates from TDLib. */ @@ -43,6 +48,21 @@ public final class Client implements Runnable { } /** + * Interface for handler of messages that are added to the internal TDLib log. + */ + public interface LogMessageHandler { + /** + * Callback called on messages that are added to the internal TDLib log. + * + * @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024. + * If 0, then TDLib will crash as soon as the callback returns. + * None of the TDLib methods can be called from the callback. + * @param message The message added to the internal TDLib log. + */ + void onLogMessage(int verbosityLevel, String message); + } + + /** * Sends a request to the TDLib. * * @param query Object representing a query to the TDLib. @@ -55,25 +75,11 @@ public final class Client implements Runnable { * @throws NullPointerException if query is null. */ public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) { - if (query == null) { - throw new NullPointerException("query is null"); - } - - readLock.lock(); - try { - if (isClientDestroyed) { - if (resultHandler != null) { - handleResult(new TdApi.Error(500, "Client is closed"), resultHandler, exceptionHandler); - } - return; - } - - long queryId = currentQueryId.incrementAndGet(); + long queryId = currentQueryId.incrementAndGet(); + if (resultHandler != null) { handlers.put(queryId, new Handler(resultHandler, exceptionHandler)); - nativeClientSend(nativeClientId, queryId, query); - } finally { - readLock.unlock(); } + nativeClientSend(nativeClientId, queryId, query); } /** @@ -97,108 +103,105 @@ public final class Client implements Runnable { * @throws NullPointerException if query is null. */ public static TdApi.Object execute(TdApi.Function query) { - if (query == null) { - throw new NullPointerException("query is null"); - } return nativeClientExecute(query); } /** - * Replaces handler for incoming updates from the TDLib. + * Creates new Client. * - * @param updatesHandler Handler with onResult method which will be called for every incoming - * update from the TDLib. - * @param exceptionHandler Exception handler with onException method which will be called on - * exception thrown from updatesHandler, if it is null, defaultExceptionHandler will be invoked. + * @param updateHandler Handler for incoming updates. + * @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be iggnored. + * @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be iggnored. + * @return created Client */ - public void setUpdatesHandler(ResultHandler updatesHandler, ExceptionHandler exceptionHandler) { - handlers.put(0L, new Handler(updatesHandler, exceptionHandler)); + public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) { + Client client = new Client(updateHandler, updateExceptionHandler, defaultExceptionHandler); + synchronized (responseReceiver) { + if (!responseReceiver.isRun) { + responseReceiver.isRun = true; + + Thread receiverThread = new Thread(responseReceiver, "TDLib thread"); + receiverThread.setDaemon(true); + receiverThread.start(); + } + } + return client; } /** - * Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler. + * Sets the handler for messages that are added to the internal TDLib log. + * None of the TDLib methods can be called from the callback. * - * @param updatesHandler Handler with onResult method which will be called for every incoming - * update from the TDLib. + * @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called. + * @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler. */ - public void setUpdatesHandler(ResultHandler updatesHandler) { - setUpdatesHandler(updatesHandler, null); + public static void setLogMessageHandler(int maxVerbosityLevel, Client.LogMessageHandler logMessageHandler) { + nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler); } - /** - * Replaces default exception handler to be invoked on exceptions thrown from updatesHandler and all other ResultHandler. - * - * @param defaultExceptionHandler Default exception handler. If null Exceptions are ignored. - */ - public void setDefaultExceptionHandler(Client.ExceptionHandler defaultExceptionHandler) { - this.defaultExceptionHandler = defaultExceptionHandler; - } + private static class ResponseReceiver implements Runnable { + public boolean isRun = false; - /** - * Overridden method from Runnable, do not call it directly. - */ - @Override - public void run() { - while (!stopFlag) { - receiveQueries(300.0 /*seconds*/); + @Override + public void run() { + while (true) { + int resultN = nativeClientReceive(clientIds, eventIds, events, 100000.0 /*seconds*/); + for (int i = 0; i < resultN; i++) { + processResult(clientIds[i], eventIds[i], events[i]); + events[i] = null; + } + } } - } - - /** - * Creates new Client. - * - * @param updatesHandler Handler for incoming updates. - * @param updatesExceptionHandler Handler for exceptions thrown from updatesHandler. If it is null, exceptions will be iggnored. - * @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be iggnored. - * @return created Client - */ - public static Client create(ResultHandler updatesHandler, ExceptionHandler updatesExceptionHandler, ExceptionHandler defaultExceptionHandler) { - Client client = new Client(updatesHandler, updatesExceptionHandler, defaultExceptionHandler); - new Thread(client, "TDLib thread").start(); - return client; - } - /** - * Closes Client. - */ - public void close() { - writeLock.lock(); - try { - if (isClientDestroyed) { - return; - } - if (!stopFlag) { - send(new TdApi.Close(), null); + private void processResult(int clientId, long id, TdApi.Object object) { + boolean isClosed = false; + if (id == 0 && object instanceof TdApi.UpdateAuthorizationState) { + TdApi.AuthorizationState authorizationState = ((TdApi.UpdateAuthorizationState) object).authorizationState; + if (authorizationState instanceof TdApi.AuthorizationStateClosed) { + isClosed = true; + } } - isClientDestroyed = true; - while (!stopFlag) { - Thread.yield(); + + Handler handler = id == 0 ? updateHandlers.get(clientId) : handlers.remove(id); + if (handler != null) { + try { + handler.resultHandler.onResult(object); + } catch (Throwable cause) { + ExceptionHandler exceptionHandler = handler.exceptionHandler; + if (exceptionHandler == null) { + exceptionHandler = defaultExceptionHandlers.get(clientId); + } + if (exceptionHandler != null) { + try { + exceptionHandler.onException(cause); + } catch (Throwable ignored) { + } + } + } } - while (handlers.size() != 1) { - receiveQueries(300.0); + + if (isClosed) { + updateHandlers.remove(clientId); // there will be no more updates + defaultExceptionHandlers.remove(clientId); // ignore further exceptions + clientCount.decrementAndGet(); } - destroyNativeClient(nativeClientId); - } finally { - writeLock.unlock(); } - } - - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final Lock readLock = readWriteLock.readLock(); - private final Lock writeLock = readWriteLock.writeLock(); - private volatile boolean stopFlag = false; - private volatile boolean isClientDestroyed = false; - private final long nativeClientId; + private static final int MAX_EVENTS = 1000; + 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 ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>(); - private final AtomicLong currentQueryId = new AtomicLong(); + private final int nativeClientId; - private volatile ExceptionHandler defaultExceptionHandler = null; + private static final ConcurrentHashMap<Integer, ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<Integer, ExceptionHandler>(); + private static final ConcurrentHashMap<Integer, Handler> updateHandlers = new ConcurrentHashMap<Integer, Handler>(); + private static final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>(); + private static final AtomicLong currentQueryId = new AtomicLong(); + private static final AtomicLong clientCount = new AtomicLong(); - private static final int MAX_EVENTS = 1000; - private final long[] eventIds = new long[MAX_EVENTS]; - private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS]; + private static final ResponseReceiver responseReceiver = new ResponseReceiver(); private static class Handler { final ResultHandler resultHandler; @@ -210,76 +213,30 @@ public final class Client implements Runnable { } } - private Client(ResultHandler updatesHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) { + private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) { + clientCount.incrementAndGet(); nativeClientId = createNativeClient(); - handlers.put(0L, new Handler(updatesHandler, updateExceptionHandler)); - this.defaultExceptionHandler = defaultExceptionHandler; - } - - @Override - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - private void processResult(long id, TdApi.Object object) { - if (object instanceof TdApi.UpdateAuthorizationState) { - if (((TdApi.UpdateAuthorizationState) object).authorizationState instanceof TdApi.AuthorizationStateClosed) { - stopFlag = true; - } - } - Handler handler; - if (id == 0) { - // update handler stays forever - handler = handlers.get(id); - } else { - handler = handlers.remove(id); - } - if (handler == null) { - return; - } - - handleResult(object, handler.resultHandler, handler.exceptionHandler); - } - - private void handleResult(TdApi.Object object, ResultHandler resultHandler, ExceptionHandler exceptionHandler) { - if (resultHandler == null) { - return; + if (updateHandler != null) { + updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler)); } - - try { - resultHandler.onResult(object); - } catch (Throwable cause) { - if (exceptionHandler == null) { - exceptionHandler = defaultExceptionHandler; - } - if (exceptionHandler != null) { - try { - exceptionHandler.onException(cause); - } catch (Throwable ignored) { - } - } + if (defaultExceptionHandler != null) { + defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler); } + send(new TdApi.GetOption("version"), null, null); } - private void receiveQueries(double timeout) { - int resultN = nativeClientReceive(nativeClientId, eventIds, events, timeout); - for (int i = 0; i < resultN; i++) { - processResult(eventIds[i], events[i]); - events[i] = null; - } + @Override + protected void finalize() throws Throwable { + send(new TdApi.Close(), null, null); } - private static native long createNativeClient(); + private static native int createNativeClient(); - private static native void nativeClientSend(long nativeClientId, long eventId, TdApi.Function function); + private static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function); - private static native int nativeClientReceive(long nativeClientId, long[] eventIds, TdApi.Object[] events, double timeout); + private static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout); private static native TdApi.Object nativeClientExecute(TdApi.Function function); - private static native void destroyNativeClient(long nativeClientId); + private static native void nativeClientSetLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler); } diff --git a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java deleted file mode 100644 index c81ffbeeb7..0000000000 --- a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -package org.drinkless.tdlib; - -/** - * Class for managing internal TDLib logging. - */ -public final class Log { - /** - * Changes TDLib log verbosity. - * - * @param verbosityLevel New value of log verbosity level. Must be non-negative. - * Value 0 corresponds to fatal errors, - * value 1 corresponds to java.util.logging.Level.SEVERE, - * value 2 corresponds to java.util.logging.Level.WARNING, - * value 3 corresponds to java.util.logging.Level.INFO, - * value 4 corresponds to java.util.logging.Level.FINE, - * value 5 corresponds to java.util.logging.Level.FINER, - * value greater than 5 can be used to enable even more logging. - * Default value of the log verbosity level is 5. - */ - public static native void setVerbosityLevel(int verbosityLevel); - - /** - * Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err. - * Use this method to write the log to a file instead. - * - * @param filePath Path to a file for writing TDLib internal log. Use an empty path to - * switch back to logging to the System.err. - * @return whether opening the log file succeeded. - */ - public static native boolean setFilePath(String filePath); - - /** - * Changes maximum size of TDLib log file. - * - * @param maxFileSize Maximum size of the file to where the internal TDLib log is written - * before the file will be auto-rotated. Must be positive. Defaults to 10 MB. - */ - public static native void setMaxFileSize(long maxFileSize); - - /** - * This function is called from the JNI when a fatal error happens to provide a better error message. - * The function does not return. - * - * @param errorMessage Error message. - */ - private static void onFatalError(String errorMessage) { - class ThrowError implements Runnable { - private ThrowError(String errorMessage) { - this.errorMessage = errorMessage; - } - - @Override - public void run() { - throw new RuntimeException("TDLib fatal error: " + errorMessage); - } - - private final String errorMessage; - } - - new Thread(new ThrowError(errorMessage), "TDLib fatal error thread").start(); - while (true) { - try { - Thread.sleep(1000); // milliseconds - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } -} diff --git a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java index 831de88f1d..7e76b42276 100644 --- a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java +++ b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,17 +7,17 @@ package org.drinkless.tdlib.example; import org.drinkless.tdlib.Client; -import org.drinkless.tdlib.Log; import org.drinkless.tdlib.TdApi; +import java.io.BufferedReader; import java.io.IOError; import java.io.IOException; -import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.NavigableSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -30,34 +30,31 @@ public final class Example { private static TdApi.AuthorizationState authorizationState = null; private static volatile boolean haveAuthorization = false; - private static volatile boolean quiting = false; + private static volatile boolean needQuit = false; + private static volatile boolean canQuit = false; private static final Client.ResultHandler defaultHandler = new DefaultHandler(); private static final Lock authorizationLock = new ReentrantLock(); private static final Condition gotAuthorization = authorizationLock.newCondition(); - private static final ConcurrentMap<Integer, TdApi.User> users = new ConcurrentHashMap<Integer, TdApi.User>(); - private static final ConcurrentMap<Integer, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Integer, TdApi.BasicGroup>(); - private static final ConcurrentMap<Integer, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Integer, TdApi.Supergroup>(); + private static final ConcurrentMap<Long, TdApi.User> users = new ConcurrentHashMap<Long, TdApi.User>(); + private static final ConcurrentMap<Long, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Long, TdApi.BasicGroup>(); + private static final ConcurrentMap<Long, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Long, TdApi.Supergroup>(); private static final ConcurrentMap<Integer, TdApi.SecretChat> secretChats = new ConcurrentHashMap<Integer, TdApi.SecretChat>(); private static final ConcurrentMap<Long, TdApi.Chat> chats = new ConcurrentHashMap<Long, TdApi.Chat>(); - private static final NavigableSet<OrderedChat> chatList = new TreeSet<OrderedChat>(); - private static boolean haveFullChatList = false; + private static final NavigableSet<OrderedChat> mainChatList = new TreeSet<OrderedChat>(); + private static boolean haveFullMainChatList = false; - private static final ConcurrentMap<Integer, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Integer, TdApi.UserFullInfo>(); - private static final ConcurrentMap<Integer, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Integer, TdApi.BasicGroupFullInfo>(); - private static final ConcurrentMap<Integer, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Integer, TdApi.SupergroupFullInfo>(); + private static final ConcurrentMap<Long, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Long, TdApi.UserFullInfo>(); + private static final ConcurrentMap<Long, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Long, TdApi.BasicGroupFullInfo>(); + private static final ConcurrentMap<Long, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Long, TdApi.SupergroupFullInfo>(); private static final String newLine = System.getProperty("line.separator"); private static final String commandsLine = "Enter command (gcs - GetChats, gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): "; private static volatile String currentPrompt = null; - static { - System.loadLibrary("tdjni"); - } - private static void print(String str) { if (currentPrompt != null) { System.out.println(""); @@ -68,18 +65,24 @@ public final class Example { } } - private static void setChatOrder(TdApi.Chat chat, long order) { - synchronized (chatList) { - if (chat.order != 0) { - boolean isRemoved = chatList.remove(new OrderedChat(chat.order, chat.id)); - assert isRemoved; - } + private static void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) { + synchronized (mainChatList) { + synchronized (chat) { + for (TdApi.ChatPosition position : chat.positions) { + if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) { + boolean isRemoved = mainChatList.remove(new OrderedChat(chat.id, position)); + assert isRemoved; + } + } - chat.order = order; + chat.positions = positions; - if (chat.order != 0) { - boolean isAdded = chatList.add(new OrderedChat(chat.order, chat.id)); - assert isAdded; + for (TdApi.ChatPosition position : chat.positions) { + if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) { + boolean isAdded = mainChatList.add(new OrderedChat(chat.id, position)); + assert isAdded; + } + } } } } @@ -90,31 +93,48 @@ public final class Example { } switch (Example.authorizationState.getConstructor()) { case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR: - TdApi.TdlibParameters parameters = new TdApi.TdlibParameters(); - parameters.databaseDirectory = "tdlib"; - parameters.useMessageDatabase = true; - parameters.useSecretChats = true; - parameters.apiId = 94575; - parameters.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; - parameters.systemLanguageCode = "en"; - parameters.deviceModel = "Desktop"; - parameters.systemVersion = "Unknown"; - parameters.applicationVersion = "1.0"; - parameters.enableStorageOptimizer = true; - - client.send(new TdApi.SetTdlibParameters(parameters), new AuthorizationRequestHandler()); - break; - case TdApi.AuthorizationStateWaitEncryptionKey.CONSTRUCTOR: - client.send(new TdApi.CheckDatabaseEncryptionKey(), new AuthorizationRequestHandler()); + TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters(); + request.databaseDirectory = "tdlib"; + request.useMessageDatabase = true; + request.useSecretChats = true; + request.apiId = 94575; + request.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; + request.systemLanguageCode = "en"; + request.deviceModel = "Desktop"; + request.applicationVersion = "1.0"; + request.enableStorageOptimizer = true; + + client.send(request, new AuthorizationRequestHandler()); break; case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: { String phoneNumber = promptString("Please enter phone number: "); - client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, false, false), new AuthorizationRequestHandler()); + client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler()); + break; + } + case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR: { + String link = ((TdApi.AuthorizationStateWaitOtherDeviceConfirmation) Example.authorizationState).link; + System.out.println("Please confirm this login link on another device: " + link); + break; + } + case TdApi.AuthorizationStateWaitEmailAddress.CONSTRUCTOR: { + String emailAddress = promptString("Please enter email address: "); + client.send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler()); + break; + } + case TdApi.AuthorizationStateWaitEmailCode.CONSTRUCTOR: { + String code = promptString("Please enter email authentication code: "); + client.send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler()); break; } case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: { String code = promptString("Please enter authentication code: "); - client.send(new TdApi.CheckAuthenticationCode(code, "", ""), new AuthorizationRequestHandler()); + client.send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler()); + break; + } + case TdApi.AuthorizationStateWaitRegistration.CONSTRUCTOR: { + String firstName = promptString("Please enter your first name: "); + String lastName = promptString("Please enter your last name: "); + client.send(new TdApi.RegisterUser(firstName, lastName), new AuthorizationRequestHandler()); break; } case TdApi.AuthorizationStateWaitPassword.CONSTRUCTOR: { @@ -141,8 +161,10 @@ public final class Example { break; case TdApi.AuthorizationStateClosed.CONSTRUCTOR: print("Closed"); - if (!quiting) { - client = Client.create(new UpdatesHandler(), null, null); // recreate client after previous has closed + if (!needQuit) { + client = Client.create(new UpdateHandler(), null, null); // recreate client after previous has closed + } else { + canQuit = true; } break; default: @@ -192,7 +214,7 @@ public final class Example { if (commands.length > 1) { limit = toInt(commands[1]); } - getChatList(limit); + getMainChatList(limit); break; } case "gc": @@ -211,7 +233,7 @@ public final class Example { client.send(new TdApi.LogOut(), defaultHandler); break; case "q": - quiting = true; + needQuit = true; haveAuthorization = false; client.send(new TdApi.Close(), defaultHandler); break; @@ -223,33 +245,26 @@ public final class Example { } } - private static void getChatList(final int limit) { - synchronized (chatList) { - if (!haveFullChatList && limit > chatList.size()) { - // have enough chats in the chat list or chat list is too small - long offsetOrder = Long.MAX_VALUE; - long offsetChatId = 0; - if (!chatList.isEmpty()) { - OrderedChat last = chatList.last(); - offsetOrder = last.order; - offsetChatId = last.chatId; - } - client.send(new TdApi.GetChats(offsetOrder, offsetChatId, limit - chatList.size()), new Client.ResultHandler() { + private static void getMainChatList(final int limit) { + synchronized (mainChatList) { + if (!haveFullMainChatList && limit > mainChatList.size()) { + // send LoadChats request if there are some unknown chats and have not enough known chats + client.send(new TdApi.LoadChats(new TdApi.ChatListMain(), limit - mainChatList.size()), new Client.ResultHandler() { @Override public void onResult(TdApi.Object object) { switch (object.getConstructor()) { case TdApi.Error.CONSTRUCTOR: - System.err.println("Receive an error for GetChats:" + newLine + object); - break; - case TdApi.Chats.CONSTRUCTOR: - long[] chatIds = ((TdApi.Chats) object).chatIds; - if (chatIds.length == 0) { - synchronized (chatList) { - haveFullChatList = true; + if (((TdApi.Error) object).code == 404) { + synchronized (mainChatList) { + haveFullMainChatList = true; } + } else { + System.err.println("Receive an error for LoadChats:" + newLine + object); } + break; + case TdApi.Ok.CONSTRUCTOR: // chats had already been received through updates, let's retry request - getChatList(limit); + getMainChatList(limit); break; default: System.err.println("Receive wrong response from TDLib:" + newLine + object); @@ -259,11 +274,10 @@ public final class Example { return; } - // have enough chats in the chat list to answer request - java.util.Iterator<OrderedChat> iter = chatList.iterator(); + java.util.Iterator<OrderedChat> iter = mainChatList.iterator(); System.out.println(); - System.out.println("First " + limit + " chat(s) out of " + chatList.size() + " known chat(s):"); - for (int i = 0; i < limit; i++) { + System.out.println("First " + limit + " chat(s) out of " + mainChatList.size() + " known chat(s):"); + for (int i = 0; i < limit && i < mainChatList.size(); i++) { long chatId = iter.next().chatId; TdApi.Chat chat = chats.get(chatId); synchronized (chat) { @@ -280,24 +294,27 @@ public final class Example { TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][]{row, row, row}); TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), false, true); - client.send(new TdApi.SendMessage(chatId, 0, false, false, replyMarkup, content), defaultHandler); + client.send(new TdApi.SendMessage(chatId, 0, 0, null, replyMarkup, content), defaultHandler); } public static void main(String[] args) throws InterruptedException { - // disable TDLib log - Log.setVerbosityLevel(0); - if (!Log.setFilePath("tdlib.log")) { + // set log message handler to handle only fatal errors (0) and plain log messages (-1) + Client.setLogMessageHandler(0, new LogMessageHandler()); + + // disable TDLib log and redirect fatal errors and plain log messages to a file + Client.execute(new TdApi.SetLogVerbosityLevel(0)); + if (Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false))) instanceof TdApi.Error) { throw new IOError(new IOException("Write access to the current directory is required")); } // create client - client = Client.create(new UpdatesHandler(), null, null); + client = Client.create(new UpdateHandler(), null, null); // test Client.execute defaultHandler.onResult(Client.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test"))); // main loop - while (!quiting) { + while (!needQuit) { // await authorization authorizationLock.lock(); try { @@ -312,21 +329,24 @@ public final class Example { getCommand(); } } + while (!canQuit) { + Thread.sleep(1); + } } private static class OrderedChat implements Comparable<OrderedChat> { - final long order; final long chatId; + final TdApi.ChatPosition position; - OrderedChat(long order, long chatId) { - this.order = order; + OrderedChat(long chatId, TdApi.ChatPosition position) { this.chatId = chatId; + this.position = position; } @Override public int compareTo(OrderedChat o) { - if (this.order != o.order) { - return o.order < this.order ? -1 : 1; + if (this.position.order != o.position.order) { + return o.position.order < this.position.order ? -1 : 1; } if (this.chatId != o.chatId) { return o.chatId < this.chatId ? -1 : 1; @@ -337,7 +357,7 @@ public final class Example { @Override public boolean equals(Object obj) { OrderedChat o = (OrderedChat) obj; - return this.order == o.order && this.chatId == o.chatId; + return this.chatId == o.chatId && this.position.order == o.position.order; } } @@ -348,7 +368,7 @@ public final class Example { } } - private static class UpdatesHandler implements Client.ResultHandler { + private static class UpdateHandler implements Client.ResultHandler { @Override public void onResult(TdApi.Object object) { switch (object.getConstructor()) { @@ -360,7 +380,7 @@ public final class Example { TdApi.UpdateUser updateUser = (TdApi.UpdateUser) object; users.put(updateUser.user.id, updateUser.user); break; - case TdApi.UpdateUserStatus.CONSTRUCTOR: { + case TdApi.UpdateUserStatus.CONSTRUCTOR: { TdApi.UpdateUserStatus updateUserStatus = (TdApi.UpdateUserStatus) object; TdApi.User user = users.get(updateUserStatus.userId); synchronized (user) { @@ -387,9 +407,9 @@ public final class Example { synchronized (chat) { chats.put(chat.id, chat); - long order = chat.order; - chat.order = 0; - setChatOrder(chat, order); + TdApi.ChatPosition[] positions = chat.positions; + chat.positions = new TdApi.ChatPosition[0]; + setChatPositions(chat, positions); } break; } @@ -414,24 +434,37 @@ public final class Example { TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { chat.lastMessage = updateChat.lastMessage; - setChatOrder(chat, updateChat.order); + setChatPositions(chat, updateChat.positions); } break; } - case TdApi.UpdateChatOrder.CONSTRUCTOR: { - TdApi.UpdateChatOrder updateChat = (TdApi.UpdateChatOrder) object; - TdApi.Chat chat = chats.get(updateChat.chatId); - synchronized (chat) { - setChatOrder(chat, updateChat.order); + case TdApi.UpdateChatPosition.CONSTRUCTOR: { + TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object; + if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR) { + break; } - break; - } - case TdApi.UpdateChatIsPinned.CONSTRUCTOR: { - TdApi.UpdateChatIsPinned updateChat = (TdApi.UpdateChatIsPinned) object; + TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { - chat.isPinned = updateChat.isPinned; - setChatOrder(chat, updateChat.order); + int i; + for (i = 0; i < chat.positions.length; i++) { + if (chat.positions[i].list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) { + break; + } + } + TdApi.ChatPosition[] new_positions = new TdApi.ChatPosition[chat.positions.length + (updateChat.position.order == 0 ? 0 : 1) - (i < chat.positions.length ? 1 : 0)]; + int pos = 0; + if (updateChat.position.order != 0) { + new_positions[pos++] = updateChat.position; + } + for (int j = 0; j < chat.positions.length; j++) { + if (j != i) { + new_positions[pos++] = chat.positions[j]; + } + } + assert pos == new_positions.length; + + setChatPositions(chat, new_positions); } break; } @@ -481,17 +514,55 @@ public final class Example { TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { chat.draftMessage = updateChat.draftMessage; - setChatOrder(chat, updateChat.order); + setChatPositions(chat, updateChat.positions); } break; } - case TdApi.UpdateNotificationSettings.CONSTRUCTOR: { - TdApi.UpdateNotificationSettings update = (TdApi.UpdateNotificationSettings) object; - if (update.scope instanceof TdApi.NotificationSettingsScopeChat) { - TdApi.Chat chat = chats.get(((TdApi.NotificationSettingsScopeChat) update.scope).chatId); - synchronized (chat) { - chat.notificationSettings = update.notificationSettings; - } + case TdApi.UpdateChatPermissions.CONSTRUCTOR: { + TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.permissions = update.permissions; + } + break; + } + case TdApi.UpdateChatNotificationSettings.CONSTRUCTOR: { + TdApi.UpdateChatNotificationSettings update = (TdApi.UpdateChatNotificationSettings) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.notificationSettings = update.notificationSettings; + } + break; + } + case TdApi.UpdateChatDefaultDisableNotification.CONSTRUCTOR: { + TdApi.UpdateChatDefaultDisableNotification update = (TdApi.UpdateChatDefaultDisableNotification) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.defaultDisableNotification = update.defaultDisableNotification; + } + break; + } + case TdApi.UpdateChatIsMarkedAsUnread.CONSTRUCTOR: { + TdApi.UpdateChatIsMarkedAsUnread update = (TdApi.UpdateChatIsMarkedAsUnread) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.isMarkedAsUnread = update.isMarkedAsUnread; + } + break; + } + case TdApi.UpdateChatIsBlocked.CONSTRUCTOR: { + TdApi.UpdateChatIsBlocked update = (TdApi.UpdateChatIsBlocked) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.isBlocked = update.isBlocked; + } + break; + } + case TdApi.UpdateChatHasScheduledMessages.CONSTRUCTOR: { + TdApi.UpdateChatHasScheduledMessages update = (TdApi.UpdateChatHasScheduledMessages) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.hasScheduledMessages = update.hasScheduledMessages; } break; } @@ -530,4 +601,85 @@ public final class Example { } } } -}
\ No newline at end of file + + private static class LogMessageHandler implements Client.LogMessageHandler { + @Override + public void onLogMessage(int verbosityLevel, String message) { + if (verbosityLevel == 0) { + onFatalError(message); + return; + } + System.err.println(message); + } + } + + private static void onFatalError(String errorMessage) { + final class ThrowError implements Runnable { + private final String errorMessage; + private final AtomicLong errorThrowTime; + + private ThrowError(String errorMessage, AtomicLong errorThrowTime) { + this.errorMessage = errorMessage; + this.errorThrowTime = errorThrowTime; + } + + @Override + public void run() { + if (isDatabaseBrokenError(errorMessage) || isDiskFullError(errorMessage) || isDiskError(errorMessage)) { + processExternalError(); + return; + } + + errorThrowTime.set(System.currentTimeMillis()); + throw new ClientError("TDLib fatal error: " + errorMessage); + } + + private void processExternalError() { + errorThrowTime.set(System.currentTimeMillis()); + throw new ExternalClientError("Fatal error: " + errorMessage); + } + + final class ClientError extends Error { + private ClientError(String message) { + super(message); + } + } + + final class ExternalClientError extends Error { + public ExternalClientError(String message) { + super(message); + } + } + + private boolean isDatabaseBrokenError(String message) { + return message.contains("Wrong key or database is corrupted") || + message.contains("SQL logic error or missing database") || + message.contains("database disk image is malformed") || + message.contains("file is encrypted or is not a database") || + message.contains("unsupported file format") || + message.contains("Database was corrupted and deleted during execution and can't be recreated"); + } + + private boolean isDiskFullError(String message) { + return message.contains("PosixError : No space left on device") || + message.contains("database or disk is full"); + } + + private boolean isDiskError(String message) { + return message.contains("I/O error") || message.contains("Structure needs cleaning"); + } + } + + final AtomicLong errorThrowTime = new AtomicLong(Long.MAX_VALUE); + new Thread(new ThrowError(errorMessage, errorThrowTime), "TDLib fatal error thread").start(); + + // wait at least 10 seconds after the error is thrown + while (errorThrowTime.get() >= System.currentTimeMillis() - 10000) { + try { + Thread.sleep(1000 /* milliseconds */); + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } + } + } +} diff --git a/protocols/Telegram/tdlib/td/example/java/td_jni.cpp b/protocols/Telegram/tdlib/td/example/java/td_jni.cpp index b9ba74a402..15c60a9545 100644 --- a/protocols/Telegram/tdlib/td/example/java/td_jni.cpp +++ b/protocols/Telegram/tdlib/td/example/java/td_jni.cpp @@ -1,11 +1,10 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <td/telegram/Client.h> -#include <td/telegram/Log.h> #include <td/telegram/td_api.h> #include <td/tl/tl_jni_object.h> @@ -26,28 +25,35 @@ static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env, return result; } -static td::Client *get_client(jlong client_id) { - return reinterpret_cast<td::Client *>(static_cast<std::uintptr_t>(client_id)); +static td::ClientManager *get_manager() { + return td::ClientManager::get_manager_singleton(); } -static jlong Client_createNativeClient(JNIEnv *env, jclass clazz) { - return static_cast<jlong>(reinterpret_cast<std::uintptr_t>(new td::Client())); +static jint Client_createNativeClient(JNIEnv *env, jclass clazz) { + return static_cast<jint>(get_manager()->create_client_id()); } -static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jlong client_id, jlong id, jobject function) { - get_client(client_id)->send({static_cast<std::uint64_t>(id), fetch_function(env, function)}); +static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) { + get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id), + fetch_function(env, function)); } -static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jlong client_id, jlongArray ids, jobjectArray events, - jdouble timeout) { - auto client = get_client(client_id); - jsize events_size = env->GetArrayLength(ids); // ids and events size must be of equal size +static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids, + jobjectArray events, jdouble timeout) { + jsize events_size = env->GetArrayLength(ids); // client_ids, ids and events must be of equal size + if (events_size == 0) { + return 0; + } jsize result_size = 0; - auto response = client->receive(timeout); - while (response.object && result_size < events_size) { - jlong result_id = static_cast<jlong>(response.id); - env->SetLongArrayRegion(ids, result_size, 1, &result_id); + auto *manager = get_manager(); + auto response = manager->receive(timeout); + while (response.object) { + auto client_id = static_cast<jint>(response.client_id); + env->SetIntArrayRegion(client_ids, result_size, 1, &client_id); + + auto request_id = static_cast<jlong>(response.request_id); + env->SetLongArrayRegion(ids, result_size, 1, &request_id); jobject object; response.object->store(env, object); @@ -55,33 +61,21 @@ static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jlong client_i env->DeleteLocalRef(object); result_size++; - response = client->receive(0); + if (result_size == events_size) { + break; + } + + response = manager->receive(0); } return result_size; } static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) { jobject result; - td::Client::execute({0, fetch_function(env, function)}).object->store(env, result); + td::ClientManager::execute(fetch_function(env, function))->store(env, result); return result; } -static void Client_destroyNativeClient(JNIEnv *env, jclass clazz, jlong client_id) { - delete get_client(client_id); -} - -static void Log_setVerbosityLevel(JNIEnv *env, jclass clazz, jint new_log_verbosity_level) { - td::Log::set_verbosity_level(static_cast<int>(new_log_verbosity_level)); -} - -static jboolean Log_setFilePath(JNIEnv *env, jclass clazz, jstring file_path) { - return td::Log::set_file_path(td::jni::from_jstring(env, file_path)) ? JNI_TRUE : JNI_FALSE; -} - -static void Log_setMaxFileSize(JNIEnv *env, jclass clazz, jlong max_file_size) { - td::Log::set_max_file_size(max_file_size); -} - static jstring Object_toString(JNIEnv *env, jobject object) { return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object))); } @@ -92,17 +86,52 @@ static jstring Function_toString(JNIEnv *env, jobject object) { static constexpr jint JAVA_VERSION = JNI_VERSION_1_6; static JavaVM *java_vm; -static jclass log_class; +static jobject log_message_handler; -static void on_fatal_error(const char *error_message) { +static void on_log_message(int verbosity_level, const char *log_message) { auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION); - jmethodID on_fatal_error_method = env->GetStaticMethodID(log_class, "onFatalError", "(Ljava/lang/String;)V"); - if (env && on_fatal_error_method) { - jstring error_str = td::jni::to_jstring(env.get(), error_message); - env->CallStaticVoidMethod(log_class, on_fatal_error_method, error_str); - if (error_str) { - env->DeleteLocalRef(error_str); + if (env == nullptr) { + return; + } + + jobject handler = env->NewLocalRef(log_message_handler); + if (!handler) { + return; + } + + jclass handler_class = env->GetObjectClass(handler); + if (handler_class) { + jmethodID on_log_message_method = env->GetMethodID(handler_class, "onLogMessage", "(ILjava/lang/String;)V"); + if (on_log_message_method) { + jstring log_message_str = td::jni::to_jstring(env.get(), log_message); + if (log_message_str) { + env->CallVoidMethod(handler, on_log_message_method, static_cast<jint>(verbosity_level), log_message_str); + env->DeleteLocalRef((jobject)log_message_str); + } + } + env->DeleteLocalRef((jobject)handler_class); + } + + env->DeleteLocalRef(handler); +} + +static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, jint max_verbosity_level, + jobject new_log_message_handler) { + if (log_message_handler) { + td::ClientManager::set_log_message_callback(0, nullptr); + jobject old_log_message_handler = log_message_handler; + log_message_handler = jobject(); + env->DeleteGlobalRef(old_log_message_handler); + } + + if (new_log_message_handler) { + log_message_handler = env->NewGlobalRef(new_log_message_handler); + if (!log_message_handler) { + // out of memory + return; } + + td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message); } } @@ -120,21 +149,17 @@ static jint register_native(JavaVM *vm) { }; auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client"); - log_class = td::jni::get_jclass(env, PACKAGE_NAME "/Log"); auto object_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Object"); auto function_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Function"); #define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;" #define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;" - register_method(client_class, "createNativeClient", "()J", Client_createNativeClient); - register_method(client_class, "nativeClientSend", "(JJ" TD_FUNCTION ")V", Client_nativeClientSend); - register_method(client_class, "nativeClientReceive", "(J[J[" TD_OBJECT "D)I", Client_nativeClientReceive); + register_method(client_class, "createNativeClient", "()I", Client_createNativeClient); + register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend); + register_method(client_class, "nativeClientReceive", "([I[J[" TD_OBJECT "D)I", Client_nativeClientReceive); register_method(client_class, "nativeClientExecute", "(" TD_FUNCTION ")" TD_OBJECT, Client_nativeClientExecute); - register_method(client_class, "destroyNativeClient", "(J)V", Client_destroyNativeClient); - - register_method(log_class, "setVerbosityLevel", "(I)V", Log_setVerbosityLevel); - register_method(log_class, "setFilePath", "(Ljava/lang/String;)Z", Log_setFilePath); - register_method(log_class, "setMaxFileSize", "(J)V", Log_setMaxFileSize); + register_method(client_class, "nativeClientSetLogMessageHandler", "(IL" PACKAGE_NAME "/Client$LogMessageHandler;)V", + Client_nativeClientSetLogMessageHandler); register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString); @@ -145,7 +170,6 @@ static jint register_native(JavaVM *vm) { td::jni::init_vars(env, PACKAGE_NAME); td::td_api::Object::init_jni_vars(env, PACKAGE_NAME); td::td_api::Function::init_jni_vars(env, PACKAGE_NAME); - td::Log::set_fatal_error_callback(on_fatal_error); return JAVA_VERSION; } |