Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2023-01-10 01:31:58 +01:00
commit 37bb42e36e
62 changed files with 583 additions and 303 deletions

View File

@ -1435,7 +1435,7 @@ Changes in 1.2.0 (20 Mar 2018):
See [using in .NET projects](README.md#using-dotnet) for more details.
* Added a C# example. See [README](example/csharp/README.md) for build and usage instructions.
* Added a build and usage example of TDLib SDK for Universal Windows Platform. See [README](example/uwp/README.md)
for detailed build and usage instructions. Also see [Unigram](https://github.com/UnigramDev/Unigram), which is
for detailed build and usage instructions. Also, see [Unigram](https://github.com/UnigramDev/Unigram), which is
a full-featured client rewritten from scratch using TDLib SDK for Universal Windows Platform in less than 2 months.
* Added a Swift example. See [README](example/swift/README.md) for build and usage instructions.
* Added an example of building TDLib for iOS, watchOS, tvOS, and also macOS. See [README](example/ios/README.md) for

View File

@ -80,7 +80,7 @@ function(get_git_head_revision _refspecvar _hashvar)
# The following git command will return a non empty string that
# points to the super project working tree if the current
# source dir is inside a git submodule.
# Otherwise the command will return an empty string.
# Otherwise, the command will return an empty string.
#
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree

View File

@ -22,7 +22,7 @@ TDLight is a fork of TDLib, a cross-platform library for building [Telegram](htt
`TDLib` has many advantages. Notably `TDLib` is:
* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, illumos, Windows Phone, WebAssembly, watchOS, tvOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort.
* **Multilanguage**: `TDLib` can be easily used with any programming language that is able to execute C functions. Additionally it already has native Java (using `JNI`) bindings and .NET (using `C++/CLI` and `C++/CX`) bindings.
* **Multilanguage**: `TDLib` can be easily used with any programming language that is able to execute C functions. Additionally, it already has native Java (using `JNI`) bindings and .NET (using `C++/CLI` and `C++/CX`) bindings.
* **Easy to use**: `TDLib` takes care of all network implementation details, encryption and local data storage.
* **High-performance**: in the [Telegram Bot API](https://core.telegram.org/bots/api), each `TDLib` instance handles more than 24000 active bots simultaneously.
* **Well-documented**: all `TDLib` API methods and public interfaces are fully documented.

View File

@ -228,13 +228,7 @@ class CreateFileBench final : public td::Benchmark {
}
}
void tear_down() final {
td::walk_path("A/", [&](td::CSlice path, auto type) {
if (type == td::WalkPath::Type::ExitDir) {
td::rmdir(path).ignore();
} else if (type == td::WalkPath::Type::NotDir) {
td::unlink(path).ignore();
}
}).ignore();
td::rmrf("A/").ignore();
}
};
@ -259,13 +253,7 @@ class WalkPathBench final : public td::Benchmark {
}).ignore();
}
void tear_down() final {
td::walk_path("A/", [&](td::CSlice path, auto type) {
if (type == td::WalkPath::Type::ExitDir) {
td::rmdir(path).ignore();
} else if (type == td::WalkPath::Type::NotDir) {
td::unlink(path).ignore();
}
}).ignore();
td::rmrf("A/").ignore();
}
};

View File

@ -888,7 +888,6 @@ int main() {
#endif
#if TD_PORT_POSIX
// TODO: yield makes it extremely slow. Yet some backoff may be necessary.
// td::bench(RingBenchmark<SemQueue>());
// td::bench(RingBenchmark<td::PollQueue<qvalue_t>>());

View File

@ -17,8 +17,23 @@ int main(int argc, char *argv[]) {
auto status = td::walk_path(dir, [&](td::CSlice path, auto type) {
if (type != td::WalkPath::Type::EnterDir) {
cnt++;
LOG(INFO) << path << " " << (type == td::WalkPath::Type::ExitDir);
}
auto type_name = [&] {
switch (type) {
case td::WalkPath::Type::EnterDir:
return td::CSlice("Open");
case td::WalkPath::Type::ExitDir:
return td::CSlice("Exit");
case td::WalkPath::Type::RegularFile:
return td::CSlice("File");
case td::WalkPath::Type::Symlink:
return td::CSlice("Link");
default:
UNREACHABLE();
return td::CSlice();
}
}();
LOG(INFO) << type_name << ' ' << path;
//if (is_dir) {
// td::rmdir(path);
//} else {

View File

@ -4,7 +4,7 @@ This directory contains basic examples of TDLib usage from different programming
If you are looking for documentation of all available TDLib methods, see the [td_api.tl](https://github.com/tdlight-team/tdlight/blob/master/td/generate/scheme/td_api.tl) scheme or the
automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of all available TDLib
[methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
Also take a look at our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
Also, take a look at our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
TDLib can be easily used from almost any programming language on any platform. See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
Choose your preferred programming language to see examples of usage and a detailed description:
@ -119,7 +119,7 @@ provides an asynchronous interface for interaction with TDLib, automatically gen
You can also use [TDLibCore](https://github.com/ph09nix/TDLibCore) library.
Also see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months,
Also, see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months,
[egram.tel](https://github.com/egramtel/egram.tel) a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia, or
[telewear](https://github.com/telewear/telewear) - a Telegram client for Samsung watches.
@ -189,7 +189,7 @@ See [rust-tdlib](https://github.com/aCLr/rust-tdlib), or [tdlib](https://github.
See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures),
[tdlib-sys](https://github.com/nuxeh/tdlib-sys), [tdjson-rs](https://github.com/mersinvald/tdjson-rs), [rust-tdlib](https://github.com/vhaoran/rust-tdlib), or [tdlib-json-sys](https://github.com/aykxt/tdlib-json-sys) for examples of TDLib Rust bindings.
Also see [Telegrand](https://github.com/melix99/telegrand) a Telegram client optimized for the GNOME desktop.
Also, see [Telegrand](https://github.com/melix99/telegrand) a Telegram client optimized for the GNOME desktop.
<a name="erlang"></a>
## Using TDLib in Erlang projects

View File

@ -8,9 +8,9 @@ cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=../example/cpp/td ..
cmake --build . --target install
```
Also see [building](https://github.com/tdlib/td#building) for additional details on TDLib building.
Also, see [building](https://github.com/tdlib/td#building) for additional details on TDLib building.
Then you can build the examples:
After this you can build the examples:
```
cd <path to TDLib sources>/example/cpp
mkdir build

View File

@ -35,4 +35,4 @@ After `TDLib` is built you can open and run TdExample project.
It contains a simple console C# application with implementation of authorization and message sending.
Just open it with Visual Studio 2015 or later and run.
Also see TdExample.csproj for example of including TDLib in C# project with all native shared library dependencies.
Also, see TdExample.csproj for example of including TDLib in C# project with all native shared library dependencies.

View File

@ -19,7 +19,7 @@ If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you w
In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake
Then you can build this example:
After this you can compile the example source code:
```
cd <path to TDLib sources>/example/java
mkdir build
@ -30,7 +30,7 @@ cmake --build . --target install
Compiled TDLib shared library and Java example after that will be placed in bin/ and Javadoc documentation in `docs/`.
Now you can run Java example:
After this you can run the Java example:
```
cd <path to TDLib sources>/example/java/bin
java '-Djava.library.path=.' org/drinkless/tdlib/example/Example

View File

@ -2,7 +2,7 @@
To run this example you need to [build](https://github.com/tdlib/td#building) TDLib and copy built tdjson shared library to this directory.
Then you can run the example:
After this you can run the example:
```
python tdjson_example.py
```

View File

@ -9,7 +9,7 @@ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=../example/swift/td
cmake --build . --target install
```
Then you can open and build the example with the latest Xcode.
After this you can open and build the example with the latest Xcode.
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@ -22,7 +22,8 @@ powershell -ExecutionPolicy ByPass .\build.ps1 -vcpkg_root C:\vcpkg
If you need to restart the build from scratch, call `.\build.ps1 -vcpkg_root C:\vcpkg -mode clean` first.
* Install Visual Studio Extension "TDLib for Universal Windows Platform" located at `build-uwp\vsix\tdlib.vsix`, which was created on the previous step by `build.ps1` script.
Now `TDLib` can be used from any UWP project, built in Visual Studio.
After this `TDLib` can be used from any UWP project, built in Visual Studio.
## Example of usage
The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or later and run.

View File

@ -432,7 +432,7 @@ animatedEmoji sticker:sticker sticker_width:int32 sticker_height:int32 fitzpatri
//@first_name First name of the user; 1-255 characters in length
//@last_name Last name of the user
//@vcard Additional data about the user in a form of vCard; 0-2048 bytes in length
//@user_id Identifier of the user, if known; otherwise 0
//@user_id Identifier of the user, if known; 0 otherwise
contact phone_number:string first_name:string last_name:string vcard:string user_id:int53 = Contact;
//@description Describes a location on planet Earth
@ -663,9 +663,12 @@ user id:int53 first_name:string last_name:string usernames:usernames phone_numbe
botInfo share_text:string description:string photo:photo animation:animation menu_button:botMenuButton commands:vector<botCommand> default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights = BotInfo;
//@description Contains full information about a user
//@personal_photo User profile photo set by the current user for the contact; may be null. If null and user.profile_photo is null, then the photo is empty, otherwise unknown. If non-null, then it is the same photo as in user.profile_photo and chat.photo. This photo isn't returned in the list of user photos
//@photo User profile photo; may be null. If null and user.profile_photo is null, then the photo is empty, otherwise unknown. If non-null and personal_photo is null, then it is the same photo as in user.profile_photo and chat.photo
//@public_photo User profile photo visible if the main photo is hidden by privacy settings; may be null. If null and user.profile_photo is null, then the photo is empty, otherwise unknown. If non-null and both photo and personal_photo are null, then it is the same photo as in user.profile_photo and chat.photo. This photo isn't returned in the list of user photos
//@personal_photo User profile photo set by the current user for the contact; may be null. If null and user.profile_photo is null, then the photo is empty; otherwise, it is unknown.
//-If non-null, then it is the same photo as in user.profile_photo and chat.photo. This photo isn't returned in the list of user photos
//@photo User profile photo; may be null. If null and user.profile_photo is null, then the photo is empty; otherwise, it is unknown.
//-If non-null and personal_photo is null, then it is the same photo as in user.profile_photo and chat.photo
//@public_photo User profile photo visible if the main photo is hidden by privacy settings; may be null. If null and user.profile_photo is null, then the photo is empty; otherwise, it is unknown.
//-If non-null and both photo and personal_photo are null, then it is the same photo as in user.profile_photo and chat.photo. This photo isn't returned in the list of user photos
//@is_blocked True, if the user is blocked by the current user
//@can_be_called True, if the user can be called
//@supports_video_calls True, if a video call can be created with the user
@ -930,7 +933,7 @@ secretChatStateClosed = SecretChatState;
//@id Secret chat identifier
//@user_id Identifier of the chat partner
//@state State of the secret chat
//@is_outbound True, if the chat was created by the current user; otherwise false
//@is_outbound True, if the chat was created by the current user; false otherwise
//@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 little-endian bytes, which must be split into groups of 2 bits, each denoting a pixel of one of 4 colors FFFFFF, D5E6F3, 2D5775, and 2F99C9.
//-The pixels must be used to make a 12x12 square image filled from left to right, top to bottom. Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers
//@layer Secret chat layer; determines features supported by the chat partner's application. Nested text entities and underline and strikethrough entities are supported if the layer >= 101,
@ -1430,7 +1433,7 @@ replyMarkupForceReply is_personal:Bool input_field_placeholder:string = ReplyMar
//@description Contains a custom keyboard layout to quickly reply to bots
//@rows A list of rows of bot keyboard buttons
//@is_persistent True, if the keyboard is supposed to be always shown when the ordinary keyboard is hidden
//@is_persistent True, if the keyboard is supposed to always be shown when the ordinary keyboard is hidden
//@resize_keyboard True, if the application needs to resize the keyboard vertically
//@one_time True, if the application needs to hide the keyboard after use
//@is_personal True, if the keyboard must automatically be shown to the current user. For outgoing messages, specify true to show the keyboard only for the mentioned users and for the target user of a reply
@ -1923,10 +1926,10 @@ paymentOption title:string url:string = PaymentOption;
//@product_photo Product photo; may be null
paymentForm id:int64 invoice:invoice seller_bot_user_id:int53 payment_provider_user_id:int53 payment_provider:PaymentProvider additional_payment_options:vector<paymentOption> saved_order_info:orderInfo saved_credentials:vector<savedCredentials> can_save_credentials:Bool need_password:Bool product_title:string product_description:formattedText product_photo:photo = PaymentForm;
//@description Contains a temporary identifier of validated order information, which is stored for one hour. Also contains the available shipping options @order_info_id Temporary identifier of the order information @shipping_options Available shipping options
//@description Contains a temporary identifier of validated order information, which is stored for one hour, and the available shipping options @order_info_id Temporary identifier of the order information @shipping_options Available shipping options
validatedOrderInfo order_info_id:string shipping_options:vector<shippingOption> = ValidatedOrderInfo;
//@description Contains the result of a payment request @success True, if the payment request was successful; otherwise the verification_url will be non-empty @verification_url URL for additional payment credentials verification
//@description Contains the result of a payment request @success True, if the payment request was successful; otherwise, the verification_url will be non-empty @verification_url URL for additional payment credentials verification
paymentResult success:Bool verification_url:string = PaymentResult;
//@description Contains information about a successful payment
@ -2399,7 +2402,7 @@ messagePinMessage message_id:int53 = MessageContent;
//@description A screenshot of a message in the chat has been taken
messageScreenshotTaken = MessageContent;
//@description A theme in the chat has been changed @theme_name If non-empty, name of a new theme, set for the chat. Otherwise chat theme was reset to the default one
//@description A theme in the chat has been changed @theme_name If non-empty, name of a new theme, set for the chat. Otherwise, chat theme was reset to the default one
messageChatSetTheme theme_name:string = MessageContent;
//@description The auto-delete or self-destruct timer for messages in the chat has been changed @message_auto_delete_time New value auto-delete or self-destruct time, in seconds; 0 if disabled @from_user_id If not 0, a user identifier, which default setting was automatically applied
@ -2414,10 +2417,10 @@ messageForumTopicCreated name:string icon:forumTopicIcon = MessageContent;
//@icon_custom_emoji_id New unique identifier of the custom emoji shown on the topic icon; 0 if none. Must be ignored if edit_icon_custom_emoji_id is false
messageForumTopicEdited name:string edit_icon_custom_emoji_id:Bool icon_custom_emoji_id:int64 = MessageContent;
//@description A forum topic has been closed or opened @is_closed True, if the topic was closed, otherwise the topic was reopened
//@description A forum topic has been closed or opened @is_closed True, if the topic was closed; otherwise, the topic was reopened
messageForumTopicIsClosedToggled is_closed:Bool = MessageContent;
//@description A General forum topic has been hidden or unhidden @is_hidden True, if the topic was hidden, otherwise the topic was unhidden
//@description A General forum topic has been hidden or unhidden @is_hidden True, if the topic was hidden; otherwise, the topic was unhidden
messageForumTopicIsHiddenToggled is_hidden:Bool = MessageContent;
//@description A profile photo was suggested to a user in a private chat @photo The suggested chat photo. Use the method setProfilePhoto with inputChatPhotoPrevious to apply the photo
@ -3778,7 +3781,8 @@ storePaymentPurposePremiumSubscription is_restore:Bool = StorePaymentPurpose;
storePaymentPurposeGiftedPremium user_id:int53 currency:string amount:int53 = StorePaymentPurpose;
//@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org
//@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method.
//-To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org
//@description A token for Firebase Cloud Messaging @token Device registration token; may be empty to deregister a device @encrypt True, if push notifications must be additionally encrypted
deviceTokenFirebaseCloudMessaging token:string encrypt:Bool = DeviceToken;
@ -4054,7 +4058,7 @@ pushMessageContentChatChangePhoto = PushMessageContent;
//@description A chat title was edited @title New chat title
pushMessageContentChatChangeTitle title:string = PushMessageContent;
//@description A chat theme was edited @theme_name If non-empty, name of a new theme, set for the chat. Otherwise chat theme was reset to the default one
//@description A chat theme was edited @theme_name If non-empty, name of a new theme, set for the chat. Otherwise, the chat theme was reset to the default one
pushMessageContentChatSetTheme theme_name:string = PushMessageContent;
//@description A chat member was deleted
@ -4407,7 +4411,7 @@ targetChatInternalLink link:InternalLinkType = TargetChat;
internalLinkTypeActiveSessions = InternalLinkType;
//@description The link is a link to an attachment menu bot to be opened in the specified or a chosen chat. Process given target_chat to open the chat.
//-Then call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then use getAttachmentMenuBot to receive information about the bot.
//-Then, call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then, use getAttachmentMenuBot to receive information about the bot.
//-If the bot isn't added to attachment menu, then user needs to confirm adding the bot to attachment menu. If user confirms adding, then use toggleBotIsAddedToAttachmentMenu to add it.
//-If the attachment menu bot can't be used in the opened chat, show an error to the user. If the bot is added to attachment menu and can be used in the chat, then use openWebApp with the given URL
//@target_chat Target chat to be opened
@ -4433,15 +4437,15 @@ internalLinkTypeBotStart bot_username:string start_parameter:string autostart:Bo
//-If administrator rights are provided by the link, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator,
//-check that the current user can edit its administrator rights, combine received rights with the requested administrator rights, show confirmation box to the user,
//-and call setChatMemberStatus with the chosen chat and confirmed administrator rights. Before call to setChatMemberStatus it may be required to upgrade the chosen basic group chat to a supergroup chat.
//-Then if start_parameter isn't empty, call sendBotStartMessage with the given start parameter and the chosen chat, otherwise just send /start message with bot's username added to the chat.
//-Then, if start_parameter isn't empty, call sendBotStartMessage with the given start parameter and the chosen chat; otherwise, just send /start message with bot's username added to the chat.
//@bot_username Username of the bot
//@start_parameter The parameter to be passed to sendBotStartMessage
//@administrator_rights Expected administrator rights for the bot; may be null
internalLinkTypeBotStartInGroup bot_username:string start_parameter:string administrator_rights:chatAdministratorRights = InternalLinkType;
//@description The link is a link to a Telegram bot, which is supposed to be added to a channel chat as an administrator. Call searchPublicChat with the given bot username and check that the user is a bot,
//-ask the current user to select a channel chat to add the bot to as an administrator. Then call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator,
//-check that the current user can edit its administrator rights and combine received rights with the requested administrator rights. Then show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed rights
//-ask the current user to select a channel chat to add the bot to as an administrator. Then, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator,
//-check that the current user can edit its administrator rights and combine received rights with the requested administrator rights. Then, show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed rights
//@bot_username Username of the bot
//@administrator_rights Expected administrator rights for the bot
internalLinkTypeBotAddToChannel bot_username:string administrator_rights:chatAdministratorRights = InternalLinkType;
@ -4486,12 +4490,13 @@ internalLinkTypeMessage url:string = InternalLinkType;
//@contains_link True, if the first line of the text contains a link. If true, the input field needs to be focused and the text after the link must be selected
internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLinkType;
//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application, otherwise ignore it
//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application; otherwise, ignore it
//@bot_user_id User identifier of the service's bot
//@scope Telegram Passport element types requested by the service
//@public_key Service's public key
//@nonce Unique request identifier provided by the service
//@callback_url An HTTP URL to open once the request is finished or canceled with the parameter tg_passport=success or tg_passport=cancel respectively. If empty, then the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel needs to be opened instead
//@callback_url An HTTP URL to open once the request is finished, canceled, or failed with the parameters tg_passport=success, tg_passport=cancel, or tg_passport=error&error=... respectively.
//-If empty, then onActivityResult method must be used to return response on Android, or the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel must be opened otherwise
internalLinkTypePassportDataRequest bot_user_id:int53 scope:string public_key:string nonce:string callback_url:string = InternalLinkType;
//@description The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberConfirmationCode with the given hash and phone number to process the link
@ -5064,7 +5069,7 @@ updateChatPermissions chat_id:int53 permissions:chatPermissions = Update;
//@positions The new chat positions in the chat lists
updateChatLastMessage chat_id:int53 last_message:message positions:vector<chatPosition> = Update;
//@description The position of a chat in a chat list has changed. Instead of this update updateChatLastMessage or updateChatDraftMessage might be sent
//@description The position of a chat in a chat list has changed. An updateChatLastMessage or updateChatDraftMessage update might be sent instead of the update
//@chat_id Chat identifier
//@position New chat position. If new order is 0, then the chat needs to be removed from the list
updateChatPosition chat_id:int53 position:chatPosition = Update;
@ -5290,7 +5295,7 @@ updateInstalledStickerSets sticker_type:StickerType sticker_set_ids:vector<int64
//@description The list of trending sticker sets was updated or some of them were viewed @sticker_type Type of the affected stickers @sticker_sets The prefix of the list of trending sticker sets with the newest trending sticker sets
updateTrendingStickerSets sticker_type:StickerType sticker_sets:trendingStickerSets = Update;
//@description The list of recently used stickers was updated @is_attached True, if the list of stickers attached to photo or video files was updated, otherwise the list of sent stickers is updated @sticker_ids The new list of file identifiers of recently used stickers
//@description The list of recently used stickers was updated @is_attached True, if the list of stickers attached to photo or video files was updated; otherwise, the list of sent stickers is updated @sticker_ids The new list of file identifiers of recently used stickers
updateRecentStickers is_attached:Bool sticker_ids:vector<int32> = Update;
//@description The list of favorite stickers was updated @sticker_ids The new list of file identifiers of favorite stickers
@ -5494,7 +5499,7 @@ getAuthorizationState = AuthorizationState;
setTdlibParameters use_test_dc:Bool database_directory:string files_directory:string database_encryption_key:bytes use_file_database:Bool use_chat_info_database:Bool use_message_database:Bool use_secret_chats:Bool api_id:int32 api_hash:string system_language_code:string device_model:string system_version:string application_version:string enable_storage_optimizer:Bool ignore_file_names:Bool = Ok;
//@description Sets the phone number of the user and sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitPhoneNumber,
//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitEmailAddress, authorizationStateWaitEmailCode, authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
//@phone_number The phone number of the user, in international format
//@settings Settings for the authentication of the user's phone number; pass null to use default settings
setAuthenticationPhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = Ok;
@ -5513,7 +5518,7 @@ checkAuthenticationEmailCode code:EmailAddressAuthentication = Ok;
checkAuthenticationCode code:string = Ok;
//@description Requests QR code authentication by scanning a QR code on another logged in device. Works only when the current authorization state is authorizationStateWaitPhoneNumber,
//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitEmailAddress, authorizationStateWaitEmailCode, authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
//@other_user_ids List of user identifiers of other users currently using the application
requestQrCodeAuthentication other_user_ids:vector<int53> = Ok;
@ -5658,7 +5663,7 @@ getMessage chat_id:int53 message_id:int53 = Message;
//@description Returns information about a message, if it is available without sending network request. This is an offline request @chat_id Identifier of the chat the message belongs to @message_id Identifier of the message to get
getMessageLocally chat_id:int53 message_id:int53 = Message;
//@description Returns information about a message that is replied by a given message. Also returns the pinned message, the game message, the invoice message, and the topic creation message for messages
//@description Returns information about a message that is replied by a given message. Also, returns the pinned message, the game message, the invoice message, and the topic creation message for messages
//-of the types messagePinMessage, messageGameScore, messagePaymentSuccessful, and topic messages without replied message respectively
//@chat_id Identifier of the chat the message belongs to
//@message_id Identifier of the reply message
@ -5700,7 +5705,7 @@ loadChats chat_list:ChatList limit:int32 = Ok;
//@limit The maximum number of chats to be returned
getChats chat_list:ChatList limit:int32 = Chats;
//@description Searches a public chat by its username. Currently, only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved
//@description Searches a public chat by its username. Currently, only private chats, supergroups and channels can be public. Returns the chat if found; otherwise, an error is returned @username Username to be resolved
searchPublicChat username:string = Chat;
//@description Searches public chats by looking for specified query in their username and title. Currently, only private chats, supergroups and channels can be public. Returns a meaningful number of results.
@ -5789,7 +5794,9 @@ getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 off
//@revoke Pass true to delete chat history for all users
deleteChatHistory chat_id:int53 remove_from_chat_list:Bool revoke:Bool = Ok;
//@description Deletes a chat along with all messages in the corresponding chat for all chat members. For group chats this will release the usernames and remove all members. Use the field chat.can_be_deleted_for_all_users to find whether the method can be applied to the chat @chat_id Chat identifier
//@description Deletes a chat along with all messages in the corresponding chat for all chat members. For group chats this will release the usernames and remove all members.
//-Use the field chat.can_be_deleted_for_all_users to find whether the method can be applied to the chat
//@chat_id Chat identifier
deleteChat chat_id:int53 = Ok;
//@description Searches for messages with given words in the chat. Returns the results in reverse chronological order, i.e. in order of decreasing message_id. Cannot be used in secret chats with a non-empty query
@ -7371,7 +7378,9 @@ toggleSupergroupJoinByRequest supergroup_id:int53 join_by_request:Bool = Ok;
//@description Toggles whether the message history of a supergroup is available to new members; requires can_change_info administrator right @supergroup_id The identifier of the supergroup @is_all_history_available The new value of is_all_history_available
toggleSupergroupIsAllHistoryAvailable supergroup_id:int53 is_all_history_available:Bool = Ok;
//@description Toggles whether non-administrators can receive only administrators and bots using getSupergroupMembers or searchChatMembers. Can be called only if supergroupFullInfo.can_hide_members == true @supergroup_id Identifier of the supergroup @has_hidden_members New value of has_hidden_members
//@description Toggles whether non-administrators can receive only administrators and bots using getSupergroupMembers or searchChatMembers. Can be called only if supergroupFullInfo.can_hide_members == true
//@supergroup_id Identifier of the supergroup
//@has_hidden_members New value of has_hidden_members
toggleSupergroupHasHiddenMembers supergroup_id:int53 has_hidden_members:Bool = Ok;
//@description Toggles whether aggressive anti-spam checks are enabled in the supergroup. Can be called only if supergroupFullInfo.can_toggle_aggressive_anti_spam == true

View File

@ -91,8 +91,7 @@ namespace mtproto {
* A notification about new session.
* It is reasonable to store unique_id with current session, in order to process duplicated notifications once.
*
* Causes all older than first_msg_id to be re-sent.
* Also there is a gap in updates, so getDifference MUST be sent
* Causes all messages older than first_msg_id to be re-sent and notifies about a gap in updates
* output:
* - new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession
*

View File

@ -55,6 +55,8 @@ class AttachMenuManager final : public Actor {
void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
static string get_attach_menu_bots_database_key();
private:
static const int32 PING_WEB_VIEW_TIMEOUT = 60;
@ -135,8 +137,6 @@ class AttachMenuManager final : public Actor {
void send_update_attach_menu_bots() const;
static string get_attach_menu_bots_database_key();
void save_attach_menu_bots();
void on_reload_attach_menu_bots(Result<telegram_api::object_ptr<telegram_api::AttachMenuBots>> &&result);

View File

@ -157,7 +157,6 @@ class UploadBackgroundQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
CHECK(file_id_.is_valid());
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
// TODO td_->background_manager_->on_upload_background_file_part_missing(file_id_, to_integer<int32>(status.message().substr(10)));

View File

@ -23,7 +23,7 @@ namespace td {
* Requests can be sent using the method ClientManager::send from any thread.
* New updates and responses to requests can be received using the method ClientManager::receive from any thread after
* the first request has been sent to the client instance. ClientManager::receive must not be called simultaneously from
* two different threads. Also note that all updates and responses to requests should be applied in the same order as
* two different threads. Also, note that all updates and responses to requests should be applied in the same order as
* they were received, to ensure consistency.
* Some TDLib requests can be executed synchronously from any thread using the method ClientManager::execute.
*
@ -177,7 +177,7 @@ class ClientManager final {
* The TDLib instance is created for the lifetime of the Client object.
* Requests to TDLib can be sent using the Client::send method from any thread.
* New updates and responses to requests can be received using the Client::receive method from any thread,
* this function must not be called simultaneously from two different threads. Also note that all updates and
* this function must not be called simultaneously from two different threads. Also, note that all updates and
* responses to requests should be applied in the same order as 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 using the Client::execute method.

View File

@ -12039,6 +12039,7 @@ void ContactsManager::on_get_user_full(tl_object_ptr<telegram_api::userFull> &&u
}
ContactsManager::UserPhotos *ContactsManager::add_user_photos(UserId user_id) {
CHECK(user_id.is_valid());
auto &user_photos_ptr = user_photos_[user_id];
if (user_photos_ptr == nullptr) {
user_photos_ptr = make_unique<UserPhotos>();

View File

@ -24,6 +24,8 @@
#include "td/utils/misc.h"
#include "td/utils/Promise.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/unicode.h"
#include "td/utils/utf8.h"
@ -1849,7 +1851,7 @@ string get_first_url(const FormattedText &text) {
}
Result<vector<MessageEntity>> parse_markdown(string &text) {
string result;
size_t result_size = 0;
vector<MessageEntity> entities;
size_t size = text.size();
int32 utf16_offset = 0;
@ -1857,7 +1859,7 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
auto c = static_cast<unsigned char>(text[i]);
if (c == '\\' && (text[i + 1] == '_' || text[i + 1] == '*' || text[i + 1] == '`' || text[i + 1] == '[')) {
i++;
result.push_back(text[i]);
text[result_size++] = text[i];
utf16_offset++;
continue;
}
@ -1865,7 +1867,7 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
if (is_utf8_character_first_code_unit(c)) {
utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i]);
text[result_size++] = text[i];
continue;
}
@ -1907,7 +1909,7 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
if (is_utf8_character_first_code_unit(cur_ch)) {
utf16_offset += 1 + (cur_ch >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i++]);
text[result_size++] = text[i++];
}
if (i == size) {
return Status::Error(400, PSLICE() << "Can't find end of the entity starting at byte offset " << begin_pos);
@ -1963,11 +1965,12 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
i += 2;
}
}
text = result;
return entities;
text.resize(result_size);
return std::move(entities);
}
static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &result) {
Result<vector<MessageEntity>> parse_markdown_v2(string &text) {
size_t result_size = 0;
vector<MessageEntity> entities;
int32 utf16_offset = 0;
@ -1994,7 +1997,7 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
if (c == '\\' && text[i + 1] > 0 && text[i + 1] <= 126) {
i++;
utf16_offset += 1;
result += text[i];
text[result_size++] = text[i];
continue;
}
@ -2015,7 +2018,7 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
if (is_utf8_character_first_code_unit(c)) {
utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i]);
text[result_size++] = text[i];
continue;
}
@ -2091,7 +2094,7 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
}
if (i != language_end && language_end < text.size() && text[language_end] != '`') {
type = MessageEntity::Type::PreCode;
argument = text.substr(i, language_end - i).str();
argument = text.substr(i, language_end - i);
i = language_end;
}
// skip one new line in the beginning of the text
@ -2121,7 +2124,7 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
return Status::Error(
400, PSLICE() << "Character '" << text[i] << "' is reserved and must be escaped with the preceding '\\'");
}
nested_entities.emplace_back(type, std::move(argument), utf16_offset, entity_byte_offset, result.size());
nested_entities.emplace_back(type, std::move(argument), utf16_offset, entity_byte_offset, result_size);
} else {
// end of an entity
auto type = nested_entities.back().type;
@ -2147,7 +2150,8 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
string url;
if (text[i + 1] != '(') {
// use text as a URL
url = result.substr(nested_entities.back().entity_begin_pos);
url = text.substr(nested_entities.back().entity_begin_pos,
result_size - nested_entities.back().entity_begin_pos);
} else {
i += 2;
auto url_begin_pos = i;
@ -2222,14 +2226,8 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
sort_entities(entities);
return entities;
}
Result<vector<MessageEntity>> parse_markdown_v2(string &text) {
string result;
TRY_RESULT(entities, do_parse_markdown_v2(text, result));
text = result;
return entities;
text.resize(result_size);
return std::move(entities);
}
static vector<Slice> find_text_url_entities_v3(Slice text) {
@ -2938,11 +2936,7 @@ FormattedText get_markdown_v3(FormattedText text) {
}
static uint32 decode_html_entity(CSlice text, size_t &pos) {
auto c = static_cast<unsigned char>(text[pos]);
if (c != '&') {
return 0;
}
CHECK(text[pos] == '&');
size_t end_pos = pos + 1;
uint32 res = 0;
if (text[pos + 1] == '#') {
@ -2990,9 +2984,15 @@ static uint32 decode_html_entity(CSlice text, size_t &pos) {
return res;
}
static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result) {
Result<vector<MessageEntity>> parse_html(string &str) {
auto str_size = str.size();
const char *text = str.c_str();
auto result_end = MutableSlice(str).ubegin();
const unsigned char *result_begin = result_end;
vector<MessageEntity> entities;
int32 utf16_offset = 0;
bool need_recheck_utf8 = false;
struct EntityInfo {
string tag_name;
@ -3000,23 +3000,28 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
int32 entity_offset;
size_t entity_begin_pos;
EntityInfo(string tag_name, string argument, int32 entity_offset, size_t entity_begin_pos)
EntityInfo(string &&tag_name, string &&argument, int32 entity_offset, size_t entity_begin_pos)
: tag_name(std::move(tag_name))
, argument(std::move(argument))
, entity_offset(entity_offset)
, entity_begin_pos(entity_begin_pos) {
}
};
std::vector<EntityInfo> nested_entities;
vector<EntityInfo> nested_entities;
for (size_t i = 0; i < text.size(); i++) {
for (size_t i = 0; i < str_size; i++) {
auto c = static_cast<unsigned char>(text[i]);
if (c == '&') {
auto ch = decode_html_entity(text, i);
if (ch != 0) {
auto code = decode_html_entity(str, i);
if (code != 0) {
i--; // i will be incremented in for
utf16_offset += 1 + (ch > 0xffff);
append_utf8_character(result, ch);
utf16_offset += 1 + (code > 0xffff);
if (code >= 0xd800 && code <= 0xdfff) {
// half of a surrogate pair
need_recheck_utf8 = true;
}
result_end = append_utf8_character_unsafe(result_end, code);
CHECK(result_end <= result_begin + i);
continue;
}
}
@ -3024,7 +3029,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
if (is_utf8_character_first_code_unit(c)) {
utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i]);
*result_end++ = c;
continue;
}
@ -3038,7 +3043,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
return Status::Error(400, PSLICE() << "Unclosed start tag at byte offset " << begin_pos);
}
string tag_name = to_lower(text.substr(begin_pos + 1, i - begin_pos - 1));
string tag_name = to_lower(Slice(text + begin_pos + 1, i - begin_pos - 1));
if (tag_name != "a" && tag_name != "b" && tag_name != "strong" && tag_name != "i" && tag_name != "em" &&
tag_name != "s" && tag_name != "strike" && tag_name != "del" && tag_name != "u" && tag_name != "ins" &&
tag_name != "tg-spoiler" && tag_name != "tg-emoji" && tag_name != "span" && tag_name != "pre" &&
@ -3059,7 +3064,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
while (!is_space(text[i]) && text[i] != '=') {
i++;
}
Slice attribute_name = text.substr(attribute_begin_pos, i - attribute_begin_pos);
Slice attribute_name(text + attribute_begin_pos, i - attribute_begin_pos);
if (attribute_name.empty()) {
return Status::Error(
400, PSLICE() << "Empty attribute name in the tag \"" << tag_name << "\" at byte offset " << begin_pos);
@ -3087,7 +3092,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
while (is_alnum(text[i]) || text[i] == '.' || text[i] == '-') {
i++;
}
attribute_value = to_lower(text.substr(token_begin_pos, i - token_begin_pos));
attribute_value = to_lower(Slice(text + token_begin_pos, i - token_begin_pos));
if (!is_space(text[i]) && text[i] != '>') {
return Status::Error(400, PSLICE() << "Unexpected end of name token at byte offset " << token_begin_pos);
@ -3095,19 +3100,23 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
} else {
// A string literal
char end_character = text[i++];
char *attribute_end = &str[i];
const char *attribute_begin = attribute_end;
while (text[i] != end_character && text[i] != 0) {
if (text[i] == '&') {
auto ch = decode_html_entity(text, i);
if (ch != 0) {
append_utf8_character(attribute_value, ch);
auto code = decode_html_entity(str, i);
if (code != 0) {
attribute_end = reinterpret_cast<char *>(
append_utf8_character_unsafe(reinterpret_cast<unsigned char *>(attribute_end), code));
continue;
}
}
attribute_value.push_back(text[i++]);
*attribute_end++ = text[i++];
}
if (text[i] == end_character) {
i++;
}
attribute_value.assign(attribute_begin, static_cast<size_t>(attribute_end - attribute_begin));
}
if (text[i] == 0) {
return Status::Error(400, PSLICE() << "Unclosed start tag at byte offset " << begin_pos);
@ -3130,7 +3139,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
<< "Tag \"span\" must have class \"tg-spoiler\" at byte offset " << begin_pos);
}
nested_entities.emplace_back(std::move(tag_name), std::move(argument), utf16_offset, result.size());
nested_entities.emplace_back(std::move(tag_name), std::move(argument), utf16_offset, result_end - result_begin);
} else {
// end of an entity
if (nested_entities.empty()) {
@ -3140,7 +3149,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
while (!is_space(text[i]) && text[i] != '>') {
i++;
}
string end_tag_name = to_lower(text.substr(begin_pos + 2, i - begin_pos - 2));
string end_tag_name = to_lower(Slice(text + begin_pos + 2, i - begin_pos - 2));
while (is_space(text[i]) && text[i] != 0) {
i++;
}
@ -3148,7 +3157,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
return Status::Error(400, PSLICE() << "Unclosed end tag at byte offset " << begin_pos);
}
string tag_name = std::move(nested_entities.back().tag_name);
const string &tag_name = nested_entities.back().tag_name;
if (!end_tag_name.empty() && end_tag_name != tag_name) {
return Status::Error(400, PSLICE() << "Unmatched end tag at byte offset " << begin_pos << ", expected \"</"
<< tag_name << ">\", found \"</" << end_tag_name << ">\"");
@ -3177,7 +3186,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
} else if (tag_name == "a") {
auto url = std::move(nested_entities.back().argument);
if (url.empty()) {
url = result.substr(nested_entities.back().entity_begin_pos);
url = Slice(result_begin + nested_entities.back().entity_begin_pos, result_end).str();
}
auto user_id = LinkManager::get_link_user_id(url);
if (user_id.is_valid()) {
@ -3226,19 +3235,13 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
sort_entities(entities);
return entities;
}
Result<vector<MessageEntity>> parse_html(string &text) {
string result;
TRY_RESULT(entities, do_parse_html(text, result));
if (!check_utf8(result)) {
str.resize(static_cast<size_t>(result_end - result_begin));
if (need_recheck_utf8 && !check_utf8(str)) {
return Status::Error(400,
"Text contains invalid Unicode characters after decoding HTML entities, check for unmatched "
"surrogate code units");
}
text = result;
return entities;
return std::move(entities);
}
vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entities(
@ -3443,7 +3446,7 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
entities.pop_back();
}
}
return entities;
return std::move(entities);
}
vector<MessageEntity> get_message_entities(const ContactsManager *contacts_manager,
@ -4398,6 +4401,8 @@ vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(co
const char *source) {
vector<tl_object_ptr<telegram_api::MessageEntity>> result;
vector<MessageEntity> splittable_entities;
constexpr size_t MAX_USER_ENTITY_COUNT = 100; // server-side limit
size_t user_entity_count = 0;
for (auto &entity : entities) {
if (!is_user_entity(entity.type)) {
continue;
@ -4406,6 +4411,15 @@ vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(co
splittable_entities.push_back(entity);
continue;
}
if (entity.type == MessageEntity::Type::CustomEmoji) {
result.push_back(make_tl_object<telegram_api::messageEntityCustomEmoji>(entity.offset, entity.length,
entity.custom_emoji_id.get()));
continue;
}
if (user_entity_count >= MAX_USER_ENTITY_COUNT) {
continue;
}
user_entity_count++;
switch (entity.type) {
case MessageEntity::Type::BlockQuote:
result.push_back(make_tl_object<telegram_api::messageEntityBlockquote>(entity.offset, entity.length));
@ -4430,16 +4444,17 @@ vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(co
r_input_user.move_as_ok()));
break;
}
case MessageEntity::Type::CustomEmoji:
result.push_back(make_tl_object<telegram_api::messageEntityCustomEmoji>(entity.offset, entity.length,
entity.custom_emoji_id.get()));
break;
default:
UNREACHABLE();
}
}
split_entities(splittable_entities, vector<MessageEntity>());
for (auto &entity : splittable_entities) {
if (user_entity_count >= MAX_USER_ENTITY_COUNT) {
break;
}
user_entity_count++;
switch (entity.type) {
case MessageEntity::Type::Bold:
result.push_back(make_tl_object<telegram_api::messageEntityBold>(entity.offset, entity.length));

View File

@ -181,7 +181,7 @@ FormattedText parse_markdown_v3(FormattedText text);
FormattedText get_markdown_v3(FormattedText text);
Result<vector<MessageEntity>> parse_html(string &text);
Result<vector<MessageEntity>> parse_html(string &str);
vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(const ContactsManager *contacts_manager,
const vector<MessageEntity> &entities,

View File

@ -4720,7 +4720,8 @@ class GetChannelDifferenceQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChannelDifferenceQuery")) {
if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChannelDifferenceQuery") &&
status.message() != "PERSISTENT_TIMESTAMP_INVALID") {
LOG(ERROR) << "Receive error for GetChannelDifferenceQuery for " << dialog_id_ << " with pts " << pts_
<< " and limit " << limit_ << ": " << status;
}
@ -6706,8 +6707,11 @@ void MessagesManager::skip_old_pending_pts_update(tl_object_ptr<telegram_api::Up
if (update_message_ids_.count(full_message_id) > 0) {
if (new_pts == old_pts || old_pts == std::numeric_limits<int32>::max()) {
// apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow
on_get_message(std::move(update_new_message->message_), true, false, false, true, true,
"updateNewMessage with an awaited message");
auto added_full_message_id = on_get_message(std::move(update_new_message->message_), true, false, false, true,
true, "updateNewMessage with an awaited message");
if (added_full_message_id != full_message_id) {
LOG(ERROR) << "Failed to add an awaited " << full_message_id << " from " << source;
}
return;
} else {
LOG(ERROR) << "Receive awaited sent " << full_message_id << " from " << source << " with pts " << new_pts
@ -8009,8 +8013,12 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p
FullMessageId full_message_id(dialog_id, message_id);
if (update_message_ids_.count(full_message_id) > 0) {
// apply sent channel message
auto added_full_message_id =
on_get_message(std::move(update_new_channel_message->message_), true, true, false, true, true,
"updateNewChannelMessage with an awaited message");
if (added_full_message_id != full_message_id) {
LOG(ERROR) << "Failed to add an awaited " << full_message_id << " from " << source;
}
promise.set_value(Unit());
return;
}
@ -8168,6 +8176,7 @@ void MessagesManager::process_pts_update(tl_object_ptr<telegram_api::Update> &&u
default:
UNREACHABLE();
}
update_ptr = nullptr;
CHECK(!td_->updates_manager_->running_get_difference());
}
@ -10483,6 +10492,17 @@ void MessagesManager::on_get_dialog_messages_search_result(
continue;
}
if (filter != MessageSearchFilter::Empty) {
const Message *m = get_message(new_full_message_id);
CHECK(m != nullptr);
auto index_mask = get_message_index_mask(new_full_message_id.get_dialog_id(), m);
if ((message_search_filter_index_mask(filter) & index_mask) == 0) {
LOG(INFO) << "Skip " << new_full_message_id << " of unexpected type";
total_count--;
continue;
}
}
// TODO check that messages are returned in decreasing message_id order
if (message_id < first_added_message_id || !first_added_message_id.is_valid()) {
first_added_message_id = message_id;
@ -13610,6 +13630,12 @@ void MessagesManager::hangup() {
while (!being_sent_messages_.empty()) {
on_send_message_fail(being_sent_messages_.begin()->first, Global::request_aborted_error());
}
if (!update_message_ids_.empty()) {
LOG(ERROR) << "Have " << update_message_ids_.size() << " awaited sent messages";
}
if (!update_scheduled_message_ids_.empty()) {
LOG(ERROR) << "Have " << update_scheduled_message_ids_.size() << " awaited sent scheduled messages";
}
}
fail_promises(load_active_live_location_messages_queries_, Global::request_aborted_error());
@ -36745,7 +36771,10 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr
if (replace_legacy) {
return false;
}
if (!is_scheduled && !message_id.is_yet_unsent()) {
if (old_message->forward_info->is_imported != new_message->forward_info->is_imported) {
return true;
}
if (!is_scheduled && !message_id.is_yet_unsent() && !old_message->forward_info->is_imported) {
return true;
}
return !is_forward_info_sender_hidden(new_message->forward_info.get()) &&
@ -39545,8 +39574,10 @@ void MessagesManager::on_get_channel_difference(
DialogId dialog_id, int32 request_pts, int32 request_limit,
tl_object_ptr<telegram_api::updates_ChannelDifference> &&difference_ptr) {
LOG(INFO) << "----- END GET CHANNEL DIFFERENCE----- for " << dialog_id;
CHECK(active_get_channel_differencies_.count(dialog_id) == 1);
active_get_channel_differencies_.erase(dialog_id);
auto it = active_get_channel_differencies_.find(dialog_id);
CHECK(it != active_get_channel_differencies_.end());
string source = std::move(it->second);
active_get_channel_differencies_.erase(it);
auto d = get_dialog_force(dialog_id, "on_get_channel_difference");
if (difference_ptr == nullptr) {
@ -39574,7 +39605,7 @@ void MessagesManager::on_get_channel_difference(
channel_get_difference_retry_timeouts_.erase(dialog_id);
LOG(INFO) << "Receive result of getChannelDifference for " << dialog_id << " with pts = " << request_pts
<< " and limit = " << request_limit << ": " << to_string(difference_ptr);
<< " and limit = " << request_limit << " from " << source << ": " << to_string(difference_ptr);
bool have_new_messages = false;
switch (difference_ptr->get_id()) {
@ -39616,7 +39647,7 @@ void MessagesManager::on_get_channel_difference(
int32 cur_pts = d->pts <= 0 ? 1 : d->pts;
LOG_IF(ERROR, cur_pts != request_pts) << "Channel PTS has changed from " << request_pts << " to " << d->pts << " in "
<< dialog_id << " during getChannelDifference";
<< dialog_id << " during getChannelDifference from " << source;
bool is_final = true;
bool is_old = false;
@ -39626,9 +39657,9 @@ void MessagesManager::on_get_channel_difference(
auto difference = move_tl_object_as<telegram_api::updates_channelDifferenceEmpty>(difference_ptr);
int32 flags = difference->flags_;
is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0;
LOG_IF(ERROR, !is_final) << "Receive channelDifferenceEmpty as result of getChannelDifference with pts = "
<< request_pts << " and limit = " << request_limit << " in " << dialog_id
<< ", but it is not final";
LOG_IF(ERROR, !is_final) << "Receive channelDifferenceEmpty as result of getChannelDifference from " << source
<< " with pts = " << request_pts << " and limit = " << request_limit << " in "
<< dialog_id << ", but it is not final";
if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) {
timeout = difference->timeout_;
}
@ -39637,9 +39668,9 @@ void MessagesManager::on_get_channel_difference(
// also, this can happen for deleted channels
if (request_pts != difference->pts_ && !td_->auth_manager_->is_bot() &&
have_input_peer(dialog_id, AccessRights::Read)) {
LOG(ERROR) << "Receive channelDifferenceEmpty as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed to "
<< difference->pts_;
LOG(ERROR) << "Receive channelDifferenceEmpty as result of getChannelDifference from " << source
<< " with pts = " << request_pts << " and limit = " << request_limit << " in " << dialog_id
<< ", but PTS has changed to " << difference->pts_;
}
set_channel_pts(d, difference->pts_, "channel difference empty");
break;
@ -39655,9 +39686,10 @@ void MessagesManager::on_get_channel_difference(
auto new_pts = difference->pts_;
if (request_pts >= new_pts && request_pts > 1 && (request_pts > new_pts || !td_->auth_manager_->is_bot())) {
LOG(ERROR) << "Receive channelDifference as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed from " << d->pts
<< " to " << new_pts << ". Difference: " << oneline(to_string(difference));
LOG(ERROR) << "Receive channelDifference as result of getChannelDifference from " << source
<< " with pts = " << request_pts << " and limit = " << request_limit << " in " << dialog_id
<< ", but PTS has changed from " << d->pts << " to " << new_pts
<< ". Difference: " << oneline(to_string(difference));
new_pts = request_pts + 1;
}
@ -39668,8 +39700,8 @@ void MessagesManager::on_get_channel_difference(
auto message_id = MessageId::get_message_id(message, false);
if (message_id <= cur_message_id) {
LOG(ERROR) << "Receive " << cur_message_id << " after " << message_id << " in channelDifference of "
<< dialog_id << " with pts " << request_pts << " and limit " << request_limit << ": "
<< to_string(difference);
<< dialog_id << " from " << source << " with pts " << request_pts << " and limit "
<< request_limit << ": " << to_string(difference);
after_get_channel_difference(dialog_id, false);
return;
}
@ -39718,9 +39750,10 @@ void MessagesManager::on_get_channel_difference(
auto new_pts = dialog->pts_;
if (request_pts > new_pts - request_limit) {
LOG(ERROR) << "Receive channelDifferenceTooLong as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed from " << d->pts
<< " to " << new_pts << ". Difference: " << oneline(to_string(difference));
LOG(ERROR) << "Receive channelDifferenceTooLong as result of getChannelDifference from " << source
<< " with pts = " << request_pts << " and limit = " << request_limit << " in " << dialog_id
<< ", but PTS has changed from " << d->pts << " to " << new_pts
<< ". Difference: " << oneline(to_string(difference));
if (request_pts >= new_pts) {
new_pts = request_pts + 1;
}
@ -39766,7 +39799,7 @@ void MessagesManager::on_get_channel_difference(
}
if (!is_final) {
LOG_IF(ERROR, timeout > 0) << "Have timeout in nonfinal ChannelDifference in " << dialog_id;
LOG_IF(ERROR, timeout > 0) << "Have timeout in non-final ChannelDifference in " << dialog_id;
get_channel_difference(dialog_id, d->pts, true, "on_get_channel_difference", is_old);
return;
}

View File

@ -595,7 +595,7 @@ void SecretChatActor::run_pfs() {
break;
}
case PfsState::SendCommit: {
// must wait till pfs_state is saved to binlog. Otherwise we may save ActionCommit to binlog without pfs_state,
// must wait till pfs_state is saved to binlog. Otherwise, we may save ActionCommit to binlog without pfs_state,
// which has the new auth_key.
if (saved_pfs_state_message_id_ < pfs_state_.wait_message_id) {
return;
@ -1183,7 +1183,7 @@ Status SecretChatActor::do_inbound_message_decrypted(unique_ptr<log_event::Inbou
// 1. [] => Add log event. [save_log_event]
// 2. [save_log_event] => Save SeqNoState [save_changes]
// 3. [save_log_event] => Add message to MessageManager [save_message]
// Note: if we are able to add message by random_id, we may not wait for (log event). Otherwise we should force
// Note: if we are able to add message by random_id, we may not wait for (log event). Otherwise, we should force
// binlog flush.
// 4. [save_log_event] => Update qts [qts]
// 5. [save_changes; save_message; ?qts) => Remove log event [remove_log_event]

View File

@ -479,10 +479,10 @@ class SecretChatActor final : public NetQueryCallback {
// This is completly flawed.
// (A-start_save_to_binlog ----> B-start_save_to_binlog+change_memory ----> A-finish_save_to_binlog+surprise)
//
// Instead I suggest general solution that is already used with SeqNoState and qts
// Instead, I suggest general solution that is already used with SeqNoState and qts
// 1. We APPLY CHANGE to memory immediately AFTER corresponding EVENT is SENT to the binlog.
// 2. We SEND CHANGE to database only after corresponding EVENT is SAVED to the binlog.
// 3. Then we are able to ERASE EVENT just AFTER the CHANGE is SAVED to the binlog.
// 3. Then, we are able to ERASE EVENT just AFTER the CHANGE is SAVED to the binlog.
//
// Actually the change will be saved to binlog too.
// So we can do it immediatelly after EVENT is SENT to the binlog, because SEND CHANGE and ERASE EVENT will be

View File

@ -934,7 +934,6 @@ class InstallStickerSetQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
@ -969,7 +968,6 @@ class UninstallStickerSetQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
@ -1029,7 +1027,6 @@ class UploadStickerFileQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
if (was_uploaded_) {
CHECK(file_id_.is_valid());
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
@ -1154,7 +1151,6 @@ class CreateNewStickerSetQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
@ -1189,7 +1185,6 @@ class AddStickerToSetQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
@ -1224,7 +1219,6 @@ class SetStickerSetThumbnailQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
@ -1258,7 +1252,6 @@ class SetStickerPositionQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
@ -1292,7 +1285,6 @@ class DeleteStickerFromSetQuery final : public Td::ResultHandler {
}
void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};

View File

@ -6,6 +6,7 @@
//
#include "td/telegram/TdDb.h"
#include "td/telegram/AttachMenuManager.h"
#include "td/telegram/DialogDb.h"
#include "td/telegram/files/FileDb.h"
#include "td/telegram/Global.h"
@ -32,7 +33,9 @@
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
#include "td/utils/port/path.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Random.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"
@ -54,6 +57,14 @@ std::string get_sqlite_path(const TdParameters &parameters) {
Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_pmc, BinlogKeyValue<Binlog> &config_pmc,
TdDb::OpenedDatabase &events, DbKey key) {
auto r_binlog_stat = stat(path);
if (r_binlog_stat.is_ok()) {
auto since_last_open = Clocks::system() - static_cast<double>(r_binlog_stat.ok().mtime_nsec_) * 1e-9;
if (since_last_open >= 86400) {
LOG(WARNING) << "Binlog wasn't opened for " << since_last_open << " seconds";
}
}
auto callback = [&](const BinlogEvent &event) {
switch (event.type_) {
case LogEvent::HandlerType::SecretChats:
@ -398,6 +409,7 @@ Status TdDb::init_sqlite(const TdParameters &parameters, const DbKey &key, const
binlog_pmc.erase("saved_contact_count");
binlog_pmc.erase("old_featured_sticker_set_count");
binlog_pmc.erase("invalidate_old_featured_sticker_sets");
binlog_pmc.erase(AttachMenuManager::get_attach_menu_bots_database_key());
}
binlog_pmc.force_sync({});

View File

@ -508,8 +508,7 @@ void UpdatesManager::set_date(int32 date, bool from_update, string date_source)
}
auto now = G()->unix_time();
if (date_ > now + 1) {
LOG(ERROR) << "Receive wrong by " << (date_ - now) << " date = " << date_ << " from " << date_source
<< ". Now = " << now;
LOG(ERROR) << "Receive wrong by " << (date_ - now) << " date = " << date_ << " from " << date_source;
date_ = now;
if (date_ <= date) {
return;
@ -1474,6 +1473,10 @@ void UpdatesManager::init_state() {
}
void UpdatesManager::ping_server() {
if (is_ping_sent_) {
return;
}
is_ping_sent_ = true;
auto promise = PromiseCreator::lambda([](Result<tl_object_ptr<telegram_api::updates_state>> result) {
auto state = result.is_ok() ? result.move_as_ok() : nullptr;
send_closure(G()->updates_manager(), &UpdatesManager::on_server_pong, std::move(state));
@ -1483,6 +1486,7 @@ void UpdatesManager::ping_server() {
void UpdatesManager::on_server_pong(tl_object_ptr<telegram_api::updates_state> &&state) {
LOG(INFO) << "Receive " << oneline(to_string(state));
is_ping_sent_ = false;
if (state == nullptr || state->pts_ > get_pts() || state->seq_ > seq_) {
get_difference("on server pong");
}
@ -1838,6 +1842,7 @@ void UpdatesManager::try_reload_data() {
}
void UpdatesManager::subscribe_to_transcribed_audio_updates(int64 transcription_id, TranscribedAudioHandler on_update) {
CHECK(transcription_id != 0);
if (pending_audio_transcriptions_.count(transcription_id) != 0) {
on_pending_audio_transcription_failed(transcription_id,
Status::Error(500, "Receive duplicate speech recognition identifier"));
@ -2296,8 +2301,10 @@ void UpdatesManager::process_updates(vector<tl_object_ptr<telegram_api::Update>>
}
process_pts_update(std::move(update));
CHECK(update == nullptr);
} else if (is_qts_update(update.get())) {
process_qts_update(std::move(update), 0, get_promise());
CHECK(update == nullptr);
} else if (update->get_id() == telegram_api::updateChannelTooLong::ID) {
td_->messages_manager_->on_update_channel_too_long(
move_tl_object_as<telegram_api::updateChannelTooLong>(update), true);
@ -2326,6 +2333,7 @@ void UpdatesManager::process_pts_update(tl_object_ptr<telegram_api::Update> &&up
if (!check_pts_update(update)) {
LOG(ERROR) << "Receive wrong pts update: " << oneline(to_string(update));
update = nullptr;
return;
}

View File

@ -246,6 +246,8 @@ class UpdatesManager final : public Actor {
int32 min_postponed_update_qts_ = 0;
double get_difference_start_time_ = 0; // time from which we started to get difference without success
bool is_ping_sent_ = false;
FlatHashMap<int64, TranscribedAudioHandler> pending_audio_transcriptions_;
MultiTimeout pending_audio_transcription_timeout_{"PendingAudioTranscriptionTimeout"};

View File

@ -179,6 +179,7 @@ void VideoNotesManager::register_video_note(FileId video_note_file_id, FullMessa
return;
}
LOG(INFO) << "Register video note " << video_note_file_id << " from " << full_message_id << " from " << source;
CHECK(video_note_file_id.is_valid());
bool is_inserted = video_note_messages_[video_note_file_id].insert(full_message_id).second;
LOG_CHECK(is_inserted) << source << ' ' << video_note_file_id << ' ' << full_message_id;
is_inserted = message_video_notes_.emplace(full_message_id, video_note_file_id).second;
@ -192,6 +193,7 @@ void VideoNotesManager::unregister_video_note(FileId video_note_file_id, FullMes
return;
}
LOG(INFO) << "Unregister video note " << video_note_file_id << " from " << full_message_id << " from " << source;
CHECK(video_note_file_id.is_valid());
auto &message_ids = video_note_messages_[video_note_file_id];
auto is_deleted = message_ids.erase(full_message_id) > 0;
LOG_CHECK(is_deleted) << source << ' ' << video_note_file_id << ' ' << full_message_id;

View File

@ -141,6 +141,7 @@ void VoiceNotesManager::register_voice_note(FileId voice_note_file_id, FullMessa
return;
}
LOG(INFO) << "Register voice note " << voice_note_file_id << " from " << full_message_id << " from " << source;
CHECK(voice_note_file_id.is_valid());
bool is_inserted = voice_note_messages_[voice_note_file_id].insert(full_message_id).second;
LOG_CHECK(is_inserted) << source << ' ' << voice_note_file_id << ' ' << full_message_id;
is_inserted = message_voice_notes_.emplace(full_message_id, voice_note_file_id).second;
@ -154,6 +155,7 @@ void VoiceNotesManager::unregister_voice_note(FileId voice_note_file_id, FullMes
return;
}
LOG(INFO) << "Unregister voice note " << voice_note_file_id << " from " << full_message_id << " from " << source;
CHECK(voice_note_file_id.is_valid());
auto &message_ids = voice_note_messages_[voice_note_file_id];
auto is_deleted = message_ids.erase(full_message_id) > 0;
LOG_CHECK(is_deleted) << source << ' ' << voice_note_file_id << ' ' << full_message_id;

View File

@ -377,7 +377,7 @@ class FileManager final : public FileLoadManager::Callback {
// After on_upload_ok all uploads of this file will be paused till merge, delete_partial_remote_location or
// explicit upload request with the same file_id.
// Also upload may be resumed after some other merges.
// Also, upload may be resumed after some other merge.
virtual void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) = 0;
virtual void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) = 0;
virtual void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) = 0;

View File

@ -112,7 +112,7 @@ void scan_fs(CancellationToken &token, CallbackT &&callback) {
if (token) {
return WalkPath::Action::Abort;
}
if (type != WalkPath::Type::NotDir) {
if (type != WalkPath::Type::RegularFile) {
return WalkPath::Action::Continue;
}
auto r_stat = stat(path);

View File

@ -104,9 +104,14 @@ class AuthDataSharedImpl final : public AuthDataShared {
}
void log_auth_key(const mtproto::AuthKey &auth_key) {
auto salts = get_future_salts();
int64 last_used = 0;
if (!salts.empty()) {
last_used = static_cast<int64>(salts[0].valid_until);
}
LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id())
<< tag("state", AuthDataShared::get_auth_key_state(auth_key))
<< tag("created_at", auth_key.created_at());
<< tag("created_at", static_cast<int64>(auth_key.created_at())) << tag("last_used", last_used);
}
};

View File

@ -392,7 +392,7 @@ void Session::on_bind_result(NetQueryPtr query) {
bool has_immunity =
!is_server_time_reliable || auth_key_age < 60 || (auth_key_age > 86400 && last_success_time > now - 86400);
auto debug = PSTRING() << ". Server time is " << server_time << ", auth key created at " << auth_key_creation_date
<< ", is_server_time_reliable = " << is_server_time_reliable
<< ", is_server_time_reliable = " << is_server_time_reliable << ", use_pfs = " << use_pfs_
<< ", last_success_time = " << last_success_time << ", now = " << now;
if (!use_pfs_) {
if (has_immunity) {
@ -688,7 +688,7 @@ void Session::on_closed(Status status) {
void Session::on_session_created(uint64 unique_id, uint64 first_id) {
// TODO: use unique_id
LOG(INFO) << "New session " << unique_id << " created with first message_id " << first_id;
if (!use_pfs_) {
if (!use_pfs_ && !auth_data_.use_pfs()) {
last_success_timestamp_ = Time::now();
}
if (is_main_) {
@ -846,7 +846,7 @@ Status Session::on_update(BufferSlice packet) {
return Status::Error("Receive at update from CDN connection");
}
if (!use_pfs_) {
if (!use_pfs_ && !auth_data_.use_pfs()) {
last_success_timestamp_ = Time::now();
}
last_activity_timestamp_ = Time::now();
@ -1517,7 +1517,7 @@ void Session::loop() {
long_poll_connection_.wakeup_at_ = 0;
// NB: order is crucial. First long_poll_connection, then main_connection
// Otherwise queries could be sent with big delay
// Otherwise, queries could be sent with big delay
connection_check_mode(&main_connection_);
connection_check_mode(&long_poll_connection_);

View File

@ -175,7 +175,7 @@ class Session final
struct ContainerInfo {
size_t ref_cnt;
std::vector<uint64> message_ids;
vector<uint64> message_ids;
};
FlatHashMap<uint64, ContainerInfo> sent_containers_;

View File

@ -28,7 +28,7 @@
* Requests can be sent using td_send and the received client identifier.
* New updates and responses to requests can be received through td_receive from any thread after the first request
* has been sent to the client instance. This function must not be called simultaneously from two different threads.
* Also note that all updates and responses to requests must be applied in the order they were received for consistency.
* Also, note that all updates and responses to requests must be applied in the order they were received for consistency.
* Some TDLib requests can be executed synchronously from any thread using td_execute.
* TDLib client instances are destroyed automatically after they are closed.
* All TDLib client instances must be closed before application termination to ensure data consistency.
@ -118,7 +118,7 @@ TDJSON_EXPORT void td_set_log_message_callback(int max_verbosity_level, td_log_m
* 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
* must not be called simultaneously from two different threads. Also note that all updates and request responses
* 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.

View File

@ -218,15 +218,15 @@ class TQueueImpl final : public TQueue {
pop(q, queue_id, it, q.tail_id);
}
void clear(QueueId queue_id, size_t keep_count) final {
std::map<EventId, RawEvent> clear(QueueId queue_id, size_t keep_count) final {
auto queue_it = queues_.find(queue_id);
if (queue_it == queues_.end()) {
return;
return {};
}
auto &q = queue_it->second;
auto size = get_size(q);
if (size <= keep_count) {
return;
return {};
}
auto start_time = Time::now();
@ -261,16 +261,32 @@ class TQueueImpl final : public TQueue {
}
auto callback_clear_time = Time::now() - start_time;
std::map<EventId, RawEvent> deleted_events;
if (keep_count > size / 2) {
for (auto it = q.events.begin(); it != end_it;) {
remove_event(q, it);
q.total_event_length -= it->second.data.size();
bool is_inserted = deleted_events.emplace(it->first, std::move(it->second)).second;
CHECK(is_inserted);
it = q.events.erase(it);
}
} else {
q.total_event_length = 0;
for (auto it = end_it; it != q.events.end();) {
q.total_event_length += it->second.data.size();
bool is_inserted = deleted_events.emplace(it->first, std::move(it->second)).second;
CHECK(is_inserted);
it = q.events.erase(it);
}
std::swap(deleted_events, q.events);
}
auto clear_time = Time::now() - start_time;
if (clear_time > 0.1) {
if (clear_time > 0.01) {
LOG(WARNING) << "Cleared " << (size - keep_count) << " TQueue events with total size "
<< (total_event_length - q.total_event_length) << " in " << clear_time - callback_clear_time
<< " seconds and deleted them from callback in " << callback_clear_time << " seconds";
}
return deleted_events;
}
Result<size_t> get(QueueId queue_id, EventId from_id, bool forget_previous, int32 unix_time_now,

View File

@ -61,9 +61,9 @@ class TQueue {
struct RawEvent {
uint64 log_event_id{0};
EventId event_id;
int32 expires_at{0};
string data;
int64 extra{0};
int32 expires_at{0};
};
using QueueId = int64;
@ -104,7 +104,7 @@ class TQueue {
virtual void forget(QueueId queue_id, EventId event_id) = 0;
virtual void clear(QueueId queue_id, size_t keep_count) = 0;
virtual std::map<EventId, RawEvent> clear(QueueId queue_id, size_t keep_count) = 0;
virtual EventId get_head(QueueId queue_id) const = 0;
virtual EventId get_tail(QueueId queue_id) const = 0;

View File

@ -10,7 +10,10 @@
#include "td/utils/crypto.h"
#include "td/utils/FlatHashMap.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/path.h"
#include "td/utils/port/wstring_convert.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
@ -18,6 +21,7 @@
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <cstring>
#include <memory>
@ -56,6 +60,97 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
return preverify_ok;
}
X509_STORE *load_system_certificate_store() {
int32 cert_count = 0;
int32 file_count = 0;
LOG(DEBUG) << "Begin to load system certificate store";
SCOPE_EXIT {
LOG(DEBUG) << "End to load " << cert_count << " certificates from " << file_count << " files from system store";
if (ERR_peek_error() != 0) {
auto error = create_openssl_error(-22, "Have unprocessed errors");
LOG(INFO) << error;
}
};
#if TD_PORT_WINDOWS
auto flags = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE system_store =
CertOpenStore(CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, HCRYPTPROV_LEGACY(), flags,
static_cast<const void *>(to_wstring("ROOT").ok().c_str()));
if (!system_store) {
return nullptr;
}
X509_STORE *store = X509_STORE_new();
if (store == nullptr) {
return nullptr;
}
for (PCCERT_CONTEXT cert_context = CertEnumCertificatesInStore(system_store, nullptr); cert_context != nullptr;
cert_context = CertEnumCertificatesInStore(system_store, cert_context)) {
const unsigned char *in = cert_context->pbCertEncoded;
X509 *x509 = d2i_X509(nullptr, &in, static_cast<long>(cert_context->cbCertEncoded));
if (x509 != nullptr) {
if (X509_STORE_add_cert(store, x509) != 1) {
auto error_code = ERR_peek_error();
auto error = create_openssl_error(-20, "Failed to add certificate");
if (ERR_GET_REASON(error_code) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
LOG(ERROR) << error;
} else {
LOG(INFO) << error;
}
} else {
cert_count++;
}
X509_free(x509);
} else {
LOG(ERROR) << create_openssl_error(-21, "Failed to load X509 certificate");
}
}
CertCloseStore(system_store, 0);
#else
X509_STORE *store = X509_STORE_new();
if (store == nullptr) {
return nullptr;
}
auto add_file = [&](CSlice path) {
if (X509_STORE_load_locations(store, path.c_str(), nullptr) != 1) {
auto error = create_openssl_error(-20, "Failed to add certificate");
LOG(INFO) << path << ": " << error;
} else {
file_count++;
}
};
string default_cert_dir = X509_get_default_cert_dir();
for (auto cert_dir : full_split(default_cert_dir, ':')) {
walk_path(cert_dir, [&](CSlice path, WalkPath::Type type) {
if (type != WalkPath::Type::RegularFile && type != WalkPath::Type::Symlink) {
return type == WalkPath::Type::EnterDir && path != cert_dir ? WalkPath::Action::SkipDir
: WalkPath::Action::Continue;
}
add_file(path);
return WalkPath::Action::Continue;
}).ignore();
}
string default_cert_path = X509_get_default_cert_file();
if (!default_cert_path.empty()) {
add_file(default_cert_path);
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
auto objects = X509_STORE_get0_objects(store);
cert_count = objects == nullptr ? 0 : sk_X509_OBJECT_num(objects);
#else
cert_count = -1;
#endif
#endif
return store;
}
using SslCtxPtr = std::shared_ptr<SSL_CTX>;
Result<SslCtxPtr> do_create_ssl_ctx(CSlice cert_file, SslCtx::VerifyPeer verify_peer) {
@ -87,54 +182,17 @@ Result<SslCtxPtr> do_create_ssl_ctx(CSlice cert_file, SslCtx::VerifyPeer verify_
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);
if (cert_file.empty()) {
#if TD_PORT_WINDOWS
LOG(DEBUG) << "Begin to load system store";
auto flags = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE system_store =
CertOpenStore(CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, HCRYPTPROV_LEGACY(), flags,
static_cast<const void *>(to_wstring("ROOT").ok().c_str()));
if (system_store) {
X509_STORE *store = X509_STORE_new();
for (PCCERT_CONTEXT cert_context = CertEnumCertificatesInStore(system_store, nullptr); cert_context != nullptr;
cert_context = CertEnumCertificatesInStore(system_store, cert_context)) {
const unsigned char *in = cert_context->pbCertEncoded;
X509 *x509 = d2i_X509(nullptr, &in, static_cast<long>(cert_context->cbCertEncoded));
if (x509 != nullptr) {
if (X509_STORE_add_cert(store, x509) != 1) {
auto error_code = ERR_peek_error();
auto error = create_openssl_error(-20, "Failed to add certificate");
if (ERR_GET_REASON(error_code) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
LOG(ERROR) << error;
} else {
LOG(INFO) << error;
}
}
X509_free(x509);
} else {
LOG(ERROR) << create_openssl_error(-21, "Failed to load X509 certificate");
}
}
CertCloseStore(system_store, 0);
SSL_CTX_set_cert_store(ssl_ctx, store);
LOG(DEBUG) << "End to load system store";
} else {
LOG(ERROR) << create_openssl_error(-22, "Failed to open system certificate store");
}
#else
if (SSL_CTX_set_default_verify_paths(ssl_ctx) == 0) {
auto error = create_openssl_error(-8, "Failed to load default verify paths");
auto *store = load_system_certificate_store();
if (store == nullptr) {
auto error = create_openssl_error(-8, "Failed to load system certificate store");
if (verify_peer == SslCtx::VerifyPeer::On) {
return std::move(error);
} else {
LOG(ERROR) << error;
}
} else {
SSL_CTX_set_cert_store(ssl_ctx, store);
}
#endif
} else {
if (SSL_CTX_load_verify_locations(ssl_ctx, cert_file.c_str(), nullptr) == 0) {
return create_openssl_error(-8, "Failed to set custom certificate file");

View File

@ -28,6 +28,14 @@ class Container {
return &slots_[slot_id].data;
}
const DataT *get(Id id) const {
int32 slot_id = decode_id(id);
if (slot_id == -1) {
return nullptr;
}
return &slots_[slot_id].data;
}
void erase(Id id) {
int32 slot_id = decode_id(id);
if (slot_id == -1) {
@ -60,7 +68,7 @@ class Container {
return static_cast<uint8>(id);
}
vector<Id> ids() {
vector<Id> ids() const {
vector<bool> is_bad(slots_.size(), false);
for (auto id : empty_slots_) {
is_bad[id] = true;
@ -73,6 +81,7 @@ class Container {
}
return res;
}
template <class F>
void for_each(const F &f) {
auto ids = this->ids();
@ -80,13 +89,24 @@ class Container {
f(id, *get(id));
}
}
template <class F>
void for_each(const F &f) const {
auto ids = this->ids();
for (auto id : ids) {
f(id, *get(id));
}
}
size_t size() const {
CHECK(empty_slots_.size() <= slots_.size());
return slots_.size() - empty_slots_.size();
}
bool empty() const {
return size() == 0;
}
void clear() {
*this = Container<DataT>();
}

View File

@ -186,9 +186,8 @@ class MpmcSleepyWaiter {
// This may put it in a Sleep for some time.
// After wait returns worker will be in Search state again.
//
// Suppose worker found a work and ready to process it.
// Then it may call stop_wait. This will cause transition from
// Search to Work state.
// If a worker found a work and ready to process it, then it may call stop_wait.
// This will cause transition from Search to Work state.
//
// Main invariant:
// After notify is called there should be at least on worker in Search or Work state.

View File

@ -43,10 +43,9 @@ class ObjectPool {
// Pattern of usage: 1. Read an object 2. Check if read was valid via is_alive
//
// It is not very usual case of acquire/release use.
// Instead of publishing an object via some flag we do the opposite.
// We publish new generation via destruction of the data.
// We publish new generation via destruction of the data instead of publishing the object via some flag.
// In usual case if we see a flag, then we are able to use an object.
// In our case if we have used an object and it is already invalid, then generation will mismatch
// In our case if we have used an object and it is already invalid, then generation will mismatch.
bool is_alive() const {
if (!storage_) {
return false;
@ -216,7 +215,7 @@ class ObjectPool {
std::atomic<Storage *> head_{static_cast<Storage *>(nullptr)};
bool check_empty_flag_ = false;
// TODO(perf): allocation Storages in chunks? Anyway we won't be able to release them.
// TODO(perf): allocation Storages in chunks? Anyway, we won't be able to release them.
// TODO(perf): memory order
// TODO(perf): use another non lockfree list for release on the same thread
// only one thread, so no aba problem

View File

@ -8,9 +8,6 @@
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/Span.h"
#include <utility>
namespace td {
@ -57,13 +54,4 @@ class Random {
};
};
template <class T, class R>
void random_shuffle(MutableSpan<T> v, R &rnd) {
for (size_t i = 1; i < v.size(); i++) {
auto pos = static_cast<size_t>(rnd()) % (i + 1);
using std::swap;
swap(v[i], v[pos]);
}
}
} // namespace td

View File

@ -32,6 +32,16 @@ class StringBuilder {
current_ptr_--;
}
void push_back(char c) {
if (unlikely(end_ptr_ <= current_ptr_)) {
if (!reserve_inner(RESERVED_SIZE)) {
on_error();
return;
}
}
*current_ptr_++ = c;
}
MutableCSlice as_cslice() {
if (current_ptr_ >= end_ptr_ + RESERVED_SIZE) {
std::abort(); // shouldn't happen
@ -40,6 +50,10 @@ class StringBuilder {
return MutableCSlice(begin_ptr_, current_ptr_);
}
size_t size() {
return static_cast<size_t>(current_ptr_ - begin_ptr_);
}
bool is_error() const {
return error_flag_;
}
@ -132,6 +146,7 @@ class StringBuilder {
}
return reserve_inner(RESERVED_SIZE);
}
bool reserve(size_t size) {
if (end_ptr_ > current_ptr_ && static_cast<size_t>(end_ptr_ - current_ptr_) >= size) {
return true;

View File

@ -182,6 +182,8 @@ Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) {
// TODO: share mode
DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE;
DWORD native_flags = 0;
DWORD creation_disposition = 0;
if (flags & Create) {
if (flags & Truncate) {
@ -197,9 +199,9 @@ Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) {
} else {
creation_disposition = OPEN_EXISTING;
}
native_flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
DWORD native_flags = 0;
if (flags & Direct) {
native_flags |= FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING;
}
@ -593,7 +595,19 @@ Result<Stat> FileFd::stat() const {
res.atime_nsec_ = filetime_to_unix_time_nsec(basic_info.LastAccessTime.QuadPart);
res.mtime_nsec_ = filetime_to_unix_time_nsec(basic_info.LastWriteTime.QuadPart);
res.is_dir_ = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
res.is_reg_ = !res.is_dir_; // TODO this is still wrong
if ((basic_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
FILE_ATTRIBUTE_TAG_INFO tag_info;
status = GetFileInformationByHandleEx(get_native_fd().fd(), FileAttributeTagInfo, &tag_info, sizeof(tag_info));
if (!status) {
return OS_ERROR("Get FileAttributeTagInfo failed");
}
res.is_reg_ = false;
res.is_symbolic_link_ =
(tag_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && tag_info.ReparseTag == IO_REPARSE_TAG_SYMLINK;
} else {
res.is_reg_ = !res.is_dir_;
res.is_symbolic_link_ = false;
}
TRY_RESULT(file_size, get_file_size(*this));
res.size_ = file_size.size_;

View File

@ -126,6 +126,7 @@ Stat from_native_stat(const struct ::stat &buf) {
res.real_size_ = buf.st_blocks * 512;
res.is_dir_ = (buf.st_mode & S_IFMT) == S_IFDIR;
res.is_reg_ = (buf.st_mode & S_IFMT) == S_IFREG;
res.is_symbolic_link_ = (buf.st_mode & S_IFMT) == S_IFLNK;
return res;
}

View File

@ -17,6 +17,7 @@ namespace td {
struct Stat {
bool is_dir_;
bool is_reg_;
bool is_symbolic_link_;
int64 size_;
int64 real_size_;
uint64 atime_nsec_;

View File

@ -574,7 +574,7 @@ class UdpSocketFdImpl {
auto error = Status::PosixError(sendmsg_errno, PSLICE() << "Send from " << get_native_fd() << " has failed");
switch (sendmsg_errno) {
// Still may send some other packets, but there is no point to resend this particular message
// We still may send some other packets, but there is no point to resend this particular message
case EACCES:
case EMSGSIZE:
case EPERM:
@ -583,7 +583,7 @@ class UdpSocketFdImpl {
is_sent = true;
return error;
// Some general problems, which may be fixed in future
// Some general issues, which may be fixed in the future
case ENOMEM:
case EDQUOT:
case EFBIG:

View File

@ -91,7 +91,11 @@ Status rmrf(CSlice path) {
case WalkPath::Type::ExitDir:
rmdir(path).ignore();
break;
case WalkPath::Type::NotDir:
case WalkPath::Type::RegularFile:
unlink(path).ignore();
break;
case WalkPath::Type::Symlink:
// never follow symbolic links, but delete the link themselves
unlink(path).ignore();
break;
}
@ -263,6 +267,8 @@ Result<bool> walk_path_dir(string &path, const WalkFunction &func) TD_WARN_UNUSE
Result<bool> walk_path_file(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
Result<bool> walk_path_symlink(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
Result<bool> walk_path(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
Result<bool> walk_path_subdir(string &path, DIR *dir, const WalkFunction &func) {
@ -296,6 +302,8 @@ Result<bool> walk_path_subdir(string &path, DIR *dir, const WalkFunction &func)
status = walk_path_dir(path, func);
} else if (entry->d_type == DT_REG) {
status = walk_path_file(path, func);
} else if (entry->d_type == DT_LNK) {
status = walk_path_symlink(path, func);
}
#else
#if !TD_SOLARIS
@ -354,7 +362,18 @@ Result<bool> walk_path_dir(string &path, const WalkFunction &func) {
}
Result<bool> walk_path_file(string &path, const WalkFunction &func) {
switch (func(path, WalkPath::Type::NotDir)) {
switch (func(path, WalkPath::Type::RegularFile)) {
case WalkPath::Action::Abort:
return false;
case WalkPath::Action::SkipDir:
case WalkPath::Action::Continue:
break;
}
return true;
}
Result<bool> walk_path_symlink(string &path, const WalkFunction &func) {
switch (func(path, WalkPath::Type::Symlink)) {
case WalkPath::Action::Abort:
return false;
case WalkPath::Action::SkipDir:
@ -368,17 +387,20 @@ Result<bool> walk_path(string &path, const WalkFunction &func) {
TRY_RESULT(fd, FileFd::open(path, FileFd::Read));
TRY_RESULT(stat, fd.stat());
bool is_dir = stat.is_dir_;
bool is_reg = stat.is_reg_;
if (is_dir) {
if (stat.is_dir_) {
return walk_path_dir(path, std::move(fd), func);
}
fd.close();
if (is_reg) {
if (stat.is_reg_) {
return walk_path_file(path, func);
}
if (stat.is_symbolic_link_) {
return walk_path_symlink(path, func);
}
return true;
}
} // namespace detail
@ -434,6 +456,7 @@ Result<string> realpath(CSlice slice, bool ignore_access_denied) {
if (res.empty()) {
return Status::Error("Empty path");
}
// TODO GetFullPathName doesn't resolve symbolic links
if (!slice.empty() && slice.end()[-1] == TD_DIR_SLASH) {
if (res.back() != TD_DIR_SLASH) {
res += TD_DIR_SLASH;
@ -588,14 +611,24 @@ static Result<bool> walk_path_dir(const std::wstring &dir_name,
if (!is_ok) {
return false;
}
} else {
switch (func(entry_name, WalkPath::Type::NotDir)) {
} else if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
switch (func(entry_name, WalkPath::Type::RegularFile)) {
case WalkPath::Action::Abort:
return false;
case WalkPath::Action::SkipDir:
case WalkPath::Action::Continue:
break;
}
} else if (file_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) {
switch (func(entry_name, WalkPath::Type::Symlink)) {
case WalkPath::Action::Abort:
return false;
case WalkPath::Action::SkipDir:
case WalkPath::Action::Continue:
break;
}
} else {
// skip other reparse points
}
}
auto status = FindNextFileW(handle, &file_data);

View File

@ -44,7 +44,7 @@ Result<string> mkdtemp(CSlice dir, Slice prefix) TD_WARN_UNUSED_RESULT;
class WalkPath {
public:
enum class Action { Continue, Abort, SkipDir };
enum class Type { EnterDir, ExitDir, NotDir };
enum class Type { EnterDir, ExitDir, RegularFile, Symlink };
template <class F, class R = decltype(std::declval<F>()("", Type::ExitDir))>
static TD_WARN_UNUSED_RESULT std::enable_if_t<std::is_same<R, Action>::value, Status> run(CSlice path, F &&func) {

View File

@ -99,7 +99,7 @@
#undef TD_HAVE_ATOMIC_SHARED_PTR
#endif
// Also no atomic operations on std::shared_ptr when clang __has_feature(cxx_atomic) is defined and zero
// Also, no atomic operations on std::shared_ptr when clang __has_feature(cxx_atomic) is defined and zero
#if defined(__has_feature)
#if !__has_feature(cxx_atomic)
#undef TD_HAVE_ATOMIC_SHARED_PTR

View File

@ -12,6 +12,7 @@
#include "td/utils/logging.h"
#include "td/utils/port/sleep.h"
#include "td/utils/Slice.h"
#include "td/utils/Span.h"
#include "td/utils/Status.h"
#include <atomic>
@ -165,6 +166,15 @@ string rand_string(int from, int to, size_t len);
vector<string> rand_split(Slice str);
template <class T, class R>
void rand_shuffle(MutableSpan<T> v, R &rnd) {
for (size_t i = 1; i < v.size(); i++) {
auto pos = static_cast<size_t>(rnd()) % (i + 1);
using std::swap;
swap(v[i], v[pos]);
}
}
template <class T1, class T2>
void assert_eq_impl(const T1 &expected, const T2 &got, const char *file, int line) {
LOG_CHECK(expected == got) << tag("expected", expected) << tag("got", got) << " in " << file << " at line " << line;

View File

@ -62,24 +62,6 @@ bool check_utf8(CSlice str) {
return false;
}
void append_utf8_character(string &str, uint32 ch) {
if (ch <= 0x7f) {
str.push_back(static_cast<char>(ch));
} else if (ch <= 0x7ff) {
str.push_back(static_cast<char>(0xc0 | (ch >> 6))); // implementation-defined
str.push_back(static_cast<char>(0x80 | (ch & 0x3f)));
} else if (ch <= 0xffff) {
str.push_back(static_cast<char>(0xe0 | (ch >> 12))); // implementation-defined
str.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3f)));
str.push_back(static_cast<char>(0x80 | (ch & 0x3f)));
} else {
str.push_back(static_cast<char>(0xf0 | (ch >> 18))); // implementation-defined
str.push_back(static_cast<char>(0x80 | ((ch >> 12) & 0x3f)));
str.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3f)));
str.push_back(static_cast<char>(0x80 | (ch & 0x3f)));
}
}
const unsigned char *next_utf8_unsafe(const unsigned char *ptr, uint32 *code) {
uint32 a = ptr[0];
if ((a & 0x80) == 0) {
@ -100,6 +82,25 @@ const unsigned char *next_utf8_unsafe(const unsigned char *ptr, uint32 *code) {
return ptr;
}
unsigned char *append_utf8_character_unsafe(unsigned char *ptr, uint32 code) {
if (code <= 0x7f) {
*ptr++ = static_cast<unsigned char>(code);
} else if (code <= 0x7ff) {
*ptr++ = static_cast<unsigned char>(0xc0 | (code >> 6));
*ptr++ = static_cast<unsigned char>(0x80 | (code & 0x3f));
} else if (code <= 0xffff) {
*ptr++ = static_cast<unsigned char>(0xe0 | (code >> 12));
*ptr++ = static_cast<unsigned char>(0x80 | ((code >> 6) & 0x3f));
*ptr++ = static_cast<unsigned char>(0x80 | (code & 0x3f));
} else {
*ptr++ = static_cast<unsigned char>(0xf0 | (code >> 18));
*ptr++ = static_cast<unsigned char>(0x80 | ((code >> 12) & 0x3f));
*ptr++ = static_cast<unsigned char>(0x80 | ((code >> 6) & 0x3f));
*ptr++ = static_cast<unsigned char>(0x80 | (code & 0x3f));
}
return ptr;
}
string utf8_to_lower(Slice str) {
string result;
auto pos = str.ubegin();

View File

@ -32,7 +32,24 @@ inline size_t utf8_length(Slice str) {
size_t utf8_utf16_length(Slice str);
/// appends a Unicode character using UTF-8 encoding
void append_utf8_character(string &str, uint32 ch);
template <class T>
void append_utf8_character(T &str, uint32 code) {
if (code <= 0x7f) {
str.push_back(static_cast<char>(code));
} else if (code <= 0x7ff) {
str.push_back(static_cast<char>(0xc0 | (code >> 6))); // implementation-defined
str.push_back(static_cast<char>(0x80 | (code & 0x3f)));
} else if (code <= 0xffff) {
str.push_back(static_cast<char>(0xe0 | (code >> 12))); // implementation-defined
str.push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
str.push_back(static_cast<char>(0x80 | (code & 0x3f)));
} else {
str.push_back(static_cast<char>(0xf0 | (code >> 18))); // implementation-defined
str.push_back(static_cast<char>(0x80 | ((code >> 12) & 0x3f)));
str.push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
str.push_back(static_cast<char>(0x80 | (code & 0x3f)));
}
}
/// moves pointer one UTF-8 character back
inline const unsigned char *prev_utf8_unsafe(const unsigned char *ptr) {
@ -45,6 +62,9 @@ inline const unsigned char *prev_utf8_unsafe(const unsigned char *ptr) {
/// moves pointer one UTF-8 character forward and saves code of the skipped character in *code
const unsigned char *next_utf8_unsafe(const unsigned char *ptr, uint32 *code);
/// appends a Unicode character using UTF-8 encoding and returns updated pointer
unsigned char *append_utf8_character_unsafe(unsigned char *ptr, uint32 code);
/// truncates UTF-8 string to the given length in Unicode characters
template <class T>
T utf8_truncate(T str, size_t length) {

View File

@ -139,7 +139,7 @@ TEST(ChainScheduler, Stress) {
int chain_n = rnd.fast(1, ChainsN);
td::vector<ChainId> chain_ids(ChainsN);
std::iota(chain_ids.begin(), chain_ids.end(), 0);
td::random_shuffle(td::as_mutable_span(chain_ids), rnd);
td::rand_shuffle(td::as_mutable_span(chain_ids), rnd);
chain_ids.resize(chain_n);
for (auto chain_id : chain_ids) {
chains[td::narrow_cast<size_t>(chain_id)].push_back(query);

View File

@ -204,7 +204,7 @@ static void BM_Get(benchmark::State &state) {
}
std::size_t key_i = 0;
td::random_shuffle(td::as_mutable_span(keys), rnd);
td::rand_shuffle(td::as_mutable_span(keys), rnd);
auto next_key = [&] {
key_i++;
if (key_i == data.size()) {

View File

@ -23,7 +23,7 @@ TEST(Heap, sort_random_perm) {
v[i] = i;
}
td::Random::Xorshift128plus rnd(123);
td::random_shuffle(td::as_mutable_span(v), rnd);
td::rand_shuffle(td::as_mutable_span(v), rnd);
td::vector<td::HeapNode> nodes(n);
td::KHeap<int> kheap;
for (int i = 0; i < n; i++) {

View File

@ -54,7 +54,7 @@ TEST(Port, files) {
const int ITER_COUNT = 1000;
for (int i = 0; i < ITER_COUNT; i++) {
td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) {
if (type == td::WalkPath::Type::NotDir) {
if (type == td::WalkPath::Type::RegularFile) {
ASSERT_TRUE(name == fd_path || name == fd2_path);
}
cnt++;
@ -167,6 +167,14 @@ TEST(Port, Writev) {
td::string content(expected_content.size(), '\0');
ASSERT_EQ(content.size(), fd.read(content).move_as_ok());
ASSERT_EQ(expected_content, content);
auto stat = td::stat(test_file_path).move_as_ok();
CHECK(!stat.is_dir_);
CHECK(stat.is_reg_);
CHECK(!stat.is_symbolic_link_);
CHECK(stat.size_ == static_cast<td::int64>(expected_content.size()));
td::unlink(test_file_path).ignore();
}
#if TD_PORT_POSIX && !TD_THREAD_UNSUPPORTED

View File

@ -10,8 +10,9 @@
#include "td/utils/tests.h"
static void check_phone_number_info(td::string phone_number_prefix, const td::string &country_code,
const td::string &calling_code, const td::string &formatted_phone_number) {
auto result = td::CountryInfoManager::get_phone_number_info_sync(td::string(), std::move(phone_number_prefix));
const td::string &calling_code, const td::string &formatted_phone_number,
bool is_anonymous = false) {
auto result = td::CountryInfoManager::get_phone_number_info_sync(td::string(), phone_number_prefix);
CHECK(result != nullptr);
if (result->country_ == nullptr) {
CHECK(country_code.empty());
@ -19,7 +20,9 @@ static void check_phone_number_info(td::string phone_number_prefix, const td::st
CHECK(result->country_->country_code_ == country_code);
}
CHECK(result->country_calling_code_ == calling_code);
// LOG(ERROR) << phone_number_prefix << ' ' << result->formatted_phone_number_ << ' ' << formatted_phone_number;
CHECK(result->formatted_phone_number_ == formatted_phone_number);
CHECK(result->is_anonymous_ == is_anonymous);
}
TEST(CountryInfo, phone_number_info) {
@ -52,7 +55,7 @@ TEST(CountryInfo, phone_number_info) {
check_phone_number_info("77654321", "KZ", "7", "765 432 1- --");
check_phone_number_info("3", "", "3", "");
check_phone_number_info("37", "", "37", "");
check_phone_number_info("372", "EE", "372", "---- ----");
check_phone_number_info("372", "EE", "372", "---- ---");
check_phone_number_info("42", "", "42", "");
check_phone_number_info("420", "CZ", "420", "--- --- ---");
check_phone_number_info("421", "SK", "421", "--- --- ---");
@ -61,27 +64,36 @@ TEST(CountryInfo, phone_number_info) {
check_phone_number_info("424", "YL", "42", "4");
check_phone_number_info("4241234567890", "YL", "42", "41234567890");
check_phone_number_info("4", "", "4", "");
check_phone_number_info("49", "DE", "49", "---- -------");
check_phone_number_info("491", "DE", "49", "1--- -------");
check_phone_number_info("492", "DE", "49", "2--- -------");
check_phone_number_info("4915", "DE", "49", "15-- -------");
check_phone_number_info("4916", "DE", "49", "16- -------");
check_phone_number_info("4917", "DE", "49", "17- -------");
check_phone_number_info("4918", "DE", "49", "18-- -------");
check_phone_number_info("493", "DE", "49", "3--- -------");
check_phone_number_info("4936", "DE", "49", "36-- -------");
check_phone_number_info("49360", "DE", "49", "360- -------");
check_phone_number_info("493601", "DE", "49", "3601 -------");
check_phone_number_info("4936014", "DE", "49", "3601 4------");
check_phone_number_info("4936015", "DE", "49", "3601 5------");
check_phone_number_info("493601419", "DE", "49", "3601 419----");
check_phone_number_info("4936014198", "DE", "49", "3601 4198--");
check_phone_number_info("49360141980", "DE", "49", "3601 41980-");
check_phone_number_info("49", "DE", "49", "");
check_phone_number_info("491", "DE", "49", "1");
check_phone_number_info("492", "DE", "49", "2");
check_phone_number_info("4915", "DE", "49", "15");
check_phone_number_info("4916", "DE", "49", "16");
check_phone_number_info("4917", "DE", "49", "17");
check_phone_number_info("4918", "DE", "49", "18");
check_phone_number_info("493", "DE", "49", "3");
check_phone_number_info("4936", "DE", "49", "36");
check_phone_number_info("49360", "DE", "49", "360");
check_phone_number_info("493601", "DE", "49", "3601");
check_phone_number_info("4936014", "DE", "49", "36014");
check_phone_number_info("4936015", "DE", "49", "36015");
check_phone_number_info("493601419", "DE", "49", "3601419");
check_phone_number_info("4936014198", "DE", "49", "36014198");
check_phone_number_info("49360141980", "DE", "49", "360141980");
check_phone_number_info("841234567890", "VN", "84", "1234567890");
check_phone_number_info("31", "NL", "31", "- -- -- -- --");
check_phone_number_info("318", "NL", "31", "8 -- -- -- --");
check_phone_number_info("319", "NL", "31", "9 -- -- -- --");
check_phone_number_info("3196", "NL", "31", "9 6- -- -- --");
check_phone_number_info("3197", "NL", "31", "97 ---- -----");
check_phone_number_info("3197", "NL", "31", "9 7- -- -- --");
check_phone_number_info("3198", "NL", "31", "9 8- -- -- --");
check_phone_number_info("88", "", "88", "");
check_phone_number_info("888", "FT", "888", "---- ----", true);
check_phone_number_info("8888", "FT", "888", "8 ---", true);
check_phone_number_info("88888", "FT", "888", "8 8--", true);
check_phone_number_info("888888", "FT", "888", "8 88-", true);
check_phone_number_info("8888888", "FT", "888", "8 888", true);
check_phone_number_info("88888888", "FT", "888", "8 8888", true);
check_phone_number_info("888888888", "FT", "888", "8 88888", true);
check_phone_number_info("8888888888", "FT", "888", "8 888888", true);
}

View File

@ -1255,7 +1255,8 @@ TEST(MessageEntities, parse_html) {
check_parse_html("", "", {});
check_parse_html("➡️ ➡️", "➡️ ➡️", {});
check_parse_html("&lt;&gt;&amp;&quot;&laquo;&raquo;&#12345678;", "<>&\"&laquo;&raquo;&#12345678;", {});
check_parse_html("&ge;&lt;&gt;&amp;&quot;&laquo;&raquo;&#12345678;", "&ge;<>&\"&laquo;&raquo;&#12345678;", {});
check_parse_html("&Or;", "&Or;", {});
check_parse_html("➡️ ➡️<i>➡️ ➡️</i>", "➡️ ➡️➡️ ➡️",
{{td::MessageEntity::Type::Italic, 5, 5}});
check_parse_html("➡️ ➡️<em>➡️ ➡️</em>", "➡️ ➡️➡️ ➡️",

View File

@ -23,6 +23,7 @@
#include "td/utils/port/signals.h"
#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/tests.h"
#include <iostream>
#include <map>
@ -437,7 +438,7 @@ class TestDownloadFile : public Task {
begin = end;
}
random_shuffle(as_mutable_span(ranges_), rnd);
rand_shuffle(as_mutable_span(ranges_), rnd);
start_chunk();
}

View File

@ -240,11 +240,12 @@ TEST(TQueue, clear) {
auto tail_id = tqueue->get_tail(1);
auto clear_start_time = td::Time::now();
size_t keep_count = td::Random::fast(0, 2);
tqueue->clear(1, keep_count);
auto deleted_events = tqueue->clear(1, keep_count);
auto finish_time = td::Time::now();
LOG(INFO) << "Added TQueue events in " << clear_start_time - start_time << " seconds and cleared them in "
<< finish_time - clear_start_time << " seconds";
CHECK(tqueue->get_size(1) == keep_count);
CHECK(tqueue->get_head(1).advance(keep_count).ok() == tail_id);
CHECK(tqueue->get_tail(1) == tail_id);
CHECK(deleted_events.size() == 100000 - keep_count);
}