initial
This commit is contained in:
5
td/example/java/.gitignore
vendored
Normal file
5
td/example/java/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
**/*build/
|
||||
/bin/
|
||||
/docs/
|
||||
/org/drinkless/tdlib/TdApi.java
|
||||
/td/
|
||||
155
td/example/java/CMakeLists.txt
Normal file
155
td/example/java/CMakeLists.txt
Normal file
@@ -0,0 +1,155 @@
|
||||
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)
|
||||
|
||||
option(TD_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.")
|
||||
|
||||
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)
|
||||
find_package(JNI REQUIRED COMPONENTS JVM)
|
||||
endif()
|
||||
message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}")
|
||||
|
||||
if (NOT Java_FOUND)
|
||||
find_package(Java REQUIRED)
|
||||
endif()
|
||||
message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}")
|
||||
|
||||
# Generating TdApi.java
|
||||
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
|
||||
set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}")
|
||||
if (TD_JSON_JAVA)
|
||||
add_custom_target(td_generate_java_api
|
||||
COMMAND cmake -E echo ""
|
||||
COMMENT "Skip generation of Java TDLib API source files"
|
||||
)
|
||||
|
||||
set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/JsonExample.java")
|
||||
set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/JsonClient.java")
|
||||
else()
|
||||
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_TLO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tlo)
|
||||
set(TD_API_TL_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tl)
|
||||
set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/JavadocTlDocumentationGenerator.php)
|
||||
set(GENERATE_JAVA_API_CMD ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api TdApi "${TD_API_TLO_PATH}" "${TD_API_JAVA_PATH}" "${TD_API_JAVA_PACKAGE}")
|
||||
if (PHP_EXECUTABLE)
|
||||
set(GENERATE_JAVA_API_CMD ${GENERATE_JAVA_API_CMD} && ${PHP_EXECUTABLE} "${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH}" "${TD_API_TL_PATH}" "${JAVA_SOURCE_PATH}/TdApi.java")
|
||||
endif()
|
||||
|
||||
add_custom_target(td_generate_java_api
|
||||
COMMAND ${GENERATE_JAVA_API_CMD}
|
||||
COMMENT "Generating Java TDLib API source files"
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api ${TD_API_TLO_PATH} ${TD_API_TL_PATH} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH}
|
||||
)
|
||||
|
||||
set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/Example.java")
|
||||
set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/Client.java" "${JAVA_SOURCE_PATH}/TdApi.java")
|
||||
endif()
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS "3.17")
|
||||
set(CMAKE_RM_COMMAND remove_directory)
|
||||
else()
|
||||
set(CMAKE_RM_COMMAND rm -rf --)
|
||||
endif()
|
||||
|
||||
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 ${CMAKE_COMMAND} -E ${CMAKE_RM_COMMAND} "${JAVA_OUTPUT_DIRECTORY}/${TD_API_JAVA_PACKAGE}"
|
||||
COMMAND ${Java_JAVAC_EXECUTABLE} -encoding UTF-8 -d "${JAVA_OUTPUT_DIRECTORY}" ${JAVA_EXAMPLE_FILES} ${JAVA_SOURCE_FILES}
|
||||
COMMENT "Building Java code"
|
||||
DEPENDS td_generate_java_api
|
||||
)
|
||||
|
||||
add_custom_target(generate_javadoc
|
||||
COMMAND ${CMAKE_COMMAND} -E ${CMAKE_RM_COMMAND} "${JAVA_OUTPUT_DIRECTORY}/../docs"
|
||||
COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d "${JAVA_OUTPUT_DIRECTORY}/../docs" ${JAVA_SOURCE_FILES}
|
||||
WORKING_DIRECTORY ${TD_API_JAVA_PATH}
|
||||
COMMENT "Generating Javadoc documentation"
|
||||
DEPENDS td_generate_java_api
|
||||
)
|
||||
|
||||
# Building shared library
|
||||
add_library(tdjni SHARED
|
||||
td_jni.cpp
|
||||
)
|
||||
target_include_directories(tdjni PRIVATE ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
|
||||
target_link_libraries(tdjni PRIVATE ${JAVA_JVM_LIBRARY})
|
||||
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}")
|
||||
|
||||
if (TD_JSON_JAVA)
|
||||
target_link_libraries(tdjni PRIVATE Td::TdJsonStatic)
|
||||
target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1)
|
||||
set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava")
|
||||
else()
|
||||
target_link_libraries(tdjni PRIVATE Td::TdStatic)
|
||||
endif()
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
47
td/example/java/README.md
Normal file
47
td/example/java/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# TDLib Java example
|
||||
|
||||
To run this example, you will need installed JDK >= 1.6.
|
||||
For Javadoc documentation generation PHP is needed.
|
||||
|
||||
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
|
||||
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 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
|
||||
|
||||
If you want to build JsonClient.java wrapper for JSON interface instead of the native JNI interface, add `-DTD_JSON_JAVA=ON` option to CMake.
|
||||
|
||||
After this you can compile the example source code:
|
||||
```
|
||||
cd <path to TDLib sources>/example/java
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=<full path to TDLib sources>/example/java/td/lib/cmake/Td -DCMAKE_INSTALL_PREFIX:PATH=.. ..
|
||||
cmake --build . --target install
|
||||
```
|
||||
|
||||
Compiled TDLib shared library and Java example after that will be placed in `bin/` and Javadoc documentation in `docs/`.
|
||||
|
||||
After this you can run the Java example:
|
||||
```
|
||||
cd <path to TDLib sources>/example/java/bin
|
||||
java '-Djava.library.path=.' org/drinkless/tdlib/example/Example
|
||||
```
|
||||
|
||||
If you built JSON interface example using `-DTD_JSON_JAVA=ON` option, then use the command `java '-Djava.library.path=.' org/drinkless/tdlib/example/JsonExample` instead.
|
||||
|
||||
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 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/`.
|
||||
|
||||
Make sure that you compiled the example for the same architecture as your JVM.
|
||||
259
td/example/java/org/drinkless/tdlib/Client.java
Normal file
259
td/example/java/org/drinkless/tdlib/Client.java
Normal file
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// 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;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Main class for interaction with the TDLib.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public interface ResultHandler {
|
||||
/**
|
||||
* Callback called on result of query to TDLib or incoming update from TDLib.
|
||||
*
|
||||
* @param object Result of query or update of type TdApi.Update about new events.
|
||||
*/
|
||||
void onResult(TdApi.Object object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for handler of exceptions thrown while invoking ResultHandler.
|
||||
* By default, all such exceptions are ignored.
|
||||
* All exceptions thrown from ExceptionHandler are ignored.
|
||||
*/
|
||||
public interface ExceptionHandler {
|
||||
/**
|
||||
* Callback called on exceptions thrown while invoking ResultHandler.
|
||||
*
|
||||
* @param e Exception thrown by ResultHandler.
|
||||
*/
|
||||
void onException(Throwable e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception class thrown when TDLib error occurred while performing {@link #execute(TdApi.Function)}.
|
||||
*/
|
||||
public static class ExecutionException extends Exception {
|
||||
/**
|
||||
* Original TDLib error occurred when performing one of the synchronous functions.
|
||||
*/
|
||||
public final TdApi.Error error;
|
||||
|
||||
/**
|
||||
* @param error TDLib error occurred while performing {@link #execute(TdApi.Function)}.
|
||||
*/
|
||||
ExecutionException (TdApi.Error error) {
|
||||
super(error.code + ": " + error.message);
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the TDLib.
|
||||
*
|
||||
* @param query Object representing a query to the TDLib.
|
||||
* @param resultHandler Result handler with onResult method which will be called with result
|
||||
* of the query or with TdApi.Error as parameter. If it is null, nothing
|
||||
* will be called.
|
||||
* @param exceptionHandler Exception handler with onException method which will be called on
|
||||
* exception thrown from resultHandler. If it is null, then
|
||||
* defaultExceptionHandler will be called.
|
||||
*/
|
||||
public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
||||
long queryId = currentQueryId.incrementAndGet();
|
||||
if (resultHandler != null) {
|
||||
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
|
||||
}
|
||||
nativeClientSend(nativeClientId, queryId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the TDLib with an empty ExceptionHandler.
|
||||
*
|
||||
* @param query Object representing a query to the TDLib.
|
||||
* @param resultHandler Result handler with onResult method which will be called with result
|
||||
* of the query or with TdApi.Error as parameter. If it is null, then
|
||||
* defaultExceptionHandler will be called.
|
||||
*/
|
||||
public void send(TdApi.Function query, ResultHandler resultHandler) {
|
||||
send(query, resultHandler, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously.
|
||||
*
|
||||
* @param query Object representing a query to the TDLib.
|
||||
* @param <T> Automatically deduced return type of the query.
|
||||
* @return request result.
|
||||
* @throws ExecutionException if query execution fails.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends TdApi.Object> T execute(TdApi.Function<T> query) throws ExecutionException {
|
||||
TdApi.Object object = nativeClientExecute(query);
|
||||
if (object instanceof TdApi.Error) {
|
||||
throw new ExecutionException((TdApi.Error) object);
|
||||
}
|
||||
return (T) object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new Client.
|
||||
*
|
||||
* @param updateHandler Handler for incoming updates.
|
||||
* @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be ignored.
|
||||
* @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be ignored.
|
||||
* @return created Client
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 static void setLogMessageHandler(int maxVerbosityLevel, Client.LogMessageHandler logMessageHandler) {
|
||||
nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler);
|
||||
}
|
||||
|
||||
private static class ResponseReceiver implements Runnable {
|
||||
public boolean isRun = false;
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isClosed) {
|
||||
updateHandlers.remove(clientId); // there will be no more updates
|
||||
defaultExceptionHandlers.remove(clientId); // ignore further exceptions
|
||||
clientCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
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 int nativeClientId;
|
||||
|
||||
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 ResponseReceiver responseReceiver = new ResponseReceiver();
|
||||
|
||||
private static class Handler {
|
||||
final ResultHandler resultHandler;
|
||||
final ExceptionHandler exceptionHandler;
|
||||
|
||||
Handler(ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
||||
this.resultHandler = resultHandler;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
}
|
||||
|
||||
private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
|
||||
clientCount.incrementAndGet();
|
||||
nativeClientId = createNativeClient();
|
||||
if (updateHandler != null) {
|
||||
updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler));
|
||||
}
|
||||
if (defaultExceptionHandler != null) {
|
||||
defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler);
|
||||
}
|
||||
send(new TdApi.GetOption("version"), null, null);
|
||||
}
|
||||
|
||||
private static native int createNativeClient();
|
||||
|
||||
private static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
|
||||
|
||||
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 nativeClientSetLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler);
|
||||
}
|
||||
79
td/example/java/org/drinkless/tdlib/JsonClient.java
Normal file
79
td/example/java/org/drinkless/tdlib/JsonClient.java
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Main class for interaction with the TDLib using JSON interface.
|
||||
*/
|
||||
public final class JsonClient {
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("tdjsonjava");
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an opaque identifier of a new TDLib instance.
|
||||
* The TDLib instance will not send updates until the first request is sent to it.
|
||||
* @return Opaque identifier of a new TDLib instance.
|
||||
*/
|
||||
public static native int createClientId();
|
||||
|
||||
/**
|
||||
* Sends request to the TDLib client. May be called from any thread.
|
||||
* @param clientId TDLib client identifier.
|
||||
* @param request JSON-serialized request.
|
||||
*/
|
||||
public static native void send(int clientId, String request);
|
||||
|
||||
/**
|
||||
* Receives incoming updates and request responses. Must not be called simultaneously from two different threads.
|
||||
* @param timeout The maximum number of seconds allowed for this function to wait for new data.
|
||||
* @return JSON-serialized incoming update or request response. May be null if the timeout expired before new data received.
|
||||
*/
|
||||
public static native String receive(double timeout);
|
||||
|
||||
/**
|
||||
* Synchronously executes a TDLib request.
|
||||
* A request can be executed synchronously, only if it is documented with "Can be called synchronously".
|
||||
* @param request JSON-serialized request.
|
||||
* @return JSON-serialized request response. May be null if the request is invalid.
|
||||
*/
|
||||
public static native String execute(String request);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 static native void setLogMessageHandler(int maxVerbosityLevel, JsonClient.LogMessageHandler logMessageHandler);
|
||||
|
||||
/**
|
||||
* The class can't be instantiated.
|
||||
*/
|
||||
private JsonClient() {
|
||||
}
|
||||
}
|
||||
780
td/example/java/org/drinkless/tdlib/example/Example.java
Normal file
780
td/example/java/org/drinkless/tdlib/example/Example.java
Normal file
@@ -0,0 +1,780 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// 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.example;
|
||||
|
||||
import org.drinkless.tdlib.Client;
|
||||
import org.drinkless.tdlib.TdApi;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Example class for TDLib usage from Java.
|
||||
*/
|
||||
public final class Example {
|
||||
private static Client client = null;
|
||||
|
||||
private static TdApi.AuthorizationState authorizationState = null;
|
||||
private static volatile boolean haveAuthorization = 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<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> mainChatList = new TreeSet<OrderedChat>();
|
||||
private static boolean haveFullMainChatList = false;
|
||||
|
||||
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;
|
||||
|
||||
private static void print(String str) {
|
||||
if (currentPrompt != null) {
|
||||
System.out.println("");
|
||||
}
|
||||
System.out.println(str);
|
||||
if (currentPrompt != null) {
|
||||
System.out.print(currentPrompt);
|
||||
}
|
||||
}
|
||||
|
||||
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.positions = positions;
|
||||
|
||||
for (TdApi.ChatPosition position : chat.positions) {
|
||||
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
|
||||
boolean isAdded = mainChatList.add(new OrderedChat(chat.id, position));
|
||||
assert isAdded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void onAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState) {
|
||||
if (authorizationState != null) {
|
||||
Example.authorizationState = authorizationState;
|
||||
}
|
||||
switch (Example.authorizationState.getConstructor()) {
|
||||
case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
|
||||
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";
|
||||
|
||||
client.send(request, new AuthorizationRequestHandler());
|
||||
break;
|
||||
case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: {
|
||||
String phoneNumber = promptString("Please enter phone number: ");
|
||||
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());
|
||||
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, false), new AuthorizationRequestHandler());
|
||||
break;
|
||||
}
|
||||
case TdApi.AuthorizationStateWaitPassword.CONSTRUCTOR: {
|
||||
String password = promptString("Please enter password: ");
|
||||
client.send(new TdApi.CheckAuthenticationPassword(password), new AuthorizationRequestHandler());
|
||||
break;
|
||||
}
|
||||
case TdApi.AuthorizationStateReady.CONSTRUCTOR:
|
||||
haveAuthorization = true;
|
||||
authorizationLock.lock();
|
||||
try {
|
||||
gotAuthorization.signal();
|
||||
} finally {
|
||||
authorizationLock.unlock();
|
||||
}
|
||||
break;
|
||||
case TdApi.AuthorizationStateLoggingOut.CONSTRUCTOR:
|
||||
haveAuthorization = false;
|
||||
print("Logging out");
|
||||
break;
|
||||
case TdApi.AuthorizationStateClosing.CONSTRUCTOR:
|
||||
haveAuthorization = false;
|
||||
print("Closing");
|
||||
break;
|
||||
case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
|
||||
print("Closed");
|
||||
if (!needQuit) {
|
||||
client = Client.create(new UpdateHandler(), null, null); // recreate client after previous has closed
|
||||
} else {
|
||||
canQuit = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.err.println("Unsupported authorization state:" + newLine + Example.authorizationState);
|
||||
}
|
||||
}
|
||||
|
||||
private static int toInt(String arg) {
|
||||
int result = 0;
|
||||
try {
|
||||
result = Integer.parseInt(arg);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long getChatId(String arg) {
|
||||
long chatId = 0;
|
||||
try {
|
||||
chatId = Long.parseLong(arg);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
return chatId;
|
||||
}
|
||||
|
||||
private static String promptString(String prompt) {
|
||||
System.out.print(prompt);
|
||||
currentPrompt = prompt;
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
String str = "";
|
||||
try {
|
||||
str = reader.readLine();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
currentPrompt = null;
|
||||
return str;
|
||||
}
|
||||
|
||||
private static void getCommand() {
|
||||
String command = promptString(commandsLine);
|
||||
String[] commands = command.split(" ", 2);
|
||||
try {
|
||||
switch (commands[0]) {
|
||||
case "gcs": {
|
||||
int limit = 20;
|
||||
if (commands.length > 1) {
|
||||
limit = toInt(commands[1]);
|
||||
}
|
||||
getMainChatList(limit);
|
||||
break;
|
||||
}
|
||||
case "gc":
|
||||
client.send(new TdApi.GetChat(getChatId(commands[1])), defaultHandler);
|
||||
break;
|
||||
case "me":
|
||||
client.send(new TdApi.GetMe(), defaultHandler);
|
||||
break;
|
||||
case "sm": {
|
||||
String[] args = commands[1].split(" ", 2);
|
||||
sendMessage(getChatId(args[0]), args[1]);
|
||||
break;
|
||||
}
|
||||
case "lo":
|
||||
haveAuthorization = false;
|
||||
client.send(new TdApi.LogOut(), defaultHandler);
|
||||
break;
|
||||
case "q":
|
||||
needQuit = true;
|
||||
haveAuthorization = false;
|
||||
client.send(new TdApi.Close(), defaultHandler);
|
||||
break;
|
||||
default:
|
||||
System.err.println("Unsupported command: " + command);
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
print("Not enough arguments");
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
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
|
||||
getMainChatList(limit);
|
||||
break;
|
||||
default:
|
||||
System.err.println("Receive wrong response from TDLib:" + newLine + object);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
java.util.Iterator<OrderedChat> iter = mainChatList.iterator();
|
||||
System.out.println();
|
||||
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) {
|
||||
System.out.println(chatId + ": " + chat.title);
|
||||
}
|
||||
}
|
||||
print("");
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendMessage(long chatId, String message) {
|
||||
// initialize reply markup just for testing
|
||||
TdApi.InlineKeyboardButton[] row = {new TdApi.InlineKeyboardButton("https://telegram.org?1", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?2", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?3", new TdApi.InlineKeyboardButtonTypeUrl())};
|
||||
TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][]{row, row, row});
|
||||
|
||||
TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), null, true);
|
||||
client.send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), defaultHandler);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
// 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
|
||||
try {
|
||||
Client.execute(new TdApi.SetLogVerbosityLevel(0));
|
||||
Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false)));
|
||||
} catch (Client.ExecutionException error) {
|
||||
throw new IOError(new IOException("Write access to the current directory is required"));
|
||||
}
|
||||
|
||||
// create client
|
||||
client = Client.create(new UpdateHandler(), null, null);
|
||||
|
||||
// main loop
|
||||
while (!needQuit) {
|
||||
// await authorization
|
||||
authorizationLock.lock();
|
||||
try {
|
||||
while (!haveAuthorization) {
|
||||
gotAuthorization.await();
|
||||
}
|
||||
} finally {
|
||||
authorizationLock.unlock();
|
||||
}
|
||||
|
||||
while (haveAuthorization) {
|
||||
getCommand();
|
||||
}
|
||||
}
|
||||
while (!canQuit) {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrderedChat implements Comparable<OrderedChat> {
|
||||
final long chatId;
|
||||
final TdApi.ChatPosition position;
|
||||
|
||||
OrderedChat(long chatId, TdApi.ChatPosition position) {
|
||||
this.chatId = chatId;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OrderedChat o) {
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
OrderedChat o = (OrderedChat) obj;
|
||||
return this.chatId == o.chatId && this.position.order == o.position.order;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DefaultHandler implements Client.ResultHandler {
|
||||
@Override
|
||||
public void onResult(TdApi.Object object) {
|
||||
print(object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class UpdateHandler implements Client.ResultHandler {
|
||||
@Override
|
||||
public void onResult(TdApi.Object object) {
|
||||
switch (object.getConstructor()) {
|
||||
case TdApi.UpdateAuthorizationState.CONSTRUCTOR:
|
||||
onAuthorizationStateUpdated(((TdApi.UpdateAuthorizationState) object).authorizationState);
|
||||
break;
|
||||
|
||||
case TdApi.UpdateUser.CONSTRUCTOR:
|
||||
TdApi.UpdateUser updateUser = (TdApi.UpdateUser) object;
|
||||
users.put(updateUser.user.id, updateUser.user);
|
||||
break;
|
||||
case TdApi.UpdateUserStatus.CONSTRUCTOR: {
|
||||
TdApi.UpdateUserStatus updateUserStatus = (TdApi.UpdateUserStatus) object;
|
||||
TdApi.User user = users.get(updateUserStatus.userId);
|
||||
synchronized (user) {
|
||||
user.status = updateUserStatus.status;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateBasicGroup.CONSTRUCTOR:
|
||||
TdApi.UpdateBasicGroup updateBasicGroup = (TdApi.UpdateBasicGroup) object;
|
||||
basicGroups.put(updateBasicGroup.basicGroup.id, updateBasicGroup.basicGroup);
|
||||
break;
|
||||
case TdApi.UpdateSupergroup.CONSTRUCTOR:
|
||||
TdApi.UpdateSupergroup updateSupergroup = (TdApi.UpdateSupergroup) object;
|
||||
supergroups.put(updateSupergroup.supergroup.id, updateSupergroup.supergroup);
|
||||
break;
|
||||
case TdApi.UpdateSecretChat.CONSTRUCTOR:
|
||||
TdApi.UpdateSecretChat updateSecretChat = (TdApi.UpdateSecretChat) object;
|
||||
secretChats.put(updateSecretChat.secretChat.id, updateSecretChat.secretChat);
|
||||
break;
|
||||
|
||||
case TdApi.UpdateNewChat.CONSTRUCTOR: {
|
||||
TdApi.UpdateNewChat updateNewChat = (TdApi.UpdateNewChat) object;
|
||||
TdApi.Chat chat = updateNewChat.chat;
|
||||
synchronized (chat) {
|
||||
chats.put(chat.id, chat);
|
||||
|
||||
TdApi.ChatPosition[] positions = chat.positions;
|
||||
chat.positions = new TdApi.ChatPosition[0];
|
||||
setChatPositions(chat, positions);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatTitle.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatTitle updateChat = (TdApi.UpdateChatTitle) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.title = updateChat.title;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatPhoto.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatPhoto updateChat = (TdApi.UpdateChatPhoto) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.photo = updateChat.photo;
|
||||
}
|
||||
break;
|
||||
}
|
||||
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.UpdateChatLastMessage.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatLastMessage updateChat = (TdApi.UpdateChatLastMessage) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.lastMessage = updateChat.lastMessage;
|
||||
setChatPositions(chat, updateChat.positions);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatPosition.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object;
|
||||
if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR) {
|
||||
break;
|
||||
}
|
||||
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
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;
|
||||
}
|
||||
case TdApi.UpdateChatReadInbox.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatReadInbox updateChat = (TdApi.UpdateChatReadInbox) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.lastReadInboxMessageId = updateChat.lastReadInboxMessageId;
|
||||
chat.unreadCount = updateChat.unreadCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatReadOutbox.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatReadOutbox updateChat = (TdApi.UpdateChatReadOutbox) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.lastReadOutboxMessageId = updateChat.lastReadOutboxMessageId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatActionBar.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatActionBar updateChat = (TdApi.UpdateChatActionBar) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.actionBar = updateChat.actionBar;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatAvailableReactions.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatAvailableReactions updateChat = (TdApi.UpdateChatAvailableReactions) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.availableReactions = updateChat.availableReactions;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatDraftMessage.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatDraftMessage updateChat = (TdApi.UpdateChatDraftMessage) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.draftMessage = updateChat.draftMessage;
|
||||
setChatPositions(chat, updateChat.positions);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatMessageSender.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatMessageSender updateChat = (TdApi.UpdateChatMessageSender) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.messageSenderId = updateChat.messageSenderId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatMessageAutoDeleteTime.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatMessageAutoDeleteTime updateChat = (TdApi.UpdateChatMessageAutoDeleteTime) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.messageAutoDeleteTime = updateChat.messageAutoDeleteTime;
|
||||
}
|
||||
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.UpdateChatPendingJoinRequests.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatPendingJoinRequests update = (TdApi.UpdateChatPendingJoinRequests) object;
|
||||
TdApi.Chat chat = chats.get(update.chatId);
|
||||
synchronized (chat) {
|
||||
chat.pendingJoinRequests = update.pendingJoinRequests;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatReplyMarkup.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatReplyMarkup updateChat = (TdApi.UpdateChatReplyMarkup) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.replyMarkupMessageId = updateChat.replyMarkupMessageId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatBackground.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatBackground updateChat = (TdApi.UpdateChatBackground) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.background = updateChat.background;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatTheme.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatTheme updateChat = (TdApi.UpdateChatTheme) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.themeName = updateChat.themeName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatUnreadMentionCount.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatUnreadMentionCount updateChat = (TdApi.UpdateChatUnreadMentionCount) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.unreadMentionCount = updateChat.unreadMentionCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatUnreadReactionCount.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatUnreadReactionCount updateChat = (TdApi.UpdateChatUnreadReactionCount) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.unreadReactionCount = updateChat.unreadReactionCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatVideoChat.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatVideoChat updateChat = (TdApi.UpdateChatVideoChat) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.videoChat = updateChat.videoChat;
|
||||
}
|
||||
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.UpdateChatHasProtectedContent.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatHasProtectedContent updateChat = (TdApi.UpdateChatHasProtectedContent) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.hasProtectedContent = updateChat.hasProtectedContent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateChatIsTranslatable.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatIsTranslatable update = (TdApi.UpdateChatIsTranslatable) object;
|
||||
TdApi.Chat chat = chats.get(update.chatId);
|
||||
synchronized (chat) {
|
||||
chat.isTranslatable = update.isTranslatable;
|
||||
}
|
||||
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.UpdateChatBlockList.CONSTRUCTOR: {
|
||||
TdApi.UpdateChatBlockList update = (TdApi.UpdateChatBlockList) object;
|
||||
TdApi.Chat chat = chats.get(update.chatId);
|
||||
synchronized (chat) {
|
||||
chat.blockList = update.blockList;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
case TdApi.UpdateMessageMentionRead.CONSTRUCTOR: {
|
||||
TdApi.UpdateMessageMentionRead updateChat = (TdApi.UpdateMessageMentionRead) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.unreadMentionCount = updateChat.unreadMentionCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TdApi.UpdateMessageUnreadReactions.CONSTRUCTOR: {
|
||||
TdApi.UpdateMessageUnreadReactions updateChat = (TdApi.UpdateMessageUnreadReactions) object;
|
||||
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||
synchronized (chat) {
|
||||
chat.unreadReactionCount = updateChat.unreadReactionCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TdApi.UpdateUserFullInfo.CONSTRUCTOR:
|
||||
TdApi.UpdateUserFullInfo updateUserFullInfo = (TdApi.UpdateUserFullInfo) object;
|
||||
usersFullInfo.put(updateUserFullInfo.userId, updateUserFullInfo.userFullInfo);
|
||||
break;
|
||||
case TdApi.UpdateBasicGroupFullInfo.CONSTRUCTOR:
|
||||
TdApi.UpdateBasicGroupFullInfo updateBasicGroupFullInfo = (TdApi.UpdateBasicGroupFullInfo) object;
|
||||
basicGroupsFullInfo.put(updateBasicGroupFullInfo.basicGroupId, updateBasicGroupFullInfo.basicGroupFullInfo);
|
||||
break;
|
||||
case TdApi.UpdateSupergroupFullInfo.CONSTRUCTOR:
|
||||
TdApi.UpdateSupergroupFullInfo updateSupergroupFullInfo = (TdApi.UpdateSupergroupFullInfo) object;
|
||||
supergroupsFullInfo.put(updateSupergroupFullInfo.supergroupId, updateSupergroupFullInfo.supergroupFullInfo);
|
||||
break;
|
||||
default:
|
||||
// print("Unsupported update:" + newLine + object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AuthorizationRequestHandler implements Client.ResultHandler {
|
||||
@Override
|
||||
public void onResult(TdApi.Object object) {
|
||||
switch (object.getConstructor()) {
|
||||
case TdApi.Error.CONSTRUCTOR:
|
||||
System.err.println("Receive an error:" + newLine + object);
|
||||
onAuthorizationStateUpdated(null); // repeat last action
|
||||
break;
|
||||
case TdApi.Ok.CONSTRUCTOR:
|
||||
// result is already received through UpdateAuthorizationState, nothing to do
|
||||
break;
|
||||
default:
|
||||
System.err.println("Receive wrong response from TDLib:" + newLine + object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
td/example/java/org/drinkless/tdlib/example/JsonExample.java
Normal file
47
td/example/java/org/drinkless/tdlib/example/JsonExample.java
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// 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.example;
|
||||
|
||||
import org.drinkless.tdlib.JsonClient;
|
||||
|
||||
/**
|
||||
* Example class for TDLib usage from Java using JSON interface.
|
||||
*/
|
||||
public final class JsonExample {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
// set log message handler to handle only fatal errors (0) and plain log messages (-1)
|
||||
JsonClient.setLogMessageHandler(0, new LogMessageHandler());
|
||||
|
||||
// disable TDLib log and redirect fatal errors and plain log messages to a file
|
||||
JsonClient.execute("{\"@type\":\"setLogVerbosityLevel\",\"new_verbosity_level\":0}");
|
||||
JsonClient.execute("{\"@type\":\"setLogStream\",\"log_stream\":{\"@type\":\"logStreamFile\",\"path\":\"tdlib.log\",\"max_file_size\":128000000}}");
|
||||
|
||||
// create client identifier
|
||||
int clientId = JsonClient.createClientId();
|
||||
|
||||
// send first request to activate the client
|
||||
JsonClient.send(clientId, "{\"@type\":\"getOption\",\"name\":\"version\"}");
|
||||
|
||||
// main loop
|
||||
while (true) {
|
||||
String result = JsonClient.receive(100.0);
|
||||
if (result != null) {
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class LogMessageHandler implements JsonClient.LogMessageHandler {
|
||||
@Override
|
||||
public void onLogMessage(int verbosityLevel, String message) {
|
||||
System.err.print(message);
|
||||
if (verbosityLevel == 0) {
|
||||
System.err.println("Receive fatal error; the process will crash now");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
241
td/example/java/td_jni.cpp
Normal file
241
td/example/java/td_jni.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
#ifdef TD_JSON_JAVA
|
||||
#include <td/telegram/td_json_client.h>
|
||||
#else
|
||||
#include <td/telegram/Client.h>
|
||||
#include <td/telegram/td_api.h>
|
||||
#endif
|
||||
|
||||
#include <td/tl/tl_jni_object.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace td_jni {
|
||||
|
||||
#ifdef TD_JSON_JAVA
|
||||
static jint JsonClient_createClientId(JNIEnv *env, jclass clazz) {
|
||||
return static_cast<jint>(td_create_client_id());
|
||||
}
|
||||
|
||||
static void JsonClient_send(JNIEnv *env, jclass clazz, jint client_id, jstring request) {
|
||||
td_send(static_cast<int>(client_id), td::jni::from_jstring(env, request).c_str());
|
||||
}
|
||||
|
||||
static jstring JsonClient_receive(JNIEnv *env, jclass clazz, jdouble timeout) {
|
||||
auto result = td_receive(timeout);
|
||||
if (result == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return td::jni::to_jstring(env, result);
|
||||
}
|
||||
|
||||
static jstring JsonClient_execute(JNIEnv *env, jclass clazz, jstring request) {
|
||||
auto result = td_execute(td::jni::from_jstring(env, request).c_str());
|
||||
if (result == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return td::jni::to_jstring(env, result);
|
||||
}
|
||||
#else
|
||||
static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env, jobject function) {
|
||||
td::jni::reset_parse_error();
|
||||
auto result = td::td_api::Function::fetch(env, function);
|
||||
if (td::jni::have_parse_error()) {
|
||||
std::abort();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static td::ClientManager *get_manager() {
|
||||
return td::ClientManager::get_manager_singleton();
|
||||
}
|
||||
|
||||
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, 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, 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 *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);
|
||||
env->SetObjectArrayElement(events, result_size, object);
|
||||
env->DeleteLocalRef(object);
|
||||
|
||||
result_size++;
|
||||
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::ClientManager::execute(fetch_function(env, function))->store(env, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static jstring Object_toString(JNIEnv *env, jobject object) {
|
||||
return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object)));
|
||||
}
|
||||
|
||||
static jstring Function_toString(JNIEnv *env, jobject object) {
|
||||
return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object)));
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr jint JAVA_VERSION = JNI_VERSION_1_6;
|
||||
static JavaVM *java_vm;
|
||||
static jobject log_message_handler;
|
||||
|
||||
static void on_log_message(int verbosity_level, const char *log_message) {
|
||||
auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
|
||||
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) {
|
||||
#ifdef TD_JSON_JAVA
|
||||
td_set_log_message_callback(0, nullptr);
|
||||
#else
|
||||
td::ClientManager::set_log_message_callback(0, nullptr);
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef TD_JSON_JAVA
|
||||
td_set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
|
||||
#else
|
||||
td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static jint register_native(JavaVM *vm) {
|
||||
JNIEnv *env;
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JAVA_VERSION) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
java_vm = vm;
|
||||
|
||||
auto register_method = [env](jclass clazz, std::string name, std::string signature, auto function_ptr) {
|
||||
td::jni::register_native_method(env, clazz, std::move(name), std::move(signature),
|
||||
reinterpret_cast<void *>(function_ptr));
|
||||
};
|
||||
|
||||
#ifdef TD_JSON_JAVA
|
||||
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/JsonClient");
|
||||
|
||||
register_method(client_class, "createClientId", "()I", JsonClient_createClientId);
|
||||
register_method(client_class, "send", "(ILjava/lang/String;)V", JsonClient_send);
|
||||
register_method(client_class, "receive", "(D)Ljava/lang/String;", JsonClient_receive);
|
||||
register_method(client_class, "execute", "(Ljava/lang/String;)Ljava/lang/String;", JsonClient_execute);
|
||||
register_method(client_class, "setLogMessageHandler", "(IL" PACKAGE_NAME "/JsonClient$LogMessageHandler;)V",
|
||||
Client_nativeClientSetLogMessageHandler);
|
||||
#else
|
||||
auto td_api_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi");
|
||||
jfieldID commit_hash_field_id =
|
||||
td::jni::get_static_field_id(env, td_api_class, "GIT_COMMIT_HASH", "Ljava/lang/String;");
|
||||
std::string td_api_version = td::jni::fetch_static_string(env, td_api_class, commit_hash_field_id);
|
||||
std::string tdjni_version = td::td_api::get_git_commit_hash();
|
||||
if (tdjni_version != td_api_version) {
|
||||
td::jni::set_fatal_error(
|
||||
env, "Mismatched TdApi.java (" + td_api_version + ") and tdjni shared library (" + tdjni_version + ") versions");
|
||||
return JAVA_VERSION;
|
||||
}
|
||||
|
||||
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client");
|
||||
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", "()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, "nativeClientSetLogMessageHandler", "(IL" PACKAGE_NAME "/Client$LogMessageHandler;)V",
|
||||
Client_nativeClientSetLogMessageHandler);
|
||||
|
||||
register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString);
|
||||
|
||||
register_method(function_class, "toString", "()Ljava/lang/String;", Function_toString);
|
||||
#undef TD_FUNCTION
|
||||
#undef TD_OBJECT
|
||||
|
||||
td::jni::init_vars(env, PACKAGE_NAME);
|
||||
td::td_api::get_package_name_ref() = PACKAGE_NAME;
|
||||
#endif
|
||||
|
||||
return JAVA_VERSION;
|
||||
}
|
||||
|
||||
} // namespace td_jni
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
static jint jni_version = td_jni::register_native(vm); // call_once
|
||||
return jni_version;
|
||||
}
|
||||
Reference in New Issue
Block a user