From 7d6f14db10e49c359be541cfc2a961bae4d612a5 Mon Sep 17 00:00:00 2001 From: levlam Date: Mon, 5 Oct 2020 16:08:07 +0300 Subject: [PATCH] Add new JSON interface. GitOrigin-RevId: aaf756de59e72f949c1150d99e1277047f25fac9 --- td/telegram/ClientJson.cpp | 72 ++++++++++++++++++++++++++++----- td/telegram/ClientJson.h | 8 ++++ td/telegram/td_json_client.cpp | 20 ++++++++-- td/telegram/td_json_client.h | 73 +++++++++++++++++++++++++++++++--- 4 files changed, 155 insertions(+), 18 deletions(-) diff --git a/td/telegram/ClientJson.cpp b/td/telegram/ClientJson.cpp index 71d424252..d5beab12d 100644 --- a/td/telegram/ClientJson.cpp +++ b/td/telegram/ClientJson.cpp @@ -13,6 +13,7 @@ #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/port/thread_local.h" +#include "td/utils/StringBuilder.h" #include @@ -52,23 +53,29 @@ static std::pair, string> to_request(Slice return std::make_pair(std::move(func), std::move(extra)); } -static std::string from_response(const td_api::Object &object, const string &extra) { +static string from_response(const td_api::Object &object, const string &extra, int client_id) { auto str = json_encode(ToJson(object)); CHECK(!str.empty() && str.back() == '}'); + str.reserve(str.size() + (extra.empty() ? 0 : 10 + extra.size()) + (client_id == 0 ? 0 : 14 + 10)); if (!extra.empty()) { str.pop_back(); - str.reserve(str.size() + 11 + extra.size()); str += ",\"@extra\":"; str += extra; str += '}'; } + if (client_id != 0) { + str.pop_back(); + str += ",\"@client_id\":"; + str += to_string(client_id); + str += '}'; + } return str; } -static TD_THREAD_LOCAL std::string *current_output; +static TD_THREAD_LOCAL string *current_output; -static const char *store_string(std::string str) { - init_thread_local(current_output); +static const char *store_string(string str) { + init_thread_local(current_output); *current_output = std::move(str); return current_output->c_str(); } @@ -85,11 +92,11 @@ void ClientJson::send(Slice request) { const char *ClientJson::receive(double timeout) { auto response = client_.receive(timeout); - if (!response.object) { + if (response.object == nullptr) { return nullptr; } - std::string extra; + string extra; if (response.id != 0) { std::lock_guard guard(mutex_); auto it = extra_.find(response.id); @@ -98,13 +105,60 @@ const char *ClientJson::receive(double timeout) { extra_.erase(it); } } - return store_string(from_response(*response.object, extra)); + return store_string(from_response(*response.object, extra, 0)); } const char *ClientJson::execute(Slice request) { auto parsed_request = to_request(request); return store_string(from_response(*Client::execute(Client::Request{0, std::move(parsed_request.first)}).object, - parsed_request.second)); + parsed_request.second, 0)); +} + +static ClientManager *get_manager() { + static ClientManager client_manager; + return &client_manager; +} + +static std::mutex extra_mutex; +static std::unordered_map extra; +static std::atomic extra_id{1}; + +int td_json_create_client() { + return static_cast(get_manager()->create_client()); +} + +void td_json_send(int client_id, Slice request) { + auto parsed_request = to_request(request); + auto request_id = extra_id.fetch_add(1, std::memory_order_relaxed); + if (!parsed_request.second.empty()) { + std::lock_guard guard(extra_mutex); + extra[request_id] = std::move(parsed_request.second); + } + get_manager()->send(client_id, request_id, std::move(parsed_request.first)); +} + +const char *td_json_receive(double timeout) { + auto response = get_manager()->receive(timeout); + if (!response.object) { + return nullptr; + } + + string extra_str; + if (response.request_id != 0) { + std::lock_guard guard(extra_mutex); + auto it = extra.find(response.request_id); + if (it != extra.end()) { + extra_str = std::move(it->second); + extra.erase(it); + } + } + return store_string(from_response(*response.object, extra_str, response.client_id)); +} + +const char *td_json_execute(Slice request) { + auto parsed_request = to_request(request); + return store_string( + from_response(*ClientManager::execute(std::move(parsed_request.first)), parsed_request.second, 0)); } } // namespace td diff --git a/td/telegram/ClientJson.h b/td/telegram/ClientJson.h index 62df510b7..9421b1d33 100644 --- a/td/telegram/ClientJson.h +++ b/td/telegram/ClientJson.h @@ -33,4 +33,12 @@ class ClientJson final { std::atomic extra_id_{1}; }; +int td_json_create_client(); + +void td_json_send(int client_id, Slice request); + +const char *td_json_receive(double timeout); + +const char *td_json_execute(Slice request); + } // namespace td diff --git a/td/telegram/td_json_client.cpp b/td/telegram/td_json_client.cpp index 33d29d4b0..3ddb5945c 100644 --- a/td/telegram/td_json_client.cpp +++ b/td/telegram/td_json_client.cpp @@ -10,10 +10,6 @@ #include "td/utils/Slice.h" -extern "C" int td_json_client_square(int x, const char *str) { - return x * x; -} - void *td_json_client_create() { return new td::ClientJson(); } @@ -33,3 +29,19 @@ const char *td_json_client_receive(void *client, double timeout) { const char *td_json_client_execute(void *client, const char *request) { return td::ClientJson::execute(td::Slice(request == nullptr ? "" : request)); } + +int td_create_client() { + return td::td_json_create_client(); +} + +void td_send(int client_id, const char *request) { + td::td_json_send(client_id, td::Slice(request == nullptr ? "" : request)); +} + +const char *td_receive(double timeout) { + return td::td_json_receive(timeout); +} + +const char *td_execute(const char *request) { + return td::td_json_execute(td::Slice(request == nullptr ? "" : request)); +} diff --git a/td/telegram/td_json_client.h b/td/telegram/td_json_client.h index d713c532c..918e40522 100644 --- a/td/telegram/td_json_client.h +++ b/td/telegram/td_json_client.h @@ -21,11 +21,11 @@ * The main TDLib interface is asynchronous. To match requests with a corresponding response a field "@extra" can * be added to the request object. The corresponding response will have an "@extra" field with exactly the same value. * - * A TDLib client instance should be created through td_json_client_create. + * A TDLib client instance can be created through td_json_client_create. * Requests then can be sent using td_json_client_send from any thread. * New updates and request responses can be received through td_json_client_receive from any thread. This function - * shouldn't be called simultaneously from two different threads. Also note that all updates and request responses - * should be applied in the order they were received to ensure consistency. + * must not be called simultaneously from two different threads. Also note that all updates and request responses + * must be applied in the order they were received to ensure consistency. * Given this information, it's advisable to call this function from a dedicated thread. * Some service TDLib requests can be executed synchronously from any thread by using td_json_client_execute. * The TDLib client instance can be destroyed via td_json_client_destroy. @@ -68,7 +68,7 @@ TDJSON_EXPORT void td_json_client_send(void *client, const char *request); /** * Receives incoming updates and request responses from the TDLib client. May be called from any thread, but - * shouldn't be called simultaneously from two different threads. + * must not be called simultaneously from two different threads. * Returned pointer will be deallocated by TDLib during next call to td_json_client_receive or td_json_client_execute * in the same thread, so it can't be used after that. * \param[in] client The client. @@ -89,11 +89,74 @@ TDJSON_EXPORT const char *td_json_client_receive(void *client, double timeout); TDJSON_EXPORT const char *td_json_client_execute(void *client, const char *request); /** - * Destroys the TDLib client instance. After this is called the client instance shouldn't be used anymore. + * Destroys the TDLib client instance. After this is called the client instance must not be used anymore. * \param[in] client The client. */ TDJSON_EXPORT void td_json_client_destroy(void *client); +/* + * New TDLib JSON interface. + * + * The main TDLib interface is asynchronous. To match requests with a corresponding response a field "@extra" can + * be added to the request object. The corresponding response will have an "@extra" field with exactly the same value. + * Each returned object will have an "@client_id" field, containing and identifier of the client for which + * a response or an update is received. + * + * A TDLib client instance can be created through td_create_client. + * Requests then can be sent using td_send from any thread and the received client identifier. + * New updates and request responses can be received through td_receive from any thread. This function + * must not be called simultaneously from two different threads. Also note that all updates and request responses + * must be applied in the order they were received to ensure consistency. + * Some TDLib requests can be executed synchronously from any thread by using td_execute. + * The TDLib client instances are destroyed automatically after they are closed. + * + * General pattern of usage: + * \code + * int client_id = td_create_client(); + * // share the client_id with other threads, which will be able to send requests via td_send + * + * const double WAIT_TIMEOUT = 10.0; // seconds + * while (true) { + * const char *result = td_receive(WAIT_TIMEOUT); + * if (result) { + * // parse the result as JSON object and process it as an incoming update or an answer to a previously sent request + * } + * } + * \endcode + */ + +/** + * Creates a new instance of TDLib. + * \return Opaque indentifier of the created TDLib client. + */ +TDJSON_EXPORT int td_create_client(); + +/** + * Sends request to the TDLib client. May be called from any thread. + * \param[in] client_id The TDLib client identifier. + * \param[in] request JSON-serialized null-terminated request to TDLib. + */ +TDJSON_EXPORT void td_send(int client_id, const char *request); + +/** + * Receives incoming updates and request responses. Must not be called simultaneously from two different threads. + * Returned pointer will be deallocated by TDLib during next call to td_receive or td_execute + * in the same thread, so it can't be used after that. + * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data. + * \return JSON-serialized null-terminated incoming update or request response. May be NULL if the timeout expires. + */ +TDJSON_EXPORT const char *td_receive(double timeout); + +/** + * Synchronously executes TDLib request. May be called from any thread. + * Only a few requests can be executed synchronously. + * Returned pointer will be deallocated by TDLib during next call to td_receive or td_execute + * in the same thread, so it can't be used after that. + * \param[in] request JSON-serialized null-terminated request to TDLib. + * \return JSON-serialized null-terminated request response. + */ +TDJSON_EXPORT const char *td_execute(const char *request); + #ifdef __cplusplus } // extern "C" #endif