summaryrefslogtreecommitdiff
path: root/protocols/Telegram/tdlib/td/example/java
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Telegram/tdlib/td/example/java')
-rw-r--r--protocols/Telegram/tdlib/td/example/java/CMakeLists.txt68
-rw-r--r--protocols/Telegram/tdlib/td/example/java/README.md16
-rw-r--r--protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java277
-rw-r--r--protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java75
-rw-r--r--protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java372
-rw-r--r--protocols/Telegram/tdlib/td/example/java/td_jni.cpp128
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;
}