Add new JSON interface.

GitOrigin-RevId: aaf756de59e72f949c1150d99e1277047f25fac9
This commit is contained in:
levlam 2020-10-05 16:08:07 +03:00
parent 119fc9563a
commit 7d6f14db10
4 changed files with 155 additions and 18 deletions

View File

@ -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 <utility>
@ -52,23 +53,29 @@ static std::pair<td_api::object_ptr<td_api::Function>, 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<string>(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<std::string>(current_output);
static const char *store_string(string str) {
init_thread_local<string>(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<std::mutex> 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<int64, string> extra;
static std::atomic<uint64> extra_id{1};
int td_json_create_client() {
return static_cast<int>(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<std::mutex> 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<std::mutex> 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

View File

@ -33,4 +33,12 @@ class ClientJson final {
std::atomic<std::uint64_t> 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

View File

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

View File

@ -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