Add Java example.
GitOrigin-RevId: 09903810042bd576bff2f59f4cd4cb498af13749
This commit is contained in:
parent
18034f7806
commit
b339833ef9
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,4 +5,4 @@
|
|||||||
auto/
|
auto/
|
||||||
db_backup
|
db_backup
|
||||||
*.pyc
|
*.pyc
|
||||||
docs
|
docs/
|
||||||
|
5
example/java/.gitignore
vendored
Normal file
5
example/java/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
**/*build/
|
||||||
|
bin/
|
||||||
|
docs/
|
||||||
|
org/drinkless/tdlib/TdApi.java
|
||||||
|
td/
|
68
example/java/CMakeLists.txt
Normal file
68
example/java/CMakeLists.txt
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
|
||||||
|
project(TdJavaExample VERSION 1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
find_package(Td REQUIRED)
|
||||||
|
|
||||||
|
if (NOT JNI_FOUND)
|
||||||
|
find_package(JNI REQUIRED)
|
||||||
|
endif()
|
||||||
|
message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}")
|
||||||
|
|
||||||
|
if (NOT Java_FOUND)
|
||||||
|
find_package(Java 1.6 REQUIRED)
|
||||||
|
endif()
|
||||||
|
message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}")
|
||||||
|
|
||||||
|
# Generating TdApi.java
|
||||||
|
find_program(PHP_EXECUTABLE php)
|
||||||
|
|
||||||
|
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
|
||||||
|
set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
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} ${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}/TdApi.java)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_target(td_generate_java_api
|
||||||
|
COMMAND ${GENERATE_JAVA_API_CMD}
|
||||||
|
COMMENT "Generate 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_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
|
||||||
|
COMMENT "Build 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
|
||||||
|
WORKING_DIRECTORY ${TD_API_JAVA_PATH}
|
||||||
|
COMMENT "Generate 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 Td::TdStatic ${JAVA_JVM_LIBRARY})
|
||||||
|
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}")
|
||||||
|
|
||||||
|
set_property(TARGET tdjni PROPERTY CXX_STANDARD 14)
|
||||||
|
|
||||||
|
add_dependencies(tdjni td_generate_java_api build_java generate_javadoc)
|
||||||
|
|
||||||
|
install(TARGETS tdjni
|
||||||
|
LIBRARY DESTINATION bin
|
||||||
|
RUNTIME DESTINATION bin
|
||||||
|
)
|
33
example/java/README.md
Normal file
33
example/java/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# TDLib Java example
|
||||||
|
|
||||||
|
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/`:
|
||||||
|
```
|
||||||
|
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 64-bit Java on Windows, you will also need to add `-G "Visual Studio 14 2015 Win64"` option to CMake.
|
||||||
|
|
||||||
|
Then you can build this example:
|
||||||
|
```
|
||||||
|
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/.
|
||||||
|
|
||||||
|
Now you can run Java example:
|
||||||
|
```
|
||||||
|
cd <path to tdlib sources>/example/java/bin
|
||||||
|
java -Djava.library.path=. org/drinkless/tdlib/example/Example
|
||||||
|
```
|
||||||
|
|
||||||
|
If you get java.lang.UnsatisfiedLinkError with "Can't find dependent libraries" you may also need to copy some dependent shared libraries to bin/.
|
285
example/java/org/drinkless/tdlib/Client.java
Normal file
285
example/java/org/drinkless/tdlib/Client.java
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the TDLib.
|
||||||
|
*
|
||||||
|
* @param query Object representing a query to the TDLib.
|
||||||
|
* @param resultHandler Result handler with onResult method which will be called with result
|
||||||
|
* of the query or with TdApi.Error as parameter. If it is null, nothing
|
||||||
|
* will be called.
|
||||||
|
* @param exceptionHandler Exception handler with onException method which will be called on
|
||||||
|
* exception thrown from resultHandler. If it is null, then
|
||||||
|
* defaultExceptionHandler will be called.
|
||||||
|
* @throws NullPointerException if query is null.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
|
||||||
|
nativeClientSend(nativeClientId, queryId, query);
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the TDLib with an empty ExceptionHandler.
|
||||||
|
*
|
||||||
|
* @param query Object representing a query to the TDLib.
|
||||||
|
* @param resultHandler Result handler with onResult method which will be called with result
|
||||||
|
* of the query or with TdApi.Error as parameter. If it is null, then
|
||||||
|
* defaultExceptionHandler will be called.
|
||||||
|
* @throws NullPointerException if query is null.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* @return request result.
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
public void setUpdatesHandler(ResultHandler updatesHandler, ExceptionHandler exceptionHandler) {
|
||||||
|
handlers.put(0L, new Handler(updatesHandler, exceptionHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler.
|
||||||
|
*
|
||||||
|
* @param updatesHandler Handler with onResult method which will be called for every incoming
|
||||||
|
* update from the TDLib.
|
||||||
|
*/
|
||||||
|
public void setUpdatesHandler(ResultHandler updatesHandler) {
|
||||||
|
setUpdatesHandler(updatesHandler, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden method from Runnable, do not call it directly.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!stopFlag) {
|
||||||
|
receiveQueries(300.0 /*seconds*/);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
isClientDestroyed = true;
|
||||||
|
while (!stopFlag) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
while (handlers.size() != 1) {
|
||||||
|
receiveQueries(300.0);
|
||||||
|
}
|
||||||
|
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 final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
|
||||||
|
private final AtomicLong currentQueryId = new AtomicLong();
|
||||||
|
|
||||||
|
private volatile ExceptionHandler defaultExceptionHandler = null;
|
||||||
|
|
||||||
|
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 class Handler {
|
||||||
|
final ResultHandler resultHandler;
|
||||||
|
final ExceptionHandler exceptionHandler;
|
||||||
|
|
||||||
|
Handler(ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
||||||
|
this.resultHandler = resultHandler;
|
||||||
|
this.exceptionHandler = exceptionHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Client(ResultHandler updatesHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
resultHandler.onResult(object);
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
if (exceptionHandler == null) {
|
||||||
|
exceptionHandler = defaultExceptionHandler;
|
||||||
|
}
|
||||||
|
if (exceptionHandler != null) {
|
||||||
|
try {
|
||||||
|
exceptionHandler.onException(cause);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native long createNativeClient();
|
||||||
|
|
||||||
|
private static native void nativeClientSend(long nativeClientId, long eventId, TdApi.Function function);
|
||||||
|
|
||||||
|
private static native int nativeClientReceive(long nativeClientId, long[] eventIds, TdApi.Object[] events, double timeout);
|
||||||
|
|
||||||
|
private static native TdApi.Object nativeClientExecute(TdApi.Function function);
|
||||||
|
|
||||||
|
private static native void destroyNativeClient(long nativeClientId);
|
||||||
|
}
|
74
example/java/org/drinkless/tdlib/Log.java
Normal file
74
example/java/org/drinkless/tdlib/Log.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
public static native void 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
503
example/java/org/drinkless/tdlib/example/Example.java
Normal file
503
example/java/org/drinkless/tdlib/example/Example.java
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
//
|
||||||
|
// 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.example;
|
||||||
|
|
||||||
|
import org.drinkless.tdlib.Client;
|
||||||
|
import org.drinkless.tdlib.Log;
|
||||||
|
import org.drinkless.tdlib.TdApi;
|
||||||
|
|
||||||
|
import java.io.Console;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
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 quiting = 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<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 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 String newLine = System.getProperty("line.separator");
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("tdjni");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void print(String str) {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
chat.order = order;
|
||||||
|
|
||||||
|
if (chat.order != 0) {
|
||||||
|
boolean isAdded = chatList.add(new OrderedChat(chat.order, chat.id));
|
||||||
|
assert isAdded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState) {
|
||||||
|
if (authorizationState != null) {
|
||||||
|
Example.authorizationState = authorizationState;
|
||||||
|
}
|
||||||
|
switch (Example.authorizationState.getConstructor()) {
|
||||||
|
case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
|
||||||
|
TdApi.TdlibParameters parameters = new TdApi.TdlibParameters();
|
||||||
|
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());
|
||||||
|
break;
|
||||||
|
case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: {
|
||||||
|
Console console = System.console();
|
||||||
|
String phoneNumber = console.readLine("Please enter phone number: ");
|
||||||
|
client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, false, false), new AuthorizationRequestHandler());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: {
|
||||||
|
Console console = System.console();
|
||||||
|
String code = console.readLine("Please enter authentication code: ");
|
||||||
|
client.send(new TdApi.CheckAuthenticationCode(code, "", ""), new AuthorizationRequestHandler());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TdApi.AuthorizationStateWaitPassword.CONSTRUCTOR: {
|
||||||
|
Console console = System.console();
|
||||||
|
String password = console.readLine("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 (!quiting) {
|
||||||
|
client = Client.create(new UpdatesHandler(), null, null); // recreate client after previous has closed
|
||||||
|
}
|
||||||
|
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 void getCommand() {
|
||||||
|
String command = System.console().readLine("Enter command (gcs - GetChats, gc - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): ");
|
||||||
|
String[] commands = command.split(" ", 2);
|
||||||
|
switch (commands[0]) {
|
||||||
|
case "gcs": {
|
||||||
|
int limit = 20;
|
||||||
|
if (commands.length > 1) {
|
||||||
|
limit = toInt(commands[1]);
|
||||||
|
}
|
||||||
|
getChatList(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":
|
||||||
|
quiting = true;
|
||||||
|
haveAuthorization = false;
|
||||||
|
client.send(new TdApi.Close(), defaultHandler);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.println("Unsupported command: " + command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// chats had already been received through updates, let's retry request
|
||||||
|
getChatList(limit);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.err.println("Receive wrong response from TDLib:" + newLine + object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// have enough chats in the chat list to answer request
|
||||||
|
java.util.Iterator<OrderedChat> iter = chatList.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++) {
|
||||||
|
long chatId = iter.next().chatId;
|
||||||
|
TdApi.Chat chat = chats.get(chatId);
|
||||||
|
synchronized (chat) {
|
||||||
|
System.out.println(chatId + ": " + chat.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(message, false, true, null, null);
|
||||||
|
client.send(new TdApi.SendMessage(chatId, 0, false, false, replyMarkup, content), defaultHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
// disable TDLib log
|
||||||
|
Log.setVerbosityLevel(0);
|
||||||
|
|
||||||
|
// create client
|
||||||
|
client = Client.create(new UpdatesHandler(), 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) {
|
||||||
|
// await authorization
|
||||||
|
authorizationLock.lock();
|
||||||
|
try {
|
||||||
|
while (!haveAuthorization) {
|
||||||
|
gotAuthorization.await();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
authorizationLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (haveAuthorization) {
|
||||||
|
getCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OrderedChat implements Comparable<OrderedChat> {
|
||||||
|
final long order;
|
||||||
|
final long chatId;
|
||||||
|
|
||||||
|
OrderedChat(long order, long chatId) {
|
||||||
|
this.order = order;
|
||||||
|
this.chatId = chatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(OrderedChat o) {
|
||||||
|
if (this.order != o.order) {
|
||||||
|
return o.order < this.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.order == o.order && this.chatId == o.chatId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultHandler implements Client.ResultHandler {
|
||||||
|
@Override
|
||||||
|
public void onResult(TdApi.Object object) {
|
||||||
|
print(object.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UpdatesHandler 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);
|
||||||
|
|
||||||
|
long order = chat.order;
|
||||||
|
chat.order = 0;
|
||||||
|
setChatOrder(chat, order);
|
||||||
|
}
|
||||||
|
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.UpdateChatLastMessage.CONSTRUCTOR: {
|
||||||
|
TdApi.UpdateChatLastMessage updateChat = (TdApi.UpdateChatLastMessage) object;
|
||||||
|
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||||
|
synchronized (chat) {
|
||||||
|
chat.lastMessage = updateChat.lastMessage;
|
||||||
|
setChatOrder(chat, updateChat.order);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TdApi.UpdateChatOrder.CONSTRUCTOR: {
|
||||||
|
TdApi.UpdateChatOrder updateChat = (TdApi.UpdateChatOrder) object;
|
||||||
|
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||||
|
synchronized (chat) {
|
||||||
|
setChatOrder(chat, updateChat.order);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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.UpdateChatUnreadMentionCount.CONSTRUCTOR: {
|
||||||
|
TdApi.UpdateChatUnreadMentionCount updateChat = (TdApi.UpdateChatUnreadMentionCount) object;
|
||||||
|
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||||
|
synchronized (chat) {
|
||||||
|
chat.unreadMentionCount = updateChat.unreadMentionCount;
|
||||||
|
}
|
||||||
|
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.UpdateChatReplyMarkup.CONSTRUCTOR: {
|
||||||
|
TdApi.UpdateChatReplyMarkup updateChat = (TdApi.UpdateChatReplyMarkup) object;
|
||||||
|
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||||
|
synchronized (chat) {
|
||||||
|
chat.replyMarkupMessageId = updateChat.replyMarkupMessageId;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TdApi.UpdateChatDraftMessage.CONSTRUCTOR: {
|
||||||
|
TdApi.UpdateChatDraftMessage updateChat = (TdApi.UpdateChatDraftMessage) object;
|
||||||
|
TdApi.Chat chat = chats.get(updateChat.chatId);
|
||||||
|
synchronized (chat) {
|
||||||
|
chat.draftMessage = updateChat.draftMessage;
|
||||||
|
setChatOrder(chat, updateChat.order);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
157
example/java/td_jni.cpp
Normal file
157
example/java/td_jni.cpp
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
#include <td/telegram/Client.h>
|
||||||
|
#include <td/telegram/Log.h>
|
||||||
|
|
||||||
|
#include <td/tl/tl_jni_object.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace td_jni {
|
||||||
|
|
||||||
|
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::Client *get_client(jlong client_id) {
|
||||||
|
return reinterpret_cast<td::Client *>(static_cast<std::uintptr_t>(client_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
static jlong Client_createNativeClient(JNIEnv *env, jclass clazz) {
|
||||||
|
return static_cast<jlong>(reinterpret_cast<std::uintptr_t>(new td::Client()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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
|
||||||
|
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);
|
||||||
|
|
||||||
|
jobject object;
|
||||||
|
response.object->store(env, object);
|
||||||
|
env->SetObjectArrayElement(events, result_size, object);
|
||||||
|
env->DeleteLocalRef(object);
|
||||||
|
|
||||||
|
result_size++;
|
||||||
|
response = client->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);
|
||||||
|
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 void Log_setFilePath(JNIEnv *env, jclass clazz, jstring file_path) {
|
||||||
|
td::Log::set_file_path(td::jni::from_jstring(env, file_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static jstring Function_toString(JNIEnv *env, jobject object) {
|
||||||
|
return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr jint JAVA_VERSION = JNI_VERSION_1_6;
|
||||||
|
static JavaVM *java_vm;
|
||||||
|
static jclass log_class;
|
||||||
|
|
||||||
|
static void on_fatal_error(const char *error_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
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, "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;)V", Log_setFilePath);
|
||||||
|
register_method(log_class, "setMaxFileSize", "(J)V", Log_setMaxFileSize);
|
||||||
|
|
||||||
|
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::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // 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