Merge commit '6b1f6b829d0d71f5d50662b1ff58b7869c1e1d7f'
Conflicts: td/telegram/Client.cpp td/telegram/cli.cpp
This commit is contained in:
commit
fc5b0e9d06
@ -813,9 +813,9 @@ target_include_directories(tdcore SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
|
|||||||
target_link_libraries(tdcore PUBLIC tdapi tdactor tdutils tdnet tddb PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
target_link_libraries(tdcore PUBLIC tdapi tdactor tdutils tdnet tddb PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
target_link_libraries(tdcore PRIVATE ws2_32 mswsock)
|
target_link_libraries(tdcore PRIVATE ws2_32 mswsock crypt32)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(tdcore PRIVATE ws2_32 Mswsock)
|
target_link_libraries(tdcore PRIVATE ws2_32 Mswsock Crypt32)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ add_executable(bench_crypto bench_crypto.cpp)
|
|||||||
target_link_libraries(bench_crypto PRIVATE tdutils ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
target_link_libraries(bench_crypto PRIVATE tdutils ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
target_link_libraries(bench_crypto PRIVATE ws2_32 mswsock)
|
target_link_libraries(bench_crypto PRIVATE ws2_32 mswsock crypt32)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(bench_crypto PRIVATE ws2_32 Mswsock)
|
target_link_libraries(bench_crypto PRIVATE ws2_32 Mswsock Crypt32)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
target_include_directories(bench_crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
|
target_include_directories(bench_crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
|
||||||
|
@ -28,6 +28,7 @@ select.large { font-size: large; }
|
|||||||
<option>C++</option>
|
<option>C++</option>
|
||||||
<option>Swift</option>
|
<option>Swift</option>
|
||||||
<option>Objective-C</option>
|
<option>Objective-C</option>
|
||||||
|
<option>Object Pascal</option>
|
||||||
<option>Dart</option>
|
<option>Dart</option>
|
||||||
<option>Rust</option>
|
<option>Rust</option>
|
||||||
<option>Erlang</option>
|
<option>Erlang</option>
|
||||||
@ -202,6 +203,8 @@ function getExampleAnchor(language) {
|
|||||||
case 'Elixir':
|
case 'Elixir':
|
||||||
case 'C':
|
case 'C':
|
||||||
return language.toLowerCase();
|
return language.toLowerCase();
|
||||||
|
case 'Object Pascal':
|
||||||
|
return 'object-pascal';
|
||||||
case 'C#':
|
case 'C#':
|
||||||
return 'csharp';
|
return 'csharp';
|
||||||
case 'C++':
|
case 'C++':
|
||||||
|
@ -18,6 +18,7 @@ Choose your preferred programming language to see examples of usage and a detail
|
|||||||
- [C++](#cxx)
|
- [C++](#cxx)
|
||||||
- [Swift](#swift)
|
- [Swift](#swift)
|
||||||
- [Objective-C](#objective-c)
|
- [Objective-C](#objective-c)
|
||||||
|
- [Object Pascal](#object-pascal)
|
||||||
- [Dart](#dart)
|
- [Dart](#dart)
|
||||||
- [Rust](#rust)
|
- [Rust](#rust)
|
||||||
- [Erlang](#erlang)
|
- [Erlang](#erlang)
|
||||||
@ -135,6 +136,15 @@ TDLib can be used from the Objective-C programming language through [JSON](https
|
|||||||
|
|
||||||
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS.
|
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS.
|
||||||
|
|
||||||
|
<a name="object-pascal"></a>
|
||||||
|
## Using TDLib in Object Pascal projects with Delphi and Lazarus
|
||||||
|
|
||||||
|
TDLib can be used from the Object Pascal programming language through the [JSON](https://github.com/tdlib/td#using-json).
|
||||||
|
|
||||||
|
See [tdlib-delphi](https://github.com/dieletro/tdlib-delphi) for an example of TDLib usage from Delphi.
|
||||||
|
|
||||||
|
See [tdlib-lazarus](https://github.com/dieletro/tdlib-lazarus) for an example of TDLib usage from Lazarus.
|
||||||
|
|
||||||
<a name="dart"></a>
|
<a name="dart"></a>
|
||||||
## Using TDLib in Dart projects
|
## Using TDLib in Dart projects
|
||||||
|
|
||||||
|
@ -13,32 +13,32 @@
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
// disable TDLib logging
|
// disable TDLib logging
|
||||||
td_json_client_execute(nullptr, "{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":0}");
|
td_execute("{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":0}");
|
||||||
|
|
||||||
void *client = td_json_client_create();
|
int client_id = td_create_client();
|
||||||
// somehow share the client with other threads, which will be able to send requests via td_json_client_send
|
// somehow share the client_id with other threads, which will be able to send requests via td_send
|
||||||
|
|
||||||
const bool test_incorrect_queries = false;
|
const bool test_incorrect_queries = false;
|
||||||
if (test_incorrect_queries) {
|
if (test_incorrect_queries) {
|
||||||
td_json_client_execute(nullptr, "{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":3}");
|
td_execute("{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":1}");
|
||||||
td_json_client_execute(nullptr, "");
|
td_execute("");
|
||||||
td_json_client_execute(nullptr, "test");
|
td_execute("test");
|
||||||
td_json_client_execute(nullptr, "\"test\"");
|
td_execute("\"test\"");
|
||||||
td_json_client_execute(nullptr, "{\"@type\":\"test\", \"@extra\":1}");
|
td_execute("{\"@type\":\"test\", \"@extra\":1}");
|
||||||
|
|
||||||
td_json_client_send(client, "{\"@type\":\"getFileMimeType\"}");
|
td_send(client_id, "{\"@type\":\"getFileMimeType\"}");
|
||||||
td_json_client_send(client, "{\"@type\":\"getFileMimeType\", \"@extra\":1}");
|
td_send(client_id, "{\"@type\":\"getFileMimeType\", \"@extra\":1}");
|
||||||
td_json_client_send(client, "{\"@type\":\"getFileMimeType\", \"@extra\":null}");
|
td_send(client_id, "{\"@type\":\"getFileMimeType\", \"@extra\":null}");
|
||||||
td_json_client_send(client, "{\"@type\":\"test\"}");
|
td_send(client_id, "{\"@type\":\"test\"}");
|
||||||
td_json_client_send(client, "[]");
|
td_send(client_id, "[]");
|
||||||
td_json_client_send(client, "{\"@type\":\"test\", \"@extra\":1}");
|
td_send(client_id, "{\"@type\":\"test\", \"@extra\":1}");
|
||||||
td_json_client_send(client, "{\"@type\":\"sendMessage\", \"chat_id\":true, \"@extra\":1}");
|
td_send(client_id, "{\"@type\":\"sendMessage\", \"chat_id\":true, \"@extra\":1}");
|
||||||
td_json_client_send(client, "test");
|
td_send(client_id, "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
const double WAIT_TIMEOUT = 10.0; // seconds
|
const double WAIT_TIMEOUT = 10.0; // seconds
|
||||||
while (true) {
|
while (true) {
|
||||||
const char *result = td_json_client_receive(client, WAIT_TIMEOUT);
|
const char *result = td_receive(WAIT_TIMEOUT);
|
||||||
if (result != nullptr) {
|
if (result != nullptr) {
|
||||||
// parse the result as a JSON object and process it as an incoming update or an answer to a previously sent request
|
// parse the result as a JSON object and process it as an incoming update or an answer to a previously sent request
|
||||||
|
|
||||||
@ -49,6 +49,4 @@ int main() {
|
|||||||
std::cout << result << std::endl;
|
std::cout << result << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td_json_client_destroy(client);
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ namespace TdExample
|
|||||||
|
|
||||||
private static TdApi.AuthorizationState _authorizationState = null;
|
private static TdApi.AuthorizationState _authorizationState = null;
|
||||||
private static volatile bool _haveAuthorization = false;
|
private static volatile bool _haveAuthorization = false;
|
||||||
private static volatile bool _quiting = false;
|
private static volatile bool _needQuit = false;
|
||||||
|
private static volatile bool _canQuit = false;
|
||||||
|
|
||||||
private static volatile AutoResetEvent _gotAuthorization = new AutoResetEvent(false);
|
private static volatile AutoResetEvent _gotAuthorization = new AutoResetEvent(false);
|
||||||
|
|
||||||
@ -133,9 +134,11 @@ namespace TdExample
|
|||||||
{
|
{
|
||||||
Print("Closed");
|
Print("Closed");
|
||||||
_client.Dispose(); // _client is closed and native resources can be disposed now
|
_client.Dispose(); // _client is closed and native resources can be disposed now
|
||||||
if (!_quiting)
|
if (!_needQuit)
|
||||||
{
|
{
|
||||||
_client = CreateTdClient(); // recreate _client after previous has closed
|
_client = CreateTdClient(); // recreate _client after previous has closed
|
||||||
|
} else {
|
||||||
|
_canQuit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -187,7 +190,7 @@ namespace TdExample
|
|||||||
_client.Send(new TdApi.Close(), _defaultHandler);
|
_client.Send(new TdApi.Close(), _defaultHandler);
|
||||||
break;
|
break;
|
||||||
case "q":
|
case "q":
|
||||||
_quiting = true;
|
_needQuit = true;
|
||||||
_haveAuthorization = false;
|
_haveAuthorization = false;
|
||||||
_client.Send(new TdApi.Close(), _defaultHandler);
|
_client.Send(new TdApi.Close(), _defaultHandler);
|
||||||
break;
|
break;
|
||||||
@ -228,7 +231,7 @@ namespace TdExample
|
|||||||
_defaultHandler.OnResult(Td.Client.Execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
|
_defaultHandler.OnResult(Td.Client.Execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
|
||||||
|
|
||||||
// main loop
|
// main loop
|
||||||
while (!_quiting)
|
while (!_needQuit)
|
||||||
{
|
{
|
||||||
// await authorization
|
// await authorization
|
||||||
_gotAuthorization.Reset();
|
_gotAuthorization.Reset();
|
||||||
@ -240,6 +243,9 @@ namespace TdExample
|
|||||||
GetCommand();
|
GetCommand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (!_canQuit) {
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefaultHandler : Td.ClientResultHandler
|
private class DefaultHandler : Td.ClientResultHandler
|
||||||
|
@ -8,14 +8,11 @@ package org.drinkless.tdlib;
|
|||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
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.
|
* Main class for interaction with the TDLib.
|
||||||
*/
|
*/
|
||||||
public final class Client implements Runnable {
|
public final class Client {
|
||||||
/**
|
/**
|
||||||
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
|
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
|
||||||
*/
|
*/
|
||||||
@ -55,25 +52,11 @@ public final class Client implements Runnable {
|
|||||||
* @throws NullPointerException if query is null.
|
* @throws NullPointerException if query is null.
|
||||||
*/
|
*/
|
||||||
public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
||||||
if (query == null) {
|
long queryId = currentQueryId.incrementAndGet();
|
||||||
throw new NullPointerException("query is null");
|
if (resultHandler != 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));
|
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
|
||||||
nativeClientSend(nativeClientId, queryId, query);
|
|
||||||
} finally {
|
|
||||||
readLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
nativeClientSend(nativeClientId, queryId, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,22 +80,9 @@ public final class Client implements Runnable {
|
|||||||
* @throws NullPointerException if query is null.
|
* @throws NullPointerException if query is null.
|
||||||
*/
|
*/
|
||||||
public static TdApi.Object execute(TdApi.Function query) {
|
public static TdApi.Object execute(TdApi.Function query) {
|
||||||
if (query == null) {
|
|
||||||
throw new NullPointerException("query is null");
|
|
||||||
}
|
|
||||||
return nativeClientExecute(query);
|
return nativeClientExecute(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Overridden method from Runnable, do not call it directly.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (!stopFlag) {
|
|
||||||
receiveQueries(300.0 /*seconds*/);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new Client.
|
* Creates new Client.
|
||||||
*
|
*
|
||||||
@ -123,54 +93,79 @@ public final class Client implements Runnable {
|
|||||||
*/
|
*/
|
||||||
public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
|
public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
|
||||||
Client client = new Client(updateHandler, updateExceptionHandler, defaultExceptionHandler);
|
Client client = new Client(updateHandler, updateExceptionHandler, defaultExceptionHandler);
|
||||||
new Thread(client, "TDLib thread").start();
|
synchronized (responseReceiver) {
|
||||||
|
if (!responseReceiver.isRun) {
|
||||||
|
responseReceiver.isRun = true;
|
||||||
|
|
||||||
|
Thread receiverThread = new Thread(responseReceiver, "TDLib thread");
|
||||||
|
receiverThread.setDaemon(true);
|
||||||
|
receiverThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static class ResponseReceiver implements Runnable {
|
||||||
* Closes Client.
|
public boolean isRun = false;
|
||||||
*/
|
|
||||||
public void close() {
|
@Override
|
||||||
writeLock.lock();
|
public void run() {
|
||||||
try {
|
while (true) {
|
||||||
if (isClientDestroyed) {
|
int resultN = nativeClientReceive(clientIds, eventIds, events, 100000.0 /*seconds*/);
|
||||||
return;
|
for (int i = 0; i < resultN; i++) {
|
||||||
|
processResult(clientIds[i], eventIds[i], events[i]);
|
||||||
|
events[i] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!stopFlag) {
|
|
||||||
send(new TdApi.Close(), null);
|
|
||||||
}
|
|
||||||
isClientDestroyed = true;
|
|
||||||
while (!stopFlag) {
|
|
||||||
Thread.yield();
|
|
||||||
}
|
|
||||||
while (!handlers.isEmpty()) {
|
|
||||||
receiveQueries(300.0);
|
|
||||||
}
|
|
||||||
updateHandlers.remove(nativeClientId);
|
|
||||||
defaultExceptionHandlers.remove(nativeClientId);
|
|
||||||
destroyNativeClient(nativeClientId);
|
|
||||||
} finally {
|
|
||||||
writeLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
private final int nativeClientId;
|
||||||
private final Lock readLock = readWriteLock.readLock();
|
|
||||||
private final Lock writeLock = readWriteLock.writeLock();
|
|
||||||
|
|
||||||
private volatile boolean stopFlag = false;
|
private static final ConcurrentHashMap<Integer, ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<Integer, ExceptionHandler>();
|
||||||
private volatile boolean isClientDestroyed = false;
|
private static final ConcurrentHashMap<Integer, Handler> updateHandlers = new ConcurrentHashMap<Integer, Handler>();
|
||||||
private final long nativeClientId;
|
private static final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
|
||||||
|
private static final AtomicLong currentQueryId = new AtomicLong();
|
||||||
|
|
||||||
private static final ConcurrentHashMap<Long, ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<Long, ExceptionHandler>();
|
private static final ResponseReceiver responseReceiver = new ResponseReceiver();
|
||||||
private static final ConcurrentHashMap<Long, Handler> updateHandlers = new ConcurrentHashMap<Long, Handler>();
|
|
||||||
|
|
||||||
private final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
|
|
||||||
private final AtomicLong currentQueryId = new AtomicLong();
|
|
||||||
|
|
||||||
private static final int MAX_EVENTS = 1000;
|
|
||||||
private final long[] eventIds = new long[MAX_EVENTS];
|
|
||||||
private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS];
|
|
||||||
|
|
||||||
private static class Handler {
|
private static class Handler {
|
||||||
final ResultHandler resultHandler;
|
final ResultHandler resultHandler;
|
||||||
@ -184,76 +179,24 @@ public final class Client implements Runnable {
|
|||||||
|
|
||||||
private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
|
private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
|
||||||
nativeClientId = createNativeClient();
|
nativeClientId = createNativeClient();
|
||||||
updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler));
|
if (updateHandler != null) {
|
||||||
|
updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler));
|
||||||
|
}
|
||||||
if (defaultExceptionHandler != null) {
|
if (defaultExceptionHandler != null) {
|
||||||
defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler);
|
defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
try {
|
send(new TdApi.Close(), null, null);
|
||||||
close();
|
|
||||||
} finally {
|
|
||||||
super.finalize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processResult(long id, TdApi.Object object) {
|
private static native int createNativeClient();
|
||||||
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 = updateHandlers.get(nativeClientId);
|
|
||||||
} else {
|
|
||||||
handler = handlers.remove(id);
|
|
||||||
}
|
|
||||||
if (handler == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResult(object, handler.resultHandler, handler.exceptionHandler);
|
private static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
|
||||||
}
|
|
||||||
|
|
||||||
private void handleResult(TdApi.Object object, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
|
private static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout);
|
||||||
if (resultHandler == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
resultHandler.onResult(object);
|
|
||||||
} catch (Throwable cause) {
|
|
||||||
if (exceptionHandler == null) {
|
|
||||||
exceptionHandler = defaultExceptionHandlers.get(nativeClientId);
|
|
||||||
}
|
|
||||||
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 TdApi.Object nativeClientExecute(TdApi.Function function);
|
||||||
|
|
||||||
private static native void destroyNativeClient(long nativeClientId);
|
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@ public final class Example {
|
|||||||
|
|
||||||
private static TdApi.AuthorizationState authorizationState = null;
|
private static TdApi.AuthorizationState authorizationState = null;
|
||||||
private static volatile boolean haveAuthorization = false;
|
private static volatile boolean haveAuthorization = false;
|
||||||
private static volatile boolean quiting = false;
|
private static volatile boolean needQuit = false;
|
||||||
|
private static volatile boolean canQuit = false;
|
||||||
|
|
||||||
private static final Client.ResultHandler defaultHandler = new DefaultHandler();
|
private static final Client.ResultHandler defaultHandler = new DefaultHandler();
|
||||||
|
|
||||||
@ -160,8 +161,10 @@ public final class Example {
|
|||||||
break;
|
break;
|
||||||
case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
|
case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
|
||||||
print("Closed");
|
print("Closed");
|
||||||
if (!quiting) {
|
if (!needQuit) {
|
||||||
client = Client.create(new UpdateHandler(), null, null); // recreate client after previous has closed
|
client = Client.create(new UpdateHandler(), null, null); // recreate client after previous has closed
|
||||||
|
} else {
|
||||||
|
canQuit = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -230,7 +233,7 @@ public final class Example {
|
|||||||
client.send(new TdApi.LogOut(), defaultHandler);
|
client.send(new TdApi.LogOut(), defaultHandler);
|
||||||
break;
|
break;
|
||||||
case "q":
|
case "q":
|
||||||
quiting = true;
|
needQuit = true;
|
||||||
haveAuthorization = false;
|
haveAuthorization = false;
|
||||||
client.send(new TdApi.Close(), defaultHandler);
|
client.send(new TdApi.Close(), defaultHandler);
|
||||||
break;
|
break;
|
||||||
@ -316,7 +319,7 @@ public final class Example {
|
|||||||
defaultHandler.onResult(Client.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
|
defaultHandler.onResult(Client.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
|
||||||
|
|
||||||
// main loop
|
// main loop
|
||||||
while (!quiting) {
|
while (!needQuit) {
|
||||||
// await authorization
|
// await authorization
|
||||||
authorizationLock.lock();
|
authorizationLock.lock();
|
||||||
try {
|
try {
|
||||||
@ -331,6 +334,9 @@ public final class Example {
|
|||||||
getCommand();
|
getCommand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (!canQuit) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class OrderedChat implements Comparable<OrderedChat> {
|
private static class OrderedChat implements Comparable<OrderedChat> {
|
||||||
|
@ -26,31 +26,35 @@ static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static td::Client *get_client(jlong client_id) {
|
static td::ClientManager *get_manager() {
|
||||||
return reinterpret_cast<td::Client *>(static_cast<std::uintptr_t>(client_id));
|
return td::ClientManager::get_manager_singleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
static jlong Client_createNativeClient(JNIEnv *env, jclass clazz) {
|
static jint Client_createNativeClient(JNIEnv *env, jclass clazz) {
|
||||||
return static_cast<jlong>(reinterpret_cast<std::uintptr_t>(new td::Client()));
|
return static_cast<jint>(get_manager()->create_client());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jlong client_id, jlong id, jobject function) {
|
static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) {
|
||||||
get_client(client_id)->send({static_cast<std::uint64_t>(id), fetch_function(env, function)});
|
get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id),
|
||||||
|
fetch_function(env, function));
|
||||||
}
|
}
|
||||||
|
|
||||||
static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jlong client_id, jlongArray ids, jobjectArray events,
|
static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids,
|
||||||
jdouble timeout) {
|
jobjectArray events, jdouble timeout) {
|
||||||
auto client = get_client(client_id);
|
jsize events_size = env->GetArrayLength(ids); // client_ids, ids and events must be of equal size
|
||||||
jsize events_size = env->GetArrayLength(ids); // ids and events size must be of equal size
|
|
||||||
if (events_size == 0) {
|
if (events_size == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
jsize result_size = 0;
|
jsize result_size = 0;
|
||||||
|
|
||||||
auto response = client->receive(timeout);
|
auto *manager = get_manager();
|
||||||
|
auto response = manager->receive(timeout);
|
||||||
while (response.object) {
|
while (response.object) {
|
||||||
jlong result_id = static_cast<jlong>(response.id);
|
jint client_id = static_cast<jint>(response.client_id);
|
||||||
env->SetLongArrayRegion(ids, result_size, 1, &result_id);
|
env->SetIntArrayRegion(client_ids, result_size, 1, &client_id);
|
||||||
|
|
||||||
|
jlong request_id = static_cast<jlong>(response.request_id);
|
||||||
|
env->SetLongArrayRegion(ids, result_size, 1, &request_id);
|
||||||
|
|
||||||
jobject object;
|
jobject object;
|
||||||
response.object->store(env, object);
|
response.object->store(env, object);
|
||||||
@ -62,21 +66,17 @@ static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jlong client_i
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
response = client->receive(0);
|
response = manager->receive(0);
|
||||||
}
|
}
|
||||||
return result_size;
|
return result_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) {
|
static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) {
|
||||||
jobject result;
|
jobject result;
|
||||||
td::Client::execute({0, fetch_function(env, function)}).object->store(env, result);
|
td::ClientManager::execute(fetch_function(env, function))->store(env, result);
|
||||||
return result;
|
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) {
|
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));
|
td::Log::set_verbosity_level(static_cast<int>(new_log_verbosity_level));
|
||||||
}
|
}
|
||||||
@ -103,8 +103,11 @@ static jclass log_class;
|
|||||||
|
|
||||||
static void on_fatal_error(const char *error_message) {
|
static void on_fatal_error(const char *error_message) {
|
||||||
auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
|
auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
|
||||||
|
if (env == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
jmethodID on_fatal_error_method = env->GetStaticMethodID(log_class, "onFatalError", "(Ljava/lang/String;)V");
|
jmethodID on_fatal_error_method = env->GetStaticMethodID(log_class, "onFatalError", "(Ljava/lang/String;)V");
|
||||||
if (env && on_fatal_error_method) {
|
if (on_fatal_error_method) {
|
||||||
jstring error_str = td::jni::to_jstring(env.get(), error_message);
|
jstring error_str = td::jni::to_jstring(env.get(), error_message);
|
||||||
env->CallStaticVoidMethod(log_class, on_fatal_error_method, error_str);
|
env->CallStaticVoidMethod(log_class, on_fatal_error_method, error_str);
|
||||||
if (error_str) {
|
if (error_str) {
|
||||||
@ -133,11 +136,10 @@ static jint register_native(JavaVM *vm) {
|
|||||||
|
|
||||||
#define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;"
|
#define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;"
|
||||||
#define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;"
|
#define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;"
|
||||||
register_method(client_class, "createNativeClient", "()J", Client_createNativeClient);
|
register_method(client_class, "createNativeClient", "()I", Client_createNativeClient);
|
||||||
register_method(client_class, "nativeClientSend", "(JJ" TD_FUNCTION ")V", Client_nativeClientSend);
|
register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend);
|
||||||
register_method(client_class, "nativeClientReceive", "(J[J[" TD_OBJECT "D)I", Client_nativeClientReceive);
|
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, "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, "setVerbosityLevel", "(I)V", Log_setVerbosityLevel);
|
||||||
register_method(log_class, "setFilePath", "(Ljava/lang/String;)Z", Log_setFilePath);
|
register_method(log_class, "setFilePath", "(Ljava/lang/String;)Z", Log_setFilePath);
|
||||||
|
@ -6,7 +6,7 @@ You need a Unix shell with `sed`, `tar` and `wget` utilities to run the provided
|
|||||||
|
|
||||||
## Building tdweb NPM package
|
## Building tdweb NPM package
|
||||||
|
|
||||||
* Install the 1.39.5 [emsdk](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html), which is known to work. Do not use the system-provided `emscripten` package, because it contains a version that is too old.
|
* Install the 2.0.6 [emsdk](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html), which is known to work. Do not use the system-provided `emscripten` package, because it contains a too old emsdk version.
|
||||||
* Install all `TDLib` build dependencies as described in [Building](https://github.com/tdlib/td#building).
|
* Install all `TDLib` build dependencies as described in [Building](https://github.com/tdlib/td#building).
|
||||||
* Run `source ./emsdk_env.sh` from `emsdk` directory to set up the correct build environment.
|
* Run `source ./emsdk_env.sh` from `emsdk` directory to set up the correct build environment.
|
||||||
* On `macOS`, install the `coreutils` [Homebrew](https://brew.sh) package and replace `realpath` in scripts with `grealpath`:
|
* On `macOS`, install the `coreutils` [Homebrew](https://brew.sh) package and replace `realpath` in scripts with `grealpath`:
|
||||||
|
@ -22,9 +22,9 @@ target_include_directories(tdsqlite SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
|
|||||||
target_link_libraries(tdsqlite PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
target_link_libraries(tdsqlite PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
target_link_libraries(tdsqlite PRIVATE ws2_32 mswsock)
|
target_link_libraries(tdsqlite PRIVATE ws2_32 mswsock crypt32)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(tdsqlite PRIVATE ws2_32 Mswsock)
|
target_link_libraries(tdsqlite PRIVATE ws2_32 Mswsock Crypt32)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "td/utils/common.h"
|
#include "td/utils/common.h"
|
||||||
#include "td/utils/crypto.h"
|
#include "td/utils/crypto.h"
|
||||||
|
#include "td/utils/ExitGuard.h"
|
||||||
#include "td/utils/logging.h"
|
#include "td/utils/logging.h"
|
||||||
#include "td/utils/misc.h"
|
#include "td/utils/misc.h"
|
||||||
#include "td/utils/MpscPollableQueue.h"
|
#include "td/utils/MpscPollableQueue.h"
|
||||||
@ -203,10 +204,12 @@ class ClientManager::Impl final {
|
|||||||
td.second.reset();
|
td.second.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (!tds_.empty()) {
|
while (!tds_.empty() && !ExitGuard::is_exited()) {
|
||||||
receive(10, false, true);
|
receive(0.1, false, true);
|
||||||
|
}
|
||||||
|
if (!ExitGuard::is_exited()) { // prevent closing of schedulers from already killed by OS threads
|
||||||
|
concurrent_scheduler_->finish();
|
||||||
}
|
}
|
||||||
concurrent_scheduler_->finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -475,7 +478,9 @@ class MultiImpl {
|
|||||||
Scheduler::instance()->finish();
|
Scheduler::instance()->finish();
|
||||||
}
|
}
|
||||||
scheduler_thread_.join();
|
scheduler_thread_.join();
|
||||||
concurrent_scheduler_->finish();
|
if (!ExitGuard::is_exited()) { // prevent closing of schedulers from already killed by OS threads
|
||||||
|
concurrent_scheduler_->finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -618,11 +623,14 @@ class ClientManager::Impl final {
|
|||||||
Impl(Impl &&) = delete;
|
Impl(Impl &&) = delete;
|
||||||
Impl &operator=(Impl &&) = delete;
|
Impl &operator=(Impl &&) = delete;
|
||||||
~Impl() {
|
~Impl() {
|
||||||
|
if (ExitGuard::is_exited()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (auto &it : impls_) {
|
for (auto &it : impls_) {
|
||||||
close_impl(it.first);
|
close_impl(it.first);
|
||||||
}
|
}
|
||||||
while (!impls_.empty()) {
|
while (!impls_.empty() && !ExitGuard::is_exited()) {
|
||||||
receive(10, false, true);
|
receive(0.1, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,8 +681,8 @@ class Client::Impl final {
|
|||||||
Impl &operator=(Impl &&) = delete;
|
Impl &operator=(Impl &&) = delete;
|
||||||
~Impl() {
|
~Impl() {
|
||||||
multi_impl_->close(td_id_);
|
multi_impl_->close(td_id_);
|
||||||
while (true) {
|
while (!ExitGuard::is_exited()) {
|
||||||
auto response = receiver_.receive(10.0, false, true);
|
auto response = receiver_.receive(0.1, false, true);
|
||||||
if (response.object == nullptr && response.client_id != 0 && response.request_id == 0) {
|
if (response.object == nullptr && response.client_id != 0 && response.request_id == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -742,4 +750,10 @@ ClientManager::~ClientManager() = default;
|
|||||||
ClientManager::ClientManager(ClientManager &&other) = default;
|
ClientManager::ClientManager(ClientManager &&other) = default;
|
||||||
ClientManager &ClientManager::operator=(ClientManager &&other) = default;
|
ClientManager &ClientManager::operator=(ClientManager &&other) = default;
|
||||||
|
|
||||||
|
ClientManager *ClientManager::get_manager_singleton() {
|
||||||
|
static ClientManager client_manager;
|
||||||
|
static ExitGuard exit_guard;
|
||||||
|
return &client_manager;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -260,6 +260,12 @@ class ClientManager final {
|
|||||||
*/
|
*/
|
||||||
ClientManager &operator=(ClientManager &&other);
|
ClientManager &operator=(ClientManager &&other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pointer to a singleton ClientManager instance.
|
||||||
|
* \return A unique singleton ClientManager instance.
|
||||||
|
*/
|
||||||
|
static ClientManager *get_manager_singleton();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Client;
|
friend class Client;
|
||||||
class Impl;
|
class Impl;
|
||||||
|
@ -115,8 +115,7 @@ const char *ClientJson::execute(Slice request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static ClientManager *get_manager() {
|
static ClientManager *get_manager() {
|
||||||
static ClientManager client_manager;
|
return ClientManager::get_manager_singleton();
|
||||||
return &client_manager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::mutex extra_mutex;
|
static std::mutex extra_mutex;
|
||||||
|
@ -12825,6 +12825,14 @@ bool ContactsManager::get_chat_is_active(ChatId chat_id) const {
|
|||||||
return c->is_active;
|
return c->is_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChannelId ContactsManager::get_chat_migrated_to_channel_id(ChatId chat_id) const {
|
||||||
|
auto c = get_chat(chat_id);
|
||||||
|
if (c == nullptr) {
|
||||||
|
return ChannelId();
|
||||||
|
}
|
||||||
|
return c->migrated_to_channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
DialogParticipantStatus ContactsManager::get_chat_status(ChatId chat_id) const {
|
DialogParticipantStatus ContactsManager::get_chat_status(ChatId chat_id) const {
|
||||||
auto c = get_chat(chat_id);
|
auto c = get_chat(chat_id);
|
||||||
if (c == nullptr) {
|
if (c == nullptr) {
|
||||||
|
@ -467,6 +467,7 @@ class ContactsManager : public Actor {
|
|||||||
void reload_chat_full(ChatId chat_id, Promise<Unit> &&promise);
|
void reload_chat_full(ChatId chat_id, Promise<Unit> &&promise);
|
||||||
|
|
||||||
bool get_chat_is_active(ChatId chat_id) const;
|
bool get_chat_is_active(ChatId chat_id) const;
|
||||||
|
ChannelId get_chat_migrated_to_channel_id(ChatId chat_id) const;
|
||||||
DialogParticipantStatus get_chat_status(ChatId chat_id) const;
|
DialogParticipantStatus get_chat_status(ChatId chat_id) const;
|
||||||
DialogParticipantStatus get_chat_permissions(ChatId chat_id) const;
|
DialogParticipantStatus get_chat_permissions(ChatId chat_id) const;
|
||||||
bool is_appointed_chat_administrator(ChatId chat_id) const;
|
bool is_appointed_chat_administrator(ChatId chat_id) const;
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "td/actor/actor.h"
|
#include "td/actor/actor.h"
|
||||||
|
|
||||||
|
#include "td/utils/ExitGuard.h"
|
||||||
#include "td/utils/FileLog.h"
|
#include "td/utils/FileLog.h"
|
||||||
#include "td/utils/logging.h"
|
#include "td/utils/logging.h"
|
||||||
#include "td/utils/misc.h"
|
#include "td/utils/misc.h"
|
||||||
@ -44,6 +45,7 @@ static std::mutex logging_mutex;
|
|||||||
static FileLog file_log;
|
static FileLog file_log;
|
||||||
static TsLog ts_log(&file_log);
|
static TsLog ts_log(&file_log);
|
||||||
static NullLog null_log;
|
static NullLog null_log;
|
||||||
|
static ExitGuard exit_guard;
|
||||||
|
|
||||||
#define ADD_TAG(tag) \
|
#define ADD_TAG(tag) \
|
||||||
{ #tag, &VERBOSITY_NAME(tag) }
|
{ #tag, &VERBOSITY_NAME(tag) }
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include "td/telegram/ChannelId.h"
|
#include "td/telegram/ChannelId.h"
|
||||||
#include "td/telegram/DialogId.h"
|
#include "td/telegram/DialogId.h"
|
||||||
#include "td/telegram/MessageId.h"
|
#include "td/telegram/MessageId.h"
|
||||||
#include "Td/telegram/td_api.h"
|
#include "td/telegram/td_api.h"
|
||||||
#include "td/telegram/telegram_api.h"
|
#include "td/telegram/telegram_api.h"
|
||||||
|
|
||||||
#include "td/utils/common.h"
|
#include "td/utils/common.h"
|
||||||
|
@ -15424,6 +15424,9 @@ std::pair<int32, vector<DialogId>> MessagesManager::search_dialogs(const string
|
|||||||
}
|
}
|
||||||
|
|
||||||
promise.set_value(Unit());
|
promise.set_value(Unit());
|
||||||
|
|
||||||
|
update_recently_found_dialogs();
|
||||||
|
|
||||||
size_t result_size = min(static_cast<size_t>(limit), recently_found_dialog_ids_.size());
|
size_t result_size = min(static_cast<size_t>(limit), recently_found_dialog_ids_.size());
|
||||||
return {narrow_cast<int32>(recently_found_dialog_ids_.size()),
|
return {narrow_cast<int32>(recently_found_dialog_ids_.size()),
|
||||||
vector<DialogId>(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.begin() + result_size)};
|
vector<DialogId>(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.begin() + result_size)};
|
||||||
@ -23734,6 +23737,26 @@ bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const {
|
|||||||
return td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast;
|
return td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const {
|
||||||
|
if (d == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (d->dialog_id.get_type() != DialogType::SecretChat) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d->order != DEFAULT_ORDER || d->messages != nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto state = td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id());
|
||||||
|
if (state != SecretChatState::Closed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int32 MessagesManager::get_message_schedule_date(const Message *m) {
|
int32 MessagesManager::get_message_schedule_date(const Message *m) {
|
||||||
if (!m->message_id.is_scheduled()) {
|
if (!m->message_id.is_scheduled()) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -28934,7 +28957,7 @@ void MessagesManager::send_dialog_action(DialogId dialog_id, MessageId top_threa
|
|||||||
|
|
||||||
auto &query_ref = set_typing_query_[dialog_id];
|
auto &query_ref = set_typing_query_[dialog_id];
|
||||||
if (!query_ref.empty() && !td_->auth_manager_->is_bot()) {
|
if (!query_ref.empty() && !td_->auth_manager_->is_bot()) {
|
||||||
LOG(INFO) << "Cancel previous set typing query";
|
LOG(INFO) << "Cancel previous send chat action query";
|
||||||
cancel_query(query_ref);
|
cancel_query(query_ref);
|
||||||
}
|
}
|
||||||
query_ref = td_->create_handler<SetTypingQuery>(std::move(promise))
|
query_ref = td_->create_handler<SetTypingQuery>(std::move(promise))
|
||||||
@ -28942,7 +28965,7 @@ void MessagesManager::send_dialog_action(DialogId dialog_id, MessageId top_threa
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
|
void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
|
||||||
LOG(INFO) << "Receive send_dialog_action timeout in " << dialog_id;
|
LOG(INFO) << "Receive send_chat_action timeout in " << dialog_id;
|
||||||
Dialog *d = get_dialog(dialog_id);
|
Dialog *d = get_dialog(dialog_id);
|
||||||
CHECK(d != nullptr);
|
CHECK(d != nullptr);
|
||||||
|
|
||||||
@ -30778,7 +30801,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
|
|||||||
if (queue_id & 1) {
|
if (queue_id & 1) {
|
||||||
LOG(INFO) << "Add " << message_id << " from " << source << " to queue " << queue_id;
|
LOG(INFO) << "Add " << message_id << " from " << source << " to queue " << queue_id;
|
||||||
yet_unsent_media_queues_[queue_id][message_id.get()]; // reserve place for promise
|
yet_unsent_media_queues_[queue_id][message_id.get()]; // reserve place for promise
|
||||||
if (!td_->auth_manager_->is_bot() && !is_dialog_action_unneeded(dialog_id)) {
|
if (!td_->auth_manager_->is_bot()) {
|
||||||
pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 1.0);
|
pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32890,11 +32913,8 @@ void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dialog_type == DialogType::SecretChat) {
|
if (dialog_type == DialogType::SecretChat) {
|
||||||
auto secret_chat_id = d->dialog_id.get_secret_chat_id();
|
auto date = td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id());
|
||||||
auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id);
|
if (date != 0 && !is_deleted_secret_chat(d)) {
|
||||||
auto state = td_->contacts_manager_->get_secret_chat_state(secret_chat_id);
|
|
||||||
// do not return removed from the chat list closed secret chats
|
|
||||||
if (date != 0 && (d->order != DEFAULT_ORDER || state != SecretChatState::Closed || d->messages != nullptr)) {
|
|
||||||
LOG(INFO) << "Creation of secret chat at " << date << " found";
|
LOG(INFO) << "Creation of secret chat at " << date << " found";
|
||||||
int64 creation_order = get_dialog_order(MessageId(), date);
|
int64 creation_order = get_dialog_order(MessageId(), date);
|
||||||
if (creation_order > new_order) {
|
if (creation_order > new_order) {
|
||||||
@ -35195,6 +35215,7 @@ void MessagesManager::save_recently_found_dialogs() {
|
|||||||
}
|
}
|
||||||
value += to_string(dialog_id.get());
|
value += to_string(dialog_id.get());
|
||||||
}
|
}
|
||||||
|
LOG(DEBUG) << "Save recently found chats " << value;
|
||||||
G()->td_db()->get_binlog_pmc()->set("recently_found_dialog_usernames_and_ids", value);
|
G()->td_db()->get_binlog_pmc()->set("recently_found_dialog_usernames_and_ids", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35213,6 +35234,7 @@ bool MessagesManager::load_recently_found_dialogs(Promise<Unit> &promise) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Loaded recently found chats " << found_dialogs_str;
|
||||||
auto found_dialogs = full_split(found_dialogs_str, ',');
|
auto found_dialogs = full_split(found_dialogs_str, ',');
|
||||||
if (recently_found_dialogs_loaded_ == 1 && resolve_recently_found_dialogs_multipromise_.promise_count() == 0) {
|
if (recently_found_dialogs_loaded_ == 1 && resolve_recently_found_dialogs_multipromise_.promise_count() == 0) {
|
||||||
// queries was sent and have already been finished
|
// queries was sent and have already been finished
|
||||||
@ -35318,7 +35340,7 @@ bool MessagesManager::add_recently_found_dialog_internal(DialogId dialog_id) {
|
|||||||
// TODO create function
|
// TODO create function
|
||||||
auto it = std::find(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.end(), dialog_id);
|
auto it = std::find(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.end(), dialog_id);
|
||||||
if (it == recently_found_dialog_ids_.end()) {
|
if (it == recently_found_dialog_ids_.end()) {
|
||||||
if (narrow_cast<int32>(recently_found_dialog_ids_.size()) == MAX_RECENT_FOUND_DIALOGS) {
|
if (narrow_cast<int32>(recently_found_dialog_ids_.size()) == MAX_RECENTLY_FOUND_DIALOGS) {
|
||||||
CHECK(!recently_found_dialog_ids_.empty());
|
CHECK(!recently_found_dialog_ids_.empty());
|
||||||
recently_found_dialog_ids_.back() = dialog_id;
|
recently_found_dialog_ids_.back() = dialog_id;
|
||||||
} else {
|
} else {
|
||||||
@ -35335,6 +35357,48 @@ bool MessagesManager::remove_recently_found_dialog_internal(DialogId dialog_id)
|
|||||||
return td::remove(recently_found_dialog_ids_, dialog_id);
|
return td::remove(recently_found_dialog_ids_, dialog_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessagesManager::update_recently_found_dialogs() {
|
||||||
|
vector<DialogId> dialog_ids;
|
||||||
|
for (auto dialog_id : recently_found_dialog_ids_) {
|
||||||
|
const Dialog *d = get_dialog(dialog_id);
|
||||||
|
if (d == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (dialog_id.get_type()) {
|
||||||
|
case DialogType::User:
|
||||||
|
// always keep
|
||||||
|
break;
|
||||||
|
case DialogType::Chat: {
|
||||||
|
auto channel_id = td_->contacts_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id());
|
||||||
|
if (channel_id.is_valid() && get_dialog(DialogId(channel_id)) != nullptr) {
|
||||||
|
dialog_id = DialogId(channel_id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DialogType::Channel:
|
||||||
|
// always keep
|
||||||
|
break;
|
||||||
|
case DialogType::SecretChat:
|
||||||
|
if (is_deleted_secret_chat(d)) {
|
||||||
|
dialog_id = DialogId();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DialogType::None:
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (dialog_id.is_valid()) {
|
||||||
|
dialog_ids.push_back(dialog_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialog_ids != recently_found_dialog_ids_) {
|
||||||
|
recently_found_dialog_ids_ = std::move(dialog_ids);
|
||||||
|
save_recently_found_dialogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MessagesManager::suffix_load_loop(Dialog *d) {
|
void MessagesManager::suffix_load_loop(Dialog *d) {
|
||||||
if (d->suffix_load_has_query_) {
|
if (d->suffix_load_has_query_) {
|
||||||
return;
|
return;
|
||||||
|
@ -1644,7 +1644,7 @@ class MessagesManager : public Actor {
|
|||||||
static constexpr int32 MIN_CHANNEL_DIFFERENCE = 10;
|
static constexpr int32 MIN_CHANNEL_DIFFERENCE = 10;
|
||||||
static constexpr int32 MAX_CHANNEL_DIFFERENCE = 100;
|
static constexpr int32 MAX_CHANNEL_DIFFERENCE = 100;
|
||||||
static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000; // server side limit
|
static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000; // server side limit
|
||||||
static constexpr int32 MAX_RECENT_FOUND_DIALOGS = 20; // some reasonable value
|
static constexpr int32 MAX_RECENTLY_FOUND_DIALOGS = 30; // some reasonable value
|
||||||
static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title
|
static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title
|
||||||
static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description
|
static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description
|
||||||
static constexpr size_t MAX_DIALOG_FILTER_TITLE_LENGTH = 12; // server side limit for dialog filter title
|
static constexpr size_t MAX_DIALOG_FILTER_TITLE_LENGTH = 12; // server side limit for dialog filter title
|
||||||
@ -2831,7 +2831,10 @@ class MessagesManager : public Actor {
|
|||||||
|
|
||||||
bool remove_recently_found_dialog_internal(DialogId dialog_id);
|
bool remove_recently_found_dialog_internal(DialogId dialog_id);
|
||||||
|
|
||||||
|
void update_recently_found_dialogs();
|
||||||
|
|
||||||
void save_recently_found_dialogs();
|
void save_recently_found_dialogs();
|
||||||
|
|
||||||
bool load_recently_found_dialogs(Promise<Unit> &promise);
|
bool load_recently_found_dialogs(Promise<Unit> &promise);
|
||||||
|
|
||||||
void reget_message_from_server_if_needed(DialogId dialog_id, const Message *m);
|
void reget_message_from_server_if_needed(DialogId dialog_id, const Message *m);
|
||||||
@ -2916,6 +2919,8 @@ class MessagesManager : public Actor {
|
|||||||
|
|
||||||
bool is_broadcast_channel(DialogId dialog_id) const;
|
bool is_broadcast_channel(DialogId dialog_id) const;
|
||||||
|
|
||||||
|
bool is_deleted_secret_chat(const Dialog *d) const;
|
||||||
|
|
||||||
static int32 get_message_schedule_date(const Message *m);
|
static int32 get_message_schedule_date(const Message *m);
|
||||||
|
|
||||||
int32 recently_found_dialogs_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded
|
int32 recently_found_dialogs_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded
|
||||||
|
@ -581,13 +581,16 @@ static vector<td_api::object_ptr<td_api::photoSize>> get_photo_sizes_object(File
|
|||||||
auto sizes = transform(photo_sizes, [file_manager](const PhotoSize &photo_size) {
|
auto sizes = transform(photo_sizes, [file_manager](const PhotoSize &photo_size) {
|
||||||
return get_photo_size_object(file_manager, &photo_size);
|
return get_photo_size_object(file_manager, &photo_size);
|
||||||
});
|
});
|
||||||
std::sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) {
|
std::stable_sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) {
|
||||||
if (lhs->photo_->expected_size_ != rhs->photo_->expected_size_) {
|
if (lhs->photo_->expected_size_ != rhs->photo_->expected_size_) {
|
||||||
return lhs->photo_->expected_size_ < rhs->photo_->expected_size_;
|
return lhs->photo_->expected_size_ < rhs->photo_->expected_size_;
|
||||||
}
|
}
|
||||||
return static_cast<uint32>(lhs->width_) * static_cast<uint32>(lhs->height_) <
|
return static_cast<uint32>(lhs->width_) * static_cast<uint32>(lhs->height_) <
|
||||||
static_cast<uint32>(rhs->width_) * static_cast<uint32>(rhs->height_);
|
static_cast<uint32>(rhs->width_) * static_cast<uint32>(rhs->height_);
|
||||||
});
|
});
|
||||||
|
td::remove_if(sizes, [](const auto &size) {
|
||||||
|
return !size->photo_->local_->can_be_downloaded_ && !size->photo_->local_->is_downloading_completed_;
|
||||||
|
});
|
||||||
return sizes;
|
return sizes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "td/utils/buffer.h"
|
#include "td/utils/buffer.h"
|
||||||
#include "td/utils/common.h"
|
#include "td/utils/common.h"
|
||||||
#include "td/utils/crypto.h"
|
#include "td/utils/crypto.h"
|
||||||
|
#include "td/utils/ExitGuard.h"
|
||||||
#include "td/utils/FileLog.h"
|
#include "td/utils/FileLog.h"
|
||||||
#include "td/utils/format.h"
|
#include "td/utils/format.h"
|
||||||
#include "td/utils/JsonBuilder.h"
|
#include "td/utils/JsonBuilder.h"
|
||||||
@ -4450,6 +4451,8 @@ void main(int argc, char **argv) {
|
|||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
Debug::DeathHandler dh;
|
Debug::DeathHandler dh;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ExitGuard exit_guard;
|
||||||
ignore_signal(SignalType::HangUp).ensure();
|
ignore_signal(SignalType::HangUp).ensure();
|
||||||
ignore_signal(SignalType::Pipe).ensure();
|
ignore_signal(SignalType::Pipe).ensure();
|
||||||
set_signal_handler(SignalType::Error, fail_signal).ensure();
|
set_signal_handler(SignalType::Error, fail_signal).ensure();
|
||||||
|
@ -85,7 +85,10 @@ std::unique_ptr<JNIEnv, JvmThreadDetacher> get_jni_env(JavaVM *java_vm, jint jni
|
|||||||
#else
|
#else
|
||||||
auto p_env = &env;
|
auto p_env = &env;
|
||||||
#endif
|
#endif
|
||||||
java_vm->AttachCurrentThread(p_env, nullptr);
|
if (java_vm->AttachCurrentThread(p_env, nullptr) != JNI_OK) {
|
||||||
|
java_vm = nullptr;
|
||||||
|
env = nullptr;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
java_vm = nullptr;
|
java_vm = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ void Scheduler::ServiceActor::tear_down() {
|
|||||||
/*** SchedlerGuard ***/
|
/*** SchedlerGuard ***/
|
||||||
SchedulerGuard::SchedulerGuard(Scheduler *scheduler, bool lock) : scheduler_(scheduler) {
|
SchedulerGuard::SchedulerGuard(Scheduler *scheduler, bool lock) : scheduler_(scheduler) {
|
||||||
if (lock) {
|
if (lock) {
|
||||||
|
// the next check can fail if OS killed the scheduler's thread without releasing the guard
|
||||||
CHECK(!scheduler_->has_guard_);
|
CHECK(!scheduler_->has_guard_);
|
||||||
scheduler_->has_guard_ = true;
|
scheduler_->has_guard_ = true;
|
||||||
}
|
}
|
||||||
|
@ -318,6 +318,12 @@ target_include_directories(tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOUR
|
|||||||
if (OPENSSL_FOUND)
|
if (OPENSSL_FOUND)
|
||||||
target_link_libraries(tdutils PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
target_link_libraries(tdutils PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
|
||||||
target_include_directories(tdutils SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
|
target_include_directories(tdutils SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
|
||||||
|
|
||||||
|
if (MINGW)
|
||||||
|
target_link_libraries(tdutils PRIVATE ws2_32 mswsock crypt32)
|
||||||
|
else()
|
||||||
|
target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Crypt32)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ZLIB_FOUND)
|
if (ZLIB_FOUND)
|
||||||
|
@ -6,8 +6,15 @@
|
|||||||
//
|
//
|
||||||
#include "td/utils/ExitGuard.h"
|
#include "td/utils/ExitGuard.h"
|
||||||
|
|
||||||
|
#include "td/utils/logging.h"
|
||||||
|
|
||||||
namespace td {
|
namespace td {
|
||||||
|
|
||||||
std::atomic<bool> ExitGuard::is_exited_{false};
|
std::atomic<bool> ExitGuard::is_exited_{false};
|
||||||
|
|
||||||
|
ExitGuard::~ExitGuard() {
|
||||||
|
is_exited_.store(true, std::memory_order_relaxed);
|
||||||
|
set_verbosity_level(VERBOSITY_NAME(FATAL));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -17,9 +17,7 @@ class ExitGuard {
|
|||||||
ExitGuard &operator=(ExitGuard &&) = delete;
|
ExitGuard &operator=(ExitGuard &&) = delete;
|
||||||
ExitGuard(const ExitGuard &) = delete;
|
ExitGuard(const ExitGuard &) = delete;
|
||||||
ExitGuard &operator=(const ExitGuard &) = delete;
|
ExitGuard &operator=(const ExitGuard &) = delete;
|
||||||
~ExitGuard() {
|
~ExitGuard();
|
||||||
is_exited_.store(true, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_exited() {
|
static bool is_exited() {
|
||||||
return is_exited_.load(std::memory_order_relaxed);
|
return is_exited_.load(std::memory_order_relaxed);
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include "td/utils/Slice.h"
|
#include "td/utils/Slice.h"
|
||||||
#include "td/utils/StringBuilder.h"
|
#include "td/utils/StringBuilder.h"
|
||||||
|
|
||||||
#include <set>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -335,9 +334,5 @@ template <class T>
|
|||||||
StringBuilder &operator<<(StringBuilder &stream, const vector<T> &vec) {
|
StringBuilder &operator<<(StringBuilder &stream, const vector<T> &vec) {
|
||||||
return stream << format::as_array(vec);
|
return stream << format::as_array(vec);
|
||||||
}
|
}
|
||||||
template <class T>
|
|
||||||
StringBuilder &operator<<(StringBuilder &stream, const std::set<T> &vec) {
|
|
||||||
return stream << format::as_array(vec);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
#include "td/utils/logging.h"
|
#include "td/utils/logging.h"
|
||||||
|
|
||||||
|
#include "td/utils/ExitGuard.h"
|
||||||
#include "td/utils/port/Clocks.h"
|
#include "td/utils/port/Clocks.h"
|
||||||
#include "td/utils/port/StdStreams.h"
|
#include "td/utils/port/StdStreams.h"
|
||||||
#include "td/utils/port/thread_local.h"
|
#include "td/utils/port/thread_local.h"
|
||||||
@ -48,6 +49,9 @@ Logger::Logger(LogInterface &log, const LogOptions &options, int log_level, Slic
|
|||||||
if (!options_.add_info) {
|
if (!options_.add_info) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (ExitGuard::is_exited()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// log level
|
// log level
|
||||||
sb_ << '[';
|
sb_ << '[';
|
||||||
@ -109,6 +113,9 @@ Logger::Logger(LogInterface &log, const LogOptions &options, int log_level, Slic
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger::~Logger() {
|
Logger::~Logger() {
|
||||||
|
if (ExitGuard::is_exited()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (options_.fix_newlines) {
|
if (options_.fix_newlines) {
|
||||||
sb_ << '\n';
|
sb_ << '\n';
|
||||||
auto slice = as_cslice();
|
auto slice = as_cslice();
|
||||||
@ -157,7 +164,7 @@ TsCerr &TsCerr::operator<<(Slice slice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TsCerr::enterCritical() {
|
void TsCerr::enterCritical() {
|
||||||
while (lock_.test_and_set(std::memory_order_acquire)) {
|
while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) {
|
||||||
// spin
|
// spin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,6 +174,16 @@ void TsCerr::exitCritical() {
|
|||||||
}
|
}
|
||||||
TsCerr::Lock TsCerr::lock_ = ATOMIC_FLAG_INIT;
|
TsCerr::Lock TsCerr::lock_ = ATOMIC_FLAG_INIT;
|
||||||
|
|
||||||
|
void TsLog::enter_critical() {
|
||||||
|
while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) {
|
||||||
|
// spin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TsLog::exit_critical() {
|
||||||
|
lock_.clear(std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
class DefaultLog : public LogInterface {
|
class DefaultLog : public LogInterface {
|
||||||
public:
|
public:
|
||||||
void append(CSlice slice, int log_level) override {
|
void append(CSlice slice, int log_level) override {
|
||||||
@ -304,4 +321,6 @@ ScopedDisableLog::~ScopedDisableLog() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ExitGuard exit_guard;
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -321,14 +321,8 @@ class TsLog : public LogInterface {
|
|||||||
private:
|
private:
|
||||||
LogInterface *log_ = nullptr;
|
LogInterface *log_ = nullptr;
|
||||||
std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
|
std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
|
||||||
void enter_critical() {
|
void enter_critical();
|
||||||
while (lock_.test_and_set(std::memory_order_acquire)) {
|
void exit_critical();
|
||||||
// spin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void exit_critical() {
|
|
||||||
lock_.clear(std::memory_order_release);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "td/utils/common.h"
|
#include "td/utils/common.h"
|
||||||
|
#include "td/utils/ExitGuard.h"
|
||||||
#include "td/utils/logging.h"
|
#include "td/utils/logging.h"
|
||||||
#include "td/utils/misc.h"
|
#include "td/utils/misc.h"
|
||||||
#include "td/utils/port/detail/PollableFd.h"
|
#include "td/utils/port/detail/PollableFd.h"
|
||||||
@ -356,6 +357,7 @@ Result<size_t> FileFd::pread(MutableSlice slice, int64 offset) const {
|
|||||||
|
|
||||||
static std::mutex in_process_lock_mutex;
|
static std::mutex in_process_lock_mutex;
|
||||||
static std::unordered_set<string> locked_files;
|
static std::unordered_set<string> locked_files;
|
||||||
|
static ExitGuard exit_guard;
|
||||||
|
|
||||||
static Status create_local_lock(const string &path, int32 &max_tries) {
|
static Status create_local_lock(const string &path, int32 &max_tries) {
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -469,12 +471,13 @@ Status FileFd::lock(const LockFlags flags, const string &path, int32 max_tries)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileFd::remove_local_lock(const string &path) {
|
void FileFd::remove_local_lock(const string &path) {
|
||||||
if (!path.empty()) {
|
if (path.empty() || ExitGuard::is_exited()) {
|
||||||
VLOG(fd) << "Unlock file \"" << path << '"';
|
return;
|
||||||
std::unique_lock<std::mutex> lock(in_process_lock_mutex);
|
|
||||||
auto erased_count = locked_files.erase(path);
|
|
||||||
CHECK(erased_count > 0);
|
|
||||||
}
|
}
|
||||||
|
VLOG(fd) << "Unlock file \"" << path << '"';
|
||||||
|
std::unique_lock<std::mutex> lock(in_process_lock_mutex);
|
||||||
|
auto erased_count = locked_files.erase(path);
|
||||||
|
CHECK(erased_count > 0 || ExitGuard::is_exited());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileFd::close() {
|
void FileFd::close() {
|
||||||
|
@ -4,11 +4,17 @@
|
|||||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
// 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)
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
//
|
//
|
||||||
#include "td/utils/tests.h"
|
#include "td/utils/common.h"
|
||||||
#include "td/utils/misc.h"
|
#include "td/utils/misc.h"
|
||||||
|
#include "td/utils/Slice.h"
|
||||||
|
#include "td/utils/Status.h"
|
||||||
|
#include "td/utils/tests.h"
|
||||||
#include "td/utils/utf8.h"
|
#include "td/utils/utf8.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace td {
|
namespace td {
|
||||||
|
|
||||||
class RangeSet {
|
class RangeSet {
|
||||||
template <class T>
|
template <class T>
|
||||||
static auto find(T &ranges, int64 begin) {
|
static auto find(T &ranges, int64 begin) {
|
||||||
@ -28,17 +34,14 @@ class RangeSet {
|
|||||||
int64 end;
|
int64 end;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr int64 BitSize = 1024;
|
static constexpr int64 BIT_SIZE = 1024;
|
||||||
static constexpr int64 MaxPartSize = 16 * 1024 * 1024;
|
|
||||||
|
|
||||||
RangeSet() = default;
|
|
||||||
|
|
||||||
static RangeSet create_one_range(int64 end, int64 begin = 0) {
|
static RangeSet create_one_range(int64 end, int64 begin = 0) {
|
||||||
RangeSet res;
|
RangeSet res;
|
||||||
res.ranges_.push_back({begin, end});
|
res.ranges_.push_back({begin, end});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
static td::Result<RangeSet> decode(CSlice data) {
|
static Result<RangeSet> decode(CSlice data) {
|
||||||
if (!check_utf8(data)) {
|
if (!check_utf8(data)) {
|
||||||
return Status::Error("Invalid encoding");
|
return Status::Error("Invalid encoding");
|
||||||
}
|
}
|
||||||
@ -50,7 +53,7 @@ class RangeSet {
|
|||||||
begin = next_utf8_unsafe(begin, &size, "RangeSet");
|
begin = next_utf8_unsafe(begin, &size, "RangeSet");
|
||||||
|
|
||||||
if (!is_empty && size != 0) {
|
if (!is_empty && size != 0) {
|
||||||
res.ranges_.push_back({curr * BitSize, (curr + size) * BitSize});
|
res.ranges_.push_back({curr * BIT_SIZE, (curr + size) * BIT_SIZE});
|
||||||
}
|
}
|
||||||
curr += size;
|
curr += size;
|
||||||
is_empty = !is_empty;
|
is_empty = !is_empty;
|
||||||
@ -58,12 +61,12 @@ class RangeSet {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string encode(int64 prefix_size = -1) const {
|
string encode(int64 prefix_size = -1) const {
|
||||||
std::vector<uint32> sizes;
|
vector<uint32> sizes;
|
||||||
uint32 all_end = 0;
|
uint32 all_end = 0;
|
||||||
|
|
||||||
if (prefix_size != -1) {
|
if (prefix_size != -1) {
|
||||||
prefix_size = (prefix_size + BitSize - 1) / BitSize * BitSize;
|
prefix_size = (prefix_size + BIT_SIZE - 1) / BIT_SIZE * BIT_SIZE;
|
||||||
}
|
}
|
||||||
for (auto it : ranges_) {
|
for (auto it : ranges_) {
|
||||||
if (prefix_size != -1 && it.begin >= prefix_size) {
|
if (prefix_size != -1 && it.begin >= prefix_size) {
|
||||||
@ -73,10 +76,10 @@ class RangeSet {
|
|||||||
it.end = prefix_size;
|
it.end = prefix_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK(it.begin % BitSize == 0);
|
CHECK(it.begin % BIT_SIZE == 0);
|
||||||
CHECK(it.end % BitSize == 0);
|
CHECK(it.end % BIT_SIZE == 0);
|
||||||
uint32 begin = narrow_cast<uint32>(it.begin / BitSize);
|
uint32 begin = narrow_cast<uint32>(it.begin / BIT_SIZE);
|
||||||
uint32 end = narrow_cast<uint32>(it.end / BitSize);
|
uint32 end = narrow_cast<uint32>(it.end / BIT_SIZE);
|
||||||
if (sizes.empty()) {
|
if (sizes.empty()) {
|
||||||
if (begin != 0) {
|
if (begin != 0) {
|
||||||
sizes.push_back(0);
|
sizes.push_back(0);
|
||||||
@ -89,7 +92,7 @@ class RangeSet {
|
|||||||
all_end = end;
|
all_end = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string res;
|
string res;
|
||||||
for (auto c : sizes) {
|
for (auto c : sizes) {
|
||||||
append_utf8_character(res, c);
|
append_utf8_character(res, c);
|
||||||
}
|
}
|
||||||
@ -149,8 +152,8 @@ class RangeSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set(int64 begin, int64 end) {
|
void set(int64 begin, int64 end) {
|
||||||
CHECK(begin % BitSize == 0);
|
CHECK(begin % BIT_SIZE == 0);
|
||||||
CHECK(end % BitSize == 0);
|
CHECK(end % BIT_SIZE == 0);
|
||||||
// 1. skip all with r.end < begin
|
// 1. skip all with r.end < begin
|
||||||
auto it_begin = find(begin);
|
auto it_begin = find(begin);
|
||||||
|
|
||||||
@ -162,16 +165,16 @@ class RangeSet {
|
|||||||
if (it_begin == it_end) {
|
if (it_begin == it_end) {
|
||||||
ranges_.insert(it_begin, Range{begin, end});
|
ranges_.insert(it_begin, Range{begin, end});
|
||||||
} else {
|
} else {
|
||||||
begin = std::min(begin, it_begin->begin);
|
begin = td::min(begin, it_begin->begin);
|
||||||
--it_end;
|
--it_end;
|
||||||
end = std::max(end, it_end->end);
|
end = td::max(end, it_end->end);
|
||||||
*it_end = Range{begin, end};
|
*it_end = Range{begin, end};
|
||||||
ranges_.erase(it_begin, it_end);
|
ranges_.erase(it_begin, it_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int32> as_vector(int32 part_size) const {
|
vector<int32> as_vector(int32 part_size) const {
|
||||||
std::vector<int32> res;
|
vector<int32> res;
|
||||||
for (auto it : ranges_) {
|
for (auto it : ranges_) {
|
||||||
auto begin = narrow_cast<int32>((it.begin + part_size - 1) / part_size);
|
auto begin = narrow_cast<int32>((it.begin + part_size - 1) / part_size);
|
||||||
auto end = narrow_cast<int32>(it.end / part_size);
|
auto end = narrow_cast<int32>(it.end / part_size);
|
||||||
@ -183,13 +186,12 @@ class RangeSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Range> ranges_;
|
vector<Range> ranges_;
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(Bitmask, simple) {
|
TEST(Bitmask, simple) {
|
||||||
auto validate_encoding = [](auto &rs) {
|
auto validate_encoding = [](auto &rs) {
|
||||||
auto str = rs.encode();
|
auto str = rs.encode();
|
||||||
LOG(ERROR) << str.size();
|
|
||||||
RangeSet rs2 = RangeSet::decode(str).move_as_ok();
|
RangeSet rs2 = RangeSet::decode(str).move_as_ok();
|
||||||
auto str2 = rs2.encode();
|
auto str2 = rs2.encode();
|
||||||
rs = std::move(rs2);
|
rs = std::move(rs2);
|
||||||
@ -238,9 +240,10 @@ TEST(Bitmask, simple) {
|
|||||||
ASSERT_EQ(8, get(3));
|
ASSERT_EQ(8, get(3));
|
||||||
|
|
||||||
ASSERT_EQ(10, rs.get_ready_prefix_size(S * 3, S * 3 + 10));
|
ASSERT_EQ(10, rs.get_ready_prefix_size(S * 3, S * 3 + 10));
|
||||||
ASSERT_TRUE(!rs.is_ready(S*11, S *12));
|
ASSERT_TRUE(!rs.is_ready(S * 11, S * 12));
|
||||||
ASSERT_EQ(3, rs.get_ready_parts(2, S * 2));
|
ASSERT_EQ(3, rs.get_ready_parts(2, S * 2));
|
||||||
ASSERT_EQ(std::vector<int32>({2, 3, 4, 7}), rs.as_vector(S * 2) );
|
ASSERT_EQ(vector<int32>({2, 3, 4, 7}), rs.as_vector(S * 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -26,13 +26,13 @@ set(TESTS_MAIN
|
|||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(all_tests STATIC ${TD_TEST_SOURCE})
|
#add_library(all_tests STATIC ${TD_TEST_SOURCE})
|
||||||
target_include_directories(all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
#target_include_directories(all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||||
target_link_libraries(all_tests PRIVATE tdcore tdclient)
|
#target_link_libraries(all_tests PRIVATE tdcore tdclient)
|
||||||
|
|
||||||
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
|
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
|
||||||
#Tests
|
#Tests
|
||||||
add_executable(test-tdutils ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE})
|
add_executable(test-tdutils EXCLUDE_FROM_ALL ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE})
|
||||||
add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
|
add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
|
||||||
if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN AND NOT (CMAKE_HOST_SYSTEM_NAME MATCHES "OpenBSD"))
|
if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN AND NOT (CMAKE_HOST_SYSTEM_NAME MATCHES "OpenBSD"))
|
||||||
target_compile_options(test-tdutils PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
|
target_compile_options(test-tdutils PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
|
||||||
|
Loading…
Reference in New Issue
Block a user