Add Java example.

GitOrigin-RevId: 09903810042bd576bff2f59f4cd4cb498af13749
This commit is contained in:
levlam 2018-01-28 13:58:33 +03:00
parent 18034f7806
commit b339833ef9
8 changed files with 1126 additions and 1 deletions

2
.gitignore vendored
View File

@ -5,4 +5,4 @@
auto/
db_backup
*.pyc
docs
docs/

5
example/java/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
**/*build/
bin/
docs/
org/drinkless/tdlib/TdApi.java
td/

View 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
View 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/.

View 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);
}

View 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();
}
}
}
}

View 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
View 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;
}